I'm using django-allauth system to register users and log them in. My app should allow users to change their usernames if needed.
If I use a custom view or form to change a username, no allauth validation happens whatsoever (because default views and forms are not triggered). Thus users can easily set whatever username they wish, ignoring all restrictions specified in settings (like "preserve casing" or "minimum length restriction").
Is there any way to use default validations built in allauth outside signup/login forms?
Right now I have to manually create a custom form and use its cleaning methods for validation, which seems to be repetitive and error-prone:
def clean_username(self):
username = self.cleaned_data.get('username')
if username:
username = username.lower()
if len(username) < settings.ACCOUNT_USERNAME_MIN_LENGTH:
raise ValidationError(_("Error text"))
if username in settings.ACCOUNT_USERNAME_BLACKLIST:
raise ValidationError(_("Error text"))
# I need something that works like the code below and validates a username
from allauth.account.adapter import DefaultAccountAdapter
DefaultAccountAdapter.clean_username(username)
return username
I am currently using django rest password reset urls for my password reset needs and I have it attributed to a URL:
url("^password-reset/", include("django_rest_passwordreset.urls", namespace="password_reset")),
However, I would like to call this endpoint in a save method of a model using the email attributed to the user model. Is there any way to do something like this?
You can't 'call an endpoint in a save method', but you can make use of Signals to achieve this.
send_password_reset_token is a receiver function that triggered when a new user is created and sends a password reset link to the newly registered user's email automatically.
from datetime import timedelta
from django.urls import reverse
from django.conf import settings
from django.utils import timezone
from django.dispatch import receiver
from django.core.mail import send_mail
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions
from django_rest_passwordreset.models import ResetPasswordToken, clear_expired, get_password_reset_token_expiry_time, \
get_password_reset_lookup_field
from django_rest_passwordreset.signals import reset_password_token_created
#receiver(post_save, sender=User)
def send_password_reset_token(sender, instance, created, *args, **kwargs):
if created:
email = instance.email
# before we continue, delete all existing expired tokens
password_reset_token_validation_time = get_password_reset_token_expiry_time()
# datetime.now minus expiry hours
now_minus_expiry_time = timezone.now() - timedelta(hours=password_reset_token_validation_time)
# delete all tokens where created_at < now - 24 hours
clear_expired(now_minus_expiry_time)
# find a user by email address (case insensitive search)
users = User.objects.filter(**{'{}__iexact'.format(get_password_reset_lookup_field()): email})
active_user_found = False
# iterate overall users and check if there is any user that is active
# also check whether the password can be changed (is useable), as there could be users that are not allowed
# to change their password (e.g., LDAP user)
for user in users:
if user.eligible_for_reset():
active_user_found = True
# No active user found, raise a validation error
# but not if DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE == True
if not active_user_found and not getattr(settings, 'DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE', False):
raise exceptions.ValidationError({
'email': [_(
"There is no active user associated with this e-mail address or the password can not be changed")],
})
# last but not least: iterate over all users that are active and can change their password
# and create a Reset Password Token and send a signal with the created token
for user in users:
if user.eligible_for_reset():
# define the token as none for now
token = None
# check if the user already has a token
if user.password_reset_tokens.all().count() > 0:
# yes, already has a token, re-use this token
token = user.password_reset_tokens.all()[0]
else:
# no token exists, generate a new token
token = ResetPasswordToken.objects.create(user=user)
# send an e-mail to the user
context = {
'current_user': token.user,
'username': token.user.username,
'email': token.user.email,
'reset_password_url': "{}?token={}".format(reverse('password_reset:reset-password-request'), token.key)
}
send_mail(
'Subject here',
f'Your password_reset link is {context["reset_password_url"]}',
'from#example.com',
['to#example.com'],
fail_silently=False,
)
ResetPasswordRequestToken generation
Django email documentation
You can make use of signal to send email:
considering a Django app named profiles(you need to find and replace all occurrence of profiles with your app name).
create new file profiles/signals.py:
from django.dispatch import receiver
from django_rest_passwordreset.signals import reset_password_token_created
from django.urls import reverse
#receiver(reset_password_token_created)
def send_token_email(sender, instance, reset_password_token, *args, **kwargs):
reset_password_url = f"{instance.request.build_absolute_uri(reverse('password_reset:reset-password-confirm'))}"
message = f"Goto <a href='{reset_password_url}'>{reset_password_url}</a> and enter new password and token({reset_password_token.key})"
# send an e-mail to the user
from django.core.mail import send_mail
send_mail('Password reset',message,'noreply#somehost.com', [reset_password_token.user.email])
update profiles/app.py add ready method:
# ...
class ProfilesConfig(AppConfig):
# ...
def ready(self):
import profiles.signals
I understand that you want to send user an email after you add a user from admin.
You can do that by adding a save_model function in the admin. You can also send the password reset email using PasswordResetForm.
The full code cane be something like this:
from django.contrib import admin
from django.contrib.auth.forms import PasswordResetForm
class UserAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
form = PasswordResetForm(data={"email": obj.email})
# calling save will send the email
form.save()
admin.site.register(User, UserAdmin)
Documentation for PasswordResetForm: https://docs.djangoproject.com/en/3.2/topics/auth/default/#django.contrib.auth.forms.PasswordResetForm
All you need do is send an email to them with a token and require that token from them.
if you use a save method within your model, that method will be called every time there is a saved change to an entry in that model which may not be what you want.
I want to login with handler.
I have a code use session but i want to use handler:
I have visit :
https://docs.djangoproject.com/en/1.11/topics/auth/default/
But i don't understand complete.
I want to log user (with username or email and password)
Do you have a code for example or project in stackoverflow or github or . . . ???
login the user is easy if you are using default user model from django.contrib.auth.models
from django.contrib.auth import authenticate, login
def user_login(request):
# check here that request.method is POST or not.
user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
if user is not None:
login(request, user)
# send some http response here that login successful or redirect to some other page
else:
# return an error page saying that username password not correct
authenticate function will check for username and password in User table in the database if it founds a user matching query then it returns the user object else it will return None. You might not want to manage sessions as django already sets a cookie for every user that successfully logs in so if user has logged in once then he will not be required to enter password again.
I am kind of new to django and I am concerned about can we use multiple models for authentication for different types of users for our application e.g for Customers use Customer model, for Suppliers use Supplier model and also keep default User registration model for administration use only? If so can you point me in the right direction, how it can be done? Which package should be used?
There is one way that I came around is by adding a foreign key to each model viz needed for authentication but that will involve joins in every query which could result in performance issues. I need to know if there is a better way. An also these custom models can benefit for all permissions stuff available in admin panel.
Expert opinion will be really appreciated.
There are a few different options to handle that.
Maybe check first the Django-docu.
I've you'ld like to customize it to authenticate your users with a mail-address, here is an example written by knbk and rahul-gupta on the ex-documentation:
Django's default authentication works on username and password fields. Email authentication backend will authenticate users based on email and password.
from django.contrib.auth import get_user_model
class EmailBackend(object):
"""
Custom Email Backend to perform authentication via email
"""
def authenticate(self, username=None, password=None):
user_model = get_user_model()
try:
user = user_model.objects.get(email=username)
if user.check_password(password): # check valid password
return user # return user to be authenticated
except user_model.DoesNotExist: # no matching user exists
return None
def get_user(self, user_id):
user_model = get_user_model()
try:
return user_model.objects.get(pk=user_id)
except user_model.DoesNotExist:
return None
Add this authentication backend to the AUTHENTICATION_BACKENDS setting.
# settings.py
AUTHENTICATION_BACKENDS = (
'my_app.backends.EmailBackend',
...
)
Maybe a dumb question, but if possible, what's the best way of writing a BasePasswordHasher subclass using username as a part of the salt? I'm rewriting a site from scratch and used this approach in php. The problem is accessing the username in a password hasher. I would be really happy to resolve this as a LOT of users would lose their passwords otherwise, so a big thanks in advance!
PHP Code:
function passHash($login, $pass)
{
return md5(md5($pass).'salt'.$login);
}
As you noticed, this cannot be done in the password hasher alone. The password hasher does not have information about the user, only the password and hash. I think you have two options.
First, and probably best, is to write a custom authentication backend. At the authentication backend level, we have access to the username and raw password. It would look like this
# settings.py
AUTHENTICATION_BACKENDS=(
'myapp.backends.LegacyBackend',
'django.contrib.auth.backends.ModelBackend',
)
# myapp.backends
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.utils.encoding import force_bytes
import hashlib
class LegacyBackend(ModelBackend):
# We only need to override the authenticate method
def authenticate(self, username=None, password=None, **kwargs):
# most of this is copied directly from ModelBackend's authenticate method
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
# This is the normal route that hands off to the password hasher pipeline
# but we will sidestep it entirely and verify the password here
#
# if user.check_password(password):
# return user
pwhash = hashlib.md5(force_bytes(password)).hexdigest()
hash = hashlib.md5(force_bytes(pwhash+"salt"+username)).hexdigest()
if hash == user.password:
# update the user's password if you want, so you can phase out this backend
user.set_password(password)
user.save(update_fields=["password"])
return user
except UserModel.DoesNotExist:
UserModel().set_password(password)
Note that I haven't tested this code, but it should work as advertised. In addition, you don't have conflicts with new users, and old users will have their passwords updated to the new hashing algorithm (default is PBKDF2+SHA256? not sure).
The second option is to write a one-off script to modify your database so the user.password fields look like legacymd5$username+salt$hash. Then you can write your custom password hasher as planned.
For anyone who finds this post as I did, everything still works as expected, except one thing. On Django 2.1, I found I had to add "request" as the first argument in the authenticate method. They must have added passing this thru at some point. I was silently failing the auth and had no idea why.