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",
|
||||
"forgot-password-title": "Forgot 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": {
|
||||
"bad-request-header": "Something was wrong with your input",
|
||||
|
@ -310,5 +319,8 @@
|
|||
"form": {
|
||||
"optional": "(optional)",
|
||||
"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: "/" });
|
||||
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 idTimestamp = (id: string) =>
|
||||
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 ApiError, { ErrorCode, type RawApiError } from "$api/error.js";
|
||||
import type { AuthUrls } from "$api/models/auth";
|
||||
import { alertKey } from "$lib";
|
||||
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 });
|
||||
return { urls };
|
||||
return { urls, alertKey: alertKey(url) };
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import AuthMethodList from "$components/settings/AuthMethodList.svelte";
|
||||
import EmailSettings from "$components/settings/EmailSettings.svelte";
|
||||
import UrlAlert from "$components/URLAlert.svelte";
|
||||
import type { ActionData, PageData } from "./$types";
|
||||
|
||||
type Props = { data: PageData; form: ActionData };
|
||||
|
@ -14,6 +15,8 @@
|
|||
let fediAccounts = $derived(data.user.auth_methods.filter((m) => m.type === "FEDIVERSE"));
|
||||
</script>
|
||||
|
||||
<UrlAlert {data} />
|
||||
|
||||
{#if data.urls.email_enabled}
|
||||
<EmailSettings user={data.user} {canRemove} {max} {form} />
|
||||
{/if}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</script>
|
||||
|
||||
<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")} />
|
||||
|
||||
|
|
|
@ -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…
Reference in a new issue