diff --git a/rest_auth/registration/urls.py b/rest_auth/registration/urls.py index 615b0a9..ad401ef 100644 --- a/rest_auth/registration/urls.py +++ b/rest_auth/registration/urls.py @@ -1,3 +1,4 @@ +from django.views.generic import TemplateView from django.conf.urls import patterns, url from .views import Register, VerifyEmail @@ -6,5 +7,11 @@ urlpatterns = patterns('', url(r'^$', Register.as_view(), name='rest_register'), url(r'^verify-email/(?P\w+)/$', VerifyEmail.as_view(), name='verify_email'), + + url(r'^account-email-verification-sent/$', TemplateView.as_view(), + name='account_email_verification_sent'), + url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), + name='account_confirm_email'), + ) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index b2e708a..c4e12a9 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -4,13 +4,25 @@ from django.conf import settings from rest_framework import serializers from rest_framework.serializers import _resolve_model from rest_framework.authtoken.models import Token +from rest_framework.authtoken.serializers import AuthTokenSerializer profile_model_path = lambda: getattr(settings, 'REST_PROFILE_MODULE', None) -class LoginSerializer(serializers.Serializer): - username = serializers.CharField(max_length=30) - password = serializers.CharField(max_length=128) + +class LoginSerializer(AuthTokenSerializer): + + def validate(self, attrs): + attrs = super(LoginSerializer, self).validate(attrs) + + if 'rest_auth.registration' in settings.INSTALLED_APPS: + from allauth.account import app_settings + if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY: + user = attrs['user'] + email_address = user.emailaddress_set.get(email=user.email) + if not email_address.verified: + raise serializers.ValidationError('E-mail is not verified.') + return attrs class TokenSerializer(serializers.ModelSerializer): diff --git a/rest_auth/tests.py b/rest_auth/tests.py index 1a78b5e..1c4d59e 100644 --- a/rest_auth/tests.py +++ b/rest_auth/tests.py @@ -8,8 +8,10 @@ from django.test import TestCase from django.contrib.auth.models import User from django.contrib.auth import get_user_model from django.core import mail +from django.test.utils import override_settings from rest_framework.serializers import _resolve_model +from rest_framework import status class APIClient(Client): @@ -133,6 +135,9 @@ class APITestCase1(TestCase, BaseAPITestCase): "password2": PASS } + REGISTRATION_DATA_WITH_EMAIL = REGISTRATION_DATA.copy() + REGISTRATION_DATA_WITH_EMAIL['email'] = EMAIL + BASIC_USER_DATA = { 'first_name': "John", 'last_name': 'Smith', @@ -164,8 +169,8 @@ class APITestCase1(TestCase, BaseAPITestCase): "username": self.USERNAME, "password": self.PASS } - # there is no users in db so it should throw error (401) - self.post(self.login_url, data=payload, status_code=401) + # there is no users in db so it should throw error (400) + self.post(self.login_url, data=payload, status_code=400) self.post(self.password_change_url, status_code=403) @@ -181,14 +186,14 @@ class APITestCase1(TestCase, BaseAPITestCase): # test inactive user user.is_active = False user.save() - self.post(self.login_url, data=payload, status_code=401) + self.post(self.login_url, data=payload, status_code=400) # test wrong username/password payload = { "username": self.USERNAME + '?', "password": self.PASS } - self.post(self.login_url, data=payload, status_code=401) + self.post(self.login_url, data=payload, status_code=400) # test empty payload self.post(self.login_url, data={}, status_code=400) @@ -210,7 +215,7 @@ class APITestCase1(TestCase, BaseAPITestCase): status_code=200) # user should not be able to login using old password - self.post(self.login_url, data=login_payload, status_code=401) + self.post(self.login_url, data=login_payload, status_code=400) # new password should work login_payload['password'] = new_password_payload['new_password1'] @@ -302,3 +307,33 @@ class APITestCase1(TestCase, BaseAPITestCase): self.assertEqual(User.objects.all().count(), user_count + 1) new_user = get_user_model().objects.latest('id') self.assertEqual(new_user.username, self.REGISTRATION_DATA['username']) + + payload = { + "username": self.USERNAME, + "password": self.PASS + } + self.post(self.login_url, data=payload, status_code=200) + + @override_settings( + ACCOUNT_EMAIL_VERIFICATION='mandatory', + ACCOUNT_EMAIL_REQUIRED=True + ) + def test_registration_with_email_verification(self): + user_count = User.objects.all().count() + mail_count = len(mail.outbox) + + # test empty payload + self.post(self.register_url, data={}, status_code=400) + + self.post(self.register_url, data=self.REGISTRATION_DATA_WITH_EMAIL, status_code=201) + self.assertEqual(User.objects.all().count(), user_count + 1) + self.assertEqual(len(mail.outbox), mail_count + 1) + new_user = get_user_model().objects.latest('id') + self.assertEqual(new_user.username, self.REGISTRATION_DATA['username']) + + # email is not verified yet + payload = { + "username": self.USERNAME, + "password": self.PASS + } + self.post(self.login_url, data=payload, status=status.HTTP_400_BAD_REQUEST) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index b1abfe0..701351f 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -1,5 +1,6 @@ from django.conf import settings from django.conf.urls import patterns, url, include +from django.views.generic import TemplateView from rest_auth.views import Login, Logout, UserDetails, \ PasswordChange, PasswordReset, PasswordResetConfirm @@ -27,5 +28,9 @@ if getattr(settings, 'IS_TEST', False): from django.contrib.auth.tests import urls urlpatterns += patterns('', url(r'^rest-registration/', include('registration.urls')), - url(r'^test-admin/', include(urls)) + url(r'^test-admin/', include(urls)), + url(r'^account-email-verification-sent/$', TemplateView.as_view(), + name='account_email_verification_sent'), + url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), + name='account_confirm_email'), ) diff --git a/rest_auth/views.py b/rest_auth/views.py index 9bc5b20..25ff836 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -62,29 +62,18 @@ class Login(LoggedOutRESTAPIView, GenericAPIView): # Create a serializer with request.DATA serializer = self.serializer_class(data=request.DATA) - if serializer.is_valid(): - # Authenticate the credentials by grabbing Django User object - user = authenticate(username=serializer.data['username'], - password=serializer.data['password']) - - if user and user.is_authenticated(): - if user.is_active: - if getattr(settings, 'REST_SESSION_LOGIN', True): - login(request, user) - - # Return REST Token object with OK HTTP status - token, created = self.token_model.objects.get_or_create(user=user) - return Response(self.token_serializer(token).data, - status=status.HTTP_200_OK) - else: - return Response({'error': 'This account is disabled.'}, - status=status.HTTP_401_UNAUTHORIZED) - else: - return Response({'error': 'Invalid Username/Password.'}, - status=status.HTTP_401_UNAUTHORIZED) - else: + if not serializer.is_valid(): return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + status=status.HTTP_400_BAD_REQUEST) + + user = serializer.object['user'] + token, created = self.token_model.objects.get_or_create(user=user) + + if getattr(settings, 'REST_SESSION_LOGIN', True): + login(request, user) + + return Response(self.token_serializer(token).data, + status=status.HTTP_200_OK) class Logout(LoggedInRESTAPIView):