Skip to content

Commit cf07157

Browse files
committed
Merge branch 'feature/rework-email-tokens' of github.com:caravancoop/rest-framework-auth-toolkit into feature/rework-email-tokens
2 parents e248ad9 + 4eced47 commit cf07157

File tree

17 files changed

+154
-86
lines changed

17 files changed

+154
-86
lines changed

.circleci/config.yml

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ jobs:
2626
- restore_cache:
2727
keys:
2828
- rest-auth-toolkit-py37-v6-{{ arch }}-{{ checksum "requirements-test.txt" }}-{{ checksum "demo/requirements.txt" }}
29-
- rest-auth-toolkit-py37-v5-{{ arch }}-{{ checksum "requirements-test.txt" }}-{{ checksum "demo/requirements.txt" }}
3029
- rest-auth-toolkit-py37-v6-{{ arch }}-{{ checksum "requirements-test.txt" }}
3130
- rest-auth-toolkit-py37-v6-{{ arch }}-
3231
- run:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{% load i18n %}
2+
<!doctype html>
3+
<html>
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
</head>
8+
<body style="background-color: #F7F8FA;">
9+
<div>
10+
11+
<p>
12+
{% trans "Follow this link to validate your email:" %}<br>
13+
{% url "pages:confirm-email" token=confirmation.external_id as confirmation_url %}
14+
<a href="{{ base_url }}{{ confirmation_url }}">{{ base_url }}{{ confirmation_url }}</a>
15+
</p>
16+
17+
<p>
18+
{% 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+
</p>
21+
22+
</div>
23+
</body>
24+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% autoescape off %}
2+
{% load i18n %}
3+
4+
{% trans "Follow this link to validate your email:" %}
5+
{{ base_url }}{% url "pages:confirm-email" token=confirmation.external_id %}
6+
7+
{% trans "Or send an API request to simulate a front-end application:" %}
8+
HTTP POST {% url "auth:confirm" %} token="{{ confirmation.external_id }}"
9+
10+
{% endautoescape %}

demo/demo/pages/auth_urls.py

-10
This file was deleted.

demo/demo/pages/templates/base.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{% load i18n %}
2+
{% get_current_language as LANGUAGE_CODE %}
3+
<!doctype html>
4+
<html lang="{{ LANGUAGE_CODE }}">
5+
<head>
6+
<meta charset="utf-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<title>{% block title %}{% endblock %}</title>
9+
</head>
10+
<body>
11+
{% block body %}{% endblock %}
12+
</body>
13+
</html>

demo/demo/pages/templates/error.html

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1+
{% extends "base.html" %}
12
{% load i18n %}
2-
{% get_current_language as LANGUAGE_CODE %}
3-
<!doctype html>
4-
<html lang="{{ LANGUAGE_CODE }}">
5-
<head>
6-
<meta charset="utf-8">
7-
<meta name="viewport" content="width=device-width, initial-scale=1">
8-
<title>{{ site_name }}</title>
9-
</head>
10-
<body>
3+
{% block title %}Error! {{ site_name }}{% endblock %}
114

5+
{% block body %}
126
<div>
137
<h1>{% trans "Error!" %}</h1>
148
<p>{{ error }}</p>
159
</div>
16-
17-
</body>
18-
</html>
10+
{% endblock %}

demo/demo/pages/templates/index.html

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
{% load i18n %}
2-
{% get_current_language as LANGUAGE_CODE %}
3-
<!doctype html>
4-
<html lang="{{ LANGUAGE_CODE }}">
5-
<head>
6-
<meta charset="utf-8">
7-
<meta name="viewport" content="width=device-width, initial-scale=1">
8-
<title>{{ site_name }}</title>
9-
</head>
10-
<body>
1+
{% extends "base.html" %}
2+
{% block title %}{{ site_name }}{% endblock %}
3+
4+
{% block body %}
115
<script>
126
window.fbAsyncInit = function() {
137
FB.init({appId: "{{ fb_app_id }}", xfbml: true, version: "v2.9"});
@@ -34,12 +28,11 @@
3428
<div id="fb-root"></div>
3529

3630
<div>
37-
<h1>Hello!</h1>
31+
<h1>Hello world!</h1>
3832
<button onclick="fb_login()">Login with Facebook</button>
3933
</div>
4034

4135
<script src="https://code.jquery.com/jquery-3.2.1.min.js"
4236
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
4337
crossorigin="anonymous"></script>
44-
</body>
45-
</html>
38+
{% endblock %}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% extends "base.html" %}
2+
{% load i18n %}
3+
{% block title %}Success! {{ site_name }}{% endblock %}
4+
5+
{% block body %}
6+
<div>
7+
<h1>{% trans "Success!" %}</h1>
8+
<p>Your address {{ email }} is now confirmed.</p>
9+
</div>
10+
{% endblock %}

demo/demo/pages/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88

99
urlpatterns = [
1010
url(r'^$', views.index, name='root'),
11+
url(r'^welcome/(?P<token>[^/.]+)$', views.confirm_email, name='confirm-email'),
1112
]

demo/demo/pages/views.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ def index(request):
1414
return render(request, 'index.html', context=ctx)
1515

1616

17-
def email_view(request, external_id):
17+
def confirm_email(request, token):
1818
"""Landing page for links in confirmation emails."""
1919
error = None
2020

2121
try:
22-
confirmation = EmailConfirmation.objects.get(external_id=external_id)
22+
confirmation = EmailConfirmation.objects.get(external_id=token)
2323
confirmation.confirm()
2424
except EmailConfirmation.DoesNotExist:
2525
error = _('Invalid link')
@@ -33,4 +33,8 @@ def email_view(request, external_id):
3333
}
3434
return render(request, 'error.html', context=ctx)
3535
else:
36-
return index(request)
36+
ctx = {
37+
'site_name': 'Demo',
38+
'email': confirmation.user.email,
39+
}
40+
return render(request, 'welcome.html', context=ctx)

demo/demo/settings.py

-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@
102102

103103

104104
STATIC_URL = '/static/'
105-
# STATIC_ROOT = os.path.join(BASE_DIR, 'static')
106105

107106

108107
REST_FRAMEWORK = {
@@ -121,6 +120,5 @@
121120
REST_AUTH_TOOLKIT = {
122121
'email_confirmation_class': 'demo.accounts.models.EmailConfirmation',
123122
'email_confirmation_from': 'auth-demo@localhost',
124-
'email_confirmation_lookup_field': 'external_id',
125123
'api_token_class': 'demo.accounts.models.APIToken',
126124
}

demo/demo/urls.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,24 @@
33
from django.urls import include, path
44
from django.contrib import admin
55

6+
<<<<<<< HEAD
67
from rest_framework.documentation import include_docs_urls
78

89
from rest_auth_toolkit.views import FacebookLoginView, LoginView, LogoutView, SignupView
10+
=======
11+
from rest_auth_toolkit.views import (
12+
EmailConfirmationView,
13+
FacebookLoginView,
14+
LoginView,
15+
LogoutView,
16+
SignupView,
17+
)
18+
>>>>>>> 4eced4707f4c7b07cbbacd242e095ed010784349
919

1020

1121
auth_urlpatterns = [
1222
path('signup/', SignupView.as_view(), name='signup'),
23+
path('confirm/', EmailConfirmationView.as_view(), name='confirm'),
1324
path('login/', LoginView.as_view(), name='login'),
1425
path('logout/', LogoutView.as_view(), name='logout'),
1526
path('fb-login/', FacebookLoginView.as_view(), name='fb-login'),
@@ -25,5 +36,4 @@
2536
path('admin/', admin.site.urls),
2637
path('api/', include(api_urlpatterns)),
2738
path('', include('demo.pages.urls')),
28-
path('', include('demo.pages.auth_urls')),
2939
]

demo/requirements.txt

+20-4
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,24 @@
44
#
55
# pip-compile requirements.in
66
#
7-
certifi==2018.8.24 # via requests
7+
certifi==2018.10.15 # via requests
88
chardet==3.0.4 # via requests
9+
<<<<<<< HEAD
910
click==6.7 # via pip-tools
1011
coreapi==2.3.3
1112
coreschema==0.0.4 # via coreapi
13+
=======
14+
click==7.0 # via pip-tools
15+
>>>>>>> 4eced4707f4c7b07cbbacd242e095ed010784349
1216
dj-database-url==0.5.0
1317
django-debug-toolbar==1.10.1
1418
django-model-utils==3.1.2
1519
django-shortuuidfield==0.1.3
16-
django==2.0.8
17-
djangorestframework==3.8.2
20+
django==2.0.9
21+
djangorestframework==3.9.0
1822
facepy==1.0.9
19-
first==2.0.1 # via pip-tools
2023
idna==2.7 # via requests
24+
<<<<<<< HEAD
2125
itypes==1.1.0 # via coreapi
2226
jinja2==2.10 # via coreschema
2327
markdown==2.6.11
@@ -32,3 +36,15 @@ six==1.11.0 # via django-shortuuidfield, facepy, pip-tools
3236
sqlparse==0.2.4 # via django-debug-toolbar
3337
uritemplate==3.0.0 # via coreapi
3438
urllib3==1.23 # via requests
39+
=======
40+
markdown==3.0.1
41+
pip-tools==3.1.0
42+
psycopg2==2.7.5
43+
pygments==2.2.0
44+
pytz==2018.7 # via django
45+
requests==2.20.0 # via facepy
46+
shortuuid==0.5.0 # via django-shortuuidfield
47+
six==1.11.0 # via django-shortuuidfield, facepy, pip-tools
48+
sqlparse==0.2.4 # via django-debug-toolbar
49+
urllib3==1.24 # via requests
50+
>>>>>>> 4eced4707f4c7b07cbbacd242e095ed010784349

rest_auth_toolkit/app.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class RestAuthToolkitConfig(AppConfig):
55
"""Default app config for RATK.
66
77
This installs a signal handler to set user.is_active when
8-
email_confirmed is emitted.
8+
email_confirmed is emitted by EmailConfirmationView.
99
"""
1010
name = 'rest_auth_toolkit'
1111

rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.html

-16
This file was deleted.

rest_auth_toolkit/templates/rest_auth_toolkit/email_confirmation.txt

-7
This file was deleted.

rest_auth_toolkit/views.py

+47-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from django.contrib.auth import get_user_model
22
from django.core.mail import send_mail
33
from django.template.loader import render_to_string
4-
from django.urls import reverse
54
from django.utils.translation import gettext as _
65

76
from rest_framework import generics, status, views
@@ -38,27 +37,59 @@ def post(self, request):
3837
3938
If the setting email_confirmation_send_email is true (default),
4039
the function send_email will be called. That function requires
41-
that your app define a route named app-auth:email-confirmation
42-
with an id parameter; the view for this route should get an
43-
email confirmation instance using the ID and call the confirm
44-
method. To use a field that's not named 'id', define the setting
45-
email_confirmation_lookup_param (this will change the URL pattern).
40+
that your project defines defines two email templates:
41+
- rest_auth_toolkit/email_confirmation.txt
42+
- rest_auth_toolkit/email_confirmation.html
43+
44+
The templates will be passed the User and EmailConfirmation instances
45+
(as variables *user* and *confirmation*). To help generating links,
46+
a variable *base_url* with a value like "https://domain" (scheme,
47+
domain and optional port depending on the request, but no path), which
48+
lets you write code like `{{ base_url }}{% url "my-route" %}`.
49+
50+
It is up to your project to define what the link is. The demo app
51+
demonstrates a simple Django view that validates the email validation
52+
token in the URL; for a project with a front-end site (e.g. a JavaScript
53+
app) on a different domain than the Django API, a custom template tag
54+
could be used to generate the right URL for the front-end site.
55+
56+
If the setting is false, the user will be active immediately.
4657
"""
4758
deserializer = self.get_serializer(data=request.data)
4859
deserializer.is_valid(raise_exception=True)
49-
user = deserializer.save()
5060

51-
if self.email_confirmation_class is None:
52-
raise MissingSetting('email_confirmation_string')
61+
confirm_email = get_setting('email_confirmation_send_email', True)
5362

54-
confirmation = self.email_confirmation_class.objects.create(user=user)
55-
if get_setting('email_confirmation_send_email', True):
63+
if not confirm_email:
64+
deserializer.save(is_active=True)
65+
else:
66+
user = deserializer.save()
67+
68+
if self.email_confirmation_class is None:
69+
raise MissingSetting('email_confirmation_class')
70+
71+
confirmation = self.email_confirmation_class.objects.create(user=user)
5672
email_field = user.get_email_field_name()
5773
send_email(request, user, getattr(user, email_field), confirmation)
5874

5975
return Response(status=status.HTTP_201_CREATED)
6076

6177

78+
class EmailConfirmationView(generics.GenericAPIView):
79+
"""Validate an email address after sign-up.
80+
81+
Response: 200 OK (no content)
82+
83+
Error response (code 400):
84+
85+
```json
86+
{"errors": {"token": "Error message"}}
87+
```
88+
"""
89+
def post(self, request):
90+
pass
91+
92+
6293
class LoginView(generics.GenericAPIView):
6394
"""Authenticate a user, return an API auth token if valid.
6495
@@ -146,14 +177,14 @@ def send_email(request, user, address, confirmation):
146177
subject = _('Confirm your email address')
147178
from_address = get_setting('email_confirmation_from')
148179

149-
lookup_field = get_setting('email_confirmation_lookup_field', 'id')
150-
confirmation_url = request.build_absolute_uri(
151-
reverse('app-auth:email-confirmation',
152-
kwargs={lookup_field: getattr(confirmation, lookup_field)}))
153180
# The url template tag doesn't include scheme/domain/port, pass a helper
154181
base_url = request.build_absolute_uri('/')[:-1]
155182

156-
context = {'base_url': base_url, 'confirmation_url': confirmation_url}
183+
context = {
184+
'user': user,
185+
'confirmation': confirmation,
186+
'base_url': base_url,
187+
}
157188
txt_content = render_to_string('rest_auth_toolkit/email_confirmation.txt', context)
158189
html_content = render_to_string('rest_auth_toolkit/email_confirmation.html', context)
159190

0 commit comments

Comments
 (0)