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:
parent
7defdac3a8
commit
9bc20be70e
24 changed files with 313 additions and 773 deletions
|
@ -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
|
|
@ -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':
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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 %}
|
10
backend/server/users/adapters.py
Normal file
10
backend/server/users/adapters.py
Normal 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
|
|
@ -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)
|
17
backend/server/users/form_overrides.py
Normal file
17
backend/server/users/form_overrides.py
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue