Skip to content

Commit

Permalink
fix: resend verification, add demo (#332)
Browse files Browse the repository at this point in the history
  • Loading branch information
aabanaag committed Nov 25, 2021
1 parent b72a55f commit eeaaab4
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 38 deletions.
3 changes: 3 additions & 0 deletions demo/demo/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions demo/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<!-- these pages don't require user token -->
<li><a href="{% url 'signup' %}">Signup</a></li>
<li><a href="{% url 'email-verification' %}">E-mail verification</a></li>
<li><a href="{% url 'resend-email-verification' %}">Resend E-mail verification</a></li>
<li><a href="{% url 'login' %}">Login</a></li>
<li><a href="{% url 'password-reset' %}">Password Reset</a></li>
<li><a href="{% url 'password-reset-confirm' %}">Password Reset Confirm</a></li>
Expand Down
16 changes: 16 additions & 0 deletions demo/templates/fragments/resend_email_verification_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!-- Signup form -->
<form class="form-horizontal ajax-post" role="form" action="{% url 'rest_resend_email' %}">{% csrf_token %}
<div class="form-group">
<label for="email" class="col-sm-2 control-label">E-mail</label>
<div class="col-sm-10">
<input name="email" type="text" class="form-control" id="email" placeholder="Email">
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Resend</button>
</div>
</div>
<div class="form-group api-response"></div>
</form>
8 changes: 8 additions & 0 deletions demo/templates/resend_email_verification.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "base.html" %}

{% block content %}
<div class="row">
<h3>Resend E-mail verification</h3><hr/>
{% include "fragments/resend_email_verification_form.html" %}
</div>
{% endblock %}
12 changes: 4 additions & 8 deletions dj_rest_auth/registration/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 44 additions & 30 deletions dj_rest_auth/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) \
Expand All @@ -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 = {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand All @@ -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 = {
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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 = {
Expand All @@ -821,29 +838,28 @@ 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)

csrfparam = {'csrfmiddlewaretoken': csrftoken}
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=[
Expand All @@ -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 = {
Expand All @@ -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)

Expand All @@ -881,19 +897,18 @@ 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)

csrfparam = {'csrfmiddlewaretoken': csrftoken}
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=[
Expand All @@ -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 = {
Expand All @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit eeaaab4

Please sign in to comment.