import { Config, TokenResponse } from './types.js'; /** * The promoted, app-facing functional auth surface for Hanzo IAM. * * This is the ONE thing an app imports. It is a thin, configured singleton * over the {@link IAM} browser engine — mechanism stays in `browser.ts`, * this module just gives it a module-level config so apps write * * import { configureIam, startLogin, handleCallback, getSession } from '@hanzo/iam/browser' * * instead of hand-rolling a local `iam-auth.ts` copy. It is the SDK promotion * of `commerce/app/site/app/lib/iam-auth.ts`. * * Three orthogonal verbs, parameterized — never a branch per provider: * * startLogin({ provider?, redirect? }) ONE Authorization-Code + PKCE flow. * `provider` is the ONLY knob: omit it * for the IAM login page, or name a * social/web3 provider to delegate the * social hop to IAM's shared org-level * OAuth client. The app NEVER registers * a per-app Google/GitHub client. * handleCallback() ONE token exchange (PKCE verifier + * state/CSRF check). No path literals — * endpoints come from OIDC discovery * (falling back to OIDC_PATHS). * getSession() / getUser() / logout() ONE session model. * * `provider` rides the authorize request as `&provider=` on * `/v1/iam/oauth/authorize`. IAM performs the social hop with its shared * client (the provider hop's redirect_uri is IAM's own callback) and returns * the code to the APP's own `redirect_uri`. PKCE-S256 always. No `/api/`. */ /** * App-facing config. `issuer` is the brand IAM origin (e.g. "https://hanzo.id"); * `clientId` is `-`. `redirect` defaults to the current origin + * `/auth/callback` — the framework's exact callback. Everything else (token, * userinfo, jwks, logout endpoints) is resolved from OIDC discovery. */ interface IamSessionConfig { /** Brand IAM origin, e.g. "https://hanzo.id". Maps to IAM `serverUrl`. */ issuer: string; /** OAuth client id (`-`). */ clientId: string; /** App OAuth callback URI. Default: `${origin}/auth/callback`. */ redirect?: string; /** OAuth scopes. Default: "openid profile email". */ scope?: string; /** Token storage. Default: sessionStorage. */ storage?: Storage; /** Same-origin proxy base for token/userinfo (keeps cross-origin off the browser). */ proxyBaseUrl?: string; } /** * Configure the module-level IAM singleton. Call once at app startup. Returns * the underlying {@link IAM} engine for advanced use. Idempotent per config. */ declare function configureIam(config: IamSessionConfig): IAM; interface StartLoginOptions { /** * Social/web3 provider hint (e.g. "google", "github", "web3"). Omit for the * IAM login page. The ONLY knob that selects the method — delegated to IAM's * shared org-level OAuth client, never a per-app registration. */ provider?: string; /** Where to land after a successful login. Stashed across the round-trip. */ redirect?: string; } /** * Start the OIDC Authorization-Code + PKCE-S256 login by redirecting to * `/v1/iam/oauth/authorize`. `provider` (if given) rides as `&provider=`. * Stashes the post-login `redirect` so {@link handleCallback} can return it. */ declare function startLogin(opts?: StartLoginOptions): Promise; /** * Build the authorize URL WITHOUT redirecting (for `` or tests). * Same PKCE + provider semantics as {@link startLogin}. */ declare function getLoginUrl(opts?: StartLoginOptions): Promise; /** * Complete the login on the app's callback route: verify state + PKCE verifier, * exchange the code at the discovered token endpoint, store tokens. Returns the * stashed post-login redirect (or "/" if none). */ declare function handleCallback(callbackUrl?: string): Promise<{ token: IAMToken; redirect: string; }>; interface IamSession { /** Whether a valid (unexpired) access token is present. */ authenticated: boolean; /** Current access token (null when unauthenticated). */ accessToken: string | null; } /** Synchronous session snapshot from local token storage. */ declare function getSession(): IamSession; /** Fetch the current user from the userinfo endpoint (null when unauthenticated). */ declare function getUser(): Promise; /** RP-initiated logout + local token clear. */ declare function logout(): Promise; /** Escape hatch: the underlying configured {@link IAM} engine. */ declare function getIam(): IAM; /** The currently active session config (null before `configureIam`). */ declare function getIamConfig(): IamSessionConfig | null; /** * Browser-side OAuth2/OIDC PKCE flow for Hanzo IAM. * * Standards core: RFC 6749 (OAuth2), RFC 7636 (PKCE), OIDC. The default * flow is a single redirect-to-authorize where the IAM server owns the * credential interaction. For first-party apps that render their OWN * branded login on their own domain, the embedded credential methods * (`loginWithPassword` / `loginWithCode`) collect the credential in-app and * mint a PKCE-bound authorization code via the IAM login endpoint — the * SAME RFC 7636 code+PKCE exchange completes it through `handleCallback`. * (Not ROPC: no password ever hits the token endpoint.) * * IAM#signinRedirect() — start PKCE redirect to the authorize endpoint * IAM#handleCallback() — exchange code at the token endpoint (PKCE) * IAM#loginWithPassword() — first-party embedded login → PKCE-bound code * IAM#loginWithCode() — embedded passwordless code login → PKCE code * IAM#sendLoginCode() — send an email/SMS verification code * IAM#refreshAccessToken()— refresh grant * IAM#signinPopup() — same flow in a popup window * IAM#signinSilent() — prompt=none iframe attempt * IAM#getUserInfo() — userinfo endpoint * IAM#logout() — RP-initiated logout * * Every endpoint resolves through `OIDC_PATHS` (see ./paths). Live OIDC * discovery is preferred when reachable; the hard-coded fallback uses * the exact same canonical paths so a CORS/network failure degrades to * correct URLs, never to the IAM HTML SPA catch-all. */ type IAMConfig = Config & { /** OAuth2 redirect URI (e.g. "https://app.hanzo.bot/auth/callback"). */ redirectUri: string; /** OAuth2 scopes (default: "openid profile email"). */ scope?: string; /** Storage to use for tokens (default: sessionStorage). */ storage?: Storage; /** * Optional proxy base URL for the token + userinfo endpoints. * When set, token exchange POSTs hit `${proxyBaseUrl}/v1/iam/oauth/token` * and userinfo GETs hit `${proxyBaseUrl}/v1/iam/oauth/userinfo` instead * of the IAM origin directly. Use this to keep cross-origin requests * off the browser (gateway proxies same-origin to IAM). */ proxyBaseUrl?: string; /** * IAM organization (e.g. "hanzo"). Required only for the first-party * embedded credential login (`loginWithPassword` / `loginWithCode`). */ organization?: string; /** IAM application name. Defaults to `clientId` (they match for Hanzo apps). */ application?: string; }; declare class IAM { private readonly config; private readonly storage; /** Storage for the one-shot PKCE transaction (state + verifier). Defaults to * localStorage so it survives the OAuth redirect; see resolveTxStorage. */ private readonly txStorage; private discoveryCache; constructor(config: IAMConfig); private getDiscovery; private tokenEndpoint; private userinfoEndpoint; private buildAuthorizeUrl; /** * Start the OAuth2 PKCE login flow by redirecting to /oauth/authorize. * Generates PKCE challenge + state, stores them in session storage, * then sets `window.location.href`. */ signinRedirect(params?: { additionalParams?: Record; }): Promise; /** * Build the authorize URL without redirecting — useful for `` * (e.g. social login buttons with `provider` in additionalParams). */ getSigninUrl(params?: { additionalParams?: Record; }): Promise; /** * Generate + persist a PKCE verifier and state for an embedded credential * login, returning the challenge to bind into the minted code. Uses the * SAME storage keys as `signinRedirect`, so `handleCallback` completes the * exact same RFC 7636 exchange regardless of how sign-in started. */ private beginCredential; /** * POST the IAM login endpoint with a PKCE `code_challenge` so the returned * authorization code is challenge-bound. Returns the app-callback URL * carrying `code` + `state`; the caller navigates there and * `handleCallback()` performs the standard PKCE token exchange. Throws on * an IAM error. No password ever reaches the token endpoint. */ private credentialLogin; /** Embedded password login → returns the app-callback URL to navigate to. */ loginWithPassword(username: string, password: string): Promise; /** Embedded passwordless code login → returns the app-callback URL. */ loginWithCode(destination: string, code: string): Promise; /** Send a passwordless email/SMS verification code for `loginWithCode`. */ sendLoginCode(destination: string): Promise; /** * Handle the OAuth2 callback after the IAM redirect. Validates state, * exchanges the authorization code for tokens with PKCE, and stores * the result. * * Call this on your callback route (e.g. /auth/callback). */ handleCallback(callbackUrl?: string): Promise; /** Refresh the access token using the stored refresh token. */ refreshAccessToken(): Promise; /** * Open the IAM login page in a popup window. Resolves when the popup * completes the OAuth flow and returns tokens. */ signinPopup(params?: { width?: number; height?: number; additionalParams?: Record; }): Promise; /** * Attempt silent authentication via a hidden iframe (`prompt=none`). * Returns null if silent auth fails (user needs to log in interactively). */ signinSilent(timeoutMs?: number): Promise; private storeTokens; /** Get the stored access token (may be expired). */ getAccessToken(): string | null; /** Get the stored refresh token. */ getRefreshToken(): string | null; /** Get the stored ID token. */ getIdToken(): string | null; /** Check if the stored access token is expired. */ isTokenExpired(): boolean; /** * Get a valid access token — refreshes automatically if expired. * Returns null if no token and no refresh token available. */ getValidAccessToken(): Promise; /** Clear all stored tokens (local logout). */ clearTokens(): void; /** * RP-initiated logout (OIDC). Best-effort POST to the logout endpoint * to terminate the server-side session, then clears local tokens. */ logout(): Promise; /** Fetch user info from /oauth/userinfo using the stored access token. */ getUserInfo(): Promise>; /** * Fetch the current user, shaped into the canonical `IAMUser` form * (camelCase, no `_` keys). Returns null when no token is present. */ getUser(): Promise; } /** * Canonical user shape returned by `IAM#getUser()`. Maps the OIDC userinfo * response (snake_case) to camelCase. */ type IAMUser = { sub: string; email?: string; name?: string; givenName?: string; familyName?: string; phoneNumber?: string; emailVerified?: boolean; picture?: string; owner?: string; }; /** * Canonical token shape (camelCase, no `_` keys) for app code. */ type IAMToken = { accessToken: string; refreshToken?: string; idToken?: string; expiresIn?: number; tokenType?: string; scope?: string; }; /** Convert the raw OAuth2 token response into the canonical `IAMToken`. */ declare function toIAMToken(t: TokenResponse): IAMToken; export { IAM, type IAMConfig, type IAMToken, type IAMUser, type IamSession, type IamSessionConfig, type StartLoginOptions, configureIam, getIam, getIamConfig, getLoginUrl, getSession, getUser, handleCallback, logout, startLogin, toIAMToken };