ui: Account recovery done
This commit is contained in:
parent
cfa36a38ca
commit
150ed5c12b
|
@ -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);
|
||||
}
|
||||
|
|
93
ui/src/lib/components/ForgottenPasswordForm.svelte
Normal file
93
ui/src/lib/components/ForgottenPasswordForm.svelte
Normal 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>
|
120
ui/src/lib/components/RecoverAccountForm.svelte
Normal file
120
ui/src/lib/components/RecoverAccountForm.svelte
Normal 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>
|
|
@ -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.",
|
||||
|
|
24
ui/src/routes/forgotten-password/+page.svelte
Normal file
24
ui/src/routes/forgotten-password/+page.svelte
Normal 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>
|
11
ui/src/routes/forgotten-password/+page.ts
Normal file
11
ui/src/routes/forgotten-password/+page.ts
Normal 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,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user