Skip to content

Commit 0bf0fb5

Browse files
merwokJulienLabonte
andcommitted
add api view to confirm emails #38
Co-authored-by: Julien Labonté <[email protected]>
1 parent 856c9f3 commit 0bf0fb5

File tree

6 files changed

+94
-38
lines changed

6 files changed

+94
-38
lines changed

demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% load i18n %}
1+
{% load i18n demotags %}
22
<!doctype html>
33
<html>
44
<head>
@@ -11,12 +11,13 @@
1111
<p>
1212
{% trans "Follow this link to validate your email:" %}<br>
1313
{% url "pages:confirm-email" token=confirmation.external_id as confirmation_url %}
14+
{% frontend_base_url as base_url %}
1415
<a href="{{ base_url }}{{ confirmation_url }}">{{ base_url }}{{ confirmation_url }}</a>
1516
</p>
1617

1718
<p>
1819
{% trans "Or send an API request to simulate a front-end application:" %}<br>
19-
<code>HTTP POST {% url "auth:confirm" %} token="{{ confirmation.external_id }}"</code>
20+
<code>HTTP POST {{ base_url }}{% url "auth:confirm" %} email="{{ user.email }}" token="{{ confirmation.external_id }}"</code>
2021
</p>
2122

2223
</div>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{% autoescape off %}
2-
{% load i18n %}
2+
{% load i18n demotags %}
33

44
{% trans "Follow this link to validate your email:" %}
5+
{% frontend_base_url as base_url %}
56
{{ base_url }}{% url "pages:confirm-email" token=confirmation.external_id %}
67

78
{% trans "Or send an API request to simulate a front-end application:" %}
8-
HTTP POST {% url "auth:confirm" %} token="{{ confirmation.external_id }}"
9+
HTTP POST {{ base_url }}{% url "auth:confirm" %} email="{{ user.email }}" token="{{ confirmation.external_id }}"
910

1011
{% endautoescape %}

demo/demo/accounts/templatetags/__init__.py

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django import template
2+
3+
4+
register = template.Library()
5+
6+
7+
@register.simple_tag(takes_context=True)
8+
def frontend_base_url(context):
9+
request = context['request']
10+
return request.build_absolute_uri('/')[:-1]

rest_auth_toolkit/serializers.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
from rest_framework import serializers
88
from rest_framework.exceptions import ValidationError
99

10+
from .utils import get_object_from_setting
11+
1012
try:
1113
import facepy
1214
except ImportError:
1315
facepy = None
1416

1517

1618
User = get_user_model()
19+
EmailConfirmation = get_object_from_setting('email_confirmation_class')
1720

1821

1922
class SignupDeserializer(serializers.ModelSerializer):
@@ -51,6 +54,31 @@ def create(self, validated_data):
5154
)
5255

5356

57+
class EmailConfirmationDeserializer(serializers.Serializer):
58+
email = serializers.EmailField()
59+
token = serializers.CharField()
60+
61+
def validate(self, data):
62+
msg = None
63+
64+
try:
65+
confirmation = EmailConfirmation.objects.get(
66+
external_id=data['token'],
67+
user__email=data['email'],
68+
)
69+
confirmation.confirm()
70+
except EmailConfirmation.DoesNotExist:
71+
msg = _('Invalid link')
72+
except EmailConfirmation.IsExpired:
73+
# FIXME it's not possible to register with the same email
74+
msg = _('Email expired, please register again')
75+
76+
if msg:
77+
raise ValidationError({'errors': [msg]})
78+
79+
return {'user': confirmation.user}
80+
81+
5482
class LoginDeserializer(serializers.Serializer):
5583
"""Deserializer to find a user from credentials."""
5684

rest_auth_toolkit/views.py

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from rest_framework.permissions import IsAuthenticated
88
from rest_framework.response import Response
99

10-
from .serializers import FacebookLoginDeserializer, LoginDeserializer, SignupDeserializer
10+
from .models import email_confirmed
11+
from .serializers import (FacebookLoginDeserializer, LoginDeserializer,
12+
SignupDeserializer, EmailConfirmationDeserializer)
1113
from .utils import get_object_from_setting, get_setting, MissingSetting
1214

1315
try:
@@ -30,16 +32,20 @@ class SignupView(generics.GenericAPIView):
3032
- rest_auth_toolkit/email_confirmation.html
3133
3234
The templates will be passed the User and EmailConfirmation instances
33-
(as variables *user* and *confirmation*). To help generating links,
34-
a variable *base_url* with a value like "https://domain" (scheme,
35-
domain and optional port depending on the request, but no path), which
36-
lets you write code like `{{ base_url }}{% url "my-route" %}`.
35+
(as variables *user* and *confirmation*) as well as the request;
36+
note that template context processors are not available in email
37+
teamplates.
3738
38-
It is up to your project to define what the link is. The demo app
39-
demonstrates a simple Django view that validates the email validation
40-
token in the URL; for a project with a front-end site (e.g. a JavaScript
41-
app) on a different domain than the Django API, a custom template tag
42-
could be used to generate the right URL for the front-end site.
39+
It is up to your project to generate a link that will work, using
40+
your own template code or custom tags.
41+
42+
The demo app shows one way to handle this: a Django view validates
43+
the email confirmation token in its URL; the link is generated with
44+
a custom template tag because Django doesn't offer a tag to create
45+
full URLs. For a project with a front-end site (e.g. a JavaScript app)
46+
on a different domain than the API powered by Django, the template tag
47+
could for example use a setting to know the front-end domain name + a
48+
mapping of front-end routes to generate the path portion of the links.
4349
4450
If the setting is false, the user will be active immediately.
4551
"""
@@ -77,19 +83,51 @@ def post(self, request):
7783
return Response(status=status.HTTP_201_CREATED)
7884

7985

86+
def send_email(request, user, address, confirmation):
87+
"""Send the confirmation email for a new user."""
88+
subject = _('Confirm your email address')
89+
from_address = get_setting('email_confirmation_from')
90+
91+
context = {
92+
'user': user,
93+
'confirmation': confirmation,
94+
}
95+
txt_content = render_to_string('rest_auth_toolkit/email_confirmation.txt',
96+
context, request=request)
97+
html_content = render_to_string('rest_auth_toolkit/email_confirmation.html',
98+
context, request=request)
99+
100+
send_mail(subject=subject,
101+
from_email=from_address, recipient_list=[address],
102+
message=txt_content, html_message=html_content,
103+
fail_silently=False)
104+
105+
80106
class EmailConfirmationView(generics.GenericAPIView):
81107
"""Validate an email address after sign-up.
82108
83-
Response: 200 OK (no content)
109+
Response
110+
111+
`200 OK`
84112
85113
Error response (code 400):
86114
87115
```json
88116
{"errors": {"token": "Error message"}}
89117
```
90118
"""
119+
authentication_classes = ()
120+
permission_classes = ()
121+
serializer_class = get_object_from_setting('email_confirmation_serializer_class',
122+
EmailConfirmationDeserializer)
123+
91124
def post(self, request):
92-
pass
125+
deserializer = self.get_serializer(data=request.data)
126+
deserializer.is_valid(raise_exception=True)
127+
128+
email_confirmed.send(sender=self.__class__,
129+
user=deserializer.validated_data['user'])
130+
return Response()
93131

94132

95133
class LoginView(generics.GenericAPIView):
@@ -176,28 +214,6 @@ def post(self, request):
176214
return Response(status=status.HTTP_200_OK)
177215

178216

179-
def send_email(request, user, address, confirmation):
180-
"""Send the confirmation email for a new user."""
181-
subject = _('Confirm your email address')
182-
from_address = get_setting('email_confirmation_from')
183-
184-
# The url template tag doesn't include scheme/domain/port, pass a helper
185-
base_url = request.build_absolute_uri('/')[:-1]
186-
187-
context = {
188-
'user': user,
189-
'confirmation': confirmation,
190-
'base_url': base_url,
191-
}
192-
txt_content = render_to_string('rest_auth_toolkit/email_confirmation.txt', context)
193-
html_content = render_to_string('rest_auth_toolkit/email_confirmation.html', context)
194-
195-
send_mail(subject=subject,
196-
from_email=from_address, recipient_list=[address],
197-
message=txt_content, html_message=html_content,
198-
fail_silently=False)
199-
200-
201217
def activate_user(sender, **kwargs):
202218
"""Mark user as active when a confirmation link is visited.
203219

0 commit comments

Comments
 (0)