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 2a3d60c..18522ed 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,12 +169,26 @@ STORAGES = { AUTH_USER_MODEL = 'users.CustomUser' +FRONTEND_URL = getenv('FRONTEND_URL', 'http://localhost:3000') + EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' SITE_ID = 1 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 new file mode 100644 index 0000000..42473bf --- /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.{% 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/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 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}