import { 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 (fallback: string, operation: () => Promise): Promise => { try { return await operation(); } catch (cause) { if (cause instanceof ClientResponseError) { throw new Error(accountError(cause, fallback), { cause }); } throw cause; } }; const withPasswordSession = async ( agent: OAuthUserAgent, password: string, operation: (client: Client, session: { email?: string }) => Promise ): Promise => { 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 => 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 => 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 => 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 => 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 } }) ); });