feat: link fediverse account to existing user

This commit is contained in:
sam 2024-12-04 01:48:52 +01:00
parent 03209e4028
commit 57e1ec09c0
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
17 changed files with 335 additions and 95 deletions

View file

@ -47,7 +47,8 @@
"successful-link-fedi": "Your account has successfully been linked to the following fediverse account:",
"successful-link-profile-hint": "You now can close this page, or go back to your profile:",
"successful-link-profile-link": "Go to your profile",
"remote-discord-account-label": "Your Discord account"
"remote-discord-account-label": "Your Discord account",
"log-in-with-fediverse-instance-placeholder": "Your instance (i.e. mastodon.social)"
},
"error": {
"bad-request-header": "Something was wrong with your input",

View file

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

View file

@ -1,7 +1,9 @@
<script lang="ts">
import type { ActionData, PageData } from "./$types";
import { t } from "$lib/i18n";
import Error from "$components/Error.svelte";
import OauthRegistrationForm from "$components/settings/OauthRegistrationForm.svelte";
import NewAuthMethod from "$components/settings/NewAuthMethod.svelte";
type Props = { data: PageData; form: ActionData };
let { data, form }: Props = $props();
@ -12,11 +14,18 @@
</svelte:head>
<div class="container">
<OauthRegistrationForm
title={$t("auth.register-with-mastodon")}
remoteLabel={$t("auth.remote-fediverse-account-label")}
remoteUser={data.remoteUser}
ticket={data.ticket}
error={form?.error}
/>
{#if data.error}
<h1>{$t("auth.register-with-mastodon")}</h1>
<Error error={data.error} />
{:else if data.isLinkRequest}
<NewAuthMethod method={data.newAuthMethod!} user={data.meUser!} />
{:else}
<OauthRegistrationForm
title={$t("auth.register-with-mastodon")}
remoteLabel={$t("auth.remote-fediverse-account-label")}
remoteUser={data.remoteUser!}
ticket={data.ticket!}
error={form?.error}
/>
{/if}
</div>

View file

@ -72,7 +72,11 @@
<h4 class="mt-4">{$t("auth.log-in-with-the-fediverse")}</h4>
<form method="POST" action="?/fedi" use:enhance>
<InputGroup>
<Input name="instance" type="text" placeholder="Your instance (i.e. mastodon.social)" />
<Input
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>
<p>

View file

@ -0,0 +1,37 @@
import { apiRequest } from "$api";
import { redirect } from "@sveltejs/kit";
export const actions = {
add: async ({ request, fetch, cookies }) => {
const body = await request.formData();
const instance = body.get("instance") as string;
const { url } = await apiRequest<{ url: string }>(
"GET",
`/auth/fediverse/add-account?instance=${encodeURIComponent(instance)}`,
{
isInternal: true,
fetch,
cookies,
},
);
redirect(303, url);
},
forceRefresh: async ({ request, fetch, cookies }) => {
const body = await request.formData();
const instance = body.get("instance") as string;
const { url } = await apiRequest<{ url: string }>(
"GET",
`/auth/fediverse/add-account?instance=${encodeURIComponent(instance)}&forceRefresh=true`,
{
isInternal: true,
fetch,
cookies,
},
);
redirect(303, url);
},
};

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { t } from "$lib/i18n";
import { Button, Input, InputGroup } from "@sveltestrap/sveltestrap";
</script>
<h3>Link a new Fediverse account</h3>
<form method="POST" action="?/add">
<InputGroup>
<Input
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>
<p>
{$t("auth.log-in-with-fediverse-error-blurb")}
<Button formaction="?/forceRefresh" type="submit" color="link">
{$t("auth.log-in-with-fediverse-force-refresh-button")}
</Button>
</p>
</form>