Skip to content

Commit acd031e

Browse files
authored
Merge pull request #30 from tsotetsi/tf-26-use-allauth-headless
TF 26 use allauth headless
2 parents 57ca8b1 + 52381a1 commit acd031e

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

backend/user_service/config/settings/base.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# TicketFlow Application definition.
1919

2020
DJANGO_APPS = [
21+
'django.contrib.sites', # Required for allauth
2122
'django.contrib.admin',
2223
'django.contrib.auth',
2324
'django.contrib.contenttypes',
@@ -29,6 +30,8 @@
2930
THIRD_PARTY_APPS = [
3031
"rest_framework",
3132
"rest_framework.authtoken",
33+
"allauth",
34+
"allauth.account",
3235
"corsheaders",
3336
"django_prometheus",
3437
"drf_spectacular",
@@ -47,6 +50,7 @@
4750
'django.middleware.security.SecurityMiddleware',
4851
'corsheaders.middleware.CorsMiddleware',
4952
'whitenoise.middleware.WhiteNoiseMiddleware', # servers static files directly from Django in production.
53+
'allauth.account.middleware.AccountMiddleware',
5054
'django.contrib.sessions.middleware.SessionMiddleware',
5155
'django.middleware.common.CommonMiddleware',
5256
'django.middleware.csrf.CsrfViewMiddleware',
@@ -87,6 +91,8 @@
8791
AUTHENTICATION_BACKENDS = [
8892
# Needed to log in by username in Django admin, regardless of `allauth`
8993
"django.contrib.auth.backends.ModelBackend",
94+
# `allauth` specific authentication methods, such as login by e-mail
95+
"allauth.account.auth_backends.AuthenticationBackend"
9096
]
9197
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
9298
AUTH_USER_MODEL = "users.User"
@@ -168,6 +174,20 @@
168174

169175
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
170176

177+
# django-allauth
178+
# -------------------------------------------------------------------------------
179+
# django-allauth - https://docs.allauth.org/en/dev/headless/openapi-specification/
180+
SITE_ID = 1 # Required for allauth
181+
182+
# Email/Account authentication settings for verification
183+
ACCOUNT_EMAIL_REQUIRED = True
184+
ACCOUNT_USERNAME_REQUIRED = False
185+
ACCOUNT_LOGIN_METHODS = {'email'}
186+
ACCOUNT_EMAIL_VERIFICATION = 'mandatory' # Ensures email verification
187+
LOGIN_ON_EMAIL_CONFIRMATION = False
188+
CONFIRM_EMAIL_ON_GET = True
189+
ACCOUNT_UNIQUE_EMAIL = True
190+
171191
# django-rest-framework
172192
# -------------------------------------------------------------------------------
173193
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
@@ -182,6 +202,9 @@
182202

183203
CORS_ORIGIN_ALLOW_ALL = True # For development only; restrict in production.
184204

205+
# Email backend (for development) TODO: Use mailpit
206+
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Use console for testing
207+
185208
# By Default swagger ui is available only to admin user(s). You can change permission classes to change that
186209
# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
187210
SPECTACULAR_SETTINGS = {

backend/user_service/config/urls.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from allauth.account.views import confirm_email, email_verification_sent
12
from django.contrib import admin
23
from django.urls import path, include, re_path
34
from django.conf import settings
@@ -7,7 +8,7 @@
78
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
89
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView
910

10-
from users.api.views import RegisterView, UserProfileView
11+
from users.api.views import RegisterView, UserProfileView, CustomConfirmEmailView
1112

1213

1314
urlpatterns = [
@@ -18,7 +19,10 @@
1819
urlpatterns += [
1920
# API BASE URL For Authentication JWT.
2021
path('api/', include("config.api_router")),
22+
path('api/login', RegisterView.as_view(), name='account_login'),
2123
path('api/register', RegisterView.as_view(), name='register'),
24+
path('api/account-confirm-email/<str:key>/', CustomConfirmEmailView.as_view() , name='account_confirm_email'),
25+
path('api/account-email-verification-sent', email_verification_sent, name='account_email_verification_sent'),
2226
path('api/profile', UserProfileView.as_view(), name='profile'),
2327
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
2428
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),

backend/user_service/requirements/base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ whitenoise==6.8.2 # serves static files from django(direct)
77
pre_commit==4.1.0
88
psycopg2-binary==2.9.10 # PostgreSQL Driver for Django
99
python-dotenv==1.0.1 # Manages env variables
10+
django-allauth==65.4.1 # Email reistration and verification
1011
django-prometheus==2.3.1 # Exposes metrics from django app
1112
django-redis==5.4.0 # Integrates django with redis
1213
# Secondary dependencies.

backend/user_service/users/api/views.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import uuid
22

3+
from allauth.account.views import ConfirmEmailView
4+
from allauth.account.utils import complete_signup
5+
from allauth.account.internal import flows
6+
from allauth.account.models import (
7+
get_emailconfirmation_model,
8+
)
9+
10+
from django.http import Http404
11+
312
from django.utils.decorators import method_decorator
413
from django.views.decorators.cache import cache_page
514
from django.views.decorators.vary import vary_on_cookie
@@ -15,7 +24,7 @@
1524

1625
from users.models import User
1726
from users.api.serializers import UserSerializer, RegisterSerializer
18-
27+
from config.settings.base import CONFIRM_EMAIL_ON_GET
1928

2029
class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
2130
serializer_class = UserSerializer
@@ -44,6 +53,7 @@ def post(self, request):
4453
serializer = RegisterSerializer(data=request.data)
4554
if serializer.is_valid():
4655
user = serializer.save()
56+
complete_signup(request, user, 'mandatory', None) # Trigger email verification
4757
refresh = RefreshToken.for_user(user)
4858
return Response({
4959
'user': RegisterSerializer(user).data,
@@ -53,6 +63,38 @@ def post(self, request):
5363
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
5464

5565

66+
class CustomConfirmEmailView(APIView):
67+
permission_classes = [permissions.AllowAny]
68+
69+
def __init__(self, **kwargs):
70+
super().__init__()
71+
self.object = None
72+
73+
def get_object(self, queryset=None):
74+
key = self.kwargs["key"]
75+
model = get_emailconfirmation_model()
76+
email_confirmation = model.from_key(key)
77+
if not email_confirmation:
78+
raise Http404()
79+
return email_confirmation
80+
81+
def get(self, *args, **kwargs):
82+
if CONFIRM_EMAIL_ON_GET:
83+
self.object = verification = self.get_object()
84+
response = flows.email_verification.verify_email_indirectly(
85+
self.request,
86+
verification.email_address.user,
87+
verification.email_address.user.email
88+
)
89+
if response:
90+
return Response({
91+
"detail": "Email verified successfully.",
92+
"data": verification.email_address.user.email
93+
},
94+
status=status.HTTP_200_OK)
95+
return Response({"detail": "Not accepting GET request on the endpoint"}, status=status.HTTP_400_BAD_REQUEST)
96+
97+
5698
class UserProfileView(APIView):
5799
permission_classes = [permissions.IsAuthenticated]
58100

0 commit comments

Comments
 (0)