@happyview/oauth-client 1.4.1 → 1.4.2-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -331,7 +331,8 @@ var HappyViewOAuthClient = class {
331
331
  provisionId: data.provision_id,
332
332
  dpopKey,
333
333
  rawJwk: data.dpop_key,
334
- pkceVerifier
334
+ pkceVerifier,
335
+ confidential: data.confidential ?? false
335
336
  };
336
337
  }
337
338
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts","../src/thumbprint.ts"],"sourcesContent":["export * from \"@atproto/jwk\";\nexport * from \"@atproto/jwk-webcrypto\";\n\nexport {\n HappyViewOAuthClient,\n LAST_ACTIVE_KEY,\n STORAGE_PREFIX,\n} from \"./client\";\nexport type { FetchMetadataOptions } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport { jwkThumbprint } from \"./thumbprint\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n OAuthCallbackError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n TokenInfo,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n\nexport class OAuthCallbackError extends HappyViewError {\n readonly params: URLSearchParams;\n readonly state: string | undefined;\n\n static from(\n err: unknown,\n params: URLSearchParams,\n state?: string,\n ): OAuthCallbackError {\n if (err instanceof OAuthCallbackError) return err;\n const message = err instanceof Error ? err.message : undefined;\n return new OAuthCallbackError(params, message, state, err);\n }\n\n constructor(\n params: URLSearchParams,\n message?: string,\n state?: string,\n cause?: unknown,\n ) {\n super(\n message ??\n params.get(\"error_description\") ??\n \"OAuth callback error\",\n );\n this.name = \"OAuthCallbackError\";\n this.params = params;\n this.state = state;\n if (cause !== undefined) {\n this.cause = cause;\n }\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n const kid = (jwk as JsonWebKey & { kid?: string }).kid ?? crypto.randomUUID();\n return WebcryptoKey.fromKeypair({ privateKey, publicKey }, kid);\n}\n","import type { Key } from \"@atproto/jwk\";\nimport type { TokenInfo } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes?: string;\n pdsUrl?: string;\n issuer?: string;\n fetch?: typeof globalThis.fetch;\n onSignOut?: () => Promise<void>;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _scopes: string | undefined;\n private readonly _pdsUrl: string | undefined;\n private readonly _issuer: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n private readonly _onSignOut: (() => Promise<void>) | undefined;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes ? options.scopes.split(\" \") : [];\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._scopes = options.scopes;\n this._pdsUrl = options.pdsUrl;\n this._issuer = options.issuer;\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this._onSignOut = options.onSignOut;\n }\n\n get sub(): string {\n return this.did;\n }\n\n getTokenInfo(): TokenInfo {\n return {\n scope: this._scopes,\n iss: this._issuer,\n aud: this._pdsUrl,\n sub: this.did,\n };\n }\n\n async signOut(): Promise<void> {\n if (this._onSignOut) {\n await this._onSignOut();\n }\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n ClientAssertionResponse,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\n/// The storage keys this client reads and writes. Exported because a consumer\n/// recovering a wedged session by hand would otherwise have to hardcode them,\n/// which silently breaks the day they are renamed.\nexport const STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport interface FetchMetadataOptions {\n clientId: string;\n fetch?: typeof globalThis.fetch;\n signal?: AbortSignal;\n}\n\nexport class HappyViewOAuthClient {\n static async fetchMetadata({\n clientId,\n fetch: fetchFn = globalThis.fetch,\n signal,\n }: FetchMetadataOptions): Promise<Record<string, unknown>> {\n signal?.throwIfAborted();\n const resp = await fetchFn(clientId, { redirect: \"error\", signal });\n if (resp.status !== 200) {\n resp.body?.cancel?.();\n throw new Error(`Failed to fetch client metadata: ${resp.status}`);\n }\n const mime = resp.headers.get(\"content-type\")?.split(\";\")[0].trim();\n if (mime !== \"application/json\") {\n resp.body?.cancel?.();\n throw new Error(`Invalid client metadata content type: ${mime}`);\n }\n return resp.json() as Promise<Record<string, unknown>>;\n }\n\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n private readonly sessionHooks: SessionEventHooks;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch =\n options.fetch ??\n (((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, init)) as typeof globalThis.fetch);\n this.sessionHooks = options.sessionHooks ?? {};\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n /**\n * Mint a `private_key_jwt` client assertion for the OAuth flow against a\n * user's PDS.\n */\n async getClientAssertion(\n issuer: string,\n publicAuth?: { provisionId: string; pkceVerifier: string },\n ): Promise<{ clientAssertion: string; clientAssertionType: string }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = { issuer };\n if (publicAuth) {\n body.provision_id = publicAuth.provisionId;\n body.pkce_verifier = publicAuth.pkceVerifier;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/client-assertion`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to mint client assertion: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ClientAssertionResponse = await resp.json();\n return {\n clientAssertion: data.client_assertion,\n clientAssertionType: data.client_assertion_type,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n await this.retirePreviousSession(data.did, params.dpopKey);\n\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n const session = new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n\n this.sessionHooks.onSessionUpdate?.(data.did);\n\n return session;\n }\n\n /**\n * Revoke this client's previous session for `did`, when the login that just\n * completed replaced it.\n */\n private async retirePreviousSession(\n did: string,\n newDpopKey: JsonWebKey,\n ): Promise<void> {\n try {\n const raw = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!raw) return;\n\n const previous = JSON.parse(raw) as StoredSession;\n\n if (\n typeof newDpopKey.x === \"string\" &&\n previous.dpopKey?.x === newDpopKey.x &&\n previous.dpopKey?.y === newDpopKey.y\n ) {\n return;\n }\n\n const session = await this.restoreSession(did);\n if (!session) return;\n\n await session.fetchHandler(`${this.instanceUrl}/oauth/sessions/${did}`, {\n method: \"DELETE\",\n });\n } catch {\n // Best effort — see above.\n }\n }\n\n /**\n * Revoke the session server-side and clear it locally.\n *\n * The local cleanup runs unconditionally. If it did not, a server that\n * refuses the revocation would leave the session in storage, `restore()`\n * would sign the user back in on the next load, and the next logout would\n * fail identically — a loop with no exit through this API.\n */\n async deleteSession(did: string): Promise<void> {\n try {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n // 404: already gone. 401/403: the credential is itself invalid, so\n // there is no live session left to revoke. None of these is a logout\n // that failed, so none of them should be reported as one.\n const alreadyUnusable =\n resp.status === 404 || resp.status === 401 || resp.status === 403;\n\n if (!resp.ok && !alreadyUnusable) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n } finally {\n // 5xx and network errors still throw — the server may genuinely still\n // hold a live session and the caller deserves to know. They throw from\n // here, after the local state is gone, so the user is logged out either\n // way.\n await this.forgetSession(did);\n }\n }\n\n /**\n * Clear a session locally without contacting the server.\n *\n * The escape hatch for a session that cannot be revoked remotely — a revoked\n * or expired credential, an unreachable instance. Nothing is revoked, so the\n * server may still consider the session live until it expires.\n */\n async forgetSession(did: string): Promise<void> {\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n\n this.sessionHooks.onSessionDelete?.(did);\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes,\n pdsUrl: data.pdsUrl,\n issuer: data.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n","/**\n * RFC 7638 JWK thumbprints, used to bind an authorization request to the DPoP\n * key that will later prove possession at the token endpoint (`dpop_jkt`).\n *\n * ⚠ THE MEMBER SET AND ORDER ARE THE WHOLE SPECIFICATION. A thumbprint is the\n * SHA-256 of a JSON object containing *only* the members required for the key\n * type, with lexicographically ordered keys and no whitespace. Hashing the JWK\n * as it arrives would fold in `d`, `kid`, `ext` and `key_ops` — all of which\n * vary between the private key the client holds and the public key the\n * authorization server derives from the DPoP proof — producing a `dpop_jkt`\n * that never matches and a failure that surfaces at the token exchange, far\n * from its cause.\n */\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/** The required members per key type, already in lexicographic order. */\nconst REQUIRED_MEMBERS: Record<string, readonly string[]> = {\n EC: [\"crv\", \"kty\", \"x\", \"y\"],\n OKP: [\"crv\", \"kty\", \"x\"],\n RSA: [\"e\", \"kty\", \"n\"],\n oct: [\"k\", \"kty\"],\n};\n\n/**\n * Compute the RFC 7638 thumbprint of `jwk` as a base64url-encoded SHA-256.\n *\n * Accepts either a private or a public JWK — the private component is not a\n * required member for any key type, so both yield the same thumbprint, which is\n * what makes this usable on the key as provisioned.\n */\nexport async function jwkThumbprint(jwk: JsonWebKey): Promise<string> {\n const kty = jwk.kty;\n if (!kty || !(kty in REQUIRED_MEMBERS)) {\n throw new TypeError(\n `Cannot compute a JWK thumbprint for unsupported key type \"${kty ?? \"undefined\"}\"`,\n );\n }\n\n const members = REQUIRED_MEMBERS[kty];\n const canonical: Record<string, string> = {};\n for (const member of members) {\n const value = (jwk as Record<string, unknown>)[member];\n if (typeof value !== \"string\" || value === \"\") {\n throw new TypeError(\n `Cannot compute a JWK thumbprint: \"${kty}\" key is missing required member \"${member}\"`,\n );\n }\n canonical[member] = value;\n }\n\n // `members` is already sorted, and object literals preserve insertion order\n // for string keys, so JSON.stringify emits the canonical form directly.\n const json = JSON.stringify(canonical);\n\n return base64url(\n await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(json)),\n );\n}\n"],"mappings":"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","names":["body","base64url"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts","../src/thumbprint.ts"],"sourcesContent":["export * from \"@atproto/jwk\";\nexport * from \"@atproto/jwk-webcrypto\";\n\nexport {\n HappyViewOAuthClient,\n LAST_ACTIVE_KEY,\n STORAGE_PREFIX,\n} from \"./client\";\nexport type { FetchMetadataOptions } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport { jwkThumbprint } from \"./thumbprint\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n OAuthCallbackError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n TokenInfo,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n\nexport class OAuthCallbackError extends HappyViewError {\n readonly params: URLSearchParams;\n readonly state: string | undefined;\n\n static from(\n err: unknown,\n params: URLSearchParams,\n state?: string,\n ): OAuthCallbackError {\n if (err instanceof OAuthCallbackError) return err;\n const message = err instanceof Error ? err.message : undefined;\n return new OAuthCallbackError(params, message, state, err);\n }\n\n constructor(\n params: URLSearchParams,\n message?: string,\n state?: string,\n cause?: unknown,\n ) {\n super(\n message ??\n params.get(\"error_description\") ??\n \"OAuth callback error\",\n );\n this.name = \"OAuthCallbackError\";\n this.params = params;\n this.state = state;\n if (cause !== undefined) {\n this.cause = cause;\n }\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n const kid = (jwk as JsonWebKey & { kid?: string }).kid ?? crypto.randomUUID();\n return WebcryptoKey.fromKeypair({ privateKey, publicKey }, kid);\n}\n","import type { Key } from \"@atproto/jwk\";\nimport type { TokenInfo } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes?: string;\n pdsUrl?: string;\n issuer?: string;\n fetch?: typeof globalThis.fetch;\n onSignOut?: () => Promise<void>;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _scopes: string | undefined;\n private readonly _pdsUrl: string | undefined;\n private readonly _issuer: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n private readonly _onSignOut: (() => Promise<void>) | undefined;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes ? options.scopes.split(\" \") : [];\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._scopes = options.scopes;\n this._pdsUrl = options.pdsUrl;\n this._issuer = options.issuer;\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this._onSignOut = options.onSignOut;\n }\n\n get sub(): string {\n return this.did;\n }\n\n getTokenInfo(): TokenInfo {\n return {\n scope: this._scopes,\n iss: this._issuer,\n aud: this._pdsUrl,\n sub: this.did,\n };\n }\n\n async signOut(): Promise<void> {\n if (this._onSignOut) {\n await this._onSignOut();\n }\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n ClientAssertionResponse,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\n/// The storage keys this client reads and writes. Exported because a consumer\n/// recovering a wedged session by hand would otherwise have to hardcode them,\n/// which silently breaks the day they are renamed.\nexport const STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport interface FetchMetadataOptions {\n clientId: string;\n fetch?: typeof globalThis.fetch;\n signal?: AbortSignal;\n}\n\nexport class HappyViewOAuthClient {\n static async fetchMetadata({\n clientId,\n fetch: fetchFn = globalThis.fetch,\n signal,\n }: FetchMetadataOptions): Promise<Record<string, unknown>> {\n signal?.throwIfAborted();\n const resp = await fetchFn(clientId, { redirect: \"error\", signal });\n if (resp.status !== 200) {\n resp.body?.cancel?.();\n throw new Error(`Failed to fetch client metadata: ${resp.status}`);\n }\n const mime = resp.headers.get(\"content-type\")?.split(\";\")[0].trim();\n if (mime !== \"application/json\") {\n resp.body?.cancel?.();\n throw new Error(`Invalid client metadata content type: ${mime}`);\n }\n return resp.json() as Promise<Record<string, unknown>>;\n }\n\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n private readonly sessionHooks: SessionEventHooks;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch =\n options.fetch ??\n (((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, init)) as typeof globalThis.fetch);\n this.sessionHooks = options.sessionHooks ?? {};\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n confidential: boolean;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n confidential: data.confidential ?? false,\n };\n }\n\n /**\n * Mint a `private_key_jwt` client assertion for the OAuth flow against a\n * user's PDS.\n */\n async getClientAssertion(\n issuer: string,\n publicAuth?: { provisionId: string; pkceVerifier: string },\n ): Promise<{ clientAssertion: string; clientAssertionType: string }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = { issuer };\n if (publicAuth) {\n body.provision_id = publicAuth.provisionId;\n body.pkce_verifier = publicAuth.pkceVerifier;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/client-assertion`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to mint client assertion: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ClientAssertionResponse = await resp.json();\n return {\n clientAssertion: data.client_assertion,\n clientAssertionType: data.client_assertion_type,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n await this.retirePreviousSession(data.did, params.dpopKey);\n\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n const session = new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n\n this.sessionHooks.onSessionUpdate?.(data.did);\n\n return session;\n }\n\n /**\n * Revoke this client's previous session for `did`, when the login that just\n * completed replaced it.\n */\n private async retirePreviousSession(\n did: string,\n newDpopKey: JsonWebKey,\n ): Promise<void> {\n try {\n const raw = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!raw) return;\n\n const previous = JSON.parse(raw) as StoredSession;\n\n if (\n typeof newDpopKey.x === \"string\" &&\n previous.dpopKey?.x === newDpopKey.x &&\n previous.dpopKey?.y === newDpopKey.y\n ) {\n return;\n }\n\n const session = await this.restoreSession(did);\n if (!session) return;\n\n await session.fetchHandler(`${this.instanceUrl}/oauth/sessions/${did}`, {\n method: \"DELETE\",\n });\n } catch {\n // Best effort — see above.\n }\n }\n\n /**\n * Revoke the session server-side and clear it locally.\n *\n * The local cleanup runs unconditionally. If it did not, a server that\n * refuses the revocation would leave the session in storage, `restore()`\n * would sign the user back in on the next load, and the next logout would\n * fail identically — a loop with no exit through this API.\n */\n async deleteSession(did: string): Promise<void> {\n try {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n // 404: already gone. 401/403: the credential is itself invalid, so\n // there is no live session left to revoke. None of these is a logout\n // that failed, so none of them should be reported as one.\n const alreadyUnusable =\n resp.status === 404 || resp.status === 401 || resp.status === 403;\n\n if (!resp.ok && !alreadyUnusable) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n } finally {\n // 5xx and network errors still throw — the server may genuinely still\n // hold a live session and the caller deserves to know. They throw from\n // here, after the local state is gone, so the user is logged out either\n // way.\n await this.forgetSession(did);\n }\n }\n\n /**\n * Clear a session locally without contacting the server.\n *\n * The escape hatch for a session that cannot be revoked remotely — a revoked\n * or expired credential, an unreachable instance. Nothing is revoked, so the\n * server may still consider the session live until it expires.\n */\n async forgetSession(did: string): Promise<void> {\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n\n this.sessionHooks.onSessionDelete?.(did);\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes,\n pdsUrl: data.pdsUrl,\n issuer: data.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n","/**\n * RFC 7638 JWK thumbprints, used to bind an authorization request to the DPoP\n * key that will later prove possession at the token endpoint (`dpop_jkt`).\n *\n * ⚠ THE MEMBER SET AND ORDER ARE THE WHOLE SPECIFICATION. A thumbprint is the\n * SHA-256 of a JSON object containing *only* the members required for the key\n * type, with lexicographically ordered keys and no whitespace. Hashing the JWK\n * as it arrives would fold in `d`, `kid`, `ext` and `key_ops` — all of which\n * vary between the private key the client holds and the public key the\n * authorization server derives from the DPoP proof — producing a `dpop_jkt`\n * that never matches and a failure that surfaces at the token exchange, far\n * from its cause.\n */\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/** The required members per key type, already in lexicographic order. */\nconst REQUIRED_MEMBERS: Record<string, readonly string[]> = {\n EC: [\"crv\", \"kty\", \"x\", \"y\"],\n OKP: [\"crv\", \"kty\", \"x\"],\n RSA: [\"e\", \"kty\", \"n\"],\n oct: [\"k\", \"kty\"],\n};\n\n/**\n * Compute the RFC 7638 thumbprint of `jwk` as a base64url-encoded SHA-256.\n *\n * Accepts either a private or a public JWK — the private component is not a\n * required member for any key type, so both yield the same thumbprint, which is\n * what makes this usable on the key as provisioned.\n */\nexport async function jwkThumbprint(jwk: JsonWebKey): Promise<string> {\n const kty = jwk.kty;\n if (!kty || !(kty in REQUIRED_MEMBERS)) {\n throw new TypeError(\n `Cannot compute a JWK thumbprint for unsupported key type \"${kty ?? \"undefined\"}\"`,\n );\n }\n\n const members = REQUIRED_MEMBERS[kty];\n const canonical: Record<string, string> = {};\n for (const member of members) {\n const value = (jwk as Record<string, unknown>)[member];\n if (typeof value !== \"string\" || value === \"\") {\n throw new TypeError(\n `Cannot compute a JWK thumbprint: \"${kty}\" key is missing required member \"${member}\"`,\n );\n }\n canonical[member] = value;\n }\n\n // `members` is already sorted, and object literals preserve insertion order\n // for string keys, so JSON.stringify emits the canonical form directly.\n const json = JSON.stringify(canonical);\n\n return base64url(\n await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(json)),\n );\n}\n"],"mappings":"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","names":["body","base64url"]}
package/dist/index.d.cts CHANGED
@@ -61,6 +61,7 @@ interface TokenInfo {
61
61
  interface ProvisionKeyResponse {
62
62
  provision_id: string;
63
63
  dpop_key: JsonWebKey;
64
+ confidential?: boolean;
64
65
  }
65
66
  interface RegisterSessionResponse {
66
67
  session_id: string;
@@ -128,6 +129,7 @@ declare class HappyViewOAuthClient {
128
129
  dpopKey: Key;
129
130
  rawJwk: JsonWebKey;
130
131
  pkceVerifier?: string;
132
+ confidential: boolean;
131
133
  }>;
132
134
  /**
133
135
  * Mint a `private_key_jwt` client assertion for the OAuth flow against a
package/dist/index.d.ts CHANGED
@@ -61,6 +61,7 @@ interface TokenInfo {
61
61
  interface ProvisionKeyResponse {
62
62
  provision_id: string;
63
63
  dpop_key: JsonWebKey;
64
+ confidential?: boolean;
64
65
  }
65
66
  interface RegisterSessionResponse {
66
67
  session_id: string;
@@ -128,6 +129,7 @@ declare class HappyViewOAuthClient {
128
129
  dpopKey: Key;
129
130
  rawJwk: JsonWebKey;
130
131
  pkceVerifier?: string;
132
+ confidential: boolean;
131
133
  }>;
132
134
  /**
133
135
  * Mint a `private_key_jwt` client assertion for the OAuth flow against a
package/dist/index.js CHANGED
@@ -293,7 +293,8 @@ var HappyViewOAuthClient = class {
293
293
  provisionId: data.provision_id,
294
294
  dpopKey,
295
295
  rawJwk: data.dpop_key,
296
- pkceVerifier
296
+ pkceVerifier,
297
+ confidential: data.confidential ?? false
297
298
  };
298
299
  }
299
300
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts","../src/thumbprint.ts"],"sourcesContent":["export * from \"@atproto/jwk\";\nexport * from \"@atproto/jwk-webcrypto\";\n\nexport {\n HappyViewOAuthClient,\n LAST_ACTIVE_KEY,\n STORAGE_PREFIX,\n} from \"./client\";\nexport type { FetchMetadataOptions } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport { jwkThumbprint } from \"./thumbprint\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n OAuthCallbackError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n TokenInfo,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n\nexport class OAuthCallbackError extends HappyViewError {\n readonly params: URLSearchParams;\n readonly state: string | undefined;\n\n static from(\n err: unknown,\n params: URLSearchParams,\n state?: string,\n ): OAuthCallbackError {\n if (err instanceof OAuthCallbackError) return err;\n const message = err instanceof Error ? err.message : undefined;\n return new OAuthCallbackError(params, message, state, err);\n }\n\n constructor(\n params: URLSearchParams,\n message?: string,\n state?: string,\n cause?: unknown,\n ) {\n super(\n message ??\n params.get(\"error_description\") ??\n \"OAuth callback error\",\n );\n this.name = \"OAuthCallbackError\";\n this.params = params;\n this.state = state;\n if (cause !== undefined) {\n this.cause = cause;\n }\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n const kid = (jwk as JsonWebKey & { kid?: string }).kid ?? crypto.randomUUID();\n return WebcryptoKey.fromKeypair({ privateKey, publicKey }, kid);\n}\n","import type { Key } from \"@atproto/jwk\";\nimport type { TokenInfo } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes?: string;\n pdsUrl?: string;\n issuer?: string;\n fetch?: typeof globalThis.fetch;\n onSignOut?: () => Promise<void>;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _scopes: string | undefined;\n private readonly _pdsUrl: string | undefined;\n private readonly _issuer: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n private readonly _onSignOut: (() => Promise<void>) | undefined;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes ? options.scopes.split(\" \") : [];\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._scopes = options.scopes;\n this._pdsUrl = options.pdsUrl;\n this._issuer = options.issuer;\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this._onSignOut = options.onSignOut;\n }\n\n get sub(): string {\n return this.did;\n }\n\n getTokenInfo(): TokenInfo {\n return {\n scope: this._scopes,\n iss: this._issuer,\n aud: this._pdsUrl,\n sub: this.did,\n };\n }\n\n async signOut(): Promise<void> {\n if (this._onSignOut) {\n await this._onSignOut();\n }\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n ClientAssertionResponse,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\n/// The storage keys this client reads and writes. Exported because a consumer\n/// recovering a wedged session by hand would otherwise have to hardcode them,\n/// which silently breaks the day they are renamed.\nexport const STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport interface FetchMetadataOptions {\n clientId: string;\n fetch?: typeof globalThis.fetch;\n signal?: AbortSignal;\n}\n\nexport class HappyViewOAuthClient {\n static async fetchMetadata({\n clientId,\n fetch: fetchFn = globalThis.fetch,\n signal,\n }: FetchMetadataOptions): Promise<Record<string, unknown>> {\n signal?.throwIfAborted();\n const resp = await fetchFn(clientId, { redirect: \"error\", signal });\n if (resp.status !== 200) {\n resp.body?.cancel?.();\n throw new Error(`Failed to fetch client metadata: ${resp.status}`);\n }\n const mime = resp.headers.get(\"content-type\")?.split(\";\")[0].trim();\n if (mime !== \"application/json\") {\n resp.body?.cancel?.();\n throw new Error(`Invalid client metadata content type: ${mime}`);\n }\n return resp.json() as Promise<Record<string, unknown>>;\n }\n\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n private readonly sessionHooks: SessionEventHooks;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch =\n options.fetch ??\n (((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, init)) as typeof globalThis.fetch);\n this.sessionHooks = options.sessionHooks ?? {};\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n };\n }\n\n /**\n * Mint a `private_key_jwt` client assertion for the OAuth flow against a\n * user's PDS.\n */\n async getClientAssertion(\n issuer: string,\n publicAuth?: { provisionId: string; pkceVerifier: string },\n ): Promise<{ clientAssertion: string; clientAssertionType: string }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = { issuer };\n if (publicAuth) {\n body.provision_id = publicAuth.provisionId;\n body.pkce_verifier = publicAuth.pkceVerifier;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/client-assertion`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to mint client assertion: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ClientAssertionResponse = await resp.json();\n return {\n clientAssertion: data.client_assertion,\n clientAssertionType: data.client_assertion_type,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n await this.retirePreviousSession(data.did, params.dpopKey);\n\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n const session = new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n\n this.sessionHooks.onSessionUpdate?.(data.did);\n\n return session;\n }\n\n /**\n * Revoke this client's previous session for `did`, when the login that just\n * completed replaced it.\n */\n private async retirePreviousSession(\n did: string,\n newDpopKey: JsonWebKey,\n ): Promise<void> {\n try {\n const raw = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!raw) return;\n\n const previous = JSON.parse(raw) as StoredSession;\n\n if (\n typeof newDpopKey.x === \"string\" &&\n previous.dpopKey?.x === newDpopKey.x &&\n previous.dpopKey?.y === newDpopKey.y\n ) {\n return;\n }\n\n const session = await this.restoreSession(did);\n if (!session) return;\n\n await session.fetchHandler(`${this.instanceUrl}/oauth/sessions/${did}`, {\n method: \"DELETE\",\n });\n } catch {\n // Best effort — see above.\n }\n }\n\n /**\n * Revoke the session server-side and clear it locally.\n *\n * The local cleanup runs unconditionally. If it did not, a server that\n * refuses the revocation would leave the session in storage, `restore()`\n * would sign the user back in on the next load, and the next logout would\n * fail identically — a loop with no exit through this API.\n */\n async deleteSession(did: string): Promise<void> {\n try {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n // 404: already gone. 401/403: the credential is itself invalid, so\n // there is no live session left to revoke. None of these is a logout\n // that failed, so none of them should be reported as one.\n const alreadyUnusable =\n resp.status === 404 || resp.status === 401 || resp.status === 403;\n\n if (!resp.ok && !alreadyUnusable) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n } finally {\n // 5xx and network errors still throw — the server may genuinely still\n // hold a live session and the caller deserves to know. They throw from\n // here, after the local state is gone, so the user is logged out either\n // way.\n await this.forgetSession(did);\n }\n }\n\n /**\n * Clear a session locally without contacting the server.\n *\n * The escape hatch for a session that cannot be revoked remotely — a revoked\n * or expired credential, an unreachable instance. Nothing is revoked, so the\n * server may still consider the session live until it expires.\n */\n async forgetSession(did: string): Promise<void> {\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n\n this.sessionHooks.onSessionDelete?.(did);\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes,\n pdsUrl: data.pdsUrl,\n issuer: data.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n","/**\n * RFC 7638 JWK thumbprints, used to bind an authorization request to the DPoP\n * key that will later prove possession at the token endpoint (`dpop_jkt`).\n *\n * ⚠ THE MEMBER SET AND ORDER ARE THE WHOLE SPECIFICATION. A thumbprint is the\n * SHA-256 of a JSON object containing *only* the members required for the key\n * type, with lexicographically ordered keys and no whitespace. Hashing the JWK\n * as it arrives would fold in `d`, `kid`, `ext` and `key_ops` — all of which\n * vary between the private key the client holds and the public key the\n * authorization server derives from the DPoP proof — producing a `dpop_jkt`\n * that never matches and a failure that surfaces at the token exchange, far\n * from its cause.\n */\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/** The required members per key type, already in lexicographic order. */\nconst REQUIRED_MEMBERS: Record<string, readonly string[]> = {\n EC: [\"crv\", \"kty\", \"x\", \"y\"],\n OKP: [\"crv\", \"kty\", \"x\"],\n RSA: [\"e\", \"kty\", \"n\"],\n oct: [\"k\", \"kty\"],\n};\n\n/**\n * Compute the RFC 7638 thumbprint of `jwk` as a base64url-encoded SHA-256.\n *\n * Accepts either a private or a public JWK — the private component is not a\n * required member for any key type, so both yield the same thumbprint, which is\n * what makes this usable on the key as provisioned.\n */\nexport async function jwkThumbprint(jwk: JsonWebKey): Promise<string> {\n const kty = jwk.kty;\n if (!kty || !(kty in REQUIRED_MEMBERS)) {\n throw new TypeError(\n `Cannot compute a JWK thumbprint for unsupported key type \"${kty ?? \"undefined\"}\"`,\n );\n }\n\n const members = REQUIRED_MEMBERS[kty];\n const canonical: Record<string, string> = {};\n for (const member of members) {\n const value = (jwk as Record<string, unknown>)[member];\n if (typeof value !== \"string\" || value === \"\") {\n throw new TypeError(\n `Cannot compute a JWK thumbprint: \"${kty}\" key is missing required member \"${member}\"`,\n );\n }\n canonical[member] = value;\n }\n\n // `members` is already sorted, and object literals preserve insertion order\n // for string keys, so JSON.stringify emits the canonical form directly.\n const json = JSON.stringify(canonical);\n\n return base64url(\n await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(json)),\n );\n}\n"],"mappings":"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","names":["body","base64url"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/import-jwk.ts","../src/session.ts","../src/storage.ts","../src/client.ts","../src/thumbprint.ts"],"sourcesContent":["export * from \"@atproto/jwk\";\nexport * from \"@atproto/jwk-webcrypto\";\n\nexport {\n HappyViewOAuthClient,\n LAST_ACTIVE_KEY,\n STORAGE_PREFIX,\n} from \"./client\";\nexport type { FetchMetadataOptions } from \"./client\";\nexport { importJwk } from \"./import-jwk\";\nexport { jwkThumbprint } from \"./thumbprint\";\nexport {\n ApiError,\n AuthenticationError,\n HappyViewError,\n InvalidStateError,\n OAuthCallbackError,\n ResolutionError,\n TokenExchangeError,\n} from \"./errors\";\nexport { HappyViewSession } from \"./session\";\nexport type { HappyViewSessionOptions } from \"./session\";\nexport { MemoryStorage } from \"./storage\";\nexport type {\n DpopProvision,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n TokenInfo,\n} from \"./types\";\n","export class HappyViewError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HappyViewError\";\n }\n}\n\nexport class ApiError extends HappyViewError {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class AuthenticationError extends HappyViewError {\n readonly status: number;\n\n constructor(message: string, status: number = 401) {\n super(message);\n this.name = \"AuthenticationError\";\n this.status = status;\n }\n}\n\nexport class InvalidStateError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidStateError\";\n }\n}\n\nexport class TokenExchangeError extends HappyViewError {\n readonly status: number;\n readonly body: string;\n\n constructor(message: string, status: number, body: string) {\n super(message);\n this.name = \"TokenExchangeError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport class ResolutionError extends HappyViewError {\n constructor(message: string) {\n super(message);\n this.name = \"ResolutionError\";\n }\n}\n\nexport class OAuthCallbackError extends HappyViewError {\n readonly params: URLSearchParams;\n readonly state: string | undefined;\n\n static from(\n err: unknown,\n params: URLSearchParams,\n state?: string,\n ): OAuthCallbackError {\n if (err instanceof OAuthCallbackError) return err;\n const message = err instanceof Error ? err.message : undefined;\n return new OAuthCallbackError(params, message, state, err);\n }\n\n constructor(\n params: URLSearchParams,\n message?: string,\n state?: string,\n cause?: unknown,\n ) {\n super(\n message ??\n params.get(\"error_description\") ??\n \"OAuth callback error\",\n );\n this.name = \"OAuthCallbackError\";\n this.params = params;\n this.state = state;\n if (cause !== undefined) {\n this.cause = cause;\n }\n }\n}\n","import { WebcryptoKey } from \"@atproto/jwk-webcrypto\";\n\n/**\n * Import a raw ES256 JWK (as returned by HappyView's /oauth/dpop-keys)\n * into a WebcryptoKey that can sign JWTs.\n */\nexport async function importJwk(jwk: JsonWebKey): Promise<WebcryptoKey> {\n const privateKey = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"sign\"],\n );\n\n // Derive public key by stripping the private component\n const { d: _, ...publicJwkFields } = jwk;\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n publicJwkFields,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n true,\n [\"verify\"],\n );\n\n const kid = (jwk as JsonWebKey & { kid?: string }).kid ?? crypto.randomUUID();\n return WebcryptoKey.fromKeypair({ privateKey, publicKey }, kid);\n}\n","import type { Key } from \"@atproto/jwk\";\nimport type { TokenInfo } from \"./types\";\n\nexport interface HappyViewSessionOptions {\n did: string;\n dpopKey: Key;\n accessToken: string;\n clientKey: string;\n instanceUrl: string;\n scopes?: string;\n pdsUrl?: string;\n issuer?: string;\n fetch?: typeof globalThis.fetch;\n onSignOut?: () => Promise<void>;\n}\n\nfunction randomHex(byteLength: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(byteLength));\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class HappyViewSession {\n readonly did: string;\n readonly scopes: string[];\n\n private readonly dpopKey: Key;\n private readonly accessToken: string;\n private readonly clientKey: string;\n private readonly instanceUrl: string;\n private readonly _scopes: string | undefined;\n private readonly _pdsUrl: string | undefined;\n private readonly _issuer: string | undefined;\n private readonly _fetch: typeof globalThis.fetch;\n private readonly _onSignOut: (() => Promise<void>) | undefined;\n private dpopNonce: string | undefined;\n\n constructor(options: HappyViewSessionOptions) {\n this.did = options.did;\n this.scopes = options.scopes ? options.scopes.split(\" \") : [];\n this.dpopKey = options.dpopKey;\n this.accessToken = options.accessToken;\n this.clientKey = options.clientKey;\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this._scopes = options.scopes;\n this._pdsUrl = options.pdsUrl;\n this._issuer = options.issuer;\n this._fetch = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof globalThis.fetch;\n this._onSignOut = options.onSignOut;\n }\n\n get sub(): string {\n return this.did;\n }\n\n getTokenInfo(): TokenInfo {\n return {\n scope: this._scopes,\n iss: this._issuer,\n aud: this._pdsUrl,\n sub: this.did,\n };\n }\n\n async signOut(): Promise<void> {\n if (this._onSignOut) {\n await this._onSignOut();\n }\n }\n\n async fetchHandler(url: string, init: RequestInit): Promise<Response> {\n const fullUrl = /^https?:\\/\\//i.test(url)\n ? url\n : `${this.instanceUrl}${url}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const htu = fullUrl.split(\"?\")[0];\n\n const sendWithProof = async (): Promise<Response> => {\n const tokenHash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(this.accessToken),\n );\n const ath = base64url(tokenHash);\n\n const proof = await this.dpopKey.createJwt(\n {\n alg: \"ES256\",\n typ: \"dpop+jwt\",\n jwk: this.dpopKey.publicJwk!,\n },\n {\n htm: method,\n htu,\n iat: Math.floor(Date.now() / 1000),\n jti: randomHex(16),\n ath,\n nonce: this.dpopNonce,\n },\n );\n\n const headers = new Headers(init.headers);\n headers.set(\"authorization\", `DPoP ${this.accessToken}`);\n headers.set(\"dpop\", proof);\n headers.set(\"x-client-key\", this.clientKey);\n\n return this._fetch(fullUrl, { ...init, headers });\n };\n\n let response: Response;\n try {\n response = await sendWithProof();\n } catch (e) {\n const info = `_fetch type: ${typeof this._fetch}, toString: ${String(this._fetch).slice(0, 120)}, url: ${fullUrl}`;\n throw new Error(`fetchHandler failed: ${(e as Error).message}\\n\\nDEBUG: ${info}`);\n }\n\n // Retry once if the server requires a DPoP nonce we didn't have\n const nonce = response.headers.get(\"dpop-nonce\");\n if (nonce && nonce !== this.dpopNonce && (response.status === 401 || response.status === 400)) {\n this.dpopNonce = nonce;\n try {\n response = await sendWithProof();\n } catch (e) {\n throw new Error(`fetchHandler retry failed: ${(e as Error).message}`);\n }\n const retryNonce = response.headers.get(\"dpop-nonce\");\n if (retryNonce) {\n this.dpopNonce = retryNonce;\n }\n } else if (nonce) {\n this.dpopNonce = nonce;\n }\n\n return response;\n }\n}\n","import type { StorageAdapter } from \"./types\";\n\nexport class MemoryStorage implements StorageAdapter {\n private data = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n\n async delete(key: string): Promise<void> {\n this.data.delete(key);\n }\n}\n","import type { Key } from \"@atproto/jwk\";\nimport { ApiError } from \"./errors\";\nimport { importJwk } from \"./import-jwk\";\nimport { HappyViewSession } from \"./session\";\nimport { MemoryStorage } from \"./storage\";\nimport type {\n ClientAssertionResponse,\n GetSessionResponse,\n HappyViewOAuthClientOptions,\n ProvisionKeyResponse,\n RegisterSessionParams,\n RegisterSessionResponse,\n SessionEventHooks,\n StorageAdapter,\n StoredSession,\n} from \"./types\";\n\n/// The storage keys this client reads and writes. Exported because a consumer\n/// recovering a wedged session by hand would otherwise have to hardcode them,\n/// which silently breaks the day they are renamed.\nexport const STORAGE_PREFIX = \"happyview:session:\";\nexport const LAST_ACTIVE_KEY = \"happyview:last-active-did\";\n\nexport interface FetchMetadataOptions {\n clientId: string;\n fetch?: typeof globalThis.fetch;\n signal?: AbortSignal;\n}\n\nexport class HappyViewOAuthClient {\n static async fetchMetadata({\n clientId,\n fetch: fetchFn = globalThis.fetch,\n signal,\n }: FetchMetadataOptions): Promise<Record<string, unknown>> {\n signal?.throwIfAborted();\n const resp = await fetchFn(clientId, { redirect: \"error\", signal });\n if (resp.status !== 200) {\n resp.body?.cancel?.();\n throw new Error(`Failed to fetch client metadata: ${resp.status}`);\n }\n const mime = resp.headers.get(\"content-type\")?.split(\";\")[0].trim();\n if (mime !== \"application/json\") {\n resp.body?.cancel?.();\n throw new Error(`Invalid client metadata content type: ${mime}`);\n }\n return resp.json() as Promise<Record<string, unknown>>;\n }\n\n protected readonly instanceUrl: string;\n protected readonly clientKey: string;\n protected readonly storage: StorageAdapter;\n private readonly clientSecret: string | undefined;\n protected readonly _fetch: typeof globalThis.fetch;\n private readonly sessionHooks: SessionEventHooks;\n\n constructor(\n options: HappyViewOAuthClientOptions & {\n fetch?: typeof globalThis.fetch;\n },\n ) {\n this.instanceUrl = options.instanceUrl.replace(/\\/+$/, \"\");\n this.clientKey = options.clientKey;\n this.clientSecret = options.clientSecret;\n this.storage = options.storage ?? new MemoryStorage();\n this._fetch =\n options.fetch ??\n (((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, init)) as typeof globalThis.fetch);\n this.sessionHooks = options.sessionHooks ?? {};\n }\n\n get isConfidential(): boolean {\n return this.clientSecret !== undefined;\n }\n\n async provisionDpopKey(): Promise<{\n provisionId: string;\n dpopKey: Key;\n rawJwk: JsonWebKey;\n pkceVerifier?: string;\n confidential: boolean;\n }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {};\n let pkceVerifier: string | undefined;\n\n if (!this.isConfidential) {\n pkceVerifier = generatePkceVerifier();\n const challenge = await computePkceChallenge(pkceVerifier);\n body.pkce_challenge = challenge;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/dpop-keys`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to provision DPoP key: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ProvisionKeyResponse = await resp.json();\n const dpopKey = await importJwk(data.dpop_key);\n return {\n provisionId: data.provision_id,\n dpopKey,\n rawJwk: data.dpop_key,\n pkceVerifier,\n confidential: data.confidential ?? false,\n };\n }\n\n /**\n * Mint a `private_key_jwt` client assertion for the OAuth flow against a\n * user's PDS.\n */\n async getClientAssertion(\n issuer: string,\n publicAuth?: { provisionId: string; pkceVerifier: string },\n ): Promise<{ clientAssertion: string; clientAssertionType: string }> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = { issuer };\n if (publicAuth) {\n body.provision_id = publicAuth.provisionId;\n body.pkce_verifier = publicAuth.pkceVerifier;\n }\n\n const resp = await this._fetch(\n `${this.instanceUrl}/oauth/client-assertion`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to mint client assertion: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: ClientAssertionResponse = await resp.json();\n return {\n clientAssertion: data.client_assertion,\n clientAssertionType: data.client_assertion_type,\n };\n }\n\n async registerSession(\n params: RegisterSessionParams,\n ): Promise<HappyViewSession> {\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"x-client-key\": this.clientKey,\n };\n if (this.clientSecret) {\n headers[\"x-client-secret\"] = this.clientSecret;\n }\n\n const body: Record<string, unknown> = {\n provision_id: params.provisionId,\n did: params.did,\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n scopes: params.scopes,\n pds_url: params.pdsUrl,\n issuer: params.issuer,\n };\n\n if (!this.isConfidential && params.pkceVerifier) {\n body.pkce_verifier = params.pkceVerifier;\n }\n\n const resp = await this._fetch(`${this.instanceUrl}/oauth/sessions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to register session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n const data: RegisterSessionResponse = await resp.json();\n\n await this.retirePreviousSession(data.did, params.dpopKey);\n\n const dpopKey = await importJwk(params.dpopKey);\n\n const storedSession: StoredSession = {\n did: data.did,\n dpopKey: params.dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n };\n await this.storage.set(\n `${STORAGE_PREFIX}${data.did}`,\n JSON.stringify(storedSession),\n );\n await this.storage.set(LAST_ACTIVE_KEY, data.did);\n\n const session = new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: params.accessToken,\n clientKey: this.clientKey,\n instanceUrl: this.instanceUrl,\n scopes: params.scopes,\n pdsUrl: params.pdsUrl,\n issuer: params.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n\n this.sessionHooks.onSessionUpdate?.(data.did);\n\n return session;\n }\n\n /**\n * Revoke this client's previous session for `did`, when the login that just\n * completed replaced it.\n */\n private async retirePreviousSession(\n did: string,\n newDpopKey: JsonWebKey,\n ): Promise<void> {\n try {\n const raw = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!raw) return;\n\n const previous = JSON.parse(raw) as StoredSession;\n\n if (\n typeof newDpopKey.x === \"string\" &&\n previous.dpopKey?.x === newDpopKey.x &&\n previous.dpopKey?.y === newDpopKey.y\n ) {\n return;\n }\n\n const session = await this.restoreSession(did);\n if (!session) return;\n\n await session.fetchHandler(`${this.instanceUrl}/oauth/sessions/${did}`, {\n method: \"DELETE\",\n });\n } catch {\n // Best effort — see above.\n }\n }\n\n /**\n * Revoke the session server-side and clear it locally.\n *\n * The local cleanup runs unconditionally. If it did not, a server that\n * refuses the revocation would leave the session in storage, `restore()`\n * would sign the user back in on the next load, and the next logout would\n * fail identically — a loop with no exit through this API.\n */\n async deleteSession(did: string): Promise<void> {\n try {\n const session = await this.restoreSession(did);\n if (session) {\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"DELETE\" },\n );\n\n // 404: already gone. 401/403: the credential is itself invalid, so\n // there is no live session left to revoke. None of these is a logout\n // that failed, so none of them should be reported as one.\n const alreadyUnusable =\n resp.status === 404 || resp.status === 401 || resp.status === 403;\n\n if (!resp.ok && !alreadyUnusable) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to delete session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n }\n } finally {\n // 5xx and network errors still throw — the server may genuinely still\n // hold a live session and the caller deserves to know. They throw from\n // here, after the local state is gone, so the user is logged out either\n // way.\n await this.forgetSession(did);\n }\n }\n\n /**\n * Clear a session locally without contacting the server.\n *\n * The escape hatch for a session that cannot be revoked remotely — a revoked\n * or expired credential, an unreachable instance. Nothing is revoked, so the\n * server may still consider the session live until it expires.\n */\n async forgetSession(did: string): Promise<void> {\n await this.storage.delete(`${STORAGE_PREFIX}${did}`);\n\n const lastActive = await this.storage.get(LAST_ACTIVE_KEY);\n if (lastActive === did) {\n await this.storage.delete(LAST_ACTIVE_KEY);\n }\n\n this.sessionHooks.onSessionDelete?.(did);\n }\n\n async restoreSession(did: string): Promise<HappyViewSession | null> {\n const stored = await this.storage.get(`${STORAGE_PREFIX}${did}`);\n if (!stored) return null;\n\n const data: StoredSession = JSON.parse(stored);\n const dpopKey = await importJwk(data.dpopKey);\n return new HappyViewSession({\n did: data.did,\n dpopKey,\n accessToken: data.accessToken,\n clientKey: data.clientKey,\n instanceUrl: data.instanceUrl,\n scopes: data.scopes,\n pdsUrl: data.pdsUrl,\n issuer: data.issuer,\n fetch: this._fetch,\n onSignOut: () => this.deleteSession(data.did),\n });\n }\n\n async getSession(did: string): Promise<GetSessionResponse> {\n const session = await this.restoreSession(did);\n if (!session) {\n throw new ApiError(\"No stored session for this DID\", 404, {});\n }\n\n const resp = await session.fetchHandler(\n `${this.instanceUrl}/oauth/sessions/${did}`,\n { method: \"GET\" },\n );\n\n if (!resp.ok) {\n const body = await resp.json().catch(() => ({}));\n throw new ApiError(\n `Failed to get session: ${resp.status} ${(body as any).error ?? (body as any).message ?? resp.statusText}`,\n resp.status,\n body,\n );\n }\n\n return resp.json();\n }\n\n async restore(): Promise<HappyViewSession | null> {\n const did = await this.storage.get(LAST_ACTIVE_KEY);\n if (!did) return null;\n return this.restoreSession(did);\n }\n}\n\n// PKCE helpers — inline since they're just a few lines of Web Crypto\nfunction generatePkceVerifier(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nasync function computePkceChallenge(verifier: string): Promise<string> {\n const hash = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n const bytes = new Uint8Array(hash);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n","/**\n * RFC 7638 JWK thumbprints, used to bind an authorization request to the DPoP\n * key that will later prove possession at the token endpoint (`dpop_jkt`).\n *\n * ⚠ THE MEMBER SET AND ORDER ARE THE WHOLE SPECIFICATION. A thumbprint is the\n * SHA-256 of a JSON object containing *only* the members required for the key\n * type, with lexicographically ordered keys and no whitespace. Hashing the JWK\n * as it arrives would fold in `d`, `kid`, `ext` and `key_ops` — all of which\n * vary between the private key the client holds and the public key the\n * authorization server derives from the DPoP proof — producing a `dpop_jkt`\n * that never matches and a failure that surfaces at the token exchange, far\n * from its cause.\n */\n\nfunction base64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/** The required members per key type, already in lexicographic order. */\nconst REQUIRED_MEMBERS: Record<string, readonly string[]> = {\n EC: [\"crv\", \"kty\", \"x\", \"y\"],\n OKP: [\"crv\", \"kty\", \"x\"],\n RSA: [\"e\", \"kty\", \"n\"],\n oct: [\"k\", \"kty\"],\n};\n\n/**\n * Compute the RFC 7638 thumbprint of `jwk` as a base64url-encoded SHA-256.\n *\n * Accepts either a private or a public JWK — the private component is not a\n * required member for any key type, so both yield the same thumbprint, which is\n * what makes this usable on the key as provisioned.\n */\nexport async function jwkThumbprint(jwk: JsonWebKey): Promise<string> {\n const kty = jwk.kty;\n if (!kty || !(kty in REQUIRED_MEMBERS)) {\n throw new TypeError(\n `Cannot compute a JWK thumbprint for unsupported key type \"${kty ?? \"undefined\"}\"`,\n );\n }\n\n const members = REQUIRED_MEMBERS[kty];\n const canonical: Record<string, string> = {};\n for (const member of members) {\n const value = (jwk as Record<string, unknown>)[member];\n if (typeof value !== \"string\" || value === \"\") {\n throw new TypeError(\n `Cannot compute a JWK thumbprint: \"${kty}\" key is missing required member \"${member}\"`,\n );\n }\n canonical[member] = value;\n }\n\n // `members` is already sorted, and object literals preserve insertion order\n // for string keys, so JSON.stringify emits the canonical form directly.\n const json = JSON.stringify(canonical);\n\n return base64url(\n await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(json)),\n );\n}\n"],"mappings":"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","names":["body","base64url"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyview/oauth-client",
3
- "version": "1.4.1",
3
+ "version": "1.4.2-dev.1",
4
4
  "description": "HappyView OAuth client for ATProto DPoP authentication",
5
5
  "license": "MIT",
6
6
  "repository": {