feat(frontend): remove auth method
This commit is contained in:
		
							parent
							
								
									373d97e70a
								
							
						
					
					
						commit
						c47fc41437
					
				
					 8 changed files with 121 additions and 6 deletions
				
			
		
							
								
								
									
										14
									
								
								Foxnouns.Frontend/src/lib/components/URLAlert.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Foxnouns.Frontend/src/lib/components/URLAlert.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  | 	import { t } from "$lib/i18n"; | ||||||
|  | 
 | ||||||
|  | 	type Props = { data?: { alertKey?: string }; key?: string }; | ||||||
|  | 
 | ||||||
|  | 	let props: Props = $props(); | ||||||
|  | 	let key = $derived(props.key ?? props.data?.alertKey); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if key} | ||||||
|  | 	<div class="alert alert-light"> | ||||||
|  | 		{$t(key)} | ||||||
|  | 	</div> | ||||||
|  | {/if} | ||||||
|  | @ -77,7 +77,16 @@ | ||||||
| 		"log-in-sign-up-link": "Sign up with email", | 		"log-in-sign-up-link": "Sign up with email", | ||||||
| 		"forgot-password-title": "Forgot password", | 		"forgot-password-title": "Forgot password", | ||||||
| 		"reset-password-title": "Reset password", | 		"reset-password-title": "Reset password", | ||||||
| 		"password-changed-hint": "Your password has been changed!" | 		"password-changed-hint": "Your password has been changed!", | ||||||
|  | 		"link-email-header": "Link a new email address", | ||||||
|  | 		"unlink-email-header": "Unlink email address", | ||||||
|  | 		"unlink-fediverse-header": "Unlink fediverse account", | ||||||
|  | 		"unlink-tumblr-header": "Unlink Tumblr account", | ||||||
|  | 		"unlink-google-header": "Unlink Google account", | ||||||
|  | 		"unlink-discord-header": "Unlink Discord account", | ||||||
|  | 		"unlink-confirmation-1": "Are you sure you want to unlink {{username}} from your account?", | ||||||
|  | 		"unlink-confirmation-2": "You will no longer be able to use this account to log in. Please make sure at least one of your other linked accounts is accessible before continuing.", | ||||||
|  | 		"unlink-button": "Unlink account" | ||||||
| 	}, | 	}, | ||||||
| 	"error": { | 	"error": { | ||||||
| 		"bad-request-header": "Something was wrong with your input", | 		"bad-request-header": "Something was wrong with your input", | ||||||
|  | @ -310,5 +319,8 @@ | ||||||
| 	"form": { | 	"form": { | ||||||
| 		"optional": "(optional)", | 		"optional": "(optional)", | ||||||
| 		"required": "Required" | 		"required": "Required" | ||||||
|  | 	}, | ||||||
|  | 	"alert": { | ||||||
|  | 		"auth-method-remove-success": "Successfully unlinked account!" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,9 +9,10 @@ export const setToken = (cookies: Cookies, token: string) => | ||||||
| 	cookies.set(TOKEN_COOKIE_NAME, token, { path: "/" }); | 	cookies.set(TOKEN_COOKIE_NAME, token, { path: "/" }); | ||||||
| export const clearToken = (cookies: Cookies) => cookies.delete(TOKEN_COOKIE_NAME, { path: "/" }); | export const clearToken = (cookies: Cookies) => cookies.delete(TOKEN_COOKIE_NAME, { path: "/" }); | ||||||
| 
 | 
 | ||||||
| // TODO: change this to something we actually clearly have the rights to use
 |  | ||||||
| export const DEFAULT_AVATAR = "https://pronouns.cc/default/512.webp"; |  | ||||||
| export const DEFAULT_FLAG = "/unknown_flag.svg"; | export const DEFAULT_FLAG = "/unknown_flag.svg"; | ||||||
| 
 | 
 | ||||||
| export const idTimestamp = (id: string) => | export const idTimestamp = (id: string) => | ||||||
| 	DateTime.fromMillis(parseInt(id, 10) / (1 << 22) + 1_640_995_200_000); | 	DateTime.fromMillis(parseInt(id, 10) / (1 << 22) + 1_640_995_200_000); | ||||||
|  | 
 | ||||||
|  | export const alertKey = (url: URL): string | undefined => | ||||||
|  | 	url.searchParams.has("alert") ? "alert." + url.searchParams.get("alert") : undefined; | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| import { apiRequest, fastRequest } from "$api"; | import { apiRequest, fastRequest } from "$api"; | ||||||
| import ApiError, { ErrorCode, type RawApiError } from "$api/error.js"; | import ApiError, { ErrorCode, type RawApiError } from "$api/error.js"; | ||||||
| import type { AuthUrls } from "$api/models/auth"; | import type { AuthUrls } from "$api/models/auth"; | ||||||
|  | import { alertKey } from "$lib"; | ||||||
| import log from "$lib/log"; | import log from "$lib/log"; | ||||||
| 
 | 
 | ||||||
| export const load = async ({ fetch }) => { | export const load = async ({ fetch, url }) => { | ||||||
| 	const urls = await apiRequest<AuthUrls>("POST", "/auth/urls", { fetch, isInternal: true }); | 	const urls = await apiRequest<AuthUrls>("POST", "/auth/urls", { fetch, isInternal: true }); | ||||||
| 	return { urls }; | 	return { urls, alertKey: alertKey(url) }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const actions = { | export const actions = { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import AuthMethodList from "$components/settings/AuthMethodList.svelte"; | 	import AuthMethodList from "$components/settings/AuthMethodList.svelte"; | ||||||
| 	import EmailSettings from "$components/settings/EmailSettings.svelte"; | 	import EmailSettings from "$components/settings/EmailSettings.svelte"; | ||||||
|  | 	import UrlAlert from "$components/URLAlert.svelte"; | ||||||
| 	import type { ActionData, PageData } from "./$types"; | 	import type { ActionData, PageData } from "./$types"; | ||||||
| 
 | 
 | ||||||
| 	type Props = { data: PageData; form: ActionData }; | 	type Props = { data: PageData; form: ActionData }; | ||||||
|  | @ -14,6 +15,8 @@ | ||||||
| 	let fediAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "FEDIVERSE")); | 	let fediAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "FEDIVERSE")); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | <UrlAlert {data} /> | ||||||
|  | 
 | ||||||
| {#if data.urls.email_enabled} | {#if data.urls.email_enabled} | ||||||
| 	<EmailSettings user={data.user} {canRemove} {max} {form} /> | 	<EmailSettings user={data.user} {canRemove} {max} {form} /> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="mx-auto w-lg-75"> | <div class="mx-auto w-lg-75"> | ||||||
| 	<h3>Link a new email address</h3> | 	<h3>{$t("auth.link-email-header")}</h3> | ||||||
| 
 | 
 | ||||||
| 	<FormStatusMarker {form} successMessage={$t("auth.check-inbox-for-link-hint")} /> | 	<FormStatusMarker {form} successMessage={$t("auth.check-inbox-for-link-hint")} /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | import { fastRequest } from "$api"; | ||||||
|  | import ApiError, { ErrorCode } from "$api/error.js"; | ||||||
|  | import log from "$lib/log.js"; | ||||||
|  | import { error, isRedirect, redirect } from "@sveltejs/kit"; | ||||||
|  | 
 | ||||||
|  | export const load = async ({ parent, params }) => { | ||||||
|  | 	const data = await parent(); | ||||||
|  | 	if (data.user.auth_methods.length < 2) { | ||||||
|  | 		error(403, { | ||||||
|  | 			message: "You cannot remove your last authentication method.", | ||||||
|  | 			status: 403, | ||||||
|  | 			code: ErrorCode.LastAuthMethod, | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const authMethod = data.meUser!.auth_methods.find((m) => m.id === params.id); | ||||||
|  | 	if (!authMethod) { | ||||||
|  | 		error(404, { | ||||||
|  | 			message: "No authentication method with that ID found.", | ||||||
|  | 			status: 404, | ||||||
|  | 			code: ErrorCode.GenericApiError, | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return { authMethod }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const actions = { | ||||||
|  | 	default: async ({ params, fetch, cookies }) => { | ||||||
|  | 		try { | ||||||
|  | 			fastRequest("DELETE", "/auth/methods/" + params.id, { fetch, cookies, isInternal: true }); | ||||||
|  | 			redirect(303, "/settings/auth?alert=auth-method-remove-success"); | ||||||
|  | 		} catch (e) { | ||||||
|  | 			if (isRedirect(e)) throw e; | ||||||
|  | 			if (e instanceof ApiError) return { error: e.obj }; | ||||||
|  | 			log.error("Could not remove auth method %s:", params.id, e); | ||||||
|  | 			throw e; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  | 	import type { AuthType } from "$api/models"; | ||||||
|  | 	import ErrorAlert from "$components/ErrorAlert.svelte"; | ||||||
|  | 	import { t } from "$lib/i18n"; | ||||||
|  | 	import type { ActionData, PageData } from "./$types"; | ||||||
|  | 
 | ||||||
|  | 	type Props = { data: PageData; form: ActionData }; | ||||||
|  | 	let { data, form }: Props = $props(); | ||||||
|  | 
 | ||||||
|  | 	const unlinkHeader = (type: AuthType): string => { | ||||||
|  | 		switch (type) { | ||||||
|  | 			case "DISCORD": | ||||||
|  | 				return $t("auth.unlink-discord-header"); | ||||||
|  | 			case "GOOGLE": | ||||||
|  | 				return $t("auth.unlink-google-header"); | ||||||
|  | 			case "TUMBLR": | ||||||
|  | 				return $t("auth.unlink-tumblr-header"); | ||||||
|  | 			case "FEDIVERSE": | ||||||
|  | 				return $t("auth.unlink-fediverse-header"); | ||||||
|  | 			case "EMAIL": | ||||||
|  | 				return $t("auth.unlink-email-header"); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <svelte:head> | ||||||
|  | 	<title>{unlinkHeader(data.authMethod.type)} • pronouns.cc</title> | ||||||
|  | </svelte:head> | ||||||
|  | 
 | ||||||
|  | <div class="mx-auto w-lg-75"> | ||||||
|  | 	<h3>{unlinkHeader(data.authMethod.type)}</h3> | ||||||
|  | 	{#if form?.error} | ||||||
|  | 		<ErrorAlert error={form.error} /> | ||||||
|  | 	{/if} | ||||||
|  | 	<p> | ||||||
|  | 		{$t("auth.unlink-confirmation-1", { | ||||||
|  | 			username: data.authMethod.remote_username || data.authMethod.remote_id, | ||||||
|  | 		})} | ||||||
|  | 		<strong>{$t("auth.unlink-confirmation-2")}</strong> | ||||||
|  | 	</p> | ||||||
|  | 	<form method="POST"> | ||||||
|  | 		<button type="submit" class="btn btn-danger">{$t("auth.unlink-button")}</button> | ||||||
|  | 	</form> | ||||||
|  | </div> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue