1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-24 07:19:36 +02:00

Initial migration to new session based auth system with AllAuth

This commit is contained in:
Sean Morley 2024-11-29 14:41:13 -05:00
parent 7defdac3a8
commit 9bc20be70e
24 changed files with 313 additions and 773 deletions

View file

@ -11,3 +11,13 @@ class AppVersionMiddleware:
response['X-AdventureLog-Version'] = '1.0.0'
return response
# make a middlewra that prints all of the request cookies
class PrintCookiesMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print(request.COOKIES)
response = self.get_response(request)
return response

View file

@ -73,6 +73,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
return queryset.order_by(ordering)
def get_queryset(self):
print(self.request.user)
# if the user is not authenticated return only public adventures for retrieve action
if not self.request.user.is_authenticated:
if self.action == 'retrieve':

View file

@ -47,10 +47,11 @@ INSTALLED_APPS = (
'django.contrib.sites',
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
# 'dj_rest_auth',
'allauth',
'allauth.account',
'dj_rest_auth.registration',
'allauth.headless',
# 'dj_rest_auth.registration',
'allauth.socialaccount',
'allauth.socialaccount.providers.facebook',
'drf_yasg',
@ -113,6 +114,7 @@ DATABASES = {
}
}
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
@ -157,16 +159,6 @@ TEMPLATES = [
},
]
REST_AUTH = {
'SESSION_LOGIN': True,
'USE_JWT': True,
'JWT_AUTH_COOKIE': 'auth',
'JWT_AUTH_HTTPONLY': False,
'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer',
'USER_DETAILS_SERIALIZER': 'users.serializers.CustomUserDetailsSerializer',
'PASSWORD_RESET_SERIALIZER': 'users.serializers.MyPasswordResetSerializer'
}
DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True'
DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.')
@ -181,8 +173,16 @@ STORAGES = {
AUTH_USER_MODEL = 'users.CustomUser'
ACCOUNT_ADAPTER = 'users.adapters.NoNewUsersAccountAdapter'
FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000')
# HEADLESS_FRONTEND_URLS = {
# "account_confirm_email": "https://app.project.org/account/verify-email/{key}",
# "account_reset_password_from_key": "https://app.org/account/password/reset/key/{key}",
# "account_signup": "https://app.org/account/signup",
# }
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = True
@ -228,12 +228,14 @@ SWAGGER_SETTINGS = {
'LOGOUT_URL': 'logout',
}
# For demo purposes only. Use a white list in the real world.
CORS_ORIGIN_ALLOW_ALL = True
from os import getenv
CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
LOGGING = {
@ -260,6 +262,7 @@ LOGGING = {
},
},
}
# https://github.com/dr5hn/countries-states-cities-database/tags
COUNTRY_REGION_JSON_VERSION = 'v2.4'
COUNTRY_REGION_JSON_VERSION = 'v2.4'
SESSION_SAVE_EVERY_REQUEST = True

View file

@ -4,7 +4,7 @@ from django.views.generic import RedirectView, TemplateView
from django.conf import settings
from django.conf.urls.static import static
from adventures import urls as adventures
from users.views import ChangeEmailView, IsRegistrationDisabled, PublicUserListView, PublicUserDetailView
from users.views import ChangeEmailView, IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView
from .views import get_csrf_token
from drf_yasg.views import get_schema_view
@ -25,6 +25,10 @@ urlpatterns = [
path('auth/users/', PublicUserListView.as_view(), name='public-user-list'),
path('auth/user/<uuid:user_id>/', PublicUserDetailView.as_view(), name='public-user-detail'),
path('auth/user-metadata/', UserMetadataView.as_view(), name='user-metadata'),
path('csrf/', get_csrf_token, name='get_csrf_token'),
re_path(r'^$', TemplateView.as_view(
template_name="home.html"), name='home'),
@ -64,11 +68,15 @@ urlpatterns = [
re_path(r'^auth/', include('dj_rest_auth.urls')),
re_path(r'^auth/registration/',
include('dj_rest_auth.registration.urls')),
re_path(r'^account/', include('allauth.urls')),
# re_path(r'^account/', include('allauth.urls')),
re_path(r'^admin/', admin.site.urls),
re_path(r'^accounts/profile/$', RedirectView.as_view(url='/',
permanent=True), name='profile-redirect'),
re_path(r'^docs/$', schema_view.with_ui('swagger',
cache_timeout=0), name='api_docs'),
# path('auth/account-confirm-email/', VerifyEmailView.as_view(), name='account_email_verification_sent'),
path("accounts/", include("allauth.urls")),
# Include the API endpoints:
path("_allauth/", include("allauth.headless.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -1,13 +0,0 @@
{% extends "account/email/base_message.txt" %}
{% load i18n %}
{% block content %}{% autoescape off %}{% blocktrans %}You're receiving this email because you or someone else has requested a password reset for your user account.
It can be safely ignored if you did not request a password reset. Click the link below to reset your password.{% endblocktrans %}
{{ frontend_url }}/settings/forgot-password/confirm?token={{ temp_key }}&uid={{ user_pk }}
{% if username %}
{% blocktrans %}In case you forgot, your username is {{ username }}.{% endblocktrans %}{% endif %}{% endautoescape %}{% endblock content %}

View file

@ -0,0 +1,10 @@
from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings
class NoNewUsersAccountAdapter(DefaultAccountAdapter):
"""
Disable new user registration.
"""
def is_open_for_signup(self, request):
is_disabled = getattr(settings, 'DISABLE_REGISTRATION', False)
return not is_disabled

View file

@ -1,3 +1,10 @@
from django.contrib import admin
# Register your models here
from django.contrib.sessions.models import Session
class SessionAdmin(admin.ModelAdmin):
def _session_data(self, obj):
return obj.get_decoded()
list_display = ['session_key', '_session_data', 'expire_date']
admin.site.register(Session, SessionAdmin)

View file

@ -0,0 +1,17 @@
from django import forms
class CustomSignupForm(forms.Form):
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
def signup(self, request, user):
# Delay the import to avoid circular import
from allauth.account.forms import SignupForm
# No need to call super() from CustomSignupForm; use the SignupForm directly if needed
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
# Save the user instance
user.save()
return user

View file

@ -1,55 +1,17 @@
from allauth.account.utils import (filter_users_by_email, user_pk_to_url_str, user_username)
from allauth.utils import build_absolute_uri
from allauth.account.adapter import get_adapter
from allauth.account.forms import default_token_generator
from allauth.account import app_settings
from django.conf import settings
from django import forms
from allauth.account.forms import ResetPasswordForm as AllAuthPasswordResetForm
class CustomSignupForm(forms.Form):
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
def clean_email(self):
"""
Invalid email should not raise error, as this would leak users
for unit test: test_password_reset_with_invalid_email
"""
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email, is_active=True)
return self.cleaned_data["email"]
def save(self, request, **kwargs):
email = self.cleaned_data['email']
token_generator = kwargs.get('token_generator', default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
path = f"custom_password_reset_url/{user_pk_to_url_str(user)}/{temp_key}/"
url = build_absolute_uri(request, path)
frontend_url = settings.FRONTEND_URL
# remove ' from frontend_url
frontend_url = frontend_url.replace("'", "")
#Values which are passed to password_reset_key_message.txt
context = {
"frontend_url": frontend_url,
"user": user,
"password_reset_url": url,
"request": request,
"path": path,
"temp_key": temp_key,
'user_pk': user_pk_to_url_str(user),
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail(
'account/email/password_reset_key', email, context
)
return self.cleaned_data['email']
def signup(self, request, user):
# Delay the import to avoid circular import
from allauth.account.forms import SignupForm
# No need to call super() from CustomSignupForm; use the SignupForm directly if needed
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
# Save the user instance
user.save()
return user

View file

@ -1,10 +1,8 @@
from rest_framework import serializers
from django.contrib.auth import get_user_model
from adventures.models import Adventure, Collection
from users.forms import CustomAllAuthPasswordResetForm
from adventures.models import Collection
from dj_rest_auth.serializers import PasswordResetSerializer
from rest_framework.exceptions import PermissionDenied
User = get_user_model()
@ -32,77 +30,7 @@ class ChangeEmailSerializer(serializers.Serializer):
return value
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
min_length=allauth_account_settings.USERNAME_MIN_LENGTH,
required=allauth_account_settings.USERNAME_REQUIRED,
)
email = serializers.EmailField(required=allauth_account_settings.EMAIL_REQUIRED)
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
first_name = serializers.CharField(required=False)
last_name = serializers.CharField(required=False)
def validate_username(self, username):
username = get_adapter().clean_username(username)
return username
def validate_email(self, email):
email = get_adapter().clean_email(email)
if allauth_account_settings.UNIQUE_EMAIL:
if email and EmailAddress.objects.is_verified(email):
raise serializers.ValidationError(
_('A user is already registered with this e-mail address.'),
)
return email
def validate_password1(self, password):
return get_adapter().clean_password(password)
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(_("The two password fields didn't match."))
# check if a user with the same email already exists
if User.objects.filter(email=data['email']).exists():
raise serializers.ValidationError("This email is already in use.")
return data
def custom_signup(self, request, user):
pass
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username', ''),
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', ''),
'first_name': self.validated_data.get('first_name', ''),
'last_name': self.validated_data.get('last_name', ''),
}
def save(self, request):
# Check if registration is disabled
if getattr(settings, 'DISABLE_REGISTRATION', False):
raise PermissionDenied("Registration is currently disabled.")
# If registration is not disabled, proceed with the original logic
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
user = adapter.save_user(request, user, self, commit=False)
if "password1" in self.cleaned_data:
try:
adapter.clean_password(self.cleaned_data['password1'], user=user)
except DjangoValidationError as exc:
raise serializers.ValidationError(
detail=serializers.as_serializer_error(exc)
)
user.save()
self.custom_signup(request, user)
setup_user_email(request, user, [])
return user
from django.conf import settings
from django.contrib.auth import get_user_model
@ -116,20 +44,6 @@ from rest_framework import serializers
from django.conf import settings
import os
# class AdventureSerializer(serializers.ModelSerializer):
# image = serializers.SerializerMethodField()
# class Meta:
# model = Adventure
# fields = ['id', 'user_id', 'type', 'name', 'location', 'activity_types', 'description',
# 'rating', 'link', 'image', 'date', 'trip_id', 'is_public', 'longitude', 'latitude']
# def get_image(self, obj):
# if obj.image:
# public_url = os.environ.get('PUBLIC_URL', '')
# return f'{public_url}/media/{obj.image.name}'
# return None
class UserDetailsSerializer(serializers.ModelSerializer):
"""
User model w/o password
@ -203,13 +117,3 @@ class CustomUserDetailsSerializer(UserDetailsSerializer):
representation['profile_pic'] = f"{public_url}/media/{instance.profile_pic.name}"
del representation['pk'] # remove the pk field from the response
return representation
class MyPasswordResetSerializer(PasswordResetSerializer):
def validate_email(self, value):
# use the custom reset form
self.reset_form = CustomAllAuthPasswordResetForm(data=self.initial_data)
if not self.reset_form.is_valid():
raise serializers.ValidationError(self.reset_form.errors)
return value

View file

@ -83,3 +83,18 @@ class PublicUserDetailView(APIView):
user.email = None
serializer = PublicUserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserMetadataView(APIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
responses={
200: openapi.Response('User metadata'),
400: 'Bad Request'
},
operation_description="Get user metadata."
)
def get(self, request):
user = request.user
serializer = PublicUserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)