feat: allow linking fediverse account to existing user

This commit is contained in:
Sam 2023-03-18 15:19:53 +01:00
parent d6bb2f7743
commit 97191933cb
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
14 changed files with 306 additions and 93 deletions

View file

@ -4,10 +4,11 @@
import { goto } from "$app/navigation";
import type { APIError, MeUser } from "$lib/api/entities";
import { apiFetch } from "$lib/api/fetch";
import { apiFetch, apiFetchClient } from "$lib/api/fetch";
import { userStore } from "$lib/store";
import type { PageData } from "./$types";
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
import { addToast } from "$lib/toast";
interface SignupResponse {
user: MeUser;
@ -67,6 +68,22 @@
deleteError = e as APIError;
}
};
const linkAccount = async () => {
try {
const resp = await apiFetchClient<MeUser>("/auth/mastodon/add-provider", "POST", {
instance: data.instance,
ticket: data.ticket,
});
localStorage.setItem("pronouns-user", JSON.stringify(resp));
userStore.set(resp);
addToast({ header: "Linked account", body: "Successfully linked account!" });
goto("/settings/auth");
} catch (e) {
data.error = e as APIError;
}
};
</script>
<svelte:head>
@ -78,7 +95,32 @@
{#if data.error}
<ErrorAlert error={data.error} />
{/if}
{#if data.ticket}
{#if data.ticket && $userStore}
<div>
<label for="fediverse">Fediverse username</label>
<input
id="fediverse"
class="form-control"
name="fediverse"
readonly
value="{data.fediverse}@{data.instance}"
/>
</div>
<div>
<label for="fediverse">pronouns.cc username</label>
<input
id="pronounscc"
class="form-control"
name="pronounscc"
readonly
value={$userStore.name}
/>
</div>
<div>
<Button on:click={linkAccount}>Link account</Button>
<Button color="secondary" href="/settings/auth">Cancel</Button>
</div>
{:else if data.ticket}
<form on:submit|preventDefault={signupForm}>
<div>
<label for="fediverse">Fediverse username</label>
@ -86,7 +128,7 @@
id="fediverse"
class="form-control"
name="fediverse"
disabled
readonly
value="{data.fediverse}@{data.instance}"
/>
</div>

View file

@ -35,7 +35,14 @@
<ListGroupItem tag="a" active={$page.url.pathname === "/settings"} href="/settings">
Your profile
</ListGroupItem>
{#if data.require_invite}
<ListGroupItem
tag="a"
active={$page.url.pathname === "/settings/auth"}
href="/settings/auth"
>
Authentication
</ListGroupItem>
{#if data.invitesEnabled}
<ListGroupItem
tag="a"
active={$page.url.pathname === "/settings/invites"}

View file

@ -1,8 +1,36 @@
import {
ErrorCode,
type APIError,
type Invite,
type MeUser,
type PartialMember,
} from "$lib/api/entities";
import { apiFetchClient } from "$lib/api/fetch";
import type { LayoutLoad } from "./$types";
export const ssr = false;
export const load = (async ({ parent }) => {
const user = await apiFetchClient<MeUser>("/users/@me");
const members = await apiFetchClient<PartialMember[]>("/users/@me/members");
let invites: Invite[] = [];
let invitesEnabled = true;
try {
invites = await apiFetchClient<Invite[]>("/auth/invites");
} catch (e) {
if ((e as APIError).code === ErrorCode.InvitesDisabled) {
invitesEnabled = false;
}
}
const data = await parent();
return data;
return {
...data,
user,
members,
invites,
invitesEnabled,
};
}) satisfies LayoutLoad;

View file

@ -1,30 +0,0 @@
import {
type Invite,
type APIError,
type MeUser,
type PartialMember,
ErrorCode,
} from "$lib/api/entities";
import { apiFetchClient } from "$lib/api/fetch";
import { error } from "@sveltejs/kit";
export const load = async () => {
try {
const user = await apiFetchClient<MeUser>("/users/@me");
const members = await apiFetchClient<PartialMember[]>("/users/@me/members");
let invites: Invite[] = [];
let invitesEnabled = true;
try {
invites = await apiFetchClient<Invite[]>("/auth/invites");
} catch (e) {
if ((e as APIError).code === ErrorCode.InvitesDisabled) {
invitesEnabled = false;
}
}
return { user, members, invites, invitesEnabled };
} catch (e) {
throw error(500, (e as APIError).message);
}
};

View file

@ -0,0 +1,115 @@
<script lang="ts">
import type { APIError } from "$lib/api/entities";
import { apiFetch } from "$lib/api/fetch";
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
import {
Button,
Card,
CardBody,
CardText,
CardTitle,
Input,
Modal,
ModalBody,
ModalFooter,
} from "sveltestrap";
import type { PageData } from "./$types";
export let data: PageData;
let canUnlink = false;
$: canUnlink =
[data.user.discord, data.user.fediverse]
.map<number>((entry) => (entry === null ? 0 : 1))
.reduce((prev, current) => prev + current) >= 2;
let error: APIError | null = null;
let instance = "";
let fediDisabled = false;
let fediLinkModalOpen = false;
let toggleFediLinkModal = () => (fediLinkModalOpen = !fediLinkModalOpen);
const fediLogin = async () => {
fediDisabled = true;
try {
const resp = await apiFetch<{ url: string }>(
`/auth/urls/fediverse?instance=${encodeURIComponent(instance)}`,
{},
);
window.location.assign(resp.url);
} catch (e) {
error = e as APIError;
} finally {
fediDisabled = false;
}
};
</script>
<div>
<h1>Authentication providers</h1>
<div>
<div class="my-2">
<Card>
<CardBody>
<CardTitle>Fediverse</CardTitle>
<CardText>
{#if data.user.fediverse}
Your currently linked Fediverse account is <b
>{data.user.fediverse_username}@{data.user.fediverse_instance}</b
>
(<code>{data.user.fediverse}</code>).
{:else}
You do not have a linked Fediverse account.
{/if}
</CardText>
{#if data.user.fediverse}
<Button color="danger" disabled={!canUnlink}>Unlink account</Button>
{:else}
<Button color="secondary" on:click={toggleFediLinkModal}>Link account</Button>
{/if}
</CardBody>
</Card>
</div>
<div class="my-2">
<Card>
<CardBody>
<CardTitle>Discord</CardTitle>
<CardText>
{#if data.user.discord}
Your currently linked Discord account is <b>{data.user.discord_username}</b>
(<code>{data.user.discord}</code>).
{:else}
You do not have a linked Discord account.
{/if}
</CardText>
{#if data.user.discord}
<Button color="danger" disabled={!canUnlink}>Unlink account</Button>
{:else}
<Button color="secondary" href={data.urls.discord}>Link account</Button>
{/if}
</CardBody>
</Card>
</div>
<Modal header="Pick an instance" isOpen={fediLinkModalOpen} toggle={toggleFediLinkModal}>
<ModalBody>
<p>
<strong>Note:</strong> Misskey (and derivatives) are not supported yet, sorry.
</p>
<Input placeholder="Instance (e.g. mastodon.social)" bind:value={instance} />
{#if error}
<div class="mt-2">
<ErrorAlert {error} />
</div>
{/if}
</ModalBody>
<ModalFooter>
<Button color="primary" disabled={fediDisabled || instance === ""} on:click={fediLogin}
>Log in</Button
>
</ModalFooter>
</Modal>
</div>
</div>

View file

@ -0,0 +1,17 @@
import { PUBLIC_BASE_URL } from "$env/static/public";
import { apiFetch } from "$lib/api/fetch";
export const load = async () => {
const resp = await apiFetch<UrlsResponse>("/auth/urls", {
method: "POST",
body: {
callback_domain: PUBLIC_BASE_URL,
},
});
return { urls: resp };
};
interface UrlsResponse {
discord: string;
}

View file

@ -1,25 +0,0 @@
import { ErrorCode, type APIError, type Invite } from "$lib/api/entities";
import { apiFetchClient } from "$lib/api/fetch";
import { error } from "@sveltejs/kit";
import type { PageLoad } from "../$types";
export const load = (async () => {
const data = {
invitesEnabled: true,
invites: [] as Invite[],
};
try {
const invites = await apiFetchClient<Invite[]>("/auth/invites");
data.invites = invites;
} catch (e) {
if ((e as APIError).code === ErrorCode.InvitesDisabled) {
data.invitesEnabled = false;
data.invites = [];
} else {
throw error((e as APIError).code, (e as APIError).message);
}
}
return data;
}) satisfies PageLoad;