mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-02 03:35:18 +02:00
Refactor email management and localization; update requirements and settings for MFA support
This commit is contained in:
parent
673a56c6a0
commit
7b7db1c530
6 changed files with 106 additions and 54 deletions
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
# slippers==0.6.2
|
||||
# django-allauth-ui==1.5.1
|
||||
# django-widget-tweaks==1.5.0
|
|
@ -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!",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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/`, {
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -161,14 +161,6 @@
|
|||
id="last_name"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<!-- <label for="first_name">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
bind:value={user.email}
|
||||
name="email"
|
||||
id="email"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
/><br /> -->
|
||||
<label for="profilePicture">{$t('auth.profile_picture')}</label>
|
||||
<input
|
||||
type="file"
|
||||
|
@ -197,7 +189,7 @@
|
|||
|
||||
{#if $page.form?.message}
|
||||
<div class="text-center text-error mt-4">
|
||||
{$page.form?.message}
|
||||
{$t($page.form.message)}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -242,30 +234,30 @@
|
|||
<p class="mb-2">
|
||||
{email.email}
|
||||
{#if email.verified}
|
||||
<div class="badge badge-success">Verified</div>
|
||||
<div class="badge badge-success">{$t('settings.verified')}</div>
|
||||
{:else}
|
||||
<div class="badge badge-error">Not Verified</div>
|
||||
<div class="badge badge-error">{$t('settings.not_verified')}</div>
|
||||
{/if}
|
||||
{#if email.primary}
|
||||
<div class="badge badge-primary">Primary</div>
|
||||
<div class="badge badge-primary">{$t('settings.primary')}</div>
|
||||
{/if}
|
||||
{#if !email.verified}
|
||||
<button class="btn btn-sm btn-secondary ml-2" on:click={() => verifyEmail(email)}
|
||||
>Verify</button
|
||||
>{$t('settings.verify')}</button
|
||||
>
|
||||
{/if}
|
||||
{#if !email.primary}
|
||||
<button class="btn btn-sm btn-secondary ml-2" on:click={() => primaryEmail(email)}
|
||||
>Make Primary</button
|
||||
>{$t('settings.make_primary')}</button
|
||||
>
|
||||
{/if}
|
||||
<button class="btn btn-sm btn-warning ml-2" on:click={() => removeEmail(email)}
|
||||
>Remove</button
|
||||
>{$t('adventures.remove')}</button
|
||||
>
|
||||
</p>
|
||||
{/each}
|
||||
{#if emails.length === 0}
|
||||
<p>No emails</p>
|
||||
<p>{$t('settings.no_emai_set')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -283,11 +275,26 @@
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button class="py-2 px-4 btn btn-primary">{$t('settings.email_change')}</button>
|
||||
<button class="py-2 px-4 mb-4 btn btn-primary">{$t('settings.email_change')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">Multi-factor Authentication Settings</h1>
|
||||
|
||||
<div class="flex justify-center mb-4">
|
||||
<div>
|
||||
{#if data.props.authenticators.length === 0}
|
||||
<p>MFA not enabled</p>
|
||||
{/if}
|
||||
{#each data.props.authenticators as authenticator}
|
||||
<p class="mb-2">
|
||||
{authenticator.type} - {authenticator.created_at}
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center mt-4">
|
||||
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">
|
||||
{$t('adventures.visited_region_check')}
|
||||
|
@ -301,12 +308,6 @@
|
|||
>{$t('adventures.update_visited_regions')}</button
|
||||
>
|
||||
</div>
|
||||
<!--
|
||||
<div class="flex flex-col items-center mt-4">
|
||||
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">Data Export</h1>
|
||||
<button class="btn btn-neutral mb-4" on:click={exportAdventures}> Export to JSON </button>
|
||||
<p>This may take a few seconds...</p>
|
||||
</div> -->
|
||||
|
||||
<small class="text-center"
|
||||
><b>For Debug Use:</b> UUID={user.uuid} | Staff user: {user.is_staff}</small
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue