Tivix / django-rest-auth

This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST (JSON)
www.tivix.com
MIT License
2.41k stars 660 forks source link

Email registration is not associated to the existing Social Account. #417

Open dperetti opened 6 years ago

dperetti commented 6 years ago

Expected

1) I sign up with FB at /rest-auth/facebook/. I get a JWT token. 2) I register an email at /rest-auth/registration, using the Bearer token in the header to get authorized. 3) The email is associated to my account.

Actual results

The bearer token is ignored and another account is created.

Issue

The strength of django-allauth is the ability to associate multiple social accounts / email in a single account. I may be missing something, but this doesn't seem possible with django-rest-auth ?

flexpeace commented 6 years ago

You are absolutely right, it doesn't connect same social email addresses, I went through the code and l realized that, connect=True is actually not used in Allauth https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/models.py#L238 and rest auth relies on this from this line of code https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/registration/serializers.py#L143 . And this creates duplicates accounts with same email address in the Django user table from this line of code https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/models.py#L231. This is problematic. I am currently looking at a way to fix this in my code

flexpeace commented 6 years ago

I managed to make it work. I am sharing my code so that it will help someone Edit this line of code https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/registration/serializers.py#L127 with

        if not login.is_existing:

            if allauth_settings.UNIQUE_EMAIL:# Application enforces UNQIUE EMAIL
                # Do we have an account already with this email address?
                account_exists = get_user_model().objects.filter(email=login.user.email,).exists()
                if account_exists:#Yes we do
                    #Grab the user for existing email address
                    get_exisiting_user=get_user_model().objects.filter(email=login.user.email,).first()
                    login.account.user = get_exisiting_user #Set the exisiting user to this Allauth social login object
                    login.account.save()#Save Allauth Social Login Object

                    from allauth.socialaccount import app_settings

                    if app_settings.STORE_TOKENS and login.token:
                        login.token.account = login.account #Set Allauth social login object to Allauth Social Application Token  Object 
                        login.token.save()
                else:#Account doesn't exist with this email address, let Allauth deal with normal creation of user account and linking of user to Allauth social login object
                    login.lookup()
                    login.save(request, connect=True)

Let me know if that helped. It is working perfectly for me

dperetti commented 6 years ago

My approach was eventually to override perform_create() in RegisterView:

class EmailRegisterView(RegisterView): 
    """
    Override rest_auth's email RegisterView in order to fix a major flaw where a user authenticated with Facebook
    cannot link up an email account.
    See also core.serializers.RegisterSerializer for another fix.
    """

    def perform_create(self, serializer):
        # fix start
        # If the user is already authenticated (because the API was called using the JWT token), we want
        # to associate this new email to the existing account, not create a separate account.
        if self.request.user and self.request.user.is_authenticated:
            user: User = self.request.user
            email = serializer.validated_data['email']
            password = serializer.validated_data['password1']
            user.email = email
            user.set_password(password)
            user.save()
            sync_user_email_addresses(user)
        # fix end
        else:
            user = serializer.save(self.request)
        if getattr(settings, 'REST_USE_JWT', False):
            self.token = jwt_encode(user)
        else:
            create_token(self.token_model, user, serializer)

        complete_signup(self.request._request, user, allauth_settings.EMAIL_VERIFICATION, None)
        return user

And in urls.py :


    path('rest-auth/registration/', EmailRegisterView.as_view(), name='rest_register'), 
    path('rest-auth/registration/', include('rest_auth.registration.urls')),