From 7b7db1c530907059960c9e475566ddb8a40034a2 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Thu, 12 Dec 2024 19:20:58 -0500 Subject: [PATCH] Refactor email management and localization; update requirements and settings for MFA support --- backend/server/main/settings.py | 5 +- backend/server/requirements.txt | 6 +- frontend/src/locales/en.json | 21 ++++++- frontend/src/locales/es.json | 24 +++++++- frontend/src/routes/settings/+page.server.ts | 41 +++++++++---- frontend/src/routes/settings/+page.svelte | 63 ++++++++++---------- 6 files changed, 106 insertions(+), 54 deletions(-) diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 5a3e258..7e4973b 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -44,14 +44,13 @@ INSTALLED_APPS = ( 'django.contrib.sites', 'rest_framework', 'rest_framework.authtoken', - "allauth_ui", 'allauth', 'allauth.account', 'allauth.mfa', 'allauth.headless', 'allauth.socialaccount', - "widget_tweaks", - "slippers", + # "widget_tweaks", + # "slippers", 'drf_yasg', 'corsheaders', 'adventures', diff --git a/backend/server/requirements.txt b/backend/server/requirements.txt index 0185310..5e2d10a 100644 --- a/backend/server/requirements.txt +++ b/backend/server/requirements.txt @@ -12,6 +12,6 @@ django-resized django-geojson setuptools gunicorn==23.0.0 -slippers==0.6.2 -django-allauth-ui==1.5.1 -django-widget-tweaks==1.5.0 \ No newline at end of file +# slippers==0.6.2 +# django-allauth-ui==1.5.1 +# django-widget-tweaks==1.5.0 \ No newline at end of file diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index b6f177e..7c9af87 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -262,7 +262,8 @@ "registration_disabled": "Registration is currently disabled.", "profile_picture": "Profile Picture", "public_profile": "Public Profile", - "public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page." + "public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page.", + "email_required": "Email is required" }, "users": { "no_users_found": "No users found with public profiles." @@ -296,7 +297,23 @@ "join_discord_desc": "to share your own photos. Post them in the #travel-share channel.", "current_password": "Current Password", "change_password_error": "Unable to change password. Invalid current password or invalid new password.", - "password_change_lopout_warning": "You will be logged out after changing your password." + "password_change_lopout_warning": "You will be logged out after changing your password.", + "generic_error": "An error occurred while processing your request.", + "email_removed": "Email removed successfully!", + "email_removed_error": "Error removing email", + "verify_email_success": "Email verification sent successfully!", + "verify_email_error": "Error verifying email. Try again in a few minutes.", + "email_added": "Email added successfully!", + "email_added_error": "Error adding email", + "email_set_primary": "Email set as primary successfully!", + "email_set_primary_error": "Error setting email as primary", + "verified": "Verified", + "primary": "Primary", + "not_verified": "Not Verified", + "make_primary": "Make Primary", + "verify": "Verify", + "no_emai_set": "No email set", + "error_change_password": "Error changing password. Please check your current password and try again." }, "collection": { "collection_created": "Collection created successfully!", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 4061d5a..8d7ad34 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -29,7 +29,8 @@ "my_tags": "Mis etiquetas", "tag": "Etiqueta", "language_selection": "Idioma", - "support": "Apoyo" + "support": "Apoyo", + "calendar": "Calendario" }, "about": { "about": "Acerca de", @@ -261,7 +262,8 @@ "registration_disabled": "El registro está actualmente deshabilitado.", "profile_picture": "Foto de perfil", "public_profile": "Perfil público", - "public_tooltip": "Con un perfil público, los usuarios pueden compartir colecciones con usted y ver su perfil en la página de usuarios." + "public_tooltip": "Con un perfil público, los usuarios pueden compartir colecciones con usted y ver su perfil en la página de usuarios.", + "email_required": "Se requiere correo electrónico" }, "users": { "no_users_found": "No se encontraron usuarios con perfiles públicos." @@ -295,7 +297,23 @@ "photo_by": "Foto por", "change_password_error": "No se puede cambiar la contraseña. \nContraseña actual no válida o contraseña nueva no válida.", "current_password": "Contraseña actual", - "password_change_lopout_warning": "Se cerrará su sesión después de cambiar su contraseña." + "password_change_lopout_warning": "Se cerrará su sesión después de cambiar su contraseña.", + "generic_error": "Se produjo un error al procesar su solicitud.", + "email_added": "¡Correo electrónico agregado exitosamente!", + "email_added_error": "Error al agregar correo electrónico", + "email_removed": "¡El correo electrónico se eliminó correctamente!", + "email_removed_error": "Error al eliminar el correo electrónico", + "email_set_primary": "¡El correo electrónico se configuró como principal correctamente!", + "email_set_primary_error": "Error al configurar el correo electrónico como principal", + "make_primary": "Hacer primario", + "no_emai_set": "No hay correo electrónico configurado", + "not_verified": "No verificado", + "primary": "Primario", + "verified": "Verificado", + "verify": "Verificar", + "verify_email_error": "Error al verificar el correo electrónico. \nInténtalo de nuevo en unos minutos.", + "verify_email_success": "¡La verificación por correo electrónico se envió correctamente!", + "error_change_password": "Error al cambiar la contraseña. \nPor favor verifique su contraseña actual e inténtelo nuevamente." }, "checklist": { "add_item": "Agregar artículo", diff --git a/frontend/src/routes/settings/+page.server.ts b/frontend/src/routes/settings/+page.server.ts index f3ea702..2337619 100644 --- a/frontend/src/routes/settings/+page.server.ts +++ b/frontend/src/routes/settings/+page.server.ts @@ -5,6 +5,17 @@ import type { User } from '$lib/types'; import { fetchCSRFToken } from '$lib/index.server'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; +type MFAAuthenticatorResponse = { + status: number; + data: { + type: string; + created_at: number; + last_used_at: number | null; + total_code_count?: number; + unused_code_count?: number; + }[]; +}; + export const load: PageServerLoad = async (event) => { if (!event.locals.user) { return redirect(302, '/'); @@ -34,10 +45,22 @@ export const load: PageServerLoad = async (event) => { return redirect(302, '/'); } + let mfaAuthenticatorFetch = await fetch( + `${endpoint}/_allauth/browser/v1/account/authenticators`, + { + headers: { + Cookie: `sessionid=${sessionId}` + } + } + ); + let mfaAuthenticatorResponse = (await mfaAuthenticatorFetch.json()) as MFAAuthenticatorResponse; + let authenticators = mfaAuthenticatorResponse.data; + return { props: { user, - emails + emails, + authenticators } }; }; @@ -129,7 +152,7 @@ export const actions: Actions = { return { success: true }; } catch (error) { console.error('Error:', error); - return { error: 'An error occurred while processing your request.' }; + return { error: 'settings.generic_error' }; } }, changePassword: async (event) => { @@ -148,10 +171,10 @@ export const actions: Actions = { const current_password = formData.get('current_password') as string | null | undefined; if (password1 !== password2) { - return fail(400, { message: 'Passwords do not match' }); + return fail(400, { message: 'settings.password_does_not_match' }); } if (!current_password) { - return fail(400, { message: 'Current password is required' }); + return fail(400, { message: 'settings.password_is_required' }); } let csrfToken = await fetchCSRFToken(); @@ -169,13 +192,7 @@ export const actions: Actions = { }) }); if (!res.ok) { - let error_message = await res.text(); - if (res.status === 400) { - // get the message key of the object - // {"status": 400, "errors": [{"message": "Please type your current password.", "code": "enter_current_password", "param": "current_password"}]} - error_message = JSON.parse(error_message).errors[0].message; - } - return fail(res.status, { message: error_message }); + return fail(res.status, { message: 'settings.error_change_password' }); } return { success: true }; }, @@ -190,7 +207,7 @@ export const actions: Actions = { const formData = await event.request.formData(); const new_email = formData.get('new_email') as string | null | undefined; if (!new_email) { - return fail(400, { message: 'Email is required' }); + return fail(400, { message: 'auth.email_required' }); } else { let csrfToken = await fetchCSRFToken(); let res = await fetch(`${endpoint}/auth/change-email/`, { diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index 5224ab8..bb2d084 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -62,10 +62,10 @@ body: JSON.stringify({ email: email.email }) }); if (res.ok) { - addToast('success', 'Email removed'); + addToast('success', $t('settings.email_removed')); emails = emails.filter((e) => e.email !== email.email); } else { - addToast('error', 'Error removing email'); + addToast('error', $t('settings.email_removed_error')); } } @@ -78,9 +78,9 @@ body: JSON.stringify({ email: email.email }) }); if (res.ok) { - addToast('success', 'Email sent to verify'); + addToast('success', $t('settings.verify_email_success')); } else { - addToast('error', 'Error verifying email. Try again in a few minutes.'); + addToast('error', $t('settings.verify_email_error')); } } @@ -93,11 +93,11 @@ body: JSON.stringify({ email: new_email }) }); if (res.ok) { - addToast('success', 'Email added'); + addToast('success', $t('settings.email_added')); emails = [...emails, { email: new_email, verified: false, primary: false }]; new_email = ''; } else { - addToast('error', 'Error adding email'); + addToast('error', $t('settings.email_added_error')); } } @@ -110,7 +110,7 @@ body: JSON.stringify({ email: email.email, primary: true }) }); if (res.ok) { - addToast('success', 'Email set as primary'); + addToast('success', $t('settings.email_set_primary')); // remove primary from all other emails and set this one as primary emails = emails.map((e) => { if (e.email === email.email) { @@ -121,7 +121,7 @@ return e; }); } else { - addToast('error', 'Error setting email as primary'); + addToast('error', $t('settings.email_set_primary_error')); } } @@ -161,14 +161,6 @@ id="last_name" class="block mb-2 input input-bordered w-full max-w-xs" />
- - {$page.form?.message} + {$t($page.form.message)} {/if} @@ -242,30 +234,30 @@

{email.email} {#if email.verified} -

Verified
+
{$t('settings.verified')}
{:else} -
Not Verified
+
{$t('settings.not_verified')}
{/if} {#if email.primary} -
Primary
+
{$t('settings.primary')}
{/if} {#if !email.verified} {$t('settings.verify')} {/if} {#if !email.primary} {$t('settings.make_primary')} {/if} {$t('adventures.remove')}

{/each} {#if emails.length === 0} -

No emails

+

{$t('settings.no_emai_set')}

{/if} @@ -283,11 +275,26 @@ />
- +
+

Multi-factor Authentication Settings

+ +
+
+ {#if data.props.authenticators.length === 0} +

MFA not enabled

+ {/if} + {#each data.props.authenticators as authenticator} +

+ {authenticator.type} - {authenticator.created_at} +

+ {/each} +
+
+

{$t('adventures.visited_region_check')} @@ -301,12 +308,6 @@ >{$t('adventures.update_visited_regions')}

- For Debug Use: UUID={user.uuid} | Staff user: {user.is_staff}