ui: Account recovery done

This commit is contained in:
nemunaire 2022-11-27 12:44:29 +01:00
parent 6db36ba920
commit cc4953e810
6 changed files with 286 additions and 0 deletions

View File

@ -18,3 +18,40 @@ export async function authUser(form: LoginForm): Promise<any> {
});
return await handleApiResponse(res);
}
export async function forgotAccountPassword(email: string): any {
const res = await fetch('api/users', {
method: 'PATCH',
headers: {'Accept': 'application/json'},
body: JSON.stringify({
email,
kind: 'recovery',
}),
});
return await handleApiResponse(res);
}
export async function recoverAccount(userid: string, key: string, password: string): any {
userid = encodeURIComponent(userid);
const res = await fetch(`api/users/${userid}/recovery`, {
method: 'POST',
headers: {'Accept': 'application/json'},
body: JSON.stringify({
key,
password,
}),
});
return await handleApiResponse(res);
}
export async function validateEmail(userid: string, key: string): any {
userid = encodeURIComponent(userid);
const res = await fetch(`api/users/${userid}/email`, {
method: 'POST',
headers: {'Accept': 'application/json'},
body: JSON.stringify({
key,
}),
});
return await handleApiResponse(res);
}

View File

@ -0,0 +1,93 @@
<script lang="ts">
import { goto } from '$app/navigation';
import {
Button,
Col,
Input,
Row,
Spinner,
} from 'sveltestrap';
import { t } from '$lib/translations';
import { forgotAccountPassword } from '$lib/api/user';
import { toasts } from '$lib/stores/toasts';
let value = "";
let emailState: boolean|undefined = undefined;
let formSent = false;
let formElm: HTMLFormElement;
function goSendLink() {
const valid = formElm.checkValidity();
emailState = valid;
if (valid) {
formSent = true;
forgotAccountPassword(value)
.then(
() => {
formSent = false;
toasts.addToast({
title: $t('email.sent-recovery'),
message: $t('email.instruction.check-inbox'),
timeout: 20000,
color: 'success',
})
goto('/login');
},
(error) => {
formSent = false
toasts.addErrorToast({
title: $t('errors.recovery'),
message: error,
timeout: 10000,
})
}
)
}
}
</script>
<form
bind:this={formElm}
on:submit|preventDefault={goSendLink}
>
<p class="text-center">
{$t('email.recover')}.
</p>
<Row>
<label for="email-input" class="col-md-4 col-form-label text-truncate text-md-right fw-bold">
{$t('email.address')}
</label>
<Col md="6">
<Input
id="email-input"
required
autofocus
type="email"
placeholder="jPostel@isi.edu"
autocomplete="username"
invalid={emailState}
bind:value={value}
/>
</Col>
</Row>
<Row class="mt-3">
<Button
class="offset-1 col-10 offset-sm-2 col-sm-8 offset-md-3 col-md-6 offset-lg-4 col-lg-4"
type="submit"
color="primary"
disabled={formSent}
>
{#if formSent}
<Spinner
label="Spinning"
size="sm"
/>
{/if}
{$t('email.send-recover')}
</Button>
</Row>
</form>

View File

@ -0,0 +1,120 @@
<script lang="ts">
import { goto } from '$app/navigation';
import {
Button,
Col,
Input,
Row,
Spinner,
} from 'sveltestrap';
import { recoverAccount } from '$lib/api/user';
import { checkWeakPassword, checkPasswordConfirmation } from '$lib/password';
import { t } from '$lib/translations';
import { toasts } from '$lib/stores/toasts';
export let data: {user: string; key: string};
let value = "";
let passwordConfirmation = "";
let passwordState: boolean|undefined;
let passwordConfirmState: boolean|undefined;
let formSent = false;
$: {
if (passwordState == false) {
passwordState = checkWeakPassword(value);
}
}
let formElm: HTMLFormElement;
function goRecover() {
const valid = formElm.checkValidity()
if (valid && passwordState && passwordConfirmState) {
formSent = true;
recoverAccount(data.user, data.key, value)
.then(
() => {
formSent = false;
toasts.addToast({
title: $t('password.redefined'),
message: $t('password.success'),
type: 'success',
timeout: 5000,
});
goto('/login');
},
(error) => {
formSent = false;
toasts.addErrorToast({
title: $t('errors.recovery'),
message: error,
timeout: 10000,
})
}
)
}
}
</script>
<form
class="container my-1"
on:submit|preventDefault={goRecover}
bind:this={formElm}
>
<p>
{$t('password.fill')}
</p>
<Row>
<label for="password-input" class="col-md-4 col-form-label text-truncate text-md-right fw-bold">
{$t('password.new')}
</label>
<Col md="6">
<Input
autocomplete="new-password"
feedback={!passwordState?$t('errors.password-weak'):null}
id="password-input"
placeholder="xXxXxXxXxX"
required
type="password"
invalid={passwordState !== undefined && !passwordState}
valid={passwordState}
bind:value={value}
on:change={() => passwordState = checkWeakPassword(value)}
/>
</Col>
</Row>
<Row class="mt-2">
<label for="passwordconfirm-input" class="col-md-4 col-form-label text-truncate text-md-right fw-bold">
{$t('password.confirmation')}
</label>
<Col md="6">
<Input
feedback={!passwordConfirmState?$t('errors.password-match'):null}
id="passwordconfirm-input"
placeholder="xXxXxXxXxX"
required
type="password"
invalid={passwordConfirmState !== undefined && !passwordConfirmState}
valid={passwordConfirmState}
bind:value={passwordConfirmation}
on:change={() => passwordConfirmState = checkPasswordConfirmation(value, passwordConfirmation)}
/>
</Col>
</Row>
<Row class="mt-3">
<Button
class="offset-1 col-10 offset-sm-2 col-sm-8 offset-md-3 col-md-6 offset-lg-4 col-lg-4"
type="submit"
color="primary"
disabled={formSent}
>
{#if formSent}
<Spinner label="Spinning" size="sm" />
{/if}
{$t('password.redefine')}
</Button>
</Row>
</form>

View File

@ -123,6 +123,7 @@
"email": {
"address": "Email address",
"instruction": {
"bad-link": "The link you follow is invalid. Check you copy the entier link and retry.",
"check-inbox": "Please check your inbox in order to validate your e-mail address.",
"new-confirmation": "If you need a new confirmation e-mail, just enter your address in the form below.",
"validate-address": "In order to validate your e-mail address, please check your inbox, and follow the link contained in the message.",

View File

@ -0,0 +1,24 @@
<script lang="ts">
import {
Alert,
Container,
} from 'sveltestrap';
import ForgottenPasswordForm from '$lib/components/ForgottenPasswordForm.svelte';
import RecoverAccountForm from '$lib/components/RecoverAccountForm.svelte';
let error = "";
export let data = { user: "", key: "" };
</script>
<Container class="my-3">
{#if error}
<Alert color="danger">
{error}
</Alert>
{:else if !data.user}
<ForgottenPasswordForm />
{:else}
<RecoverAccountForm {data} />
{/if}
</Container>

View File

@ -0,0 +1,11 @@
import type { Load } from '@sveltejs/kit';
export const load: Load = async({ url }: {url: URL}) => {
const user = url.searchParams.get("u");
const key = url.searchParams.get("k");
return {
user,
key,
};
}