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 @@
+
+
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