I have a simple django app where users can create and login to their accounts.
When a user is registering for a new account, the user object is created and saved in the database with the is_active flag set to false. Once the user clicks the confirmation email, the user object has its is_active flag set to true.
I have built out a password reset flow using Django's views: PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, and PasswordResetCompleteView.
Everything works as expected unless I am trying to reset the password for an account which has not yet been activated (is_active == False), in which case, the reset password email is never sent to the user.
The edge case I am considering here is for a user who created an account, and never clicked the registration link which expires after 72 hours, and thus have a user account which exists but is not active. Then the user wants to get a new registration link, and to do so I require a user to enter their username and password (so that no malicious actors can spam a random users email inbox with new registration link emails). If the user has since forgotten their password, they are bricked and cannot activate their account, nor can they refresh their password.
How can I send a password reset link to accounts which are not active?
I was able to solve this by creating my own child of Django's PasswordResetForm and overriding the get_users method which checks if a user is_active.
class ResetPasswordForm(PasswordResetForm):
# Override the get_users method and delete the requirement that a user is_active
# This is to account for the edge case where a user registered an account
# but never accepted the registration email, then they also forget their
# password, we need to be able to send a password reset link for a non active
# user.
def get_users(self, email):
"""Given an email, return matching user(s) who should receive a reset.
This allows subclasses to more easily customize the default policies
that prevent inactive users and users with unusable passwords from
resetting their password.
"""
email_field_name = UserModel.get_email_field_name()
active_users = UserModel._default_manager.filter(**{
'%s__iexact' % email_field_name: email
})
return (
u for u in active_users
if u.has_usable_password() and
_unicode_ci_compare(email, getattr(u, email_field_name))
)
The is_active requirement was originally introduced to Djangos PasswordResetForm here.
have you tried to use django-allauth?
I think it solves this.
Django AllAuth - How to manually send a reset-password email?
Related
I am building an web application using existing Auth features in Django where admin create user profile with username and password. Admin created user name and password will be given to user to login. So for security reasons I need to ask user to reset password (given by admin) after user's first successful login. To reset password, I will be displaying template where user should be entering only new password and new password again for confirmation. This new password will be updated in sqlite database.
So whenever admin changes the users password. I need to ask users to reset password after first successful login.
I know this if off topic for stacoverflow and for reasons I couldnt post a question in StackExchange. Please refer me or help will be appreciated.
You can have a boolean attribute in the User model which gets set True, whenever Admin user sets the password for that user.
Then in middleware, you can check if that User needs to set the password, then correspondingly you can redirect the user to Reset password view.
You can use these blogs to create a password reset views. Middleware logic you need to write by yourself, for setting that attribute you can override User save in admin.py, set that attribute and call super
I want to differentiate between the users who signed up and are verified through email confirmation link, and those who signed up but not verified through email confirmation.
I tried is_active and is_authenticated but did not got the desired results.
Adding a new field is an okay solution if you have a custom user model or some secondary model to store addition user information. If you don't have any of this and you are not planning to add them then just use groups.
First create a group called Email Verified. You can do this via django admin or via data migration.
Group.objects.create(name='Email Verified')
Then to mark users as email verified you can do
email_verified_group = Group.objects.get(name='Email Verified')
the_user.groups.add(email_verified_group)
To check if the user is email verified or not
is_email_verified = the_user.groups.filter(name='Email Verified').exists()
So i'm piggybacking on this post here:
Python Social Auth duplicating e-mails for different users
Here's my issue:
I provide a user with ability to signup either via regular email sign up, facebook auth or twitter auth.
I'm also using same package Social Django Auth App for the user login pages.
I realized that a user might try sign up with a Facebook account (associated to one email) and then try again later to register with Twitter (which could have the same email address). Based on the aforementioned post, I added a function to check for duplicate emails like so:
def check_email_exists(request, backend, details, uid, user=None, *args, **kwargs):
email = details.get('email', '')
provider = backend.name
# check if social user exists to allow logging in (not sure if this is necessary)
social = backend.strategy.storage.user.get_social_auth(provider, uid)
# check if given email is in use
count = User.objects.filter(username=email).count()
success_message = messages.success(request, 'Sorry User With That Email Already Exists')
# user is not logged in, social profile with given uid doesn't exist
# and email is in use
if not user and not social and count:
return HttpResponseRedirect(reverse('accounts:sign_up', success_message))
and my pipeline with the function:
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',
'social_core.pipeline.user.get_username',
'dealmazing.utils.check_email_exists',
'social_core.pipeline.social_auth.associate_by_email', # <--- enable this one
'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',
)
UPON Testing--if i go to sign up with Twitter account of an already registered email address--it works. YAY!
BUT the main issue comes when i go to try to login via using either Facebook or Twitter. The function is checking on those logins as well and spitting me back the 'Email Allready Exists..' error.
So I somehow need to decipher between a login and a registration, but I'm having trouble finding how i actually do this with the social auth package.
Any ideas?
The difference between login and register is up to your project, it looks like in your scenario, a user landing in your pipeline function that matches an email in your DB should be considered like a login attempt and not a new singup. That's basically the functionality of associate_by_email method.
You might see the potential risk in this when a user uses a service that doesn't validate the email addresses, they can take control over somebody else account. You can mitigate this by doing validation on your end after any new social account is added, or by trusting services that are known to validate emails on their ends.
I would say, that you have to remove from the pipeline
'social_core.pipeline.social_auth.associate_by_email'
Reason: your application is not supporting unique user emails in User data model, and you are getting yourself into trouble. If you are not verifying user emails, then the trouble might be even bigger.
I don't know what it is good time to use the param.
djangoproject it describes below:
Boolean. Designates whether this user account should be considered active. We recommend that you set this flag to False instead of deleting accounts; that way, if your applications have any foreign keys to users, the foreign keys won’t break.
This doesn’t necessarily control whether or not the user can log in. Authentication backends aren’t required to check for the is_active flag, and the default backends do not. If you want to reject a login based on is_active being False, it’s up to you to check that in your own login view or a custom authentication backend. However, the AuthenticationForm used by the login() view (which is the default) does perform this check, as do the permission-checking methods such as has_perm() and the authentication in the Django admin. All of those functions/methods will return False for inactive users.
readthedocs it describes below:
Authorization for inactive users
An inactive user is a one that is authenticated but has its attribute is_active set to False. However this does not mean they are not authorized to do anything. For example they are allowed to activate their account.
The support for anonymous users in the permission system allows for a scenario where anonymous users have permissions to do something while inactive authenticated users do not.
Do not forget to test for the is_active attribute of the user in your own backend permission methods.
Anyone can give some example that let me know the param needs to notice or how to use it.
from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None: #to check whether user is available or not?
# the password verified for the user
if user.is_active:
print("User is valid, active and authenticated")
else:
print("The password is valid, but the account has been disabled!")
else:
# the authentication system was unable to verify the username and password
print("The username and password were incorrect.")
This will be helpful for you to understand django authentication.
An inactive user is one that has its is_active field set to False.
As of django version 1.10: The ModelBackend (the default authentication backend) and RemoteUserBackend authentication backend prohibits these inactive users from authenticating. So, if you use those backends you won't need to use the following style:
#authentication has been successful now...
if user.is_active:
login(request,user)
#redirect to success page
else:
#return disabled account error message
If a custom user model doesn’t have an is_active field, all users will be allowed to authenticate.
Previous to version 1.10 ModelBackend allowed inactive users to authenticate- this was useful first where you allowed the user to authenticate and then you allowed a user to activate their account (only after they had successfully authenticated).
Please note that the #login_required decorator does not check for the is_active flag on the user. #login_required
check AUTHENTICATION_BACKENDS to see which ones you are using.
see https://docs.djangoproject.com/en/1.10/topics/auth/customizing/
def MyFormView(request):
if request.method == 'POST':
m_form = myform(request.POST)
a_form = AccountForm(request.POST)
if m_form.is_valid() and a_form.is_valid():
user = m_form.save()
user.is_active = True
a_form.instance.user = user
a_form.save()
messages.success(request,f'Your Account Has Been created')
return redirect('/')
else:
m_form = myform()
a_form = AccountForm()
return render(request, 'app/index.html', {'m_form':m_form, 'a_form':a_form})
I'm using Django-Allauth to successfully log in with my Facebook account on my webserver. The problem is, when I use the default SignUpView to do so, the username stored in the DB is the username Facebook returns from the social login. Something like this:
<firstname>.<lastname>.<integer>
I've figured out how to connect my Facebook account with an already created standard user account ( not social ) at a later time using the view at:
/accounts/social/connections/
And how to change emails with
/accounts/email/
Can someone please show me how I can force a user to pick a non-facebook username when they log in with Facebook on my site without first creating a standard user?
Thanks!
============ EDIT =======================
So adding this to settings.py prompts for username and password to associate with facebook login, but does not prompty for a password:
SOCIALACCOUNT_AUTO_SIGNUP = False
I still need to figure out how to prompt for a password in this state.
This fixes it - inherit from SignupForm for social auth signup form.
class SignupForm(SignupForm):