iMerica / dj-rest-auth

Authentication for Django Rest Framework
https://dj-rest-auth.readthedocs.io/en/latest/index.html
MIT License
1.66k stars 305 forks source link

Limit social login to only a certain email address domain #236

Open samul-1 opened 3 years ago

samul-1 commented 3 years ago

Hello,

I'm getting started with dj-rest-auth and I have an app which allows user to only sign in via google. However, I want users to only be able to access my service with a specific google apps email domain.

I'm using dj-rest-auth in conjunction with django-allauth.

This is what I tried: I created allauth adapters like this:

class CustomAccountAdapter(DefaultAccountAdapter):
    def is_open_for_signup(self, request):
        return False  # No email/password signups allowed

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def is_open_for_signup(self, request, socialaccount):
        u = socialaccount.user
        return u.email.split("@")[1] == "mydomain.com"

and in my settings I have

ACCOUNT_ADAPTER = "users.adapters.CustomAccountAdapter"
SOCIALACCOUNT_ADAPTER = "users.adapters.CustomSocialAccountAdapter"

With this solution, if the user tries to sign in with a valid email, everything works, whereas if they use a non-allowed email domain, I get this error in my console:

Traceback (most recent call last):
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/dj_rest_auth/views.py", line 48, in dispatch
    return super(LoginView, self).dispatch(*args, **kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/dj_rest_auth/views.py", line 138, in post
    self.serializer.is_valid(raise_exception=True)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/rest_framework/serializers.py", line 234, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/rest_framework/serializers.py", line 436, in run_validation
    value = self.validate(value)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/dj_rest_auth/registration/serializers.py", line 134, in validate
    complete_social_login(request, login)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/allauth/socialaccount/helpers.py", line 151, in complete_social_login
    return _complete_social_login(request, sociallogin)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/allauth/socialaccount/helpers.py", line 172, in _complete_social_login
    ret = _process_signup(request, sociallogin)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/allauth/socialaccount/helpers.py", line 38, in _process_signup
    return render(
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/shortcuts.py", line 19, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/backends/django.py", line 61, in render
    return self.template.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 170, in render
    return self._render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render
    return self.nodelist.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/loader_tags.py", line 150, in render
    return compiled_parent._render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render
    return self.nodelist.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/defaulttags.py", line 312, in render
    return nodelist.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/template/defaulttags.py", line 446, in render
    url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/urls/base.py", line 87, in reverse
    return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
  File "/home/my_user/.local/share/virtualenvs/my_project-mzTAT1MS/lib/python3.8/site-packages/django/urls/resolvers.py", line 685, in _reverse_with_prefix
    raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'account_login' not found. 'account_login' is not a valid view function or pattern name.
[17/Mar/2021 18:49:42] "POST /dj-rest-auth/google HTTP/1.1" 500 246781

What I would like to do is for my django app to send a 403 response to the user (or similar) with a message like "You can only access this service with an [at] whatever email address". I'm trying to figure out at which point the dj-rest-auth views call the user adapters and what to do to handle the failure and achieve what I'm after.

I appreciate any guidance.

escaper01 commented 3 years ago

I'm currently using this validator for my username Field that I have found on stack overflow, you can learn how to raise error when the criteria of your validator don't met

from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
class UserNameValidator():
    """ Validate username to be alpha numeric and ascii only.
    Pass this class to ACCOUNT_USERNAME_VALIDATORS
    See https://django-allauth.readthedocs.io/en/latest/configuration.html
    """

    def __call__(self, username="", *args, **kwargs):
        valid = username.isalnum() and username.isascii()
        if not valid:
            raise ValidationError(_("Only alphanumeric characters are allowed"))

# KLUDGE: instantiate a list of validators
# See https://github.com/pennersr/django-allauth/pull/1648#issuecomment-284223497
customvalidators = [UserNameValidator()]

ACCOUNT_USERNAME_VALIDATORS = 'accounts.validators.customvalidators' I hope this will help you to prevent your app from crashing, As you may notice if I use other that alphanum chars I get as a response username: ["Only alphanumeric characters are allowed"]