feat: forgot password/reset password

This commit is contained in:
sam 2024-12-14 16:32:08 +01:00
parent 26b32b40e2
commit 9d33093339
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
17 changed files with 374 additions and 25 deletions

View file

@ -35,7 +35,7 @@
</div>
</div>
<div class="col-md">
<FormStatusMarker {form} />
<FormStatusMarker {form} successMessage={$t("auth.password-changed-hint")} />
<h4>Change password</h4>
<form method="POST" action="?/password">
<div class="mb-1">

View file

@ -61,7 +61,13 @@
"add-email-address": "Add email address",
"no-email-addresses": "You haven't linked any email addresses yet.",
"check-inbox-for-link-hint": "Check your inbox for a link!",
"successful-link-email": "Your account has successfully been linked to the following email address:"
"successful-link-email": "Your account has successfully been linked to the following email address:",
"reset-password-button": "Reset password",
"log-in-forgot-password-link": "Forgot your password?",
"log-in-sign-up-link": "Sign up with email",
"forgot-password-title": "Forgot password",
"reset-password-title": "Reset password",
"password-changed-hint": "Your password has been changed!"
},
"error": {
"bad-request-header": "Something was wrong with your input",

View file

@ -0,0 +1,34 @@
import { apiRequest, fastRequest } from "$api";
import ApiError from "$api/error.js";
import type { AuthUrls } from "$api/models/auth";
import log from "$lib/log.js";
import { redirect } from "@sveltejs/kit";
export const load = async ({ parent, fetch }) => {
const { meUser } = await parent();
if (meUser) redirect(303, `/@${meUser.username}`);
const urls = await apiRequest<AuthUrls>("POST", "/auth/urls", { fetch, isInternal: true });
if (!urls.email_enabled) redirect(303, "/");
};
export const actions = {
default: async ({ request, fetch }) => {
const data = await request.formData();
const email = data.get("email") as string;
try {
await fastRequest("POST", "/auth/email/forgot-password", {
body: { email },
isInternal: true,
fetch,
});
return { ok: true, error: null };
} catch (e) {
if (e instanceof ApiError) return { ok: false, error: e.obj };
log.error("error sending forget password email:", e);
throw e;
}
},
};

View file

@ -0,0 +1,35 @@
<script lang="ts">
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
import { t } from "$lib/i18n";
import type { ActionData } from "./$types";
type Props = { form: ActionData };
let { form }: Props = $props();
</script>
<svelte:head>
<title>{$t("auth.forgot-password-title")} • pronouns.cc</title>
</svelte:head>
<div class="container">
<div class="mx-auto w-lg-50">
<h3>{$t("auth.forgot-password-title")}</h3>
<FormStatusMarker {form} successMessage={$t("auth.check-inbox-for-link-hint")} />
<form method="POST">
<label for="email" class="form-label">{$t("auth.log-in-form-email-label")}</label>
<input
required
type="email"
id="email"
name="email"
placeholder="me@example.com"
class="form-control mb-2"
/>
<div class="d-grid">
<button type="submit" class="btn btn-primary">{$t("auth.reset-password-button")}</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,48 @@
import { apiRequest, fastRequest } from "$api";
import ApiError, { ErrorCode, type RawApiError } from "$api/error";
import type { AuthUrls } from "$api/models";
import log from "$lib/log";
import { redirect } from "@sveltejs/kit";
export const load = async ({ params, parent, fetch }) => {
const { meUser } = await parent();
if (meUser) redirect(303, `/@${meUser.username}`);
const urls = await apiRequest<AuthUrls>("POST", "/auth/urls", { fetch, isInternal: true });
if (!urls.email_enabled) redirect(303, "/");
return { state: params.code };
};
export const actions = {
default: async ({ request, fetch }) => {
const data = await request.formData();
const state = data.get("state") as string;
const password = data.get("password") as string;
const password2 = data.get("confirm-password") as string;
if (password !== password2) {
return {
ok: false,
error: {
status: 400,
message: "Passwords don't match",
code: ErrorCode.BadRequest,
} as RawApiError,
};
}
try {
await fastRequest("POST", "/auth/email/reset-password", {
body: { state, password },
isInternal: true,
fetch,
});
return { ok: true, error: null };
} catch (e) {
if (e instanceof ApiError) return { ok: false, error: e.obj };
log.error("error resetting password:", e);
throw e;
}
},
};

View file

@ -0,0 +1,41 @@
<script lang="ts">
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
import { t } from "$lib/i18n";
import type { ActionData, PageData } from "./$types";
type Props = { data: PageData; form: ActionData };
let { data, form }: Props = $props();
</script>
<svelte:head>
<title>{$t("auth.reset-password-title")} • pronouns.cc</title>
</svelte:head>
<div class="container">
<div class="mx-auto w-lg-50">
<h3>{$t("auth.reset-password-title")}</h3>
<FormStatusMarker {form} successMessage={$t("auth.password-changed-hint")} />
<form method="POST">
<input type="hidden" name="state" readonly value={data.state} />
<div class="mb-2">
<label for="password" class="form-label">{$t("auth.log-in-form-password-label")}</label>
<input required type="password" id="password" name="password" class="form-control" />
</div>
<div class="mb-2">
<label for="confirm-password" class="form-label">{$t("auth.confirm-password-label")}</label>
<input
required
type="password"
id="confirm-password"
name="confirm-password"
class="form-control"
/>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">{$t("auth.reset-password-button")}</button>
</div>
</form>
</div>
</div>

View file

@ -2,7 +2,6 @@
import type { ActionData, PageData } from "./$types";
import { t } from "$lib/i18n";
import { enhance } from "$app/forms";
import { Button, ButtonGroup, Input, InputGroup } from "@sveltestrap/sveltestrap";
import ErrorAlert from "$components/ErrorAlert.svelte";
type Props = { data: PageData; form: ActionData };
@ -21,29 +20,34 @@
</div>
<div class="row">
{#if data.urls.email_enabled}
<div class="col col-md mb-4">
<div class="col-md mb-4">
<h2>{$t("auth.log-in-form-title")}</h2>
<form method="POST" action="?/login" use:enhance>
<div class="mb-2">
<label class="form-label" for="email">{$t("auth.log-in-form-email-label")}</label>
<Input type="email" id="email" name="email" placeholder="me@example.com" />
<input
class="form-control"
type="email"
id="email"
name="email"
placeholder="me@example.com"
/>
</div>
<div class="mb-2">
<label class="form-label" for="password">{$t("auth.log-in-form-password-label")}</label>
<Input type="password" id="password" name="password" />
<input class="form-control" type="password" id="password" name="password" />
</div>
<ButtonGroup>
<Button type="submit" color="primary">{$t("auth.log-in-button")}</Button>
<a class="btn btn-secondary" href="/auth/register">
{$t("auth.register-with-email-button")}
</a>
</ButtonGroup>
<button class="btn btn-primary" type="submit">{$t("auth.log-in-button")}</button>
</form>
<p class="mt-2">
<a href="/auth/register">{$t("auth.log-in-sign-up-link")}</a>
<a href="/auth/forgot-password">{$t("auth.log-in-forgot-password-link")}</a>
</p>
</div>
{:else}
<div class="col-lg-3"></div>
{/if}
<div class="col col-md">
<div class="col-md">
<h3>{$t("auth.log-in-3rd-party-header")}</h3>
<p>{$t("auth.log-in-3rd-party-desc")}</p>
<form method="POST" action="?/fediToggle" use:enhance>
@ -71,19 +75,20 @@
{#if form?.showFediBox}
<h4 class="mt-4">{$t("auth.log-in-with-the-fediverse")}</h4>
<form method="POST" action="?/fedi" use:enhance>
<InputGroup>
<Input
<div class="input-group">
<input
class="form-control"
name="instance"
type="text"
placeholder={$t("auth.log-in-with-fediverse-instance-placeholder")}
/>
<Button type="submit" color="secondary">{$t("auth.log-in-button")}</Button>
</InputGroup>
<button class="btn btn-secondary" type="submit">{$t("auth.log-in-button")}</button>
</div>
<p>
{$t("auth.log-in-with-fediverse-error-blurb")}
<Button formaction="?/fediForceRefresh" type="submit" color="link">
<button class="btn btn-link" formaction="?/fediForceRefresh" type="submit">
{$t("auth.log-in-with-fediverse-force-refresh-button")}
</Button>
</button>
</p>
</form>
{/if}