feat(frontend): discord registration/login/linking

also moves the registration form found on the mastodon callback page
into a component so we're not repeating the same code for every auth method
This commit is contained in:
sam 2024-11-28 21:35:55 +01:00
parent 4780be3019
commit de733a0682
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
19 changed files with 545 additions and 212 deletions

View file

@ -0,0 +1,64 @@
import { apiRequest } from "$api";
import ApiError, { ErrorCode } from "$api/error";
import type { AddAccountResponse, CallbackResponse } from "$api/models/auth";
import { setToken } from "$lib";
import createRegisterAction from "$lib/actions/register.js";
import log from "$lib/log.js";
import { isRedirect, redirect } from "@sveltejs/kit";
export const load = async ({ url, parent, fetch, cookies }) => {
const code = url.searchParams.get("code") as string | null;
const state = url.searchParams.get("state") as string | null;
if (!code || !state) throw new ApiError(undefined, ErrorCode.BadRequest).obj;
const { meUser } = await parent();
if (meUser) {
try {
const resp = await apiRequest<AddAccountResponse>(
"POST",
"/auth/discord/add-account/callback",
{
isInternal: true,
body: { code, state },
fetch,
cookies,
},
);
return { hasAccount: true, isLinkRequest: true, newAuthMethod: resp };
} catch (e) {
if (e instanceof ApiError) return { isLinkRequest: true, error: e.obj };
log.error("error linking new discord account to user %s:", meUser.id, e);
throw e;
}
}
try {
const resp = await apiRequest<CallbackResponse>("POST", "/auth/discord/callback", {
body: { code, state },
isInternal: true,
fetch,
});
if (resp.has_account) {
setToken(cookies, resp.token!);
redirect(303, `/@${resp.user!.username}`);
}
return {
hasAccount: false,
isLinkRequest: false,
ticket: resp.ticket!,
remoteUser: resp.remote_username!,
};
} catch (e) {
if (isRedirect(e)) throw e;
if (e instanceof ApiError) return { isLinkRequest: false, error: e.obj };
log.error("error while requesting discord callback:", e);
throw e;
}
};
export const actions = {
default: createRegisterAction("/auth/discord/register"),
};

View file

@ -0,0 +1,31 @@
<script lang="ts">
import Error from "$components/Error.svelte";
import NewAuthMethod from "$components/settings/NewAuthMethod.svelte";
import OauthRegistrationForm from "$components/settings/OauthRegistrationForm.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.register-with-discord")} • pronouns.cc</title>
</svelte:head>
<div class="container">
{#if data.error}
<h1>{$t("auth.register-with-discord")}</h1>
<Error error={data.error} />
{:else if data.isLinkRequest}
<NewAuthMethod method={data.newAuthMethod!} user={data.meUser!} />
{:else}
<OauthRegistrationForm
title={$t("auth.register-with-discord")}
remoteLabel={$t("auth.remote-discord-account-label")}
remoteUser={data.remoteUser!}
ticket={data.ticket!}
error={form?.error}
/>
{/if}
</div>

View file

@ -1,9 +1,9 @@
import { apiRequest } from "$api";
import ApiError, { ErrorCode, type RawApiError } from "$api/error";
import type { AuthResponse, CallbackResponse } from "$api/models/auth.js";
import ApiError, { ErrorCode } from "$api/error";
import type { CallbackResponse } from "$api/models/auth.js";
import { setToken } from "$lib";
import log from "$lib/log.js";
import { isRedirect, redirect } from "@sveltejs/kit";
import createRegisterAction from "$lib/actions/register.js";
import { redirect } from "@sveltejs/kit";
export const load = async ({ parent, params, url, fetch, cookies }) => {
const { meUser } = await parent();
@ -33,30 +33,5 @@ export const load = async ({ parent, params, url, fetch, cookies }) => {
};
export const actions = {
default: async ({ request, fetch, cookies }) => {
const data = await request.formData();
const username = data.get("username") as string | null;
const ticket = data.get("ticket") as string | null;
if (!username || !ticket)
return {
error: { message: "Bad request", code: ErrorCode.BadRequest, status: 403 } as RawApiError,
};
try {
const resp = await apiRequest<AuthResponse>("POST", "/auth/fediverse/register", {
body: { username, ticket },
isInternal: true,
fetch,
});
setToken(cookies, resp.token);
redirect(303, "/auth/welcome");
} catch (e) {
if (isRedirect(e)) throw e;
log.error("Could not sign up user with username %s:", username, e);
if (e instanceof ApiError) return { error: e.obj };
throw e;
}
},
default: createRegisterAction("/auth/fediverse/register"),
};

View file

@ -1,9 +1,7 @@
<script lang="ts">
import { Button, Input, Label } from "@sveltestrap/sveltestrap";
import type { ActionData, PageData } from "./$types";
import { t } from "$lib/i18n";
import { enhance } from "$app/forms";
import ErrorAlert from "$components/ErrorAlert.svelte";
import OauthRegistrationForm from "$components/settings/OauthRegistrationForm.svelte";
type Props = { data: PageData; form: ActionData };
let { data, form }: Props = $props();
@ -14,22 +12,11 @@
</svelte:head>
<div class="container">
<h1>{$t("auth.register-with-mastodon")}</h1>
{#if form?.error}
<ErrorAlert error={form?.error} />
{/if}
<form method="POST" use:enhance>
<div class="mb-3">
<Label>{$t("auth.remote-fediverse-account-label")}</Label>
<Input type="text" readonly value={data.remoteUser} />
</div>
<div class="mb-3">
<Label>{$t("auth.register-username-label")}</Label>
<Input type="text" name="username" required />
</div>
<input type="hidden" name="ticket" value={data.ticket} />
<Button color="primary" type="submit">{$t("auth.register-button")}</Button>
</form>
<OauthRegistrationForm
title={$t("auth.register-with-mastodon")}
remoteLabel={$t("auth.remote-fediverse-account-label")}
remoteUser={data.remoteUser}
ticket={data.ticket}
error={form?.error}
/>
</div>