Coder Social home page Coder Social logo

Comments (11)

aminehaddad avatar aminehaddad commented on June 17, 2024 21

@ivellios, @omab, there are a few issues with using just email address as the main account:

  1. if a user already exists with that email address and database has emails as unique, then social-auth will generate an invalid email address for the user.
  2. If user does not provide email over social networks (permissions, etc.), it will use FirstNameLastName (username) which is NOT correct if you depend on username being the email address.
  3. If user creates social email account and/or regular email account, email sensitivity matters and user is forced to login using [email protected] (which is normal for email standards but not for users to remember which letters were upper/lower case).
  4. Custom case if email is username: If user is already logged in as [email protected], and logs in as [email protected], then accounts should NOT be linked (validate both emails).

To solve these problems, I created a custom get_username pipeline:

from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
import urllib

# This is initially from https://github.com/python-social-auth/social-core/blob/master/social_core/pipeline/user.py
def get_username(strategy, details, backend, user=None, *args, **kwargs):
    # Get the logged in user (if any)
    logged_in_user = strategy.storage.user.get_username(user)

    # Custom: check for email being provided
    if not details.get('email'):
        error = "Sorry, but your social network (Facebook or Google) needs to provide us your email address."
        return HttpResponseRedirect(reverse('repairs-social-network-error') + "?error=" + urllib.quote_plus(error))

    # Custom: if user is already logged in, double check his email matches the social network email
    if logged_in_user:
        if logged_in_user.lower() != details.get('email').lower():
            error = "Sorry, but you are already logged in with another account, and the email addresses do not match. Try logging out first, please."
            return HttpResponseRedirect(reverse('repairs-social-network-error') + "?error=" + urllib.quote_plus(error))

    return {
        'username': details.get('email').lower(),
    }

In settings:

INSTALLED_APPS += [
    'social_django',
]

# User models
AUTH_USER_MODEL = 'repairs_accounts.MyUser'
SOCIAL_AUTH_USER_MODEL = 'repairs_accounts.MyUser'

# Allowed authentication backends (social + django)
AUTHENTICATION_BACKENDS = [
    # Social logins
    'social_core.backends.facebook.FacebookOAuth2',
    'social_core.backends.google.GoogleOAuth2',

    # Default django login
    'django.contrib.auth.backends.ModelBackend',
]

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',

    # Make up a username for this person, appends a random string at the end if
    # there's any collision.
    # 'social_core.pipeline.user.get_username',

    # CUSTOM: this gets email address as the username and validates it matches
    # the logged in user's email address.
    'repairs_accounts.pipeline.get_username',

    # 'social_core.pipeline.mail.mail_validation',
    'social_core.pipeline.social_auth.associate_by_email',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details'
)

# Facebook settings
SOCIAL_AUTH_FACEBOOK_KEY = '..'
SOCIAL_AUTH_FACEBOOK_SECRET = '..'

SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
    'fields': 'id, name, email, age_range',
}
SOCIAL_AUTH_FACEBOOK_AUTH_EXTRA_ARGUMENTS = {
    # 'auth_type': 'reauthenticate',
}

# Google settings
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '..'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '..'
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {
      # 'access_type': 'offline',
      # 'approval_prompt': 'force',
}

If you have a custom User (BaseUserManager) that only takes email address, you need to let social-auth pass the field 'username' or else it will crash:

class MyUserManager(BaseUserManager):
    def create_user(self, email, password=None, username=""):
        """
        Creates and saves a User with the given email and password.

        NOTE: Argument 'username' is needed for social-auth. It is not actually used.
        """
        if not email:
            raise ValueError('Users must have an email address.')

        # Validate email is unique in database
        if MyUser.objects.filter(email = self.normalize_email(email).lower()).exists():
            raise ValueError('This email has already been registered.')

        user = self.model(
            email=self.normalize_email(email).lower(),
        )

        user.set_password(password)

        # Save and catch IntegrityError (due to email being unique)
        try:
            user.save(using=self._db)

        except IntegrityError:
            raise ValueError('This email has already been registered.')

        return user

class MyUser(AbstractBaseUser):
    objects = MyUserManager()

    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
        error_messages={
            'unique':"This email has already been registered.",
        }
    )

    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    # Custom: was this email validated, at some point, by sending an email?
    is_email_validated = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'

    def get_full_name(self):
        # The user is identified by their email address
        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        return self.email

    def __str__(self):
        return self.email

    def __unicode__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

It takes a while to solve this (plus handling email/password registrations without social networks) but it ends up working great.

from social-app-django.

lsandoval0000 avatar lsandoval0000 commented on June 17, 2024 6

I got the same problem but, just adding
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email, age_range', }
solved my problem ✌️

from social-app-django.

sandeepbalagopal09 avatar sandeepbalagopal09 commented on June 17, 2024 2

@aminehaddad but what happens for the people register to facebook with their phone number only ? Those people also need to login to the website right.

from social-app-django.

omab avatar omab commented on June 17, 2024

Is your facebook app allowed to fetch emails and retrieving them?

from social-app-django.

ivellios avatar ivellios commented on June 17, 2024

@omab - yes, I get public information + e-mail address. E-mail address is saved in my db, but it does not set it as username.

from social-app-django.

omab avatar omab commented on June 17, 2024

@ivellios do you have SOCIAL_AUTH_USER_FIELDS defined in your settings? If that's the case, what's the value for it? If not, then we need to debug the get_username pipeline to check what's going on with the username value since I can't reproduce the issue locally. This is the particular block of code that handles the case https://github.com/python-social-auth/social-core/blob/4167972181054d94c5091250b6545fa2fa6840a8/social_core/pipeline/user.py#L39-L44, the first if clause is the one that handles the "email as username" option.

from social-app-django.

ivellios avatar ivellios commented on June 17, 2024

I have SOCIAL_AUTH_USER_FIELDS = ['email', 'username']
Also I do have: SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']

I tried to set it to only ['email'] but then I get error. Maybe I set SOCIAL_AUTH_USER_FIELDS wrong? Also, I didn't find anything like that in docs when I was going through the configuration steps.

from social-app-django.

omab avatar omab commented on June 17, 2024

@ivellios are you able to conduct a debug of that particular function pointed in my previous comment?

from social-app-django.

ivellios avatar ivellios commented on June 17, 2024

I had to leave this issue for a few weeks, but coming back to it I find this answer :-)

@aminehaddad - that works great! Thanks for your help! 👍

from social-app-django.

milossh avatar milossh commented on June 17, 2024

@aminehaddad Just one question, though: log-in method tries to first associate user, and if it's not there, it goes to create_user, by default. Now, I know that you can override said behavior with SOCIAL_AUTH_PIPELINE, and I have by removing #'social_core.pipeline.user.create_user'. The problem is that, in that case, if user has no account associated with their email on facebook, nothing will happen. Is there a way to simply redirect user to an URL if that happens - everything is ok except that user with said email is not registered already?

from social-app-django.

tapionx avatar tapionx commented on June 17, 2024

@aminehaddad you saved my day!
@omab i think this issue should be re-opened and fixed somehow with some kind of settings in the documentation for this use case (facebook login with email as username)

from social-app-django.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.