adds password reset

This commit is contained in:
Christian Beutel
2024-11-17 21:49:46 +01:00
parent 7c1304f159
commit b9ae01846b
30 changed files with 791 additions and 247 deletions

View File

@@ -0,0 +1,26 @@
<script>
import { page } from "$app/stores";
</script>
<div
class="flex flex-col gap-y-4 items-center justify-center relative"
style="height: calc(100dvh - 120px); font-size: 4rem"
>
<img
class="absolute top-1/2 left-1/2 aspect-square rounded-xl object-cover"
width="500"
src="/imgs/error.webp"
alt="Default error img"
/>
<div
class="z-10 rounded-full p-8 bg-background flex flex-col gap-y-4 items-center"
style="max-width: 400px"
>
<h1 class="font-bold" style="line-height: 4rem;">
{$page.status}
</h1>
<p class="text-lg text-center">
{$page.error?.message}
</p>
</div>
</div>

View File

@@ -0,0 +1,20 @@
import { pb } from "$lib/pocketbase";
import { error, json, type RequestEvent } from "@sveltejs/kit";
import { ClientResponseError } from "pocketbase";
export async function POST(event: RequestEvent) {
try {
let data;
try {
data = await event.request.json()
} catch (e) {
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
}
const r = await pb.collection('users').confirmPasswordReset(data.token, data.password, data.passwordConfirm);
return json(r);
} catch (e: any) {
throw error(e.status, e);
}
}

View File

@@ -0,0 +1,20 @@
import { pb } from "$lib/pocketbase";
import { error, json, type RequestEvent } from "@sveltejs/kit";
import { ClientResponseError } from "pocketbase";
export async function POST(event: RequestEvent) {
try {
let data;
try {
data = await event.request.json()
} catch (e) {
throw new ClientResponseError({ status: 400, response: { message: "Invalid json" } })
}
const r = await pb.collection('users').requestPasswordReset(data.email);
return json(r);
} catch (e: any) {
throw error(e.status, e);
}
}

View File

@@ -0,0 +1,7 @@
import { error, type ServerLoad } from "@sveltejs/kit";
export const load: ServerLoad = async ({ params, locals, url, fetch }) => {
if(!params.token) {
error(400, "Invalid token")
}
};

View File

@@ -0,0 +1,98 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from "$app/stores";
import Button from "$lib/components/base/button.svelte";
import TextField from "$lib/components/base/text_field.svelte";
import LogoTextTwoLineDark from "$lib/components/logo/logo_text_two_line_dark.svelte";
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
import type { User } from "$lib/models/user";
import { theme } from "$lib/stores/theme_store";
import { show_toast } from "$lib/stores/toast_store";
import {
users_confirm_reset,
users_reset_password,
} from "$lib/stores/user_store";
import { createForm } from "$lib/vendor/svelte-form-lib";
import { _ } from "svelte-i18n";
import { object, ref, string } from "yup";
let loading: boolean = false;
const { form, errors, handleChange, handleSubmit } = createForm({
initialValues: {
password: "",
passwordConfirm: "",
},
validationSchema: object({
password: string().required($_("required")),
passwordConfirm: string()
.required($_("required"))
.oneOf([ref("password"), ""], $_('passwords-must-match')),
}),
onSubmit: async (form) => {
loading = true;
try {
await users_confirm_reset({
...form,
token: $page.params.token,
});
goto("/login");
show_toast({
icon: "check",
type: "success",
text: $_("new-password-success"),
});
} catch (e) {
show_toast({
icon: "close",
type: "error",
text: $_("new-password-error"),
});
} finally {
loading = false;
}
},
});
</script>
<svelte:head>
<title>{$_("login")} | wanderer</title>
</svelte:head>
<main class="flex justify-center">
<form
class="login-panel max-w-md border border-input-border rounded-xl p-8 flex flex-col justify-center items-center gap-4 w-[28rem] mt-8"
on:submit={handleSubmit}
>
{#if $theme == "light"}
<LogoTextTwoLineDark></LogoTextTwoLineDark>
{:else}
<LogoTextTwoLineLight></LogoTextTwoLineLight>
{/if}
<h4 class="text-xl font-semibold">{$_("new-password-text")}</h4>
<div class="space-y-6 w-80">
<TextField
name="password"
label={$_("password")}
type="password"
bind:value={$form.password}
on:change={handleChange}
error={$errors.password}
></TextField>
<TextField
name="password"
label={$_("password-confirm")}
type="password"
bind:value={$form.passwordConfirm}
on:change={handleChange}
error={$errors.passwordConfirm}
></TextField>
<Button
primary={true}
extraClasses={"min-w-full"}
type="submit"
{loading}>{$_("reset-password")}</Button
>
</div>
</form>
</main>

View File

@@ -0,0 +1,83 @@
<script lang="ts">
import { goto } from "$app/navigation";
import Button from "$lib/components/base/button.svelte";
import TextField from "$lib/components/base/text_field.svelte";
import LogoTextTwoLineDark from "$lib/components/logo/logo_text_two_line_dark.svelte";
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
import type { User } from "$lib/models/user";
import { theme } from "$lib/stores/theme_store";
import { show_toast } from "$lib/stores/toast_store";
import { users_reset_password } from "$lib/stores/user_store";
import { createForm } from "$lib/vendor/svelte-form-lib";
import { _ } from "svelte-i18n";
import { object, string } from "yup";
let loading: boolean = false;
const { form, errors, handleChange, handleSubmit } = createForm({
initialValues: {
email: "",
},
validationSchema: object<User>({
email: string().required($_("required")),
}),
onSubmit: async (email) => {
loading = true;
try {
await users_reset_password(email);
goto("/");
show_toast({
icon: "check",
type: "success",
text: $_("password-reset-sent"),
});
} catch (e) {
show_toast({
icon: "close",
type: "error",
text: $_("error-during-password-reset"),
});
} finally {
loading = false;
}
},
});
</script>
<svelte:head>
<title>{$_("login")} | wanderer</title>
</svelte:head>
<main class="flex justify-center">
<form
class="login-panel max-w-md border border-input-border rounded-xl p-8 flex flex-col justify-center items-center gap-4 w-[28rem] mt-8"
on:submit={handleSubmit}
>
{#if $theme == "light"}
<LogoTextTwoLineDark></LogoTextTwoLineDark>
{:else}
<LogoTextTwoLineLight></LogoTextTwoLineLight>
{/if}
<h4 class="text-xl font-semibold">{$_("forgot-your-password")}</h4>
<p class="text-gray-500">{$_('password-reset-text')}</p>
<div class="space-y-6 w-80">
<TextField
name="email"
label={$_("email")}
bind:value={$form.email}
on:change={handleChange}
error={$errors.email}
></TextField>
<div class="grid grid-cols-2 gap-x-4">
<a class="btn-secondary text-center" href="/login"
>{$_("back-to-login")}</a
>
<Button
primary={true}
extraClasses={"min-w-full"}
type="submit"
{loading}>{$_("reset-password")}</Button
>
</div>
</div>
</form>
</main>

View File

@@ -97,14 +97,20 @@
on:change={handleChange}
error={$errors.username}
></TextField>
<TextField
name="password"
label={$_("password")}
type="password"
bind:value={$form.password}
on:change={handleChange}
error={$errors.password}
></TextField>
<div class="flex flex-col">
<TextField
name="password"
label={$_("password")}
type="password"
bind:value={$form.password}
on:change={handleChange}
error={$errors.password}
></TextField>
<a
class="text-xs underline decoration-dashed float-end mt-1 self-end"
href="/auth/reset">{$_("forgot-your-password")}</a
>
</div>
<Button
primary={true}
extraClasses={"min-w-full"}