Refactor email management and localization; update requirements and settings for MFA support

This commit is contained in:
Sean Morley
2024-12-12 19:20:58 -05:00
parent 673a56c6a0
commit 7b7db1c530
6 changed files with 106 additions and 54 deletions

View File

@@ -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/`, {

View File

@@ -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