Django - auth user with the email domain and no password - django

I have a request to alter a standard Django login of email and password to allow any user login without a password, but only if they are on a certain domain ... e.g. somebody#example.com ... where the user is allowed in due to them being on the correct domain.
Any suggestions?

Assuming that 'being on the correct domain' means they have an email address for the relevant domain, you could write a custom authentication backend that
looks to check that there is a single user with that email address (and not more than one, which will also mean updating registration flows to ensure email addresses are guaranteed unique, plus possibly checking your DB for duplicates already, just in case)
gets that User and splits off the domain of their email address to check it against a list/whatever of allowed no-password-required domains
return the User from your custom auth backend as if the normal password check had been satisfied, even though it was never checked with check_password(). The Django docs and various djangosnippets.org snippets show how to do this.
In addition:
you will have to use a new/overridden authentication Form class for the admin login view that doesn't require a password field (but still shows it for non-special logins), so that it doesn't complain if there is no password entered.
And finally:
get religion, if you don't already have it
pray to your G/god(s) that no one else learns that the site that will allow no-password authentication with an email address, and especially that they don't also get hold of the email address(es) in question, particularly if your site holds ANY personal data about third parties or has to be PCI-DSS compliant etc, etc.
strongly consider saying 'No' to your client/user/manager/whoever requested this, for the reason immediately above. Passwords are used for a reason.
Or, finally, finally:
skip all of the above and tell your client/user/manager about some of the various password storage tools out there - eg this and this

If your user has an openid with the email somebody#example.com then you can use an OpenId solution (say Django-openid; there are others too) to verify his identity and allow him access.
If that is unlikely, then you'll need to find a custom way of ensuring that the user is who he claims to be.

Somehow like this:
if cleaned_data['email'].endswith('#example.com'):
user = None
try:
user = User.objects.get(email = cleaned_data['email'])
except:
pass
if user:
login(request, user)
Your concept allows everyone knowing or guessing one of the affected email-addresses to login without using a password!
Best regards!

Related

Django: unset a user's password, but still allow password reset

I want to reset/unset the passwords of my users, they should be forced to use the "password reset", and set a new one, that validates with the new password validators.
I found Django docs, so set_unusable_password() is not an option, as the reset is not allowed afterwards. I found that using user.password = '' works - user cannot login, and password reset is working.
Still, this solution feels a bit awkward, and I cant find any real ressources on this topic - how is it security wise, etc.?
As the docs link you posted states: since Django 2.1 you should be able to use set_unusable_password and still request a password reset.
See https://docs.djangoproject.com/en/4.0/releases/2.1/#miscellaneous:
User.has_usable_password() and the is_password_usable() function no longer return False if the password is None or an empty string, or if the password uses a hasher that’s not in the PASSWORD_HASHERS setting. This undocumented behavior was a regression in Django 1.6 and prevented users with such passwords from requesting a password reset. Audit your code to confirm that your usage of these APIs don’t rely on the old behavior.
So if you are using Django <1.6 or >=2.1, it's probably safer to use the suggested way of setting a "None" password (which generates a "fake" hash that can never be a valid encoded hash).
For the Django versions in between, I would suggest ensuring that you never allow submitting an empty string to authenticate, since I think one could potentially log in this way.

Invalid email in Django Password Reset

I am implementing Django Password Reset to send a recovery password link when the user type his/her email id using django.contrib.auth.urls, which works as perfectly.
This is from Django Documentation,
If the email address provided does not exist in the system, the user is inactive, or has an unusable password, the user will still be redirected to this view but no email will be sent.
My question is,
If I add something like EmailValidation to check if the user typed email exists in the database or not and raise ValidationError, will that be a security problem?
Obviously, because it will allow a hacker to run brute force to guess emails. And if the password strength of the user is not strong enough, he might use brute force or guesses to forceful login(if there is no other security methods). I would suggest to put a captcha on reset page as well, to prevent the bots in reset password page.

Display something else than username in Cognito Built-In Sign-In Page

In AWS Cognito, username is unique and cannot be changed, that's why I'm using it with an internal auto-generated ID. Most of Cognito API requests like adminGetUser only uses username as the user identifier.
And this is recommended by AWS:
If your application does not require a username, you do not need to ask users to provide one. Your app can create a unique username for users in the background. This is useful if, for example, you want users to register and sign in with an email address and password.
Everything is working nicely, but I've got problems with the Cognito Built-in Sign-in page:
This page is intended to be viewed by the end user, and it is displaying username, which I'm using as an internal ID 🤔
The Built-in Sign-in page can be customized but only CSS and logo, I cannot see any option to display preferred_username or email instead of username.
Is there a way? A workaround for my use case? Am I using Cognito the wrong way?
It's finally working with preferred_username
The only thing is that the really first time username is used instead of preferred_username 🤔
Probably an AWS bug

AWS Cognito Workflow: Using email alias for primary username

So I am trying to get my head around AWS Cognito but I have hit some walls.
So, right now I can register an account, and verify it and sign in. Simple enough. The edge cases are where my walls are.
Here's the info I have so far:
username's cannot be changed once created
I am using UUIDs as my username values
email is marked as an alias, which in Cognito terms means I can use it to sign in with in addition to username.
if email is chosen as an alias, per the docs, the same value cannot be used as the username (http://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases):
If email is selected as an alias, a username cannot match a valid email format. Similarly, if phone number is selected as an alias, a username that matches a valid phone number pattern will not be accepted by the service for that user pool.
The email address can ONLY be used to sign in once the account has been verified (http://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases)
Phone numbers and email addresses only become active aliases for a user after the phone numbers and email addresses have been verified. We therefore recommend that you choose automatic verification of email addresses and phone numbers if you choose to use them as aliases.
Here in lies my edge case.
If a user signs up, but does NOT immediately verify:
they get called away
maybe the app crashes
they lose connectivity
their battery dies
they force quit
app get's accidentally deleted.
In their mind they have signed up just not verified their account. At this point it effectively leaves no way to verify their account they thought they signed up for. I guess it could be solved with messaging:
"Warning your account will not be created until you verify your email address." or something along those lines. Anyway...
They can't attempt to sign in as they won't know the UUID that was randomly assigned as their username.
Even if that wasn't the case, they provided their email address as their username. From the user's POV they would have no idea what their username could even be since they only entered their email address.
The best they could hope for is to try to sign up again. (Assuming they read the verification warning above) In this case now Cognito potentially has abandoned unconfirmed accounts piling up.
"Piling up" may be too strong a phrase, this is likely a pretty fringe case.
Now the plus side is, since they have not "verified" their email they can sign up again with the same email address since the email doesn't get uniquely constrained until it's verified. If someone tries to verify an address that has already been verified they get a AliasExistsException. This actually brings up an interesting point which I just tested as well.
I can register with an email address, then verify that email address so the account becomes confirmed. I can then turn right around and sign up with the same email address and I don't get an official AWS error until I try go to verify that account with the duplicate email address. There isn't any way to surface this error earlier? I guess the expectation is that it's on the developer to write a verification service in the Pre-Signup Trigger:
This trigger is invoked when a user submits their information to sign up, allowing you to perform custom validation to accept or deny the sign up request.
To sum up, and to restate the question:
It seems to be required, practically speaking, that when using an email address with Cognito a Pre-Signup Lambda is required to ensure an account with an email doesn't already exist since the AWS Exception won't be handled until a verification attempt is made.
Is my assumption here correct? By required here I think it's pretty reasonable to let a user know an email address is not available as soon as possible. For example:
John Doe : jdoe#gmail.com
Jane Doe : jdoe#gmail.com
You are correct.
Another solution is to create a lambda (not triggered by preSignUp) and called whenever the user finished typing into the email field. And getting a response "This email is already used" or "This email is available" before even sending the sign-up event.
Referring the first part of your question. If the user does not immediately verify their email. You probably mean confirmation by code. I prefer using confirmation by link sent to email which avoids this problem.
Knowing that this is an old question, here's a solution for posterity... I am using generated UUIDs for usernames, just like you, undisclosed to the user.
When the user wants to confirm the code at a later time (or perhaps ask to resend it), he doesn't know the username but he does know the email address that he registered with...
You can search for Cognito users with a certain email (or any other attribute) using ListUsers with a filter like email = "user#signupemail.com".
Once you find the user, you can access their username via response.Users[0].Username, and use it to confirm the account.

How to design email and username login

I have two question about usernames and emails
1. I judge username is a Email if '#' in username, and auth it follow:
email_user = User.objects.get(email__iexact=username)
authenticate(username=email_user.username)
Is that a good way that you recommended? or you may have a better advice?
I know a AbstractBaseUser can do it, but I think use User is more reasonable.
2. Should I store the user's email within the User.email field?
Imagine if I sign up a new user with:
username: '123'
email: '456#google.com'
and when I signup success, then I find that my email is wrong,
and now another user that email is '456#google.com' can't signup again.
I just want to a email is verified that can associate with the user.
what's your advice?
If you want to use email as your unique sign in key, it would save you a lot of trouble in future development of your website if you make a custom User model using AbstractBaseUser. If you want i can post a sample working code
In reference to your second question - You can use Cryptographic signing in Django (https://docs.djangoproject.com/ja/1.9/topics/signing/) to produce a key. Further send this key as a link (eg www.example.com/verify/:some_crypto_key:) and send it as a link to user's email address. This key will contain user id and time stamp. If you receive a request on that link, it means that email is legit. You may find a package that does a similar task maybe.
EDIT:
Implementation (short way) - As the user signups on your website, Immediately ask him/her to verify account using the link you have sent to the given email. If you do not receive a response from that email within a given time (say 20 mins), delete that user entry. This means that you can not let the user access your website until he/she verifies the account.
Flaw - Consider a situation where the user has submitted a wrong email. It is obvious that the user will never be able to verify it but for those 20 mins if co-incidentally the actual user with that same email tries to signup on your website, he won't be able to access. This is very unlikely. Also this user will receive an email from your website saying that user has signed-up on a website (so here you can provide another link, 'if this was not you, please click here' kind of thing)
Unless you have a burning desire to write your own custom user model, which will let you replace the username field with the email, I would recommend using something like Django AllAuth. It includes email verification (as outlined in your question), and can be set to use email as username fairly easily. It's a well established library with lots of support, and will be more immediately usable than rolling your own.
(That said - rolling your own is an illuminating experience, and RA123's point is the answer you should accept if you're going down that road.)