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 @@
/>
-
+
+
+
+
+
+ {#if data.props.authenticators.length === 0}
+
MFA not enabled
+ {/if}
+ {#each data.props.authenticators as authenticator}
+
+ {authenticator.type} - {authenticator.created_at}
+
+ {/each}
+
+
+
-
For Debug Use: UUID={user.uuid} | Staff user: {user.is_staff}