ui: Account recovery done
This commit is contained in:
parent
6db36ba920
commit
cc4953e810
|
@ -18,3 +18,40 @@ export async function authUser(form: LoginForm): Promise<any> {
|
||||||
});
|
});
|
||||||
return await handleApiResponse(res);
|
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": {
|
"email": {
|
||||||
"address": "Email address",
|
"address": "Email address",
|
||||||
"instruction": {
|
"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.",
|
"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.",
|
"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.",
|
"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