Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115import { Client, ClientResponseError, ok, simpleFetchHandler } from "@atcute/client";import { mainSchema as createSessionSchema } from "@atcute/atproto/types/server/createSession";import { mainSchema as deleteAccountSchema } from "@atcute/atproto/types/server/deleteAccount";import { mainSchema as deleteSessionSchema } from "@atcute/atproto/types/server/deleteSession";import { mainSchema as requestAccountDeleteSchema } from "@atcute/atproto/types/server/requestAccountDelete";import { mainSchema as requestPasswordResetSchema } from "@atcute/atproto/types/server/requestPasswordReset";import { mainSchema as resetPasswordSchema } from "@atcute/atproto/types/server/resetPassword";import type { Did } from "@atcute/lexicons/syntax";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { sentence } from "#lib/format.ts";
const pdsClient = (service: string, token?: string): Client => new Client({ handler: simpleFetchHandler({ service, fetch: token ? (input, init) => { const headers = new Headers(init?.headers); headers.set("authorization", `Bearer ${token}`); return fetch(input, { ...init, headers }); } : fetch }) });
const accountError = (cause: ClientResponseError, fallback: string): string => { if (cause.error === "AuthenticationRequired" || cause.error === "AuthenticationFailed") { return "Password is incorrect."; } if (cause.error === "AuthFactorTokenRequired") { return "Your account uses two-factor sign in, which this page does not support yet."; } if (cause.error === "InvalidToken" || cause.error === "ExpiredToken") { return "The code is invalid or has expired. Send a new code and try again."; } if (cause.description) return sentence(cause.description); return fallback;};
const guard = async <T>(fallback: string, operation: () => Promise<T>): Promise<T> => { try { return await operation(); } catch (cause) { if (cause instanceof ClientResponseError) { throw new Error(accountError(cause, fallback), { cause }); } throw cause; }};
const withPasswordSession = async <T>( agent: OAuthUserAgent, password: string, operation: (client: Client, session: { email?: string }) => Promise<T>): Promise<T> => { const service = agent.session.info.aud; const session = await ok( pdsClient(service).call(createSessionSchema, { input: { identifier: agent.sub, password } }) ); try { return await operation(pdsClient(service, session.accessJwt), session); } finally { await pdsClient(service, session.refreshJwt) .call(deleteSessionSchema) .catch(() => undefined); }};
export const requestAccountDeletion = (agent: OAuthUserAgent, password: string): Promise<void> => guard("Could not send a deletion code. Try again.", () => withPasswordSession(agent, password, async (client) => { await ok(client.call(requestAccountDeleteSchema)); }) );
export const deleteAccount = ( agent: OAuthUserAgent, input: { password: string; token: string }): Promise<Did> => guard("Could not delete your account. Try again.", async () => { const did = agent.sub; await ok( pdsClient(agent.session.info.aud).call(deleteAccountSchema, { input: { did, password: input.password, token: input.token.trim() } }) ); return did; });
export const requestPasswordReset = (agent: OAuthUserAgent, password: string): Promise<void> => guard("Could not send a reset code. Try again.", () => withPasswordSession(agent, password, async (_client, { email }) => { if (!email) throw new Error("Your account has no email address to send a code to."); await ok( pdsClient(agent.session.info.aud).call(requestPasswordResetSchema, { input: { email } }) ); }) );
export const resetPassword = ( agent: OAuthUserAgent, input: { token: string; password: string }): Promise<void> => guard("Could not change your password. Try again.", async () => { await ok( pdsClient(agent.session.info.aud).call(resetPasswordSchema, { input: { token: input.token.trim(), password: input.password } }) ); });