Skip to content

Commit

Permalink
add api view to confirm emails #38
Browse files Browse the repository at this point in the history
Co-authored-by: Julien Labonté <[email protected]>
  • Loading branch information
merwok and JulienLabonte committed Nov 10, 2018
1 parent 856c9f3 commit 0bf0fb5
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load i18n %}
{% load i18n demotags %}
<!doctype html>
<html>
<head>
Expand All @@ -11,12 +11,13 @@
<p>
{% trans "Follow this link to validate your email:" %}<br>
{% url "pages:confirm-email" token=confirmation.external_id as confirmation_url %}
{% frontend_base_url as base_url %}
<a href="{{ base_url }}{{ confirmation_url }}">{{ base_url }}{{ confirmation_url }}</a>
</p>

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

</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{% autoescape off %}
{% load i18n %}
{% load i18n demotags %}

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

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

{% endautoescape %}
Empty file.
10 changes: 10 additions & 0 deletions demo/demo/accounts/templatetags/demotags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django import template


register = template.Library()


@register.simple_tag(takes_context=True)
def frontend_base_url(context):
request = context['request']
return request.build_absolute_uri('/')[:-1]
28 changes: 28 additions & 0 deletions rest_auth_toolkit/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from .utils import get_object_from_setting

try:
import facepy
except ImportError:
facepy = None


User = get_user_model()
EmailConfirmation = get_object_from_setting('email_confirmation_class')


class SignupDeserializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -51,6 +54,31 @@ def create(self, validated_data):
)


class EmailConfirmationDeserializer(serializers.Serializer):
email = serializers.EmailField()
token = serializers.CharField()

def validate(self, data):
msg = None

try:
confirmation = EmailConfirmation.objects.get(
external_id=data['token'],
user__email=data['email'],
)
confirmation.confirm()
except EmailConfirmation.DoesNotExist:
msg = _('Invalid link')
except EmailConfirmation.IsExpired:
# FIXME it's not possible to register with the same email
msg = _('Email expired, please register again')

if msg:
raise ValidationError({'errors': [msg]})

return {'user': confirmation.user}


class LoginDeserializer(serializers.Serializer):
"""Deserializer to find a user from credentials."""

Expand Down
84 changes: 50 additions & 34 deletions rest_auth_toolkit/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from .serializers import FacebookLoginDeserializer, LoginDeserializer, SignupDeserializer
from .models import email_confirmed
from .serializers import (FacebookLoginDeserializer, LoginDeserializer,
SignupDeserializer, EmailConfirmationDeserializer)
from .utils import get_object_from_setting, get_setting, MissingSetting

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


def send_email(request, user, address, confirmation):
"""Send the confirmation email for a new user."""
subject = _('Confirm your email address')
from_address = get_setting('email_confirmation_from')

context = {
'user': user,
'confirmation': confirmation,
}
txt_content = render_to_string('rest_auth_toolkit/email_confirmation.txt',
context, request=request)
html_content = render_to_string('rest_auth_toolkit/email_confirmation.html',
context, request=request)

send_mail(subject=subject,
from_email=from_address, recipient_list=[address],
message=txt_content, html_message=html_content,
fail_silently=False)


class EmailConfirmationView(generics.GenericAPIView):
"""Validate an email address after sign-up.
Response: 200 OK (no content)
Response
`200 OK`
Error response (code 400):
```json
{"errors": {"token": "Error message"}}
```
"""
authentication_classes = ()
permission_classes = ()
serializer_class = get_object_from_setting('email_confirmation_serializer_class',
EmailConfirmationDeserializer)

def post(self, request):
pass
deserializer = self.get_serializer(data=request.data)
deserializer.is_valid(raise_exception=True)

email_confirmed.send(sender=self.__class__,
user=deserializer.validated_data['user'])
return Response()


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


def send_email(request, user, address, confirmation):
"""Send the confirmation email for a new user."""
subject = _('Confirm your email address')
from_address = get_setting('email_confirmation_from')

# The url template tag doesn't include scheme/domain/port, pass a helper
base_url = request.build_absolute_uri('/')[:-1]

context = {
'user': user,
'confirmation': confirmation,
'base_url': base_url,
}
txt_content = render_to_string('rest_auth_toolkit/email_confirmation.txt', context)
html_content = render_to_string('rest_auth_toolkit/email_confirmation.html', context)

send_mail(subject=subject,
from_email=from_address, recipient_list=[address],
message=txt_content, html_message=html_content,
fail_silently=False)


def activate_user(sender, **kwargs):
"""Mark user as active when a confirmation link is visited.
Expand Down

0 comments on commit 0bf0fb5

Please sign in to comment.