From 526fd6a7cb290f964012b3d8fbca6a21edd70934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Wed, 17 Oct 2018 21:32:42 -0400 Subject: [PATCH 01/13] make email confirmation fully optional #38 --- rest_auth_toolkit/views.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/rest_auth_toolkit/views.py b/rest_auth_toolkit/views.py index e6d43a3..6275a2a 100644 --- a/rest_auth_toolkit/views.py +++ b/rest_auth_toolkit/views.py @@ -31,6 +31,8 @@ class SignupView(generics.GenericAPIView): email confirmation instance using the ID and call the confirm method. To use a field that's not named 'id', define the setting email_confirmation_lookup_param (this will change the URL pattern). + + If the setting is false, the user will be active immediately. """ authentication_classes = () permission_classes = () @@ -48,13 +50,18 @@ def post(self, request): """ deserializer = self.get_serializer(data=request.data) deserializer.is_valid(raise_exception=True) - user = deserializer.save() - if self.email_confirmation_class is None: - raise MissingSetting('email_confirmation_string') + confirm_email = get_setting('email_confirmation_send_email', True) + + if not confirm_email: + deserializer.save(is_active=True) + else: + user = deserializer.save() + + if self.email_confirmation_class is None: + raise MissingSetting('email_confirmation_class') - confirmation = self.email_confirmation_class.objects.create(user=user) - if get_setting('email_confirmation_send_email', True): + confirmation = self.email_confirmation_class.objects.create(user=user) email_field = user.get_email_field_name() send_email(request, user, getattr(user, email_field), confirmation) From e8864b6dc8a256346b838b02673d0dff0a047099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Wed, 24 Oct 2018 12:32:49 -0400 Subject: [PATCH 02/13] improve confirmation email link generation #38 --- .../rest_auth_toolkit/email_confirmation.html | 3 +- .../rest_auth_toolkit/email_confirmation.txt | 2 +- demo/demo/pages/auth_urls.py | 10 ------ demo/demo/pages/templates/base.html | 13 ++++++++ demo/demo/pages/templates/error.html | 16 +++------- demo/demo/pages/templates/index.html | 19 ++++-------- demo/demo/pages/templates/welcome.html | 10 ++++++ demo/demo/pages/urls.py | 1 + demo/demo/pages/views.py | 10 ++++-- demo/demo/settings.py | 2 -- demo/demo/urls.py | 1 - rest_auth_toolkit/views.py | 31 ++++++++++++------- 12 files changed, 64 insertions(+), 54 deletions(-) rename {rest_auth_toolkit => demo/demo/accounts}/templates/rest_auth_toolkit/email_confirmation.html (61%) rename {rest_auth_toolkit => demo/demo/accounts}/templates/rest_auth_toolkit/email_confirmation.txt (59%) delete mode 100644 demo/demo/pages/auth_urls.py create mode 100644 demo/demo/pages/templates/base.html create mode 100644 demo/demo/pages/templates/welcome.html diff --git a/rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.html b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html similarity index 61% rename from rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.html rename to demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html index 3ad3539..2427f4d 100644 --- a/rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.html +++ b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html @@ -9,7 +9,8 @@

{% trans "Follow this link to validate your email:" %}
- {{ confirmation_url }}

+ {% url "pages:confirm-email" token=confirmation.external_id as confirmation_url %} + {{ base_url }}{{ confirmation_url }}

diff --git a/rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.txt b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt similarity index 59% rename from rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.txt rename to demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt index 22a01d5..2d6d03b 100644 --- a/rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.txt +++ b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt @@ -2,6 +2,6 @@ {% load i18n %} {% trans "Follow this link to validate your email:" %} -{{ confirmation_url }} +{{ base_url }}{% url "pages:confirm-email" token=confirmation.external_id %} {% endautoescape %} diff --git a/demo/demo/pages/auth_urls.py b/demo/demo/pages/auth_urls.py deleted file mode 100644 index f4e63d1..0000000 --- a/demo/demo/pages/auth_urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.conf.urls import url - -from .views import email_view - - -app_name = 'app-auth' - -urlpatterns = [ - url(r'^emails/(?P[^/.]+)$', email_view, name='email-confirmation'), -] diff --git a/demo/demo/pages/templates/base.html b/demo/demo/pages/templates/base.html new file mode 100644 index 0000000..88bf32e --- /dev/null +++ b/demo/demo/pages/templates/base.html @@ -0,0 +1,13 @@ +{% load i18n %} +{% get_current_language as LANGUAGE_CODE %} + + + + + + {% block title %}{% endblock %} + + + {% block body %}{% endblock %} + + diff --git a/demo/demo/pages/templates/error.html b/demo/demo/pages/templates/error.html index cb2a99e..a791ac2 100644 --- a/demo/demo/pages/templates/error.html +++ b/demo/demo/pages/templates/error.html @@ -1,18 +1,10 @@ +{% extends "base.html" %} {% load i18n %} -{% get_current_language as LANGUAGE_CODE %} - - - - - - {{ site_name }} - - +{% block title %}Error! {{ site_name }}{% endblock %} +{% block body %}

{% trans "Error!" %}

{{ error }}

- - - +{% endblock %} diff --git a/demo/demo/pages/templates/index.html b/demo/demo/pages/templates/index.html index 2d42d3a..3022ceb 100644 --- a/demo/demo/pages/templates/index.html +++ b/demo/demo/pages/templates/index.html @@ -1,13 +1,7 @@ -{% load i18n %} -{% get_current_language as LANGUAGE_CODE %} - - - - - - {{ site_name }} - - +{% extends "base.html" %} +{% block title %}{{ site_name }}{% endblock %} + +{% block body %} - - +{% endblock %} diff --git a/demo/demo/pages/templates/welcome.html b/demo/demo/pages/templates/welcome.html new file mode 100644 index 0000000..e21a569 --- /dev/null +++ b/demo/demo/pages/templates/welcome.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load i18n %} +{% block title %}Success! {{ site_name }}{% endblock %} + +{% block body %} +
+

{% trans "Success!" %}

+

Your address {{ email }} is now confirmed.

+
+{% endblock %} diff --git a/demo/demo/pages/urls.py b/demo/demo/pages/urls.py index d6285d5..6def972 100644 --- a/demo/demo/pages/urls.py +++ b/demo/demo/pages/urls.py @@ -8,4 +8,5 @@ urlpatterns = [ url(r'^$', views.index, name='root'), + url(r'^welcome/(?P[^/.]+)$', views.confirm_email, name='confirm-email'), ] diff --git a/demo/demo/pages/views.py b/demo/demo/pages/views.py index 7c95058..c0fd954 100644 --- a/demo/demo/pages/views.py +++ b/demo/demo/pages/views.py @@ -14,12 +14,12 @@ def index(request): return render(request, 'index.html', context=ctx) -def email_view(request, external_id): +def confirm_email(request, token): """Landing page for links in confirmation emails.""" error = None try: - confirmation = EmailConfirmation.objects.get(external_id=external_id) + confirmation = EmailConfirmation.objects.get(external_id=token) confirmation.confirm() except EmailConfirmation.DoesNotExist: error = _('Invalid link') @@ -33,4 +33,8 @@ def email_view(request, external_id): } return render(request, 'error.html', context=ctx) else: - return index(request) + ctx = { + 'site_name': 'Demo', + 'email': confirmation.user.email, + } + return render(request, 'welcome.html', context=ctx) diff --git a/demo/demo/settings.py b/demo/demo/settings.py index 3276676..4cc69d0 100644 --- a/demo/demo/settings.py +++ b/demo/demo/settings.py @@ -102,7 +102,6 @@ STATIC_URL = '/static/' -# STATIC_ROOT = os.path.join(BASE_DIR, 'static') REST_FRAMEWORK = { @@ -122,6 +121,5 @@ REST_AUTH_TOOLKIT = { 'email_confirmation_class': 'demo.accounts.models.EmailConfirmation', 'email_confirmation_from': 'auth-demo@localhost', - 'email_confirmation_lookup_field': 'external_id', 'api_token_class': 'demo.accounts.models.APIToken', } diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 8c03624..31b2dd9 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -32,5 +32,4 @@ class GoAwayRenderer(DocumentationRenderer): path('admin/', admin.site.urls), path('api/', include(api_urlpatterns)), path('', include('demo.pages.urls')), - path('', include('demo.pages.auth_urls')), ] diff --git a/rest_auth_toolkit/views.py b/rest_auth_toolkit/views.py index 6275a2a..e35f39b 100644 --- a/rest_auth_toolkit/views.py +++ b/rest_auth_toolkit/views.py @@ -1,7 +1,6 @@ from django.contrib.auth import get_user_model from django.core.mail import send_mail from django.template.loader import render_to_string -from django.urls import reverse from django.utils.translation import gettext as _ from rest_framework import generics, status, views @@ -26,11 +25,21 @@ class SignupView(generics.GenericAPIView): If the setting email_confirmation_send_email is true (default), the function send_email will be called. That function requires - that your app define a route named app-auth:email-confirmation - with an id parameter; the view for this route should get an - email confirmation instance using the ID and call the confirm - method. To use a field that's not named 'id', define the setting - email_confirmation_lookup_param (this will change the URL pattern). + that your project defines defines two email templates: + - rest_auth_toolkit/email_confirmation.txt + - 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" %}`. + + 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. If the setting is false, the user will be active immediately. """ @@ -157,14 +166,14 @@ def send_email(request, user, address, confirmation): subject = _('Confirm your email address') from_address = get_setting('email_confirmation_from') - lookup_field = get_setting('email_confirmation_lookup_field', 'id') - confirmation_url = request.build_absolute_uri( - reverse('app-auth:email-confirmation', - kwargs={lookup_field: getattr(confirmation, lookup_field)})) # The url template tag doesn't include scheme/domain/port, pass a helper base_url = request.build_absolute_uri('/')[:-1] - context = {'base_url': base_url, 'confirmation_url': confirmation_url} + 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) From 272cabb1ec3d8a5b3446305b5359ec7b59b8435d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Mon, 29 Oct 2018 19:31:08 -0400 Subject: [PATCH 03/13] demo: update dependencies --- demo/requirements.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 5d6bf77..241188a 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -4,26 +4,25 @@ # # pip-compile requirements.in # -certifi==2018.8.24 # via requests +certifi==2018.10.15 # via requests chardet==3.0.4 # via requests -click==6.7 # via pip-tools +click==7.0 # via pip-tools coreapi==2.3.3 coreschema==0.0.4 # via coreapi dj-database-url==0.5.0 django-debug-toolbar==1.10.1 django-model-utils==3.1.2 django-shortuuidfield==0.1.3 -django==2.0.8 +django==2.0.9 djangorestframework==3.9.0 facepy==1.0.9 -first==2.0.1 # via pip-tools idna==2.7 # via requests itypes==1.1.0 # via coreapi jinja2==2.10 # via coreschema markupsafe==1.1.0 # via jinja2 -pip-tools==2.0.2 +pip-tools==3.1.0 psycopg2==2.7.5 -pytz==2018.5 # via django +pytz==2018.7 # via django requests==2.19.1 # via coreapi, facepy shortuuid==0.5.0 # via django-shortuuidfield six==1.11.0 # via django-shortuuidfield, facepy, pip-tools From eb4ba797851f60c9997cc41cdbb361713349f026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Tue, 30 Oct 2018 12:50:21 -0400 Subject: [PATCH 04/13] add skeleton for email confirmation endpoint --- .../rest_auth_toolkit/email_confirmation.html | 13 ++++++++++--- .../rest_auth_toolkit/email_confirmation.txt | 3 +++ demo/demo/urls.py | 9 ++++++++- rest_auth_toolkit/app.py | 2 +- rest_auth_toolkit/views.py | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html index 2427f4d..3599187 100644 --- a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html +++ b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html @@ -8,9 +8,16 @@
-

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

+

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

+ +

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

diff --git a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt index 2d6d03b..8551bb2 100644 --- a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt +++ b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt @@ -4,4 +4,7 @@ {% trans "Follow this link to validate your email:" %} {{ 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 }}" + {% endautoescape %} diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 31b2dd9..6a998be 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -6,7 +6,13 @@ from rest_framework.documentation import include_docs_urls from rest_framework.renderers import DocumentationRenderer -from rest_auth_toolkit.views import FacebookLoginView, LoginView, LogoutView, SignupView +from rest_auth_toolkit.views import ( + EmailConfirmationView, + FacebookLoginView, + LoginView, + LogoutView, + SignupView, +) class GoAwayRenderer(DocumentationRenderer): @@ -16,6 +22,7 @@ class GoAwayRenderer(DocumentationRenderer): auth_urlpatterns = [ path('signup/', SignupView.as_view(), name='signup'), + path('confirm/', EmailConfirmationView.as_view(), name='confirm'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('fb-login/', FacebookLoginView.as_view(), name='fb-login'), diff --git a/rest_auth_toolkit/app.py b/rest_auth_toolkit/app.py index 594ce45..48edb28 100644 --- a/rest_auth_toolkit/app.py +++ b/rest_auth_toolkit/app.py @@ -5,7 +5,7 @@ class RestAuthToolkitConfig(AppConfig): """Default app config for RATK. This installs a signal handler to set user.is_active when - email_confirmed is emitted. + email_confirmed is emitted by EmailConfirmationView. """ name = 'rest_auth_toolkit' diff --git a/rest_auth_toolkit/views.py b/rest_auth_toolkit/views.py index e35f39b..a4a3364 100644 --- a/rest_auth_toolkit/views.py +++ b/rest_auth_toolkit/views.py @@ -77,6 +77,21 @@ def post(self, request): return Response(status=status.HTTP_201_CREATED) +class EmailConfirmationView(generics.GenericAPIView): + """Validate an email address after sign-up. + + Response: 200 OK (no content) + + Error response (code 400): + + ```json + {"errors": {"token": "Error message"}} + ``` + """ + def post(self, request): + pass + + class LoginView(generics.GenericAPIView): """Email address log-in endpoint. From 0bf0fb50830e23b8cb56f9de92037e9e846ab840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Sat, 10 Nov 2018 16:30:54 -0500 Subject: [PATCH 05/13] add api view to confirm emails #38 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Julien Labonté --- .../rest_auth_toolkit/email_confirmation.html | 5 +- .../rest_auth_toolkit/email_confirmation.txt | 5 +- demo/demo/accounts/templatetags/__init__.py | 0 demo/demo/accounts/templatetags/demotags.py | 10 +++ rest_auth_toolkit/serializers.py | 28 +++++++ rest_auth_toolkit/views.py | 84 +++++++++++-------- 6 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 demo/demo/accounts/templatetags/__init__.py create mode 100644 demo/demo/accounts/templatetags/demotags.py diff --git a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html index 3599187..42f3464 100644 --- a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html +++ b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n demotags %} @@ -11,12 +11,13 @@

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

{% 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 }}"

diff --git a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt index 8551bb2..51d09b7 100644 --- a/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt +++ b/demo/demo/accounts/templates/rest_auth_toolkit/email_confirmation.txt @@ -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 %} diff --git a/demo/demo/accounts/templatetags/__init__.py b/demo/demo/accounts/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demo/demo/accounts/templatetags/demotags.py b/demo/demo/accounts/templatetags/demotags.py new file mode 100644 index 0000000..3dd9a11 --- /dev/null +++ b/demo/demo/accounts/templatetags/demotags.py @@ -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] diff --git a/rest_auth_toolkit/serializers.py b/rest_auth_toolkit/serializers.py index ad61067..0972ac5 100644 --- a/rest_auth_toolkit/serializers.py +++ b/rest_auth_toolkit/serializers.py @@ -7,6 +7,8 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError +from .utils import get_object_from_setting + try: import facepy except ImportError: @@ -14,6 +16,7 @@ User = get_user_model() +EmailConfirmation = get_object_from_setting('email_confirmation_class') class SignupDeserializer(serializers.ModelSerializer): @@ -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.""" diff --git a/rest_auth_toolkit/views.py b/rest_auth_toolkit/views.py index a4a3364..472692a 100644 --- a/rest_auth_toolkit/views.py +++ b/rest_auth_toolkit/views.py @@ -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: @@ -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. """ @@ -77,10 +83,32 @@ 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): @@ -88,8 +116,18 @@ class EmailConfirmationView(generics.GenericAPIView): {"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): @@ -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. From 03456b6e6bde9141e7780db6f9e4fb7bc2c5b84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Wed, 27 Feb 2019 22:28:29 -0500 Subject: [PATCH 06/13] back to dev --- CHANGELOG.md | 2 ++ rest_auth_toolkit/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f84bc..c7b83e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog for Rest-Framework-Auth-Toolkit +## v0.11 (unreleased) + ## v0.10 diff --git a/rest_auth_toolkit/__init__.py b/rest_auth_toolkit/__init__.py index 3a1ac8b..a99b3d9 100644 --- a/rest_auth_toolkit/__init__.py +++ b/rest_auth_toolkit/__init__.py @@ -1,5 +1,5 @@ """Simple + flexible signup and login for Django APIs""" -__version__ = '0.10' +__version__ = '0.11.dev' default_app_config = 'rest_auth_toolkit.app.RestAuthToolkitConfig' From 8bf70b3bc7ccd70bd170cf0d74e86ce263406b74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 12 Apr 2019 19:49:30 -0400 Subject: [PATCH 07/13] update jinja2 from 2.10 to 2.10.1 (#76) --- demo/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index e1bfd51..2d0bfc6 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -18,14 +18,14 @@ djangorestframework==3.9.1 facepy==1.0.9 idna==2.8 # via requests itypes==1.1.0 # via coreapi -jinja2==2.10 # via coreschema +jinja2==2.10.1 # via coreschema markupsafe==1.1.1 # via jinja2 pip-tools==3.4.0 psycopg2==2.7.7 pytz==2018.9 # via django -requests==2.21.0 # via coreapi +requests==2.21.0 # via coreapi, facepy shortuuid==0.5.0 # via django-shortuuidfield -six==1.12.0 # via django-shortuuidfield, pip-tools +six==1.12.0 # via django-shortuuidfield, facepy, pip-tools sqlparse==0.2.4 # via django-debug-toolbar uritemplate==3.0.0 # via coreapi urllib3==1.24.1 # via requests From 51ab26f2be0e32debcf99356cb9d7cd411f674c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Fri, 19 Apr 2019 14:38:47 -0400 Subject: [PATCH 08/13] update dependencies (#82) --- demo/requirements.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 2d0bfc6..84a2a1a 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -4,7 +4,7 @@ # # pip-compile requirements.in # -certifi==2018.11.29 # via requests +certifi==2019.3.9 # via requests chardet==3.0.4 # via requests click==7.0 # via pip-tools coreapi==2.3.3 @@ -13,19 +13,19 @@ dj-database-url==0.5.0 django-debug-toolbar==1.11 django-model-utils==3.1.2 django-shortuuidfield==0.1.3 -django==2.1.7 -djangorestframework==3.9.1 -facepy==1.0.9 +django==2.1.8 +djangorestframework==3.9.2 +facepy==1.0.10 idna==2.8 # via requests itypes==1.1.0 # via coreapi jinja2==2.10.1 # via coreschema markupsafe==1.1.1 # via jinja2 -pip-tools==3.4.0 -psycopg2==2.7.7 -pytz==2018.9 # via django +pip-tools==3.6.0 +psycopg2==2.8.2 +pytz==2019.1 # via django requests==2.21.0 # via coreapi, facepy shortuuid==0.5.0 # via django-shortuuidfield six==1.12.0 # via django-shortuuidfield, facepy, pip-tools -sqlparse==0.2.4 # via django-debug-toolbar +sqlparse==0.3.0 # via django-debug-toolbar uritemplate==3.0.0 # via coreapi -urllib3==1.24.1 # via requests +urllib3==1.24.2 # via requests From 2207f822f3174f53b3432d04b9be45ca9ea6df90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 29 Apr 2019 12:39:20 -0400 Subject: [PATCH 09/13] update pip-tools from 3.6.0 to 3.6.1 (#83) Signed-off-by: dependabot[bot] --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 84a2a1a..6a30a18 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -20,7 +20,7 @@ idna==2.8 # via requests itypes==1.1.0 # via coreapi jinja2==2.10.1 # via coreschema markupsafe==1.1.1 # via jinja2 -pip-tools==3.6.0 +pip-tools==3.6.1 psycopg2==2.8.2 pytz==2019.1 # via django requests==2.21.0 # via coreapi, facepy From f612dc782effb0c46124241c25ee3a16cd437934 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" Date: Mon, 10 Jun 2019 15:03:31 -0400 Subject: [PATCH 10/13] update djangorestframework from 3.9.2 to 3.9.4 (#86) --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 6a30a18..4dcc790 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -14,7 +14,7 @@ django-debug-toolbar==1.11 django-model-utils==3.1.2 django-shortuuidfield==0.1.3 django==2.1.8 -djangorestframework==3.9.2 +djangorestframework==3.9.4 facepy==1.0.10 idna==2.8 # via requests itypes==1.1.0 # via coreapi From b37d04f79c0a25c1504185d08048554fb76d59e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 10 Jun 2019 15:23:29 -0400 Subject: [PATCH 11/13] update django from 2.1.8 to 2.1.9 (#88) Signed-off-by: dependabot[bot] --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 4dcc790..25167e9 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -13,7 +13,7 @@ dj-database-url==0.5.0 django-debug-toolbar==1.11 django-model-utils==3.1.2 django-shortuuidfield==0.1.3 -django==2.1.8 +django==2.1.9 djangorestframework==3.9.4 facepy==1.0.10 idna==2.8 # via requests From 524b1dcb4a8cdb1279f0871d4033ead7f0e9b339 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2019 17:25:46 -0400 Subject: [PATCH 12/13] updqte django from 2.1.9 to 2.1.10 (#94) --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 25167e9..352ab0e 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -13,7 +13,7 @@ dj-database-url==0.5.0 django-debug-toolbar==1.11 django-model-utils==3.1.2 django-shortuuidfield==0.1.3 -django==2.1.9 +django==2.1.10 djangorestframework==3.9.4 facepy==1.0.10 idna==2.8 # via requests From 339376ec3d1697f65ae0ee3e32255afaa3c478f5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2019 10:32:43 -0400 Subject: [PATCH 13/13] update django from 2.1.10 to 2.1.11 (#101) --- demo/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.txt b/demo/requirements.txt index 352ab0e..c4c2fba 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -13,7 +13,7 @@ dj-database-url==0.5.0 django-debug-toolbar==1.11 django-model-utils==3.1.2 django-shortuuidfield==0.1.3 -django==2.1.10 +django==2.1.11 djangorestframework==3.9.4 facepy==1.0.10 idna==2.8 # via requests