From 12595483fceec4cd4a2352b844b7efbb0279a614 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sun, 4 Aug 2024 13:57:33 -0400 Subject: [PATCH 1/3] feat: Update NoteModal component to allow editing and submitting notes --- frontend/src/lib/components/NoteModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/NoteModal.svelte b/frontend/src/lib/components/NoteModal.svelte index 67efc1b..2da411f 100644 --- a/frontend/src/lib/components/NoteModal.svelte +++ b/frontend/src/lib/components/NoteModal.svelte @@ -113,7 +113,7 @@

Editing note {initialName}

{/if} - {#if user?.pk == note?.user_id} + {#if (note && user?.pk == note?.user_id) || !note}
From fd94f030083b1c8c9ebf819219e13ea3c827fdff Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sun, 4 Aug 2024 17:30:43 -0400 Subject: [PATCH 2/3] password reset email --- backend/server/main/settings.py | 4 +- .../email/password_reset_key_message.txt | 13 ++++ backend/server/users/forms.py | 50 +++++++++++++ backend/server/users/serializers.py | 12 ++++ .../settings/forgot-password/+page.server.ts | 7 +- .../settings/forgot-password/+page.svelte | 2 +- .../forgot-password/confirm/+page.server.ts | 72 +++++++++++++------ .../forgot-password/confirm/+page.svelte | 30 ++++++++ 8 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 backend/server/templates/account/email/password_reset_key_message.txt create mode 100644 backend/server/users/forms.py diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 2a3d60c..3df32e2 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -155,7 +155,7 @@ REST_AUTH = { 'JWT_AUTH_HTTPONLY': False, 'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer', 'USER_DETAILS_SERIALIZER': 'users.serializers.CustomUserDetailsSerializer', - + 'PASSWORD_RESET_SERIALIZER': 'users.serializers.MyPasswordResetSerializer' } STORAGES = { @@ -169,6 +169,8 @@ STORAGES = { AUTH_USER_MODEL = 'users.CustomUser' +FRONTEND_URL = 'http://localhost:5173' + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' SITE_ID = 1 ACCOUNT_EMAIL_REQUIRED = True diff --git a/backend/server/templates/account/email/password_reset_key_message.txt b/backend/server/templates/account/email/password_reset_key_message.txt new file mode 100644 index 0000000..1716b1d --- /dev/null +++ b/backend/server/templates/account/email/password_reset_key_message.txt @@ -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 %} \ No newline at end of file diff --git a/backend/server/users/forms.py b/backend/server/users/forms.py new file mode 100644 index 0000000..e6d8c8d --- /dev/null +++ b/backend/server/users/forms.py @@ -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'] + + diff --git a/backend/server/users/serializers.py b/backend/server/users/serializers.py index 4881949..35ed373 100644 --- a/backend/server/users/serializers.py +++ b/backend/server/users/serializers.py @@ -2,6 +2,8 @@ from rest_framework import serializers from django.contrib.auth import get_user_model from adventures.models import Adventure +from users.forms import CustomAllAuthPasswordResetForm +from dj_rest_auth.serializers import PasswordResetSerializer User = get_user_model() @@ -177,3 +179,13 @@ class CustomUserDetailsSerializer(UserDetailsSerializer): public_url = public_url.replace("'", "") representation['profile_pic'] = f"{public_url}/media/{instance.profile_pic.name}" 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 \ No newline at end of file diff --git a/frontend/src/routes/settings/forgot-password/+page.server.ts b/frontend/src/routes/settings/forgot-password/+page.server.ts index 6a0c90b..86dd787 100644 --- a/frontend/src/routes/settings/forgot-password/+page.server.ts +++ b/frontend/src/routes/settings/forgot-password/+page.server.ts @@ -22,8 +22,13 @@ export const actions: Actions = { email }) }); + 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 }; } diff --git a/frontend/src/routes/settings/forgot-password/+page.svelte b/frontend/src/routes/settings/forgot-password/+page.svelte index d0d3857..d1a4bef 100644 --- a/frontend/src/routes/settings/forgot-password/+page.svelte +++ b/frontend/src/routes/settings/forgot-password/+page.svelte @@ -6,7 +6,7 @@

Reset Password

- + { const token = event.url.searchParams.get('token'); const uid = event.url.searchParams.get('uid'); - console.log('token', token); - if (!token) { - 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: 'password', - new_password2: 'password' - }) - }); - let data = await response.json(); - console.log('data', data); - } - - return {}; + return { + props: { + token, + uid + } + }; }) 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'); + } + } + } +}; diff --git a/frontend/src/routes/settings/forgot-password/confirm/+page.svelte b/frontend/src/routes/settings/forgot-password/confirm/+page.svelte index 0d9aa7f..d2b42a6 100644 --- a/frontend/src/routes/settings/forgot-password/confirm/+page.svelte +++ b/frontend/src/routes/settings/forgot-password/confirm/+page.svelte @@ -1,5 +1,35 @@ + +

Change Password

+ + + +
+ + + + {#if $page.form?.message} +
+ {$page.form?.message} +
+ {/if} +
+ From e753d023add2c8ffa1b64a03ba10f44e93a07608 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sun, 4 Aug 2024 18:05:19 -0400 Subject: [PATCH 3/3] email providers --- README.md | 23 ++++++++++--------- backend/server/.env.example | 15 +++++++++++- backend/server/main/settings.py | 14 ++++++++++- .../email/password_reset_key_message.txt | 2 +- docker-compose.yml | 1 + documentation/docs/Installation/docker.md | 23 ++++++++++--------- 6 files changed, 53 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 6904c2b..ae52447 100644 --- a/README.md +++ b/README.md @@ -54,17 +54,18 @@ Here is a summary of the configuration options available in the `docker-compose. ### Backend Container (server) -| Name | Required | Description | Default Value | -| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `PGHOST` | Yes | Databse host. | db | -| `PGDATABASE` | Yes | Database. | database | -| `PGUSER` | Yes | Database user. | adventure | -| `PGPASSWORD` | Yes | Database password. | changeme123 | -| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin | -| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin | -| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | admin@example.com | -| `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 | -| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. | +| Name | Required | Description | Default Value | +| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `PGHOST` | Yes | Databse host. | db | +| `PGDATABASE` | Yes | Database. | database | +| `PGUSER` | Yes | Database user. | adventure | +| `PGPASSWORD` | Yes | Database password. | changeme123 | +| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin | +| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin | +| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | admin@example.com | +| `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 | +| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. | +| `FRONTEND_URL` | Yes | This is the publically accessible url to the **frontend** container. This link should be accessable for all users. Used for email generation. | http://localhost:3000 | ### Proxy Container (nginx) Configuration diff --git a/backend/server/.env.example b/backend/server/.env.example index ea63047..04eb77f 100644 --- a/backend/server/.env.example +++ b/backend/server/.env.example @@ -7,4 +7,17 @@ SECRET_KEY='pleasechangethisbecauseifyoudontitwillbeverybadandyouwillgethackedin PUBLIC_URL='http://127.0.0.1:8000' -DEBUG=True \ No newline at end of file +DEBUG=True + +FRONTEND_URL='http://localhost:3000' + +EMAIL_BACKEND='console' + +# EMAIL_BACKEND='email' +# EMAIL_HOST='smtp.gmail.com' +# EMAIL_USE_TLS=False +# EMAIL_PORT=587 +# EMAIL_USE_SSL=True +# EMAIL_HOST_USER='user' +# EMAIL_HOST_PASSWORD='password' +# DEFAULT_FROM_EMAIL='user@example.com' \ No newline at end of file diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 3df32e2..18522ed 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -169,7 +169,7 @@ STORAGES = { AUTH_USER_MODEL = 'users.CustomUser' -FRONTEND_URL = 'http://localhost:5173' +FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000') EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' SITE_ID = 1 @@ -177,6 +177,18 @@ ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_AUTHENTICATION_METHOD = 'username' ACCOUNT_EMAIL_VERIFICATION = 'optional' +if getenv('EMAIL_BACKEND', 'console') == 'console': + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +else: + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_HOST = getenv('EMAIL_HOST') + EMAIL_USE_TLS = getenv('EMAIL_USE_TLS', 'True') == 'True' + EMAIL_PORT = getenv('EMAIL_PORT', 587) + EMAIL_USE_SSL = getenv('EMAIL_USE_SSL', 'False') == 'True' + EMAIL_HOST_USER = getenv('EMAIL_HOST_USER') + EMAIL_HOST_PASSWORD = getenv('EMAIL_HOST_PASSWORD') + DEFAULT_FROM_EMAIL = getenv('DEFAULT_FROM_EMAIL') + # EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # EMAIL_HOST = 'smtp.resend.com' # EMAIL_USE_TLS = False diff --git a/backend/server/templates/account/email/password_reset_key_message.txt b/backend/server/templates/account/email/password_reset_key_message.txt index 1716b1d..42473bf 100644 --- a/backend/server/templates/account/email/password_reset_key_message.txt +++ b/backend/server/templates/account/email/password_reset_key_message.txt @@ -3,7 +3,7 @@ {% 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 %} +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 }} diff --git a/docker-compose.yml b/docker-compose.yml index 375bad0..a6df3fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,7 @@ services: - PUBLIC_URL='http://localhost:81' - CSRF_TRUSTED_ORIGINS=https://api.adventurelog.app,https://adventurelog.app - DEBUG=False + - FRONTEND_URL='http://localhost:8080' ports: - "8000:8000" depends_on: diff --git a/documentation/docs/Installation/docker.md b/documentation/docs/Installation/docker.md index d2236fb..0c12fde 100644 --- a/documentation/docs/Installation/docker.md +++ b/documentation/docs/Installation/docker.md @@ -35,17 +35,18 @@ Here is a summary of the configuration options available in the `docker-compose. ### Backend Container (server) -| Name | Required | Description | Default Value | -| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `PGHOST` | Yes | Databse host. | db | -| `PGDATABASE` | Yes | Database. | database | -| `PGUSER` | Yes | Database user. | adventure | -| `PGPASSWORD` | Yes | Database password. | changeme123 | -| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin | -| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin | -| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | admin@example.com | -| `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 | -| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. | +| Name | Required | Description | Default Value | +| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `PGHOST` | Yes | Databse host. | db | +| `PGDATABASE` | Yes | Database. | database | +| `PGUSER` | Yes | Database user. | adventure | +| `PGPASSWORD` | Yes | Database password. | changeme123 | +| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin | +| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin | +| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | admin@example.com | +| `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 | +| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. | +| `FRONTEND_URL` | Yes | This is the publically accessible url to the **frontend** container. This link should be accessable for all users. Used for email generation. | http://localhost:3000 | ### Proxy Container (nginx) Configuration