import { ApiClient } from '@microsoft/rayfin-lib'; import { AuthApi } from './AuthApi.js'; import type { SignUpCredentials, SignUpResponse, PasswordGrantCredentials, TokenResponse, Session, OpaqueSession, SignOutAllResponse, JwksResponse, AuthEvent, PasswordResetResponse, ResendVerificationEmailResponse, EmailVerificationResponse, MagicLinkOptions, MagicLinkResult, MagicLinkCallbackResult, AuthSettingsConfig } from './types.js'; /** * Storage interface used by {@link Auth} to persist the session (and PKCE state) * across page loads. Defaults to `window.localStorage`; provide a custom * implementation (or `false` for memory-only) via the `Auth` constructor options. */ export interface AuthStorage { getItem(key: string): string | null | Promise; setItem(key: string, value: string): void | Promise; removeItem(key: string): void | Promise; clear?(): void | Promise; keys?(prefix: string): string[] | Promise; } export interface AuthOptions { storage?: AuthStorage | boolean; storageKeyPrefix?: string; /** When false, all storage I/O is skipped — pure in-memory session. Default: true */ persistSession?: boolean; /** When false, session expiration timers are not scheduled and automatic refresh on 401 is disabled. Default: true */ autoRefreshToken?: boolean; /** When false, StorageEvent listener is not registered. Default: auto-detected (true in browser with localStorage). */ multiTabSync?: boolean; } /** * The main Auth module class. * This provides the high-level API for authentication operations and manages the user session. */ export declare class Auth { private authApi; protected storage: AuthStorage | null; protected readonly AUTH_TOKEN_KEY: string; protected readonly AUTH_TOKEN_BASE = "authSession"; protected readonly PKCE_STATE_PREFIX_BASE = "rayfin_pkce_"; protected readonly PKCE_STATE_PREFIX: string; private static readonly PKCE_STATE_MAX_AGE_MS; private static readonly PKCE_STALE_CLEANUP_AGE_MS; private static readonly REFRESH_SKEW_MS; private static readonly REFRESH_BACKOFF_BASE_MS; private static readonly REFRESH_BACKOFF_MAX_MS; private readonly refreshLockName; private internalSession; private accessToken; private refreshToken; private refreshInFlight; private lastRefreshFailure; private storageEventListener; private storageEventSeq; private boundHandleVisibilityChange; private boundHandleFocus; private boundHandleOnline; protected authStateChangeListeners: ((session: OpaqueSession | null) => void)[]; private eventListeners; private sessionExpirationTimer; private readonly persistSession; private readonly autoRefreshToken; private readonly multiTabSync; private pkceMemoryStore; private initPromise; private syncRestoreDone; private manualRefreshActive; /** * @param apiClient - An instance of ApiClient to be used by the AuthApi. */ constructor(apiClient: ApiClient, options?: AuthOptions); /** * Registers a new user with email and password. * * After successful signup, you must call signIn() to obtain an access token. * This design supports future email verification flows. * * Emits `AUTH_SIGNUP` event on success. * * @param credentials - The signup credentials (email, password). * @returns A promise that resolves with signup response (userId, email, role, createdAt). * @throws `AuthError` - If credentials are invalid or email already registered. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * // Listen for signup events * auth.on('AUTH_SIGNUP', (session) => { * console.log('User signed up!'); * }); * * // Step 1: Sign up * const signupResponse = await auth.signUp({ * email: 'user@example.com', * password: 'password123' * }); * console.log('User created:', signupResponse.userId); * * // Step 2: Sign in to get access token * await auth.signIn({ * email: 'user@example.com', * password: 'password123' * }); * ``` */ signUp(credentials: SignUpCredentials): Promise; /** * Signs in a user with email and password using OAuth 2.1 password grant (ROPC). * * After successful signin, the user session will be stored internally. * Returns an access token (JWT signed with asymmetric keys) and optionally a refresh token. * * @param credentials - The email and password for authentication. * @returns A promise that resolves with the OAuth 2.1 token response. * @throws `AuthError` - If signin fails due to invalid credentials. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * const tokenResponse = await auth.signIn({ * email: 'user@example.com', * password: 'password123' * }); * console.log('Signed in, access token expires in:', tokenResponse.expiresIn, 'seconds'); * ``` */ signIn(credentials: PasswordGrantCredentials): Promise; /** * Signs out the current user by revoking their access token (OAuth 2.0 Token Revocation). * * Clears the internal session and revokes the token on the server. * Per RFC 7009, the server returns success regardless of token validity. * * @returns A promise that resolves when signout is complete. * @throws `AuthError` - If client authentication fails. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * // Listen for sign-out events to update UI * auth.on('AUTH_LOGOUT', () => { * navigate('/login'); * }); * * try { * await auth.signOut(); * // Internal session is cleared even if the server call fails. * } catch (error) { * console.error('Sign-out had a problem:', error.message); * } * ``` */ signOut(): Promise; /** * Revokes all active sessions for the authenticated user. * * This invalidates all tokens issued to the user, forcing re-authentication. * Useful for security incidents or password changes. * * @returns A promise that resolves with the count of sessions revoked. * @throws `AuthError` - If no active session or client authentication fails. * * @example * ```typescript * const result = await auth.signOutAll(); * console.log(`Revoked ${result.count} sessions`); * ``` */ signOutAll(): Promise; /** * Retrieves the JSON Web Key Set (JWKS) containing public keys for JWT verification. * * This endpoint provides public keys used to verify JWT access tokens and refresh * tokens issued by the authorization server. Follows RFC 7517 (JSON Web Key) and * OpenID Connect Discovery standards. * * The response may contain multiple keys to support key rotation: * - Active key: The current signing key used for new tokens * - Previous key: The previous signing key for validating tokens signed before rotation * * Use the `kid` (key ID) from the JWT header to find the matching verification key. * * @returns A promise that resolves with the JWKS containing public keys. * @throws `NetworkError` - For network-related issues. * * @example * ```typescript * const jwks = await auth.getJwks(); * console.log('Public keys:', jwks.keys); * // Find key by kid * const key = jwks.keys.find(k => k.kid === 'desired-kid'); * ``` */ getJwks(): Promise; /** * Gets the authentication settings from the backend. * This is a public endpoint that does not require authentication. * * @returns A promise that resolves with the authentication configuration. * @throws `NetworkError` - For network-related issues. * * @example * ```typescript * const settings = await auth.getAuthSettings(); * if (settings.password.enabled) { * // Show password login form * } * if (settings.passwordless.magicLink.enabled) { * // Show magic link option * } * ``` */ getAuthSettings(): Promise; /** * Returns the internal AuthApi instance. * Used by companion auth provider packages (e.g., fabric provider). * @returns The AuthApi instance used by this Auth instance. */ getAuthApi(): AuthApi; /** * Returns the current authenticated session. * @returns The current session object or null if no user is authenticated. */ getSession(): OpaqueSession; /** * Returns true if the current session has a refresh token available. * @returns True if refresh token exists and session is active. */ hasRefreshToken(): boolean; /** * Refreshes the current session using the stored refresh token. * Uses a promise lock to prevent concurrent refresh requests. * * @returns A promise that resolves with new token response. * @throws `AuthError` - If no refresh token is available or refresh fails. * * @example * ```typescript * try { * const tokens = await auth.refreshSession(); * console.log('Session refreshed:', tokens); * } catch (error) { * console.error('Refresh failed:', error); * } * ``` */ refreshSession(): Promise; /** * Acquires a cross-tab lock via navigator.locks before refreshing. * Only one tab at a time can hold the lock; others wait for it. * After acquiring the lock, checks if the session was already refreshed * by another tab (via the storage event listener) to avoid a redundant call. */ private refreshWithCrossTabLock; /** * Per-instance refresh with promise lock (same-tab dedup). */ private refreshWithLocalLock; /** * Internal method that performs the actual refresh token grant. * Should only be called through refreshSession() to ensure locking. */ private _doRefresh; /** * Attaches authorization header injection and automatic token refresh to * another {@link ApiClient} instance, so requests made through it carry the * current access token and trigger a refresh on 401 responses. * * @param client - The API client to wire up with this `Auth` instance. */ attachToClient(client: ApiClient): void; /** * Cleans up resources used by the Auth instance. * Removes storage event listener and clears timers. * Should be called when the Auth instance is no longer needed. */ destroy(): void; /** * Starts automatic token refresh scheduling. * Re-enables the session expiration timer and triggers an immediate refresh * if the current access token is expired or near-expiry. * * Only meaningful when `autoRefreshToken` is `false` — gives the consumer * manual control over the refresh cycle. When `autoRefreshToken` is `true` * (default), this is a no-op. * * @example React Native AppState integration * ```typescript * AppState.addEventListener('change', (state) => { * state === 'active' ? auth.startAutoRefresh() : auth.stopAutoRefresh(); * }); * ``` */ startAutoRefresh(): Promise; /** * Stops automatic token refresh scheduling. * Cancels any pending session expiration timer so no automatic refresh occurs * until `startAutoRefresh()` is called again. * * Only meaningful when `autoRefreshToken` is `false`. When `autoRefreshToken` * is `true` (default), this is a no-op. */ stopAutoRefresh(): void; /** * Primary event listener for session changes (PRD-aligned method name). * Fires on: SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED, etc. * @param callback - Function called with session or null * @returns Cleanup function to unsubscribe */ onSessionChange(callback: (session: OpaqueSession | null) => void): () => void; /** * Internal method to notify all registered listeners about session changes. * @param session - The new session or null if the user is logged out. */ protected emitAuthStateChange(session: OpaqueSession | null): void; private emitAuthEvent; /** * Subscribes to a specific authentication event. * * @param event - The auth event to listen for (e.g. `AUTH_LOGIN`, `AUTH_LOGOUT`, `AUTH_REFRESH`). * @param handler - Callback invoked with the current session when the event fires. * @returns A cleanup function that unsubscribes the handler. */ on(event: AuthEvent, handler: (session: OpaqueSession) => void): () => void; private clearInternalSession; /** * Schedules session expiration timer based on expiresAt in session. * When access token expires, automatically refreshes if refresh token exists. * Otherwise, emits AUTH_SESSION_EXPIRED event and clears session. */ private scheduleSessionExpiration; /** * Checks whether the access token is expired or about to expire and * proactively refreshes the session if a refresh token is available. * * Called by visibilitychange, focus, and online event handlers so that * a tab returning from the background gets a fresh AT before any API * call is attempted. The existing `refreshInFlight` promise lock in * `refreshSession()` prevents concurrent refresh requests. */ private checkAndRefreshIfNeeded; /** * Kicks off a refresh without awaiting. Expected failures (AuthError) are * logged at debug level; unexpected errors at warn level for diagnostics. * Only error codes and type names are logged — no tokens or PII. */ private fireAndForgetRefresh; /** * Clears any active session expiration timer. */ private clearSessionExpirationTimer; /** * Validates an access token by verifying its signature, expiry, sub, and aud claims. * Uses the JWKS endpoint to fetch public keys for signature verification. * * @param token - The JWT access token to validate * @returns True if the token is valid, false otherwise */ private validateAccessToken; /** * Verifies JWT signature using Web Crypto API. * Supports ES256 (ECDSA with P-256 and SHA-256). */ private verifyJwtSignature; /** * Verifies ECDSA signature (ES256/ES384/ES512). */ private verifyEcdsaSignature; /** * Verifies RSA signature (RS256/RS384/RS512). */ private verifyRsaSignature; /** * Decodes a base64url-encoded string to a UTF-8 string. */ private base64UrlDecode; /** * Decodes a base64url-encoded string to an ArrayBuffer. */ private base64UrlDecodeToArrayBuffer; protected getInternalSessionFromStorage(): Promise; protected setInternalSessionToStorage(session: Session): Promise; protected clearSessionFromStorage(): Promise; /** * Verifies a user's email address with a verification token. * * This method is called after a user clicks the verification link in their email. * Upon success, the user's email is marked as verified. The user must then sign in * to obtain an access token. * * Emits `AUTH_EMAIL_VERIFIED` event on success. * * @param token - The verification token from the email link. * @returns A promise that resolves with the verification response. * @throws `AuthError` - If the token is invalid, expired, or already used. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * // Listen for email verification events * auth.on('AUTH_EMAIL_VERIFIED', () => { * console.log('Email verified! You can now sign in.'); * }); * * // Extract token from URL query parameter * const urlParams = new URLSearchParams(window.location.search); * const token = urlParams.get('token'); * * if (token) { * try { * const result = await auth.verifyEmail(token); * console.log(result.message); // "Email verified successfully!" * } catch (error) { * console.error('Verification failed:', error.message); * } * } * ``` */ verifyEmail(token: string): Promise; /** * Resends the email verification link to the specified email address. * * This allows users to request a new verification email if they didn't receive * the original or if it expired. Previous unused verification tokens are automatically * invalidated when a new one is generated. For security, always returns success * to prevent email enumeration attacks. * * Emits `AUTH_VERIFICATION_EMAIL_RESENT` event on success. * * @param email - The email address to resend the verification link to. * @returns A promise that resolves with a success message. * @throws `AuthError` - If the email is invalid. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * // Listen for resend events * auth.on('AUTH_VERIFICATION_EMAIL_RESENT', () => { * console.log('Verification email resent!'); * }); * * try { * const result = await auth.resendVerificationEmail('user@example.com'); * console.log(result.message); * } catch (error) { * console.error('Resend failed:', error.message); * } * ``` */ resendVerificationEmail(email: string): Promise; /** * Requests a password reset email for the specified email address. * * This initiates the password reset flow. If an account exists with the provided * email, a reset link will be sent to that address. For security, always returns * success to prevent email enumeration attacks. * * Emits `AUTH_PASSWORD_RESET_REQUESTED` event on success. * * @param email - The email address to send the reset link to. * @returns A promise that resolves with a success message. * @throws `AuthError` - If the email is invalid. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * // Listen for password reset events * auth.on('AUTH_PASSWORD_RESET_REQUESTED', () => { * console.log('Password reset email sent!'); * }); * * try { * const result = await auth.requestPasswordReset('user@example.com'); * console.log(result.message); * } catch (error) { * console.error('Reset request failed:', error.message); * } * ``` */ requestPasswordReset(email: string): Promise; /** * Completes the password reset process with a reset token and new password. * * This method is called after a user clicks the reset link in their email and * submits a new password. Upon success, all existing sessions are invalidated * for security and the user must sign in with the new password. * * Emits `AUTH_PASSWORD_RESET_COMPLETED` event on success. * * @param token - The reset token from the email link. * @param newPassword - The new password to set. * @returns A promise that resolves with a success message. * @throws `AuthError` - If the token is invalid, expired, already used, or password is too short. * @throws `NetworkError` - For network-related issues. * @throws `SdkError` - For any other unexpected SDK errors. * * @example * ```typescript * // Listen for password reset completion events * auth.on('AUTH_PASSWORD_RESET_COMPLETED', () => { * console.log('Password reset complete! Please sign in with new password.'); * }); * * // Extract token from URL query parameter * const urlParams = new URLSearchParams(window.location.search); * const token = urlParams.get('token'); * * if (token) { * try { * const result = await auth.completePasswordReset(token, 'newSecurePassword123'); * console.log(result.message); // "Password updated successfully." * } catch (error) { * console.error('Password reset failed:', error.message); * } * } * ``` */ completePasswordReset(token: string, newPassword: string): Promise; /** * Initiates a magic link authentication flow. * * This method generates PKCE parameters, stores the code verifier in localStorage, * and sends a magic link email to the specified address. When the user clicks the link, * they will be redirected to your application with a verification code and state parameter. * * Use `handleMagicLinkCallback()` to complete the authentication when the user returns. * * Emits `AUTH_MAGIC_LINK_SENT` event on success, `AUTH_MAGIC_LINK_ERROR` on failure. * * @param options - The magic link options (email and redirectUri). * @returns A promise that resolves with the result containing the state parameter. * @throws `AuthError` - If email or redirectUri is missing, or if localStorage is unavailable. * @throws `NetworkError` - For network-related issues. * * @example * ```typescript * // Listen for magic link events * auth.on('AUTH_MAGIC_LINK_SENT', () => { * console.log('Magic link sent! Check your email.'); * }); * * try { * const result = await auth.sendMagicLink({ * email: 'user@example.com', * redirectUri: 'https://myapp.com/auth/callback' * }); * console.log('State:', result.state); // Stored for callback correlation * } catch (error) { * console.error('Failed to send magic link:', error.message); * } * ``` */ sendMagicLink(options: MagicLinkOptions): Promise; /** * Handles the callback from a magic link authentication flow. * * This method extracts the verification code and state from the URL, * retrieves the stored PKCE verifier, and exchanges the code for tokens. * On success, a session is created and the user is authenticated. * * Emits `AUTH_LOGIN` on success. * * @param url - Optional URL to parse. Defaults to window.location.href. * @returns A promise that resolves with the callback result. * * @example * ```typescript * // In your callback page/route * const result = await auth.handleMagicLinkCallback(); * if (result.success) { * console.log('Authenticated!', result.session); * // Redirect to app * } else { * console.error('Authentication failed:', result.error); * // Show error message * } * ``` */ handleMagicLinkCallback(url?: string): Promise; /** * Checks if the current URL appears to be a magic link callback. * * This is a quick check to determine if the current page load is from * a magic link click. Use `handleMagicLinkCallback()` to actually process it. * * @param url - Optional URL to check. Defaults to window.location.href. * @returns True if the URL contains magic link callback parameters. * * @example * ```typescript * // On app initialization * if (auth.isMagicLinkCallback()) { * const result = await auth.handleMagicLinkCallback(); * // Handle result... * } * ``` */ isMagicLinkCallback(url?: string): boolean; /** * Extracts the verification code from a magic link callback URL. * * @param url - Optional URL to parse. Defaults to window.location.href. * @returns The verification code or null if not present. */ getMagicLinkVerificationCode(url?: string): string | null; } //# sourceMappingURL=Auth.d.ts.map