reset password views - email is not unique on the default user model

I might be off-track here, but I implemented a register view using the default user model. Then I implemented reset password, using the default views (PasswordResetView, PasswordResetDoneView etc).

Then I was playing around with it with emails sent to the console. It all worked.

However, I noticed that I was able to register different user names with the same email account.
Surely , this is problematic default behavior when resetting passwords? e.g. which user name is getting reset, since there might be multiple users with the same email?

I managed to overcome this, though, with a custom user model.

I just want your thoughts on it.

Update : I see what I did wrong initially. I subclassed the class UserCreationForm and added in a field for email which is required, and I didn’t use the default django create user view…I just used the default form.

But I think the criticism still stands - email is not unique on the default user model?

Your comment regarding multiple users using the same email address does appear to be correct. If nothing else, I can use the admin to create new users with the same address as existing users.

I have no idea what that means with reference to the password reset process. It might be interesting to try it to see what happens. (It would be kinda neat if it reset the passwords to all the accounts sharing that address.)

Ken

1 Like

Thanks, I thought I did something very wrong. I see how hackers can exploit it. Maybe. The default password reset process? I dunno which account it is looking at (given there are multiple emails linked to different users). Also becomes more complex, when you’re logging in via the same machine (obviously)

It’s very common for websites today to basically make users’ e-mail the username, or at least make it an “alternative username”. For instance, when I log in using github, I can input “username or e-mail”.

Django is more old school in that the default User model is built on the assumption that everyone has a username or a password, while everything else is more or less optional. Since the username is what primarily identifies the user it makes sense for the email field to not enforce the UNIQUE constraint. Someone who has a single e-mail might want to have multiple user accounts for instance. If you listen to the podcast Django chat this comes up now and then - I’m not sure, but it might be discussed in their authentication episode among others.

If we look in Django’s codebase, it actually spells this out explicitly for us:

# django/contrib/auth/models.py
class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
        validators=[username_validator],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    email = models.EmailField(_('email address'), blank=True) # nothing about unique here
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    # ...



class User(AbstractUser):
    """
    Users within the Django authentication system are represented by this
    model.

    Username and password are required. Other fields are optional.
    """
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

There are different opinions on how to deal with this nowadays, as you can hear on the podcast I mentioned. wsvincent, one of the co-hosts, mostly advocates creating a custom user model, with this issue being one of his most important reasons for doing so. You can take a look at his guide on extending the User model and/or take a look at his DjangoX project which is “an open-source Django starter framework that includes a custom user model, email/password by default instead of username/email/password, social authentication, and more.”. I tried cloning the project and something that strikes me as odd is that even though emails are handled with a separate SQL table, the ‘email’ field is still in the usual user table, which as far as I understand is completely unnecessary. Maybe removing it is too convoluted a process to fit the straightforward approach of DjangoX? Either way, you could try getting some inspiration from DjangoX.

I see how hackers can exploit it. Maybe.

I don’t think that’s necessarily the case, it depends more on what you do with the website and what you allow users to do/change when it comes to user settings. As long as you don’t assume that users with the same e-mail address are the same person (unless you add e. g. e-mail verification) I’m guessing you’ll be fine. Maybe I’m missing something though.

1 Like

On a side, but related note, the package django-allauth, https://django-allauth.readthedocs.io/en/latest/index.html is a neat and often used package which allows one to easily authenticate on email and enforces a unique email constraint. Here’s a quick guide on how to get it up and running: https://learndjango.com/tutorials/django-allauth-tutorial

1 Like

Hi, there thanks for all replies. No, it’s my mistake. I screwed up and assumed from looking at last console email msg it was not sending the right emails with correct user name…(I had like 20 users all with same email addy).

Anyway, I’ve looked into the code more at the back-end in Django, and now realise that even if you have registered multiple users with the same email address - the reset of the email actually sends out multiple emails to the email account - 1 for each unique user.

In the password reset form :

for user in self.get_users(email):
             user_email = getattr(user, email_field_name)
             context = {
                 'email': user_email,
                 'domain': domain,
                 'site_name': site_name,
                 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                 'user': user,
                 'token': token_generator.make_token(user),
                 'protocol': 'https' if use_https else 'http',
                 **(extra_email_context or {}),
             }
             self.send_mail(
                 subject_template_name, email_template_name, context, from_email,
                 user_email, html_email_template_name=html_email_template_name,
             )

still trying to work out how to post code… sigh

Enclose your code between lines consisting only of three backtick - ` characters. That’s a line of just ```, then your code, then another line of ```. Do not include the ``` on the same line as the code, and be sure you’re using the backtick ` and not the apostrophe '.

Ken

1 Like

Hey thanks for the patience… :slight_smile: