mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-21 13:59:36 +02:00
password reset email
This commit is contained in:
parent
12595483fc
commit
fd94f03008
8 changed files with 165 additions and 25 deletions
|
@ -155,7 +155,7 @@ REST_AUTH = {
|
||||||
'JWT_AUTH_HTTPONLY': False,
|
'JWT_AUTH_HTTPONLY': False,
|
||||||
'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer',
|
'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer',
|
||||||
'USER_DETAILS_SERIALIZER': 'users.serializers.CustomUserDetailsSerializer',
|
'USER_DETAILS_SERIALIZER': 'users.serializers.CustomUserDetailsSerializer',
|
||||||
|
'PASSWORD_RESET_SERIALIZER': 'users.serializers.MyPasswordResetSerializer'
|
||||||
}
|
}
|
||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
|
@ -169,6 +169,8 @@ STORAGES = {
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'users.CustomUser'
|
AUTH_USER_MODEL = 'users.CustomUser'
|
||||||
|
|
||||||
|
FRONTEND_URL = 'http://localhost:5173'
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
ACCOUNT_EMAIL_REQUIRED = True
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% 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. TEST FOR AdventurELOG{% 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 %}
|
50
backend/server/users/forms.py
Normal file
50
backend/server/users/forms.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
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 allauth.account.forms import ResetPasswordForm as AllAuthPasswordResetForm
|
||||||
|
|
||||||
|
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)
|
||||||
|
#Values which are passed to password_reset_key_message.txt
|
||||||
|
context = {
|
||||||
|
"frontend_url": settings.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']
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ from rest_framework import serializers
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from adventures.models import Adventure
|
from adventures.models import Adventure
|
||||||
|
from users.forms import CustomAllAuthPasswordResetForm
|
||||||
|
from dj_rest_auth.serializers import PasswordResetSerializer
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
@ -177,3 +179,13 @@ class CustomUserDetailsSerializer(UserDetailsSerializer):
|
||||||
public_url = public_url.replace("'", "")
|
public_url = public_url.replace("'", "")
|
||||||
representation['profile_pic'] = f"{public_url}/media/{instance.profile_pic.name}"
|
representation['profile_pic'] = f"{public_url}/media/{instance.profile_pic.name}"
|
||||||
return representation
|
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
|
|
@ -22,8 +22,13 @@ export const actions: Actions = {
|
||||||
email
|
email
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
return fail(res.status, { message: await res.json() });
|
let message = await res.json();
|
||||||
|
|
||||||
|
const key = Object.keys(message)[0];
|
||||||
|
|
||||||
|
return fail(res.status, { message: message[key] });
|
||||||
}
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<h1 class="text-center font-extrabold text-4xl mb-6">Reset Password</h1>
|
<h1 class="text-center font-extrabold text-4xl mb-6">Reset Password</h1>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<form method="post" action="?/forgotPassword" class="w-full max-w-xs">
|
<form method="post" action="?/forgotPassword" class="w-full max-w-xs" use:enhance>
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
<input
|
<input
|
||||||
name="email"
|
name="email"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { fail, redirect } from '@sveltejs/kit';
|
import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
||||||
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||||
|
@ -6,26 +6,54 @@ const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||||
export const load = (async (event) => {
|
export const load = (async (event) => {
|
||||||
const token = event.url.searchParams.get('token');
|
const token = event.url.searchParams.get('token');
|
||||||
const uid = event.url.searchParams.get('uid');
|
const uid = event.url.searchParams.get('uid');
|
||||||
console.log('token', token);
|
|
||||||
|
|
||||||
if (!token) {
|
return {
|
||||||
return redirect(302, '/settings/forgot-password');
|
props: {
|
||||||
} else {
|
token,
|
||||||
let response = await fetch(`${serverEndpoint}/auth/password/reset/confirm/`, {
|
uid
|
||||||
method: 'POST',
|
}
|
||||||
headers: {
|
};
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
token: token,
|
|
||||||
uid: uid,
|
|
||||||
new_password1: 'password',
|
|
||||||
new_password2: 'password'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let data = await response.json();
|
|
||||||
console.log('data', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
reset: async (event) => {
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
|
||||||
|
const new_password1 = formData.get('new_password1') as string;
|
||||||
|
const new_password2 = formData.get('new_password2') as string;
|
||||||
|
const token = formData.get('token') as string;
|
||||||
|
const uid = formData.get('uid') as string;
|
||||||
|
|
||||||
|
if (!new_password1 || !new_password2) {
|
||||||
|
return fail(400, { message: 'Password is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_password1 !== new_password2) {
|
||||||
|
return fail(400, { message: 'Passwords do not match' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token || !uid) {
|
||||||
|
return redirect(302, '/settings/forgot-password');
|
||||||
|
} else {
|
||||||
|
let response = await fetch(`${serverEndpoint}/auth/password/reset/confirm/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: token,
|
||||||
|
uid: uid,
|
||||||
|
new_password1,
|
||||||
|
new_password2
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
let responseJson = await response.json();
|
||||||
|
const key = Object.keys(responseJson)[0];
|
||||||
|
return fail(response.status, { message: responseJson[key] });
|
||||||
|
} else {
|
||||||
|
return redirect(302, '/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,35 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import { page } from '$app/stores';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<h1 class="text-center font-bold text-4xl mb-4">Change Password</h1>
|
||||||
|
<form action="?/reset" method="post" use:enhance>
|
||||||
|
<input type="hidden" name="uid" value={data.props.uid} />
|
||||||
|
<input type="hidden" name="token" value={data.props.token} />
|
||||||
|
<div class="flex items-center justify-center gap-4">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="input input-bordered w-full max-w-xs"
|
||||||
|
id="new_password1"
|
||||||
|
name="new_password1"
|
||||||
|
placeholder="New Password"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="input input-bordered w-full max-w-xs"
|
||||||
|
id="new_password2"
|
||||||
|
name="new_password2"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
/>
|
||||||
|
<button type="submit" class="btn btn-primary"> Submit </button>
|
||||||
|
{#if $page.form?.message}
|
||||||
|
<div class="text-center text-error mt-4">
|
||||||
|
{$page.form?.message}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue