{"version":3,"sources":["../../src/auth/oauth-client.ts"],"sourcesContent":["/**\n * OAuth 2.0 Authorization Code + PKCE client orchestration.\n *\n * @remarks\n * Drives the full authorize → callback → token-exchange → refresh dance on top\n * of the {@link TokenStore} and PKCE primitives that ship with this package.\n * Implements RFC 6749 §4.1 with the RFC 7636 PKCE extension (S256 only).\n *\n * @category Auth\n * @module auth/oauth-client\n */\n\nimport { computePkceChallenge, generatePkceVerifier } from \"./pkce\";\nimport {\n  InMemoryTokenStore,\n  type TokenRecord,\n  type TokenStore,\n} from \"./token-store\";\n\n/**\n * Constructor options for {@link OAuthClient}.\n */\nexport interface OAuthClientConfig {\n  /** Authorization endpoint, e.g. `https://account.vana.org/oauth/authorize`. */\n  authorizationEndpoint: string;\n  /** Token endpoint, e.g. `https://account.vana.org/oauth/token`. */\n  tokenEndpoint: string;\n  /** OAuth `client_id` (public; PKCE protects the flow). */\n  clientId: string;\n  /** Redirect URI registered with the authorization server. */\n  redirectUri: string;\n  /** Default scope; can be overridden per call. */\n  scope?: string;\n  /**\n   * Where to persist access + refresh tokens and the in-flight code verifier\n   * between `authorize` → `callback`. Defaults to a fresh\n   * {@link InMemoryTokenStore}. Use IndexedDB/localStorage-backed\n   * implementations for browser apps where the user navigates away during the\n   * dance.\n   */\n  tokenStore?: TokenStore;\n  /** Override the global `fetch` (e.g. for tests). Defaults to `globalThis.fetch`. */\n  fetchImpl?: typeof fetch;\n  /**\n   * Override the random-state generator (mostly for tests). Must return a\n   * URL-safe string of >= 16 bytes of entropy.\n   */\n  generateState?: () => string;\n}\n\n/**\n * Result of {@link OAuthClient.buildAuthorizationUrl}.\n */\nexport interface AuthorizationUrlResult {\n  /** The full authorize URL to redirect / `window.open` to. */\n  url: string;\n  /** The `state` value the auth server will echo back; used for CSRF check. */\n  state: string;\n}\n\n/** TTL for the in-flight verifier record (seconds). */\nconst VERIFIER_TTL_SECONDS = 600;\n\n/** RFC 6749 spec-compliant OAuth error payload shape. */\ninterface OAuthErrorBody {\n  error?: string;\n  error_description?: string;\n  error_uri?: string;\n}\n\n/** Successful token-endpoint response shape (RFC 6749 §5.1). */\ninterface TokenEndpointResponse {\n  access_token: string;\n  token_type?: string;\n  expires_in?: number;\n  refresh_token?: string;\n  scope?: string;\n}\n\n/**\n * Authorize-URL parameters the client owns. Callers may NOT supply these\n * via `extraParams` — otherwise PKCE/CSRF protection can be silently\n * bypassed (e.g. `extraParams: { state: \"x\" }` would store the verifier\n * under the generated state but send `x` on the wire, breaking the\n * callback CSRF check; `code_challenge_method` could downgrade S256).\n */\nconst RESERVED_AUTHORIZE_PARAMS = new Set([\n  \"response_type\",\n  \"client_id\",\n  \"redirect_uri\",\n  \"scope\",\n  \"state\",\n  \"code_challenge\",\n  \"code_challenge_method\",\n]);\n\n/**\n * OAuth 2.0 Authorization Code + PKCE client.\n *\n * @remarks\n * Storage layout under the supplied {@link TokenStore} (all keys namespaced):\n * - `oauth:tokens:{clientId}`  → access token record\n * - `oauth:refresh:{clientId}` → refresh token record (no expiry)\n * - `oauth:verifier:{state}`   → in-flight PKCE verifier (10 min TTL)\n *\n * @category Auth\n */\nexport class OAuthClient {\n  readonly #config: Required<\n    Omit<\n      OAuthClientConfig,\n      \"scope\" | \"tokenStore\" | \"fetchImpl\" | \"generateState\"\n    >\n  > & {\n    scope?: string;\n    tokenStore: TokenStore;\n    fetchImpl: typeof fetch;\n    generateState: () => string;\n  };\n\n  public constructor(config: OAuthClientConfig) {\n    const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n    if (typeof fetchImpl !== \"function\") {\n      throw new TypeError(\n        \"OAuthClient requires a global `fetch` or an explicit `fetchImpl`\",\n      );\n    }\n\n    this.#config = {\n      authorizationEndpoint: config.authorizationEndpoint,\n      tokenEndpoint: config.tokenEndpoint,\n      clientId: config.clientId,\n      redirectUri: config.redirectUri,\n      scope: config.scope,\n      tokenStore: config.tokenStore ?? new InMemoryTokenStore(),\n      fetchImpl,\n      generateState: config.generateState ?? defaultGenerateState,\n    };\n  }\n\n  /** Build the authorize URL and persist the PKCE verifier keyed by `state`. */\n  public async buildAuthorizationUrl(\n    opts: {\n      state?: string;\n      scope?: string;\n      extraParams?: Record<string, string>;\n    } = {},\n  ): Promise<AuthorizationUrlResult> {\n    const state = opts.state ?? this.#config.generateState();\n    const scope = opts.scope ?? this.#config.scope;\n\n    const verifier = generatePkceVerifier();\n    const challenge = await computePkceChallenge(verifier);\n\n    await this.#config.tokenStore.set(this.#verifierKey(state), {\n      token: verifier,\n      expiresAt: Math.floor(Date.now() / 1000) + VERIFIER_TTL_SECONDS,\n    });\n\n    const params = new URLSearchParams();\n    params.set(\"response_type\", \"code\");\n    params.set(\"client_id\", this.#config.clientId);\n    params.set(\"redirect_uri\", this.#config.redirectUri);\n    if (scope !== undefined && scope.length > 0) {\n      params.set(\"scope\", scope);\n    }\n    params.set(\"state\", state);\n    params.set(\"code_challenge\", challenge);\n    params.set(\"code_challenge_method\", \"S256\");\n    if (opts.extraParams !== undefined) {\n      for (const k of Object.keys(opts.extraParams)) {\n        if (RESERVED_AUTHORIZE_PARAMS.has(k)) {\n          throw new Error(\n            `extraParams may not override the reserved OAuth/PKCE parameter \"${k}\"`,\n          );\n        }\n      }\n      for (const [k, v] of Object.entries(opts.extraParams)) {\n        params.set(k, v);\n      }\n    }\n\n    const sep = this.#config.authorizationEndpoint.includes(\"?\") ? \"&\" : \"?\";\n    const url = `${this.#config.authorizationEndpoint}${sep}${params.toString()}`;\n\n    return { url, state };\n  }\n\n  /**\n   * Handle the redirect-callback URL. Validates `state`, retrieves the saved\n   * verifier, exchanges the authorization code + verifier for tokens, and\n   * persists them. Returns the access {@link TokenRecord}.\n   */\n  public async handleCallback(callbackUrl: string): Promise<TokenRecord> {\n    const parsed = new URL(callbackUrl);\n    const params = parsed.searchParams;\n\n    const errorCode = params.get(\"error\");\n    if (errorCode !== null) {\n      throw new Error(\n        formatOAuthError({\n          error: errorCode,\n          error_description: params.get(\"error_description\") ?? undefined,\n        }),\n      );\n    }\n\n    const code = params.get(\"code\");\n    const state = params.get(\"state\");\n    if (code === null || state === null) {\n      throw new Error(\"OAuth callback is missing `code` or `state`\");\n    }\n\n    const verifierRecord = await this.#config.tokenStore.get(\n      this.#verifierKey(state),\n    );\n    if (verifierRecord === null) {\n      throw new Error(\n        \"OAuth callback state does not match any in-flight verifier (possible CSRF or expired flow)\",\n      );\n    }\n\n    const body = new URLSearchParams();\n    body.set(\"grant_type\", \"authorization_code\");\n    body.set(\"code\", code);\n    body.set(\"redirect_uri\", this.#config.redirectUri);\n    body.set(\"client_id\", this.#config.clientId);\n    body.set(\"code_verifier\", verifierRecord.token);\n\n    let tokens: TokenEndpointResponse;\n    try {\n      tokens = await this.#tokenRequest(body);\n    } finally {\n      // Always clear the one-shot verifier, even on a failed exchange.\n      await this.#config.tokenStore.delete(this.#verifierKey(state));\n    }\n\n    return this.#persistTokens(tokens);\n  }\n\n  /**\n   * Exchange a stored refresh token for a fresh access token. Throws if no\n   * refresh token is available.\n   */\n  public async refresh(): Promise<TokenRecord> {\n    const refreshRecord = await this.#config.tokenStore.get(this.#refreshKey());\n    if (refreshRecord === null) {\n      throw new Error(\"OAuth refresh failed: no refresh token stored\");\n    }\n\n    const body = new URLSearchParams();\n    body.set(\"grant_type\", \"refresh_token\");\n    body.set(\"refresh_token\", refreshRecord.token);\n    body.set(\"client_id\", this.#config.clientId);\n\n    const tokens = await this.#tokenRequest(body);\n    return this.#persistTokens(tokens, refreshRecord.token);\n  }\n\n  /**\n   * Get the current access token if valid (refreshing first if expired and a\n   * refresh token is available). Returns `null` when no usable token exists.\n   */\n  public async getAccessToken(): Promise<string | null> {\n    const stored = await this.#config.tokenStore.get(this.#accessKey());\n    if (stored !== null) return stored.token;\n\n    // Stored access token is missing or already evicted by the store's TTL.\n    const refresh = await this.#config.tokenStore.get(this.#refreshKey());\n    if (refresh === null) return null;\n\n    try {\n      const refreshed = await this.refresh();\n      return refreshed.token;\n    } catch {\n      return null;\n    }\n  }\n\n  /** Forget tokens (logout). Does NOT call any remote revocation endpoint. */\n  public async signOut(): Promise<void> {\n    await this.#config.tokenStore.delete(this.#accessKey());\n    await this.#config.tokenStore.delete(this.#refreshKey());\n  }\n\n  #accessKey(): string {\n    return `oauth:tokens:${this.#config.clientId}`;\n  }\n\n  #refreshKey(): string {\n    return `oauth:refresh:${this.#config.clientId}`;\n  }\n\n  #verifierKey(state: string): string {\n    return `oauth:verifier:${state}`;\n  }\n\n  async #tokenRequest(body: URLSearchParams): Promise<TokenEndpointResponse> {\n    const response = await this.#config.fetchImpl(this.#config.tokenEndpoint, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n        Accept: \"application/json\",\n      },\n      body: body.toString(),\n    });\n\n    const text = await response.text();\n    const parsed = parseJsonBody(text);\n\n    if (!response.ok) {\n      throw new Error(formatOAuthError(parsed ?? {}, response.status));\n    }\n\n    if (\n      parsed === null ||\n      typeof parsed !== \"object\" ||\n      typeof (parsed as { access_token?: unknown }).access_token !== \"string\"\n    ) {\n      throw new Error(\n        \"OAuth token endpoint returned a response without an `access_token` string\",\n      );\n    }\n\n    return parsed as TokenEndpointResponse;\n  }\n\n  async #persistTokens(\n    tokens: TokenEndpointResponse,\n    previousRefreshToken?: string,\n  ): Promise<TokenRecord> {\n    const record: TokenRecord = { token: tokens.access_token };\n    if (typeof tokens.expires_in === \"number\" && tokens.expires_in > 0) {\n      record.expiresAt = Math.floor(Date.now() / 1000) + tokens.expires_in;\n    }\n    await this.#config.tokenStore.set(this.#accessKey(), record);\n\n    const newRefresh = tokens.refresh_token ?? previousRefreshToken;\n    if (newRefresh !== undefined) {\n      await this.#config.tokenStore.set(this.#refreshKey(), {\n        token: newRefresh,\n      });\n    }\n\n    return record;\n  }\n}\n\nfunction defaultGenerateState(): string {\n  const bytes = new Uint8Array(24);\n  crypto.getRandomValues(bytes);\n  let binary = \"\";\n  for (let i = 0; i < bytes.length; i++) {\n    binary += String.fromCharCode(bytes[i] as number);\n  }\n  return btoa(binary)\n    .replace(/\\+/g, \"-\")\n    .replace(/\\//g, \"_\")\n    .replace(/=+$/, \"\");\n}\n\nfunction parseJsonBody(text: string): unknown {\n  if (text.length === 0) return null;\n  try {\n    return JSON.parse(text) as unknown;\n  } catch {\n    return null;\n  }\n}\n\nfunction formatOAuthError(body: OAuthErrorBody, status?: number): string {\n  const parts: string[] = [\"OAuth token request failed\"];\n  if (status !== undefined) parts.push(`(HTTP ${String(status)})`);\n  if (body.error !== undefined && body.error.length > 0) {\n    parts.push(`: ${body.error}`);\n    if (\n      body.error_description !== undefined &&\n      body.error_description.length > 0\n    ) {\n      parts.push(`- ${body.error_description}`);\n    }\n  }\n  return parts.join(\" \").replace(\" : \", \": \").replace(\" - \", \" - \");\n}\n"],"mappings":"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","names":[]}