djoser
djoser copied to clipboard
Allow configuration for case-insensitive emails
Thanks for writing Djoser! I really like the library and API.
The only thing I've found frustrating recently is that emails are automatically treated as case-sensitive per discussion in #87. What I've had to do is override all of the default serializers with the following method:
def validate_email(self, value):
return value.lower()
I suspect that a lot of sites using Djoser store emails as all-lowercase. Would you consider a PR that would apply something like the following code? This would incorporate a default value of CASE_SENSITIVE_EMAIL = True
but also allow me to easily change it globally for Djoser in terms of storing email, user login, password reset, etc. Happy to submit if it's helpful. Let me know what you think!
def validate_email(self, value):
if settings.DJOSER['CASE_SENSITIVE_EMAIL'] is False:
return value.lower()
return value
You brought up interesting topic. We want djoser
to be very flexible while providing sane defaults. Your idea seems to adhere to these goals. So please do send a PR - I'll be more than happy to review it. But instead of if {setting} is False
just write if not {setting}
:wink:
@YPCrumble, thanks for using djoser!
In general I believe this is a bit broader feature which I would call "data sanitization". This can be applied to username, email address, etc.
I've just found similar discussion in #146
@haxoza makes sense. As I think through it, I can't think of a field other than email and username that would make sense for default sanitization. I also can't think of a sanitization method other than case-insensitivity that makes sense to include in Djoser.
If you agree my suggestion would be to simply add two optional settings:
DJOSER_CASE_SENSITIVE_EMAIL = True
DJOSER_CASE_SENSITIVE_USERNAME = True
If you agree I'm happy to make the PR.
Is this a feature that's being considered? I would like to make username case insensitive, but I don't currently see any means of doing so. What are my options?
I don't think we should do this on Djoser level as it wouldn't work correctly in Django Admin, unless you want to alter initial user input (username.lower()
or sth).
Levi Payne shows how to use CaseInsensitiveFieldMixin
on his blog.
So if you had such mixin
class CaseInsensitiveFieldMixin:
"""
Field mixin that uses case-insensitive lookup alternatives if they exist.
"""
LOOKUP_CONVERSIONS = {
'exact': 'iexact',
'contains': 'icontains',
'startswith': 'istartswith',
'endswith': 'iendswith',
'regex': 'iregex',
}
def get_lookup(self, lookup_name):
converted = self.LOOKUP_CONVERSIONS.get(lookup_name, lookup_name)
return super().get_lookup(converted) # noqa
you can easily create case insensitive email, without changing initial user input at all
class CaseInsensitiveEmailField(CaseInsensitiveFieldMixin, models.EmailField):
def to_python(self, value):
value = super().to_python(value)
if isinstance(value, str):
value = value.lower()
return value
This solution will work for all cases.
If you see any downsides to this approach let me know.
You can create a new serializer subclassing djoser's TokenCreateSerializer and override the validate method.
class LoginSerializer(TokenCreateSerializer):
def validate(self, attrs):
attrs["email"] = attrs["email"].lower()
return super(LoginSerializer, self).validate(attrs)
@Badredine-Kheddaoui could you explain a little more how you implemented this ? I've tried adding the serializer and then updated the DJOSER_SETTINGS with
DJOSER = {
'SERIALIZERS': {
'token_create': 'app.users.serializers.LoginSerializer',
}
}
but it doesn't seem to work. any thought's ?
Thanks
@brsc2909 you would have to override this for every serializer that can create an account, not just for login. Something like the following:
'SERIALIZERS': {
'password_reset': 'accounts.serializers.PasswordResetSerializer',
'user_create': 'accounts.serializers.AccountCreateSerializer',
'token_create': 'accounts.serializers.AccountTokenCreateSerializer',
'login': 'accounts.serializers.AccountTokenCreateSerializer',
'set_username': 'accounts.serializers.AccountSetUsernameSerializer',
}
@Badredine-Kheddaoui could you explain a little more how you implemented this ? I've tried adding the serializer and then updated the DJOSER_SETTINGS with
DJOSER = { 'SERIALIZERS': { 'token_create': 'app.users.serializers.LoginSerializer', } }
but it doesn't seem to work. any thought's ?
Thanks
Here's the serializer I implemented:
class LoginSerializer(TokenCreateSerializer):
def validate(self, attrs):
attrs["email"] = attrs.get("email").lower()
return super(LoginSerializer, self).validate(attrs)
TokenCreateSerializer
is djoser's
initial user input (
What is wrong with altering the user's input email case?
Just in case someone else runs into a similar need anytime soon.
At first, I was worried that doing anything like this would break email standards. But it seems that while the original standard deemed that different casing meant different users, this is no longer the case for modern email providers (to keep from having emails spoofed).
I found the following answer to be helpful and worked for my situation.
https://stackoverflow.com/questions/50895643/removing-case-sensitivity-from-email-in-django-login-form#answer-58495709
class LowercaseEmailField(models.EmailField):
"""
Override EmailField to convert emails to lowercase before saving.
"""
def to_python(self, value):
"""
Convert email to lowercase.
"""
value = super(LowercaseEmailField, self).to_python(value)
# Value can be None so check that it's a string before lowercasing.
if isinstance(value, str):
return value.lower()
return value
Then there is no need to override the other forms, serializers, etc.
I keep thinking about how best to migrate many existing users to new usernames, and if there's an OS opportunity for reuse...