iMerica / dj-rest-auth

Authentication for Django Rest Framework
https://dj-rest-auth.readthedocs.io/en/latest/index.html
MIT License
1.63k stars 304 forks source link

Manually creating social users #530

Open andresmrm opened 11 months ago

andresmrm commented 11 months ago

Hi! I need to have strict control over user creation. No automatic sign-up/registration. The desired flow is:

  1. I create a Django user through Django Admin, setting a Google email for it.
  2. Then the user can login with a Google account with the same email. If someone tries to login with a Google account which email isn't present in the User table, the access is denied.

At first I wanted to manually created all the required data for user registration before the user logins. But it looks like allauth creates data at socialaccount table with an ID that comes from Google login process.

So I came up with the code bellow, that seems to work, but I would like to know if this is the recommended approach.

First I disabled allauth.urls and dj_rest_auth.urls, as stated here, to restrict sign-ups. And then added only a GoogleLogin view as stated in the docs.

The auto_signup option seems required so I can decide in the is_open_for_signup if I want to accept the user.

# settings.py
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_ADAPTER = 'myapp.users.adapter.AccountAdapter'
SOCIALACCOUNT_ADAPTER = 'myapp.users.adapter.SocialAccountAdapter'
SOCIALACCOUNT_AUTO_SIGNUP = True

# adapter.py
from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.models import EmailAddress
from allauth.account.utils import user_email
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.contrib.auth import get_user_model
from django.core.exceptions import BadRequest, PermissionDenied

class AccountAdapter(DefaultAccountAdapter):
    def is_open_for_signup(self, request, **kwargs):
        return False

class SocialAccountAdapter(DefaultSocialAccountAdapter):
    def is_auto_signup_allowed(self, request, sociallogin):
        return True

    def is_open_for_signup(self, request, sociallogin):
        email = user_email(sociallogin.user)
        User = get_user_model()

        if not email:
            raise BadRequest()

        # After allauth 0.55 release?
        # if EmailAddress.objects.is_verified(email):
        #    raise PermissionDenied()

        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            raise PermissionDenied()

        if sociallogin.user.first_name:
            user.first_name = sociallogin.user.first_name
        if sociallogin.user.last_name:
            user.last_name = sociallogin.user.last_name
        # Keep previously manually created user.
        sociallogin.user = user

        return True

Until recently allauth had a scary warning related to using the email of an existent user. But I'm not sure if this applies to my case, since I'm only using Google for social login (besides regular local login).

Thanks for the attention!

andresmrm commented 11 months ago

Just as an update, I opened another issue in allauth with what I think is a little improved version of this code.