feat: allow linking fediverse account to existing user
This commit is contained in:
parent
d6bb2f7743
commit
97191933cb
14 changed files with 306 additions and 93 deletions
|
@ -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"}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
115
frontend/src/routes/settings/auth/+page.svelte
Normal file
115
frontend/src/routes/settings/auth/+page.svelte
Normal 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>
|
17
frontend/src/routes/settings/auth/+page.ts
Normal file
17
frontend/src/routes/settings/auth/+page.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
Loading…
Add table
Add a link
Reference in a new issue