passwordSignIn
uses tcmts.atproto.jwtExpiry, which uses Buffer
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 the sign-in, so when its tokens can't be refreshed it signs in again by itself.
An app password may write any record, so the sign-in covers every scope.
import { jwtExpiry, Refused, resolveIdentity } from "#codebase/tcmts/atproto"; import type { OAuthSession } from "#codebase/tcmts/atproto"; export async function passwordSignIn(who: string, password: string): Promise<OAuthSession> { const id = await resolveIdentity(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, }; }