diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 3497353e..d53abf14 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -35,6 +35,9 @@ url(r'^password-change/$', TemplateView.as_view(template_name="password_change.html"), name='password-change'), + url(r'^resend-email-verification/$', + TemplateView.as_view(template_name="resend_email_verification.html"), + name='resend-email-verification'), # this url is used to generate email content diff --git a/demo/templates/base.html b/demo/templates/base.html index 47e4e649..498285cb 100644 --- a/demo/templates/base.html +++ b/demo/templates/base.html @@ -34,6 +34,7 @@
  • Signup
  • E-mail verification
  • +
  • Resend E-mail verification
  • Login
  • Password Reset
  • Password Reset Confirm
  • diff --git a/demo/templates/fragments/resend_email_verification_form.html b/demo/templates/fragments/resend_email_verification_form.html new file mode 100644 index 00000000..2702dc84 --- /dev/null +++ b/demo/templates/fragments/resend_email_verification_form.html @@ -0,0 +1,16 @@ + +
    {% csrf_token %} +
    + +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    diff --git a/demo/templates/resend_email_verification.html b/demo/templates/resend_email_verification.html new file mode 100644 index 00000000..080a6f09 --- /dev/null +++ b/demo/templates/resend_email_verification.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +
    +

    Resend E-mail verification


    + {% include "fragments/resend_email_verification_form.html" %} +
    +{% endblock %} diff --git a/dj_rest_auth/registration/views.py b/dj_rest_auth/registration/views.py index 077ab8db..31f92827 100644 --- a/dj_rest_auth/registration/views.py +++ b/dj_rest_auth/registration/views.py @@ -112,23 +112,19 @@ def post(self, request, *args, **kwargs): class ResendEmailVerificationView(CreateAPIView): permission_classes = (AllowAny,) serializer_class = ResendEmailVerificationSerializer + queryset = EmailAddress.objects.all() def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - email = EmailAddress.objects.get(**serializer.validated_data) - if not email: - raise ValidationError("Account does not exist") + email = EmailAddress.objects.filter(**serializer.validated_data).first() + if email and not email.verified: + email.send_confirmation(request) - if email.verified: - raise ValidationError("Account is already verified") - - email.send_confirmation() return Response({'detail': _('ok')}, status=status.HTTP_200_OK) - class SocialLoginView(LoginView): """ class used for social authentications diff --git a/dj_rest_auth/tests/test_api.py b/dj_rest_auth/tests/test_api.py index 9a4673c7..e2de8207 100644 --- a/dj_rest_auth/tests/test_api.py +++ b/dj_rest_auth/tests/test_api.py @@ -14,7 +14,6 @@ from dj_rest_auth.models import get_token_model from .mixins import CustomPermissionClass, TestsMixin - try: from django.urls import reverse except ImportError: # pragma: no cover @@ -513,17 +512,21 @@ def test_registration_with_email_verification(self): data=self.REGISTRATION_DATA_WITH_EMAIL, status_code=status.HTTP_201_CREATED, ) + self.assertNotIn('key', result.data) self.assertEqual(get_user_model().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']) + # increment count + mail_count += 1 + # test browsable endpoint result = self.get( self.verify_email_url, + status_code=status.HTTP_405_METHOD_NOT_ALLOWED ) - self.assertEqual(result.status_code, 405) self.assertEqual(result.json['detail'], 'Method "GET" not allowed.') # email is not verified yet @@ -543,6 +546,8 @@ def test_registration_with_email_verification(self): data={'email': self.EMAIL}, status_code=status.HTTP_200_OK ) + # check mail count + self.assertEqual(len(mail.outbox), mail_count + 1) # verify email email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL) \ @@ -557,6 +562,21 @@ def test_registration_with_email_verification(self): self._login() self._logout() + def test_should_not_resend_email_verification_for_nonexistent_email(self): + # mail count before resend + mail_count = len(mail.outbox) + + # resend non-existent email + resend_email_result = self.post( + self.resend_email_url, + data={'email': 'test@test.com'}, + status_code=status.HTTP_200_OK + ) + + self.assertEqual(resend_email_result.status_code, status.HTTP_200_OK) + # verify that mail count did not increment + self.assertEqual(mail_count, len(mail.outbox)) + @override_settings(ACCOUNT_LOGOUT_ON_GET=True) def test_logout_on_get(self): payload = { @@ -709,7 +729,6 @@ def test_custom_jwt_claims(self): self.assertEquals(claims['name'], 'person') self.assertEquals(claims['email'], 'person1@world.com') - @override_settings(REST_USE_JWT=True) @override_settings(JWT_AUTH_COOKIE='jwt-auth') @override_settings( @@ -741,7 +760,6 @@ def test_custom_jwt_claims_cookie_w_authentication(self): resp = self.get('/protected-view/') self.assertEquals(resp.status_code, 200) - @override_settings(REST_USE_JWT=True) @override_settings(JWT_AUTH_COOKIE='jwt-auth') @override_settings(JWT_AUTH_COOKIE_USE_CSRF=False) @@ -754,8 +772,8 @@ def test_custom_jwt_claims_cookie_w_authentication(self): ), ) @override_settings(REST_SESSION_LOGIN=False) - @override_settings(CSRF_COOKIE_SECURE =True) - @override_settings(CSRF_COOKIE_HTTPONLY =True) + @override_settings(CSRF_COOKIE_SECURE=True) + @override_settings(CSRF_COOKIE_HTTPONLY=True) def test_wo_csrf_enforcement(self): from .mixins import APIClient payload = { @@ -772,9 +790,9 @@ def test_wo_csrf_enforcement(self): ## TEST WITH JWT AUTH HEADER jwtclient = APIClient(enforce_csrf_checks=True) token = resp.data['access_token'] - resp = jwtclient.get('/protected-view/', HTTP_AUTHORIZATION='Bearer '+token) + resp = jwtclient.get('/protected-view/', HTTP_AUTHORIZATION='Bearer ' + token) self.assertEquals(resp.status_code, 200) - resp = jwtclient.post('/protected-view/', {}, HTTP_AUTHORIZATION='Bearer '+token) + resp = jwtclient.post('/protected-view/', {}, HTTP_AUTHORIZATION='Bearer ' + token) self.assertEquals(resp.status_code, 200) ## TEST WITH COOKIES @@ -784,7 +802,6 @@ def test_wo_csrf_enforcement(self): resp = client.post('/protected-view/', {}) self.assertEquals(resp.status_code, 200) - @override_settings(REST_USE_JWT=True) @override_settings(JWT_AUTH_COOKIE='jwt-auth') @override_settings(JWT_AUTH_COOKIE_USE_CSRF=True) @@ -797,8 +814,8 @@ def test_wo_csrf_enforcement(self): ), ) @override_settings(REST_SESSION_LOGIN=False) - @override_settings(CSRF_COOKIE_SECURE =True) - @override_settings(CSRF_COOKIE_HTTPONLY =True) + @override_settings(CSRF_COOKIE_SECURE=True) + @override_settings(CSRF_COOKIE_HTTPONLY=True) def test_csrf_wo_login_csrf_enforcement(self): from .mixins import APIClient payload = { @@ -821,17 +838,17 @@ def test_csrf_wo_login_csrf_enforcement(self): token = resp.data['access_token'] resp = jwtclient.get('/protected-view/') self.assertEquals(resp.status_code, 403) - resp = jwtclient.get('/protected-view/', HTTP_AUTHORIZATION='Bearer '+token) + resp = jwtclient.get('/protected-view/', HTTP_AUTHORIZATION='Bearer ' + token) self.assertEquals(resp.status_code, 200) resp = jwtclient.post('/protected-view/', {}) self.assertEquals(resp.status_code, 403) - resp = jwtclient.post('/protected-view/', {}, HTTP_AUTHORIZATION='Bearer '+token) + resp = jwtclient.post('/protected-view/', {}, HTTP_AUTHORIZATION='Bearer ' + token) self.assertEquals(resp.status_code, 200) - ## TEST WITH COOKIES + # TEST WITH COOKIES resp = client.get('/protected-view/') self.assertEquals(resp.status_code, 200) - #fail w/o csrftoken in payload + # fail w/o csrftoken in payload resp = client.post('/protected-view/', {}) self.assertEquals(resp.status_code, 403) @@ -839,11 +856,10 @@ def test_csrf_wo_login_csrf_enforcement(self): resp = client.post('/protected-view/', csrfparam) self.assertEquals(resp.status_code, 200) - @override_settings(REST_USE_JWT=True) @override_settings(JWT_AUTH_COOKIE='jwt-auth') @override_settings(JWT_AUTH_COOKIE_USE_CSRF=True) - @override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=True) #True at your own risk + @override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=True) # True at your own risk @override_settings( REST_FRAMEWORK=dict( DEFAULT_AUTHENTICATION_CLASSES=[ @@ -852,8 +868,8 @@ def test_csrf_wo_login_csrf_enforcement(self): ), ) @override_settings(REST_SESSION_LOGIN=False) - @override_settings(CSRF_COOKIE_SECURE =True) - @override_settings(CSRF_COOKIE_HTTPONLY =True) + @override_settings(CSRF_COOKIE_SECURE=True) + @override_settings(CSRF_COOKIE_HTTPONLY=True) def test_csrf_w_login_csrf_enforcement(self): from .mixins import APIClient payload = { @@ -866,7 +882,7 @@ def test_csrf_w_login_csrf_enforcement(self): client.get(reverse('getcsrf')) csrftoken = client.cookies['csrftoken'].value - #fail w/o csrftoken in payload + # fail w/o csrftoken in payload resp = client.post(self.login_url, payload) self.assertEquals(resp.status_code, 403) @@ -881,7 +897,7 @@ def test_csrf_w_login_csrf_enforcement(self): ## TEST WITH COOKIES resp = client.get('/protected-view/') self.assertEquals(resp.status_code, 200) - #fail w/o csrftoken in payload + # fail w/o csrftoken in payload resp = client.post('/protected-view/', {}) self.assertEquals(resp.status_code, 403) @@ -889,11 +905,10 @@ def test_csrf_w_login_csrf_enforcement(self): resp = client.post('/protected-view/', csrfparam) self.assertEquals(resp.status_code, 200) - @override_settings(REST_USE_JWT=True) @override_settings(JWT_AUTH_COOKIE='jwt-auth') @override_settings(JWT_AUTH_COOKIE_USE_CSRF=False) - @override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=True) #True at your own risk + @override_settings(JWT_AUTH_COOKIE_ENFORCE_CSRF_ON_UNAUTHENTICATED=True) # True at your own risk @override_settings( REST_FRAMEWORK=dict( DEFAULT_AUTHENTICATION_CLASSES=[ @@ -902,8 +917,8 @@ def test_csrf_w_login_csrf_enforcement(self): ), ) @override_settings(REST_SESSION_LOGIN=False) - @override_settings(CSRF_COOKIE_SECURE =True) - @override_settings(CSRF_COOKIE_HTTPONLY =True) + @override_settings(CSRF_COOKIE_SECURE=True) + @override_settings(CSRF_COOKIE_HTTPONLY=True) def test_csrf_w_login_csrf_enforcement_2(self): from .mixins import APIClient payload = { @@ -916,7 +931,7 @@ def test_csrf_w_login_csrf_enforcement_2(self): client.get(reverse('getcsrf')) csrftoken = client.cookies['csrftoken'].value - #fail w/o csrftoken in payload + # fail w/o csrftoken in payload resp = client.post(self.login_url, payload) self.assertEquals(resp.status_code, 403) @@ -926,12 +941,12 @@ def test_csrf_w_login_csrf_enforcement_2(self): self.assertTrue('csrftoken' in list(client.cookies.keys())) self.assertEquals(resp.status_code, 200) - ## TEST WITH JWT AUTH HEADER does not make sense + # TEST WITH JWT AUTH HEADER does not make sense - ## TEST WITH COOKIES + # TEST WITH COOKIES resp = client.get('/protected-view/') self.assertEquals(resp.status_code, 200) - #fail w/o csrftoken in payload + # fail w/o csrftoken in payload resp = client.post('/protected-view/', {}) self.assertEquals(resp.status_code, 403) @@ -1018,7 +1033,6 @@ def test_rest_session_login_sets_session_cookie(self): resp = self.post(self.login_url, data=payload, status_code=200) self.assertTrue(settings.SESSION_COOKIE_NAME in resp.cookies.keys()) - @modify_settings(INSTALLED_APPS={'remove': ['rest_framework.authtoken']}) def test_misconfigured_token_model(self): # default token model, but authtoken app not installed raises error