feat: forgot password/reset password
This commit is contained in:
		
							parent
							
								
									26b32b40e2
								
							
						
					
					
						commit
						9d33093339
					
				
					 17 changed files with 374 additions and 25 deletions
				
			
		|  | @ -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"> | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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; | ||||
| 		} | ||||
| 	}, | ||||
| }; | ||||
|  | @ -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> | ||||
|  | @ -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; | ||||
| 		} | ||||
| 	}, | ||||
| }; | ||||
|  | @ -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> | ||||
|  | @ -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} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue