appPasswordSession
uses tcmts.atproto.jwtExpiry, which uses Buffer
A sign-in as who (a handle or DID) with an app password (com.atproto.server.createSession at their PDS): no
browser. The password is kept with it, so when its tokens can't be refreshed it signs in again by itself. An app
password may write any record, so it covers every scope.
import { jwtExpiry, Refused, resolve } from "#codebase/tcmts/atproto"; import type { OAuthSession } from "#codebase/tcmts/atproto"; export async function appPasswordSession(who: string, password: string): Promise<OAuthSession> { const id = await resolve(who); const res = await fetch(`${id.pds}/xrpc/com.atproto.server.createSession`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ identifier: id.did, password }), }); if (!res.ok) { const e = (await res.json().catch(() => ({}))) as { error?: string; message?: string }; throw new Refused( `signing in as ${id.handle ?? id.did} with an app password: ${e.message ?? e.error ?? res.status}`, ); } const t = (await res.json()) as { did: string; accessJwt: string; refreshJwt: string }; if (t.did !== id.did) throw new Error(`signed in as ${t.did}, not ${id.did}`); return { did: id.did, ...(id.handle ? { handle: id.handle } : {}), pds: id.pds, issuer: id.pds, tokenEndpoint: `${id.pds}/xrpc/com.atproto.server.refreshSession`, clientId: "", accessToken: t.accessJwt, refreshToken: t.refreshJwt, expiresAt: jwtExpiry(t.accessJwt), scope: "app-password", appPassword: password, }; }