@korajs/auth 0.3.2 → 0.3.3
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/server.cjs +12 -16
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +12 -16
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/built-in/auth-routes.ts","../src/tokens/token-manager.ts","../src/types.ts","../src/tokens/jwt.ts","../src/provider/built-in/user-store.ts","../src/provider/built-in/password-reset.ts","../src/provider/built-in/email-verification.ts","../src/provider/built-in/sqlite-user-store.ts","../src/provider/built-in/postgres-user-store.ts","../src/provider/adapter.ts","../src/provider/external/external-jwt-provider.ts","../src/provider/external/clerk-adapter.ts","../src/provider/external/supabase-adapter.ts","../src/passkey/passkey-server.ts","../src/org/org-types.ts","../src/org/org-routes.ts","../src/org/org-store.ts","../src/rbac/rbac-types.ts","../src/rbac/rbac-engine.ts","../src/rbac/scope-resolver.ts","../src/provider/oauth/oauth-types.ts","../src/provider/oauth/oauth-flow.ts","../src/session/session.ts","../src/mfa/totp.ts","../src/admin/admin-api.ts","../src/admin/audit-log.ts","../src/admin/webhooks.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport type { AuthTokens } from '../../types'\nimport { TokenManager } from '../../tokens/token-manager'\nimport {\n\tcomputePublicKeyThumbprint,\n\tverifyChallenge,\n} from '../../device/device-identity'\nimport { hashPassword, verifyPassword } from './password-hash'\nimport {\n\ttype UserStore,\n\ttype AuthUser,\n\ttype AuthDevice,\n} from './user-store'\n\n// ============================================================================\n// Challenge Store\n// ============================================================================\n\n/**\n * Interface for server-side challenge storage.\n *\n * Challenges must be stored server-side with expiry and single-use semantics\n * to prevent replay attacks on device verification.\n */\nexport interface ChallengeStore {\n\t/**\n\t * Store a challenge for later verification.\n\t * @param challenge - The challenge string\n\t * @param deviceId - The device this challenge is intended for\n\t * @param expiresAt - Timestamp (ms since epoch) when this challenge expires\n\t */\n\tstore(challenge: string, deviceId: string, expiresAt: number): Promise<void>\n\n\t/**\n\t * Consume a challenge (single-use). Returns the associated device ID if the\n\t * challenge is valid and not expired, or null if it doesn't exist, has expired,\n\t * or was already consumed.\n\t */\n\tconsume(challenge: string): Promise<{ deviceId: string } | null>\n}\n\n/**\n * In-memory challenge store with expiry and single-use semantics.\n * Suitable for development and testing. Use Redis or a database in production.\n */\nexport class InMemoryChallengeStore implements ChallengeStore {\n\tprivate readonly challenges = new Map<string, { deviceId: string; expiresAt: number }>()\n\n\tasync store(challenge: string, deviceId: string, expiresAt: number): Promise<void> {\n\t\tthis.challenges.set(challenge, { deviceId, expiresAt })\n\t}\n\n\tasync consume(challenge: string): Promise<{ deviceId: string } | null> {\n\t\tconst entry = this.challenges.get(challenge)\n\t\tif (entry === undefined) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Always delete (single-use)\n\t\tthis.challenges.delete(challenge)\n\n\t\t// Check expiry\n\t\tif (Date.now() > entry.expiresAt) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn { deviceId: entry.deviceId }\n\t}\n\n\t/**\n\t * Remove expired challenges to prevent unbounded memory growth.\n\t */\n\tcleanup(): void {\n\t\tconst now = Date.now()\n\t\tfor (const [challenge, entry] of this.challenges) {\n\t\t\tif (now > entry.expiresAt) {\n\t\t\t\tthis.challenges.delete(challenge)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Rate Limiter\n// ============================================================================\n\n/**\n * Interface for rate limiting auth endpoints.\n *\n * Rate limiting is critical for preventing brute-force password guessing\n * and credential stuffing attacks.\n */\nexport interface RateLimiter {\n\t/**\n\t * Check if an action is allowed for the given key.\n\t * @param key - Rate limit key (e.g., IP address, email, or composite key)\n\t * @returns true if the action is allowed, false if rate limited\n\t */\n\tisAllowed(key: string): Promise<boolean>\n\n\t/**\n\t * Record that an action was performed for the given key.\n\t * Call this after each authentication attempt.\n\t */\n\trecord(key: string): Promise<void>\n\n\t/**\n\t * Reset the rate limit for a key (e.g., after a successful login).\n\t */\n\treset(key: string): Promise<void>\n}\n\n/**\n * In-memory sliding window rate limiter.\n * Suitable for development and single-server deployments.\n * Use Redis-based rate limiting for multi-server production deployments.\n */\nexport class InMemoryRateLimiter implements RateLimiter {\n\tprivate readonly attempts = new Map<string, number[]>()\n\tprivate readonly maxAttempts: number\n\tprivate readonly windowMs: number\n\n\t/**\n\t * @param maxAttempts - Maximum number of attempts within the time window (default: 10)\n\t * @param windowMs - Time window in milliseconds (default: 60,000 = 1 minute)\n\t */\n\tconstructor(maxAttempts = 10, windowMs = 60_000) {\n\t\tthis.maxAttempts = maxAttempts\n\t\tthis.windowMs = windowMs\n\t}\n\n\tasync isAllowed(key: string): Promise<boolean> {\n\t\tconst now = Date.now()\n\t\tconst attempts = this.attempts.get(key) ?? []\n\t\tconst recentAttempts = attempts.filter((t) => now - t < this.windowMs)\n\t\treturn recentAttempts.length < this.maxAttempts\n\t}\n\n\tasync record(key: string): Promise<void> {\n\t\tconst now = Date.now()\n\t\tconst attempts = this.attempts.get(key) ?? []\n\t\t// Keep only recent attempts to bound memory\n\t\tconst recentAttempts = attempts.filter((t) => now - t < this.windowMs)\n\t\trecentAttempts.push(now)\n\t\tthis.attempts.set(key, recentAttempts)\n\t}\n\n\tasync reset(key: string): Promise<void> {\n\t\tthis.attempts.delete(key)\n\t}\n}\n\n// ============================================================================\n// Auth Routes Configuration\n// ============================================================================\n\n/**\n * Configuration for building the built-in auth routes.\n */\nexport interface AuthRoutesConfig {\n\t/** The user/device store backing the auth routes */\n\tuserStore: UserStore\n\t/** The token manager for issuing and validating JWTs */\n\ttokenManager: TokenManager\n\t/**\n\t * Optional challenge store for device verification.\n\t * Required for secure device proof-of-possession verification.\n\t * If not provided, an in-memory store is created automatically.\n\t */\n\tchallengeStore?: ChallengeStore\n\t/**\n\t * Optional rate limiter for authentication endpoints.\n\t * If not provided, an in-memory rate limiter is created with defaults\n\t * (10 attempts per minute).\n\t */\n\trateLimiter?: RateLimiter\n}\n\n/**\n * Response envelope returned by all auth route handlers.\n *\n * Successful responses include a `data` field; failures include an `error` string.\n * The `status` field maps directly to an HTTP status code.\n */\nexport interface AuthRouteResponse<T> {\n\t/** HTTP status code */\n\tstatus: number\n\t/** Either the success payload or an error message */\n\tbody: { data: T } | { error: string }\n}\n\n/** Minimum password length enforced at sign-up. */\nconst MIN_PASSWORD_LENGTH = 8\n\n/** Maximum password length to prevent hash-DoS attacks via extremely long passwords. */\nconst MAX_PASSWORD_LENGTH = 128\n\n/** Maximum length for user/device name fields. */\nconst MAX_NAME_LENGTH = 200\n\n/** Challenge validity window in milliseconds (60 seconds). */\nconst CHALLENGE_TTL_MS = 60_000\n\n/**\n * Simple email format validation.\n * Checks for the presence of exactly one @ with non-empty local and domain parts,\n * and at least one dot in the domain. This is intentionally lenient — real email\n * validation happens by sending a confirmation email, not by regex.\n */\nfunction isValidEmail(email: string): boolean {\n\tif (email.length === 0 || email.length > 254) {\n\t\treturn false\n\t}\n\tconst atIndex = email.indexOf('@')\n\tif (atIndex < 1) {\n\t\treturn false\n\t}\n\tconst domain = email.slice(atIndex + 1)\n\tif (domain.length === 0 || !domain.includes('.')) {\n\t\treturn false\n\t}\n\t// No double-@ or spaces\n\tif (email.indexOf('@', atIndex + 1) !== -1) {\n\t\treturn false\n\t}\n\tif (email.includes(' ')) {\n\t\treturn false\n\t}\n\treturn true\n}\n\n/**\n * Sanitize and limit a name string.\n * Trims whitespace, enforces max length, and strips control characters.\n */\nfunction sanitizeName(name: string): string {\n\t// Strip ASCII control characters (0x00-0x1F, 0x7F)\n\tconst cleaned = name.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\tconst trimmed = cleaned.trim()\n\tif (trimmed.length > MAX_NAME_LENGTH) {\n\t\treturn trimmed.slice(0, MAX_NAME_LENGTH)\n\t}\n\treturn trimmed\n}\n\n/**\n * HTTP route handlers for the built-in Kora auth provider.\n *\n * These are framework-agnostic functions that accept parsed request bodies and return\n * structured responses. The server package is responsible for wiring them into its\n * HTTP server (e.g., mapping `POST /auth/signup` to `handleSignUp`).\n *\n * All handlers follow the same pattern:\n * - Validate input\n * - Perform the operation\n * - Return `{ status, body: { data } }` on success\n * - Return `{ status, body: { error } }` on failure\n *\n * Security features:\n * - Rate limiting on sign-in/sign-up to prevent brute-force attacks\n * - Server-side challenge store for device verification (single-use, time-limited)\n * - Token revocation on sign-out and device revocation\n * - Input sanitization on all name fields\n * - Maximum password length to prevent hash-DoS\n *\n * @example\n * ```typescript\n * const routes = new BuiltInAuthRoutes({\n * userStore: new InMemoryUserStore(),\n * tokenManager: new TokenManager({ secret: TokenManager.generateSecret() }),\n * })\n *\n * // Wire into an HTTP server:\n * app.post('/auth/signup', async (req, res) => {\n * const result = await routes.handleSignUp(req.body)\n * res.status(result.status).json(result.body)\n * })\n * ```\n */\nexport class BuiltInAuthRoutes {\n\tprivate readonly userStore: UserStore\n\tprivate readonly tokenManager: TokenManager\n\tprivate readonly challengeStore: ChallengeStore\n\tprivate readonly rateLimiter: RateLimiter\n\n\tconstructor(config: AuthRoutesConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.tokenManager = config.tokenManager\n\t\tthis.challengeStore = config.challengeStore ?? new InMemoryChallengeStore()\n\t\tthis.rateLimiter = config.rateLimiter ?? new InMemoryRateLimiter()\n\t}\n\n\t/**\n\t * Handle user sign-up (POST /auth/signup).\n\t *\n\t * Validates email format and password length, hashes the password,\n\t * creates the user, optionally registers a device, and issues tokens.\n\t *\n\t * @param body - Sign-up request body\n\t * @param body.email - The user's email address\n\t * @param body.password - The plaintext password (8-128 characters)\n\t * @param body.name - Optional display name (defaults to email local part)\n\t * @param body.deviceId - Optional device ID to register\n\t * @param body.devicePublicKey - Optional device public key (base64url)\n\t * @param clientIp - Optional client IP for rate limiting\n\t * @returns Auth response with the created user and tokens, or an error\n\t */\n\tasync handleSignUp(body: {\n\t\temail: string\n\t\tpassword: string\n\t\tname?: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}, clientIp?: string): Promise<AuthRouteResponse<{ user: AuthUser; tokens: AuthTokens }>> {\n\t\t// Rate limiting\n\t\tconst rateLimitKey = clientIp ?? 'global'\n\t\tif (!(await this.rateLimiter.isAllowed(rateLimitKey))) {\n\t\t\treturn {\n\t\t\t\tstatus: 429,\n\t\t\t\tbody: { error: 'Too many requests. Please try again later.' },\n\t\t\t}\n\t\t}\n\t\tawait this.rateLimiter.record(rateLimitKey)\n\n\t\t// Validate email format\n\t\tif (!isValidEmail(body.email)) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: {\n\t\t\t\t\terror: 'Invalid email address. Please provide a valid email in the format user@domain.com.',\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// Validate password length (min and max)\n\t\tif (body.password.length < MIN_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: {\n\t\t\t\t\terror: `Password must be at least ${MIN_PASSWORD_LENGTH} characters long.`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tif (body.password.length > MAX_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: {\n\t\t\t\t\terror: `Password must be at most ${MAX_PASSWORD_LENGTH} characters long.`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// Hash the password\n\t\tconst { hash, salt } = await hashPassword(body.password)\n\n\t\t// Sanitize the display name\n\t\tconst rawName = body.name ?? body.email.split('@')[0] ?? body.email\n\t\tconst name = sanitizeName(rawName)\n\n\t\t// Create the user — may throw DuplicateEmailError\n\t\tlet user: AuthUser\n\t\ttry {\n\t\t\tuser = await this.userStore.createUser({\n\t\t\t\temail: body.email,\n\t\t\t\tpasswordHash: hash,\n\t\t\t\tsalt,\n\t\t\t\tname,\n\t\t\t})\n\t\t} catch (err: unknown) {\n\t\t\tif (err instanceof Error && err.name === 'DuplicateEmailError') {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 409,\n\t\t\t\t\tbody: { error: 'An account with this email already exists.' },\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\n\t\t// Use provided deviceId or generate a placeholder\n\t\tconst deviceId = body.deviceId ?? `device-${user.id}`\n\n\t\t// Register device if device info was provided\n\t\tif (body.deviceId !== undefined && body.devicePublicKey !== undefined) {\n\t\t\tawait this.userStore.registerDevice({\n\t\t\t\tid: body.deviceId,\n\t\t\t\tuserId: user.id,\n\t\t\t\tpublicKey: body.devicePublicKey,\n\t\t\t\tname: 'Primary Device',\n\t\t\t})\n\t\t}\n\n\t\t// Issue tokens\n\t\tconst tokens = this.tokenManager.issueTokens(user.id, deviceId)\n\n\t\treturn {\n\t\t\tstatus: 201,\n\t\t\tbody: { data: { user, tokens } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle user sign-in (POST /auth/signin).\n\t *\n\t * Looks up the user by email, verifies the password, optionally registers\n\t * a new device, and issues tokens.\n\t *\n\t * @param body - Sign-in request body\n\t * @param body.email - The user's email address\n\t * @param body.password - The plaintext password\n\t * @param body.deviceId - Optional device ID to register\n\t * @param body.devicePublicKey - Optional device public key (base64url)\n\t * @param clientIp - Optional client IP for rate limiting\n\t * @returns Auth response with the user and tokens, or an error\n\t */\n\tasync handleSignIn(body: {\n\t\temail: string\n\t\tpassword: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}, clientIp?: string): Promise<AuthRouteResponse<{ user: AuthUser; tokens: AuthTokens }>> {\n\t\t// Rate limiting (use email + IP composite key for per-account protection)\n\t\tconst rateLimitKey = clientIp\n\t\t\t? `signin:${body.email.toLowerCase()}:${clientIp}`\n\t\t\t: `signin:${body.email.toLowerCase()}`\n\n\t\tif (!(await this.rateLimiter.isAllowed(rateLimitKey))) {\n\t\t\treturn {\n\t\t\t\tstatus: 429,\n\t\t\t\tbody: { error: 'Too many sign-in attempts. Please try again later.' },\n\t\t\t}\n\t\t}\n\t\tawait this.rateLimiter.record(rateLimitKey)\n\n\t\tconst storedUser = await this.userStore.findByEmail(body.email)\n\t\tif (storedUser === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid email or password.' },\n\t\t\t}\n\t\t}\n\n\t\tconst passwordValid = await verifyPassword(\n\t\t\tbody.password,\n\t\t\tstoredUser.passwordHash,\n\t\t\tstoredUser.salt,\n\t\t)\n\t\tif (!passwordValid) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid email or password.' },\n\t\t\t}\n\t\t}\n\n\t\t// Successful login — reset rate limit for this key\n\t\tawait this.rateLimiter.reset(rateLimitKey)\n\n\t\t// Use provided deviceId or generate a placeholder\n\t\tconst deviceId = body.deviceId ?? `device-${storedUser.id}`\n\n\t\t// Register device if device info was provided\n\t\tif (body.deviceId !== undefined && body.devicePublicKey !== undefined) {\n\t\t\tawait this.userStore.registerDevice({\n\t\t\t\tid: body.deviceId,\n\t\t\t\tuserId: storedUser.id,\n\t\t\t\tpublicKey: body.devicePublicKey,\n\t\t\t\tname: 'Device',\n\t\t\t})\n\t\t}\n\n\t\tconst tokens = this.tokenManager.issueTokens(storedUser.id, deviceId)\n\n\t\tconst user: AuthUser = {\n\t\t\tid: storedUser.id,\n\t\t\temail: storedUser.email,\n\t\t\tname: storedUser.name,\n\t\t\temailVerified: storedUser.emailVerified,\n\t\t\tcreatedAt: storedUser.createdAt,\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { user, tokens } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle token refresh (POST /auth/refresh).\n\t *\n\t * Validates the provided refresh token and issues a new token pair\n\t * (refresh token rotation with reuse detection). The old refresh token\n\t * is marked as consumed in the revocation store.\n\t *\n\t * @param body - Refresh request body\n\t * @param body.refreshToken - The current refresh token\n\t * @returns Auth response with new tokens, or an error\n\t */\n\tasync handleRefresh(body: {\n\t\trefreshToken: string\n\t}): Promise<AuthRouteResponse<AuthTokens>> {\n\t\tconst result = await this.tokenManager.refreshAccessToken(body.refreshToken)\n\n\t\tif (result === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired refresh token.' },\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: result },\n\t\t}\n\t}\n\n\t/**\n\t * Handle sign-out (POST /auth/signout).\n\t *\n\t * Validates the access token and revokes the current refresh token\n\t * (if a revocation store is configured). This ensures that stolen\n\t * refresh tokens cannot be used after the user signs out.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param body - Sign-out request body\n\t * @param body.refreshToken - The current refresh token to revoke\n\t * @returns Auth response with success flag, or an error\n\t */\n\tasync handleSignOut(\n\t\taccessToken: string,\n\t\tbody: { refreshToken?: string },\n\t): Promise<AuthRouteResponse<{ success: boolean }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Revoke the access token itself\n\t\tawait this.tokenManager.revokeToken(payload.jti, payload.exp)\n\n\t\t// Revoke the refresh token if provided\n\t\tif (body.refreshToken) {\n\t\t\tconst refreshPayload = this.tokenManager.validateToken(body.refreshToken)\n\t\t\tif (refreshPayload !== null && refreshPayload.type === 'refresh') {\n\t\t\t\tawait this.tokenManager.revokeToken(refreshPayload.jti, refreshPayload.exp)\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { success: true } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle get-current-user (GET /auth/me).\n\t *\n\t * Validates the access token and returns the authenticated user's profile.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @returns Auth response with the user profile, or an error\n\t */\n\tasync handleGetMe(accessToken: string): Promise<AuthRouteResponse<AuthUser>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\tconst storedUser = await this.userStore.findById(payload.sub)\n\t\tif (storedUser === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'User not found.' },\n\t\t\t}\n\t\t}\n\n\t\tconst user: AuthUser = {\n\t\t\tid: storedUser.id,\n\t\t\temail: storedUser.email,\n\t\t\tname: storedUser.name,\n\t\t\temailVerified: storedUser.emailVerified,\n\t\t\tcreatedAt: storedUser.createdAt,\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: user },\n\t\t}\n\t}\n\n\t/**\n\t * Handle list-devices (GET /auth/devices).\n\t *\n\t * Validates the access token and returns all devices registered for the user.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @returns Auth response with the device list, or an error\n\t */\n\tasync handleListDevices(\n\t\taccessToken: string,\n\t): Promise<AuthRouteResponse<AuthDevice[]>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\tconst devices = await this.userStore.listDevices(payload.sub)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: devices },\n\t\t}\n\t}\n\n\t/**\n\t * Handle device revocation (DELETE /auth/device/:id).\n\t *\n\t * Validates the access token, revokes the specified device, and invalidates\n\t * all tokens issued to that device. Only the device's owner can revoke it.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param deviceId - The ID of the device to revoke\n\t * @returns Auth response with success flag, or an error\n\t */\n\tasync handleRevokeDevice(\n\t\taccessToken: string,\n\t\tdeviceId: string,\n\t): Promise<AuthRouteResponse<{ success: boolean }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the device belongs to the authenticated user\n\t\tconst device = await this.userStore.findDevice(deviceId)\n\t\tif (device === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'Device not found.' },\n\t\t\t}\n\t\t}\n\n\t\tif (device.userId !== payload.sub) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: 'You can only revoke your own devices.' },\n\t\t\t}\n\t\t}\n\n\t\tawait this.userStore.revokeDevice(deviceId)\n\n\t\t// Invalidate all tokens for this device\n\t\tawait this.tokenManager.revokeDeviceTokens(deviceId)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { success: true } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle device registration (POST /auth/device/register).\n\t *\n\t * Requires a valid access token. Registers a new device for the authenticated\n\t * user and issues a device credential token bound to the device's public key.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param body - Device registration request body\n\t * @param body.deviceId - Unique identifier for the device\n\t * @param body.publicKey - The device's public key as a JWK JSON string\n\t * @param body.name - Human-readable device name (e.g., \"Chrome on MacBook\")\n\t * @returns Auth response with the registered device and device credential, or an error\n\t */\n\tasync handleDeviceRegister(\n\t\taccessToken: string,\n\t\tbody: {\n\t\t\tdeviceId: string\n\t\t\tpublicKey: string\n\t\t\tname: string\n\t\t},\n\t): Promise<AuthRouteResponse<{ device: AuthDevice; deviceCredential: string }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Sanitize the device name\n\t\tconst deviceName = sanitizeName(body.name)\n\t\tif (deviceName.length === 0) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Device name must not be empty.' },\n\t\t\t}\n\t\t}\n\n\t\t// Parse the JWK JSON string into a JsonWebKey object\n\t\tlet publicKeyJwk: JsonWebKey\n\t\ttry {\n\t\t\tpublicKeyJwk = JSON.parse(body.publicKey) as JsonWebKey\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Invalid public key format. Expected a JSON-encoded JWK string.' },\n\t\t\t}\n\t\t}\n\n\t\t// Compute the SHA-256 thumbprint of the public key for binding to the credential\n\t\tlet thumbprint: string\n\t\ttry {\n\t\t\tthumbprint = await computePublicKeyThumbprint(publicKeyJwk)\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Failed to compute public key thumbprint. Ensure the key is a valid EC P-256 JWK.' },\n\t\t\t}\n\t\t}\n\n\t\t// Register the device in the user store\n\t\tconst device = await this.userStore.registerDevice({\n\t\t\tid: body.deviceId,\n\t\t\tuserId: payload.sub,\n\t\t\tpublicKey: body.publicKey,\n\t\t\tname: deviceName,\n\t\t})\n\n\t\t// Issue a device credential token bound to the public key thumbprint\n\t\tconst deviceCredential = this.tokenManager.issueDeviceCredential(\n\t\t\tpayload.sub,\n\t\t\tbody.deviceId,\n\t\t\tthumbprint,\n\t\t)\n\n\t\treturn {\n\t\t\tstatus: 201,\n\t\t\tbody: { data: { device, deviceCredential } },\n\t\t}\n\t}\n\n\t/**\n\t * Generate a challenge for device proof-of-possession verification.\n\t *\n\t * Creates a cryptographically random challenge, stores it server-side with\n\t * a 60-second TTL and the target device ID, and returns the challenge string.\n\t * The client signs this challenge with its private key and submits it via\n\t * {@link handleDeviceVerify}.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param deviceId - The device this challenge is intended for\n\t * @returns Auth response with the challenge string, or an error\n\t */\n\tasync handleDeviceChallenge(\n\t\taccessToken: string,\n\t\tdeviceId: string,\n\t): Promise<AuthRouteResponse<{ challenge: string }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the device exists and belongs to this user\n\t\tconst device = await this.userStore.findDevice(deviceId)\n\t\tif (device === null || device.userId !== payload.sub) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'Device not found.' },\n\t\t\t}\n\t\t}\n\n\t\tif (device.revoked) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: 'Device has been revoked.' },\n\t\t\t}\n\t\t}\n\n\t\tconst challenge = randomBytes(32).toString('hex')\n\t\tconst expiresAt = Date.now() + CHALLENGE_TTL_MS\n\n\t\tawait this.challengeStore.store(challenge, deviceId, expiresAt)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { challenge } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle device proof-of-possession verification (POST /auth/device/verify).\n\t *\n\t * Verifies that the device holds the private key corresponding to its registered\n\t * public key by checking a signed challenge. The challenge must have been previously\n\t * issued via {@link handleDeviceChallenge} and is single-use.\n\t *\n\t * On success, issues fresh tokens for the device.\n\t *\n\t * @param body - Device verification request body\n\t * @param body.deviceId - The ID of the device to verify\n\t * @param body.challenge - The challenge string (from handleDeviceChallenge)\n\t * @param body.signature - The base64url-encoded ECDSA signature of the challenge\n\t * @returns Auth response with fresh tokens on success, or an error\n\t */\n\tasync handleDeviceVerify(body: {\n\t\tdeviceId: string\n\t\tchallenge: string\n\t\tsignature: string\n\t}): Promise<AuthRouteResponse<{ tokens: AuthTokens }>> {\n\t\t// Consume the challenge (single-use, time-limited)\n\t\tconst challengeEntry = await this.challengeStore.consume(body.challenge)\n\t\tif (challengeEntry === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired challenge. Request a new challenge and try again.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the challenge was issued for this device\n\t\tif (challengeEntry.deviceId !== body.deviceId) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Challenge was not issued for this device.' },\n\t\t\t}\n\t\t}\n\n\t\t// Look up the device in the store\n\t\tconst device = await this.userStore.findDevice(body.deviceId)\n\t\tif (device === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'Device not found.' },\n\t\t\t}\n\t\t}\n\n\t\t// Revoked devices cannot verify\n\t\tif (device.revoked) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: 'Device has been revoked and cannot authenticate.' },\n\t\t\t}\n\t\t}\n\n\t\t// Parse the stored public key JWK\n\t\tlet publicKeyJwk: JsonWebKey\n\t\ttry {\n\t\t\tpublicKeyJwk = JSON.parse(device.publicKey) as JsonWebKey\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 500,\n\t\t\t\tbody: { error: 'Device has an invalid stored public key.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the signature against the challenge using the device's public key\n\t\tlet isValid: boolean\n\t\ttry {\n\t\t\tisValid = await verifyChallenge(publicKeyJwk, body.challenge, body.signature)\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Signature verification failed. The signature or public key format may be invalid.' },\n\t\t\t}\n\t\t}\n\n\t\tif (!isValid) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid signature. Proof-of-possession verification failed.' },\n\t\t\t}\n\t\t}\n\n\t\t// Compute thumbprint for the device credential\n\t\tlet thumbprint: string\n\t\ttry {\n\t\t\tthumbprint = await computePublicKeyThumbprint(publicKeyJwk)\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 500,\n\t\t\t\tbody: { error: 'Failed to compute public key thumbprint.' },\n\t\t\t}\n\t\t}\n\n\t\t// Issue fresh tokens for this device\n\t\tconst tokens = this.tokenManager.issueTokens(device.userId, device.id, thumbprint)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { tokens } },\n\t\t}\n\t}\n\n\t/**\n\t * Generates a random challenge string for proof-of-possession verification.\n\t *\n\t * **Deprecated:** Use {@link handleDeviceChallenge} instead, which stores\n\t * the challenge server-side with expiry and single-use semantics.\n\t *\n\t * @returns A 64-character hex string (32 random bytes)\n\t */\n\tstatic generateChallenge(): string {\n\t\treturn randomBytes(32).toString('hex')\n\t}\n\n\t/**\n\t * Creates a sync server auth provider compatible with `@korajs/server`.\n\t *\n\t * The returned object implements the `AuthProvider` interface from\n\t * `@korajs/server`, validating access tokens and returning an auth\n\t * context containing the user ID and device metadata. This bridges\n\t * the built-in auth system with the sync server's authentication layer.\n\t *\n\t * Also checks device revocation status during authentication, ensuring\n\t * that revoked devices are rejected even if their tokens haven't expired.\n\t *\n\t * @returns An object with an `authenticate` method suitable for KoraSyncServer's `auth` config\n\t *\n\t * @example\n\t * ```typescript\n\t * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n\t * const syncServer = new KoraSyncServer({\n\t * store,\n\t * auth: routes.toSyncAuthProvider(),\n\t * })\n\t * ```\n\t */\n\ttoSyncAuthProvider(): {\n\t\tauthenticate(token: string): Promise<{\n\t\t\tuserId: string\n\t\t\tscopes?: Record<string, Record<string, unknown>>\n\t\t\tmetadata?: Record<string, unknown>\n\t\t} | null>\n\t} {\n\t\tconst tokenManager = this.tokenManager\n\t\tconst userStore = this.userStore\n\n\t\treturn {\n\t\t\tasync authenticate(token: string) {\n\t\t\t\tconst payload = tokenManager.validateToken(token)\n\t\t\t\tif (payload === null || payload.type !== 'access') {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\t// Verify the user still exists\n\t\t\t\tconst user = await userStore.findById(payload.sub)\n\t\t\t\tif (user === null) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\t// Check device revocation status\n\t\t\t\tconst device = await userStore.findDevice(payload.dev)\n\t\t\t\tif (device !== null && device.revoked) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\t// Touch the device to update last-seen timestamp\n\t\t\t\tawait userStore.touchDevice(payload.dev)\n\n\t\t\t\treturn {\n\t\t\t\t\tuserId: payload.sub,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tdeviceId: payload.dev,\n\t\t\t\t\t\temail: user.email,\n\t\t\t\t\t\tname: user.name,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n","import { randomBytes, randomUUID } from 'node:crypto'\nimport type {\n\tAuthTokens,\n\tDeviceCredentialPayload,\n\tTokenPayload,\n} from '../types'\nimport {\n\tDEFAULT_ACCESS_TOKEN_LIFETIME,\n\tDEFAULT_DEVICE_CREDENTIAL_LIFETIME,\n\tDEFAULT_REFRESH_TOKEN_LIFETIME,\n} from '../types'\nimport { encodeJwt, isExpired, verifyJwt } from './jwt'\n\n/**\n * Minimum HMAC secret length in bytes. A 256-bit key provides full security\n * for HMAC-SHA256 (NIST SP 800-107). Shorter keys weaken the MAC and are\n * vulnerable to brute-force attacks.\n */\nconst MIN_SECRET_LENGTH = 32\n\n/**\n * Interface for server-side token revocation storage.\n *\n * Implementing this interface allows the TokenManager to:\n * - Revoke individual tokens by their `jti`\n * - Detect refresh token reuse (potential theft indicator)\n * - Invalidate all tokens for a specific device on revocation\n *\n * The in-memory implementation ({@link InMemoryTokenRevocationStore}) is suitable\n * for development. Production deployments should use a persistent store (Redis, database).\n */\nexport interface TokenRevocationStore {\n\t/**\n\t * Check whether a token has been revoked.\n\t * @param jti - The JWT ID to check\n\t * @returns true if the token has been revoked\n\t */\n\tisRevoked(jti: string): Promise<boolean>\n\n\t/**\n\t * Revoke a specific token by its JWT ID.\n\t * @param jti - The JWT ID to revoke\n\t * @param expiresAt - The token's original expiration time (seconds since epoch).\n\t * The store may use this to auto-purge expired revocations.\n\t */\n\trevoke(jti: string, expiresAt: number): Promise<void>\n\n\t/**\n\t * Revoke all tokens associated with a specific device.\n\t * Called when a device is revoked to invalidate all its tokens.\n\t * @param deviceId - The device ID whose tokens should be revoked\n\t */\n\trevokeAllForDevice(deviceId: string): Promise<void>\n}\n\n/**\n * In-memory token revocation store.\n *\n * Suitable for development and testing. Revoked tokens are stored in a Set\n * and automatically cleaned up when they would have expired naturally.\n *\n * **Not suitable for production**: revocations are lost on server restart\n * and not shared across server instances. Use a Redis or database-backed\n * store in production.\n */\nexport class InMemoryTokenRevocationStore implements TokenRevocationStore {\n\tprivate readonly revokedTokens = new Map<string, number>()\n\tprivate readonly revokedDevices = new Set<string>()\n\n\tasync isRevoked(jti: string): Promise<boolean> {\n\t\treturn this.revokedTokens.has(jti)\n\t}\n\n\tasync revoke(jti: string, expiresAt: number): Promise<void> {\n\t\tthis.revokedTokens.set(jti, expiresAt)\n\t}\n\n\tasync revokeAllForDevice(deviceId: string): Promise<void> {\n\t\tthis.revokedDevices.add(deviceId)\n\t}\n\n\t/**\n\t * Check if a device has been revoked.\n\t */\n\tisDeviceRevoked(deviceId: string): boolean {\n\t\treturn this.revokedDevices.has(deviceId)\n\t}\n\n\t/**\n\t * Remove expired revocations to prevent unbounded memory growth.\n\t * Call periodically (e.g., every hour) in long-running servers.\n\t */\n\tcleanup(): void {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tfor (const [jti, expiresAt] of this.revokedTokens) {\n\t\t\tif (nowSeconds > expiresAt) {\n\t\t\t\tthis.revokedTokens.delete(jti)\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Configuration for the server-side TokenManager.\n */\nexport interface TokenManagerConfig {\n\t/**\n\t * Secret key for signing JWTs (HMAC-SHA256).\n\t *\n\t * Must be at least 32 characters (256 bits). Use {@link TokenManager.generateSecret}\n\t * to create a cryptographically random secret.\n\t *\n\t * For key rotation, provide an array of secrets. The first secret is used for\n\t * signing new tokens; all secrets are tried during verification (newest first).\n\t * This allows graceful rotation: add the new secret at index 0, then remove\n\t * the old secret after all tokens signed with it have expired.\n\t */\n\tsecret: string | string[]\n\n\t/** Access token lifetime in milliseconds (default: 15 minutes) */\n\taccessTokenLifetime?: number\n\n\t/** Refresh token lifetime in milliseconds (default: 90 days) */\n\trefreshTokenLifetime?: number\n\n\t/** Device credential lifetime in milliseconds (default: 90 days) */\n\tdeviceCredentialLifetime?: number\n\n\t/**\n\t * Optional token revocation store. When provided, enables:\n\t * - Individual token revocation via `revokeToken()`\n\t * - Refresh token reuse detection (consumed tokens are tracked)\n\t * - Device-level token invalidation via `revokeDeviceTokens()`\n\t *\n\t * Without a revocation store, tokens are valid until they expire.\n\t */\n\trevocationStore?: TokenRevocationStore\n}\n\n/**\n * Server-side token manager responsible for issuing, refreshing, and validating\n * Kora authentication tokens.\n *\n * Uses HMAC-SHA256 signed JWTs with unique `jti` identifiers for every token.\n * Supports key rotation (multiple secrets), token revocation, and refresh token\n * reuse detection.\n *\n * @example\n * ```typescript\n * const tokenManager = new TokenManager({\n * secret: TokenManager.generateSecret(),\n * revocationStore: new InMemoryTokenRevocationStore(),\n * })\n *\n * // Issue all tokens at once\n * const tokens = tokenManager.issueTokens('user-123', 'device-456')\n *\n * // Validate an access token\n * const payload = tokenManager.validateToken(tokens.accessToken)\n *\n * // Refresh when the access token expires\n * const newTokens = await tokenManager.refreshAccessToken(tokens.refreshToken)\n * ```\n */\nexport class TokenManager {\n\t/** All signing/verification secrets (index 0 = current signing key) */\n\tprivate readonly secrets: string[]\n\tprivate readonly accessTokenLifetime: number\n\tprivate readonly refreshTokenLifetime: number\n\tprivate readonly deviceCredentialLifetime: number\n\tprivate readonly revocationStore: TokenRevocationStore | undefined\n\n\tconstructor(config: TokenManagerConfig) {\n\t\tconst secrets = Array.isArray(config.secret) ? config.secret : [config.secret]\n\n\t\tif (secrets.length === 0) {\n\t\t\tthrow new Error('TokenManager requires at least one secret.')\n\t\t}\n\n\t\tfor (const secret of secrets) {\n\t\t\tif (secret.length < MIN_SECRET_LENGTH) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`JWT secret must be at least ${MIN_SECRET_LENGTH} characters (256 bits) for HMAC-SHA256 security. ` +\n\t\t\t\t\t`Received ${secret.length} characters. Use TokenManager.generateSecret() to generate a secure secret.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tthis.secrets = secrets\n\t\tthis.accessTokenLifetime = config.accessTokenLifetime ?? DEFAULT_ACCESS_TOKEN_LIFETIME\n\t\tthis.refreshTokenLifetime = config.refreshTokenLifetime ?? DEFAULT_REFRESH_TOKEN_LIFETIME\n\t\tthis.deviceCredentialLifetime =\n\t\t\tconfig.deviceCredentialLifetime ?? DEFAULT_DEVICE_CREDENTIAL_LIFETIME\n\t\tthis.revocationStore = config.revocationStore\n\t}\n\n\t/**\n\t * Generate a cryptographically random secret suitable for HMAC-SHA256 signing.\n\t *\n\t * Returns a 64-character hex string (32 bytes / 256 bits of entropy).\n\t * Store this securely (environment variable, secrets manager) — never in source code.\n\t *\n\t * @returns A random 256-bit hex-encoded secret\n\t */\n\tstatic generateSecret(): string {\n\t\treturn randomBytes(32).toString('hex')\n\t}\n\n\t/**\n\t * Issue a signed JWT access token.\n\t *\n\t * Access tokens are short-lived (default 15 minutes) and used to authorize\n\t * API requests. When expired, use {@link refreshAccessToken} with a valid\n\t * refresh token to obtain a new one.\n\t *\n\t * @param userId - The subject (user ID) to encode in the token\n\t * @param deviceId - The device ID of the requesting device\n\t * @returns A signed JWT string with type 'access'\n\t */\n\tissueAccessToken(userId: string, deviceId: string): string {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tconst payload: TokenPayload = {\n\t\t\tjti: randomUUID(),\n\t\t\tsub: userId,\n\t\t\tdev: deviceId,\n\t\t\ttype: 'access',\n\t\t\tiat: nowSeconds,\n\t\t\texp: nowSeconds + Math.floor(this.accessTokenLifetime / 1000),\n\t\t}\n\t\treturn encodeJwt(payload as unknown as Record<string, unknown>, this.secrets[0] as string)\n\t}\n\n\t/**\n\t * Issue a signed JWT refresh token.\n\t *\n\t * Refresh tokens are longer-lived (default 90 days) and used exclusively\n\t * to obtain new access tokens via {@link refreshAccessToken}. They should\n\t * be stored securely and never sent to resource APIs.\n\t *\n\t * @param userId - The subject (user ID) to encode in the token\n\t * @param deviceId - The device ID of the requesting device\n\t * @returns A signed JWT string with type 'refresh'\n\t */\n\tissueRefreshToken(userId: string, deviceId: string): string {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tconst payload: TokenPayload = {\n\t\t\tjti: randomUUID(),\n\t\t\tsub: userId,\n\t\t\tdev: deviceId,\n\t\t\ttype: 'refresh',\n\t\t\tiat: nowSeconds,\n\t\t\texp: nowSeconds + Math.floor(this.refreshTokenLifetime / 1000),\n\t\t}\n\t\treturn encodeJwt(payload as unknown as Record<string, unknown>, this.secrets[0] as string)\n\t}\n\n\t/**\n\t * Issue a signed device credential token.\n\t *\n\t * Device credentials are long-lived tokens bound to a device's public key.\n\t * They include a `mustCheckinBy` deadline; if the device does not check in\n\t * before this deadline, the credential should be treated as revoked.\n\t *\n\t * @param userId - The subject (user ID) to encode in the token\n\t * @param deviceId - The device ID of the requesting device\n\t * @param publicKeyThumbprint - SHA-256 thumbprint of the device's public key\n\t * @returns A signed JWT string with type 'device_credential'\n\t */\n\tissueDeviceCredential(\n\t\tuserId: string,\n\t\tdeviceId: string,\n\t\tpublicKeyThumbprint: string,\n\t): string {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tconst lifetimeSeconds = Math.floor(this.deviceCredentialLifetime / 1000)\n\t\tconst payload: DeviceCredentialPayload = {\n\t\t\tjti: randomUUID(),\n\t\t\tsub: userId,\n\t\t\tdev: deviceId,\n\t\t\ttype: 'device_credential',\n\t\t\tiat: nowSeconds,\n\t\t\texp: nowSeconds + lifetimeSeconds,\n\t\t\tdpk: publicKeyThumbprint,\n\t\t\tmustCheckinBy: nowSeconds + lifetimeSeconds,\n\t\t}\n\t\treturn encodeJwt(payload as unknown as Record<string, unknown>, this.secrets[0] as string)\n\t}\n\n\t/**\n\t * Issue a complete set of authentication tokens.\n\t *\n\t * Always issues an access token and refresh token. If a `publicKeyThumbprint`\n\t * is provided, also issues a device credential.\n\t *\n\t * @param userId - The subject (user ID) to encode in the tokens\n\t * @param deviceId - The device ID of the requesting device\n\t * @param publicKeyThumbprint - Optional SHA-256 thumbprint of the device's public key.\n\t * When provided, a device credential is included in the returned tokens.\n\t * @returns An {@link AuthTokens} object containing the issued tokens\n\t */\n\tissueTokens(\n\t\tuserId: string,\n\t\tdeviceId: string,\n\t\tpublicKeyThumbprint?: string,\n\t): AuthTokens {\n\t\tconst tokens: AuthTokens = {\n\t\t\taccessToken: this.issueAccessToken(userId, deviceId),\n\t\t\trefreshToken: this.issueRefreshToken(userId, deviceId),\n\t\t}\n\n\t\tif (publicKeyThumbprint !== undefined) {\n\t\t\ttokens.deviceCredential = this.issueDeviceCredential(\n\t\t\t\tuserId,\n\t\t\t\tdeviceId,\n\t\t\t\tpublicKeyThumbprint,\n\t\t\t)\n\t\t}\n\n\t\treturn tokens\n\t}\n\n\t/**\n\t * Validate and decode a token.\n\t *\n\t * Verifies the HMAC-SHA256 signature (trying all configured secrets for key rotation),\n\t * checks that the token has not expired, and validates all required claims.\n\t * Returns null (rather than throwing) for invalid or expired tokens, so callers\n\t * can handle authentication failure without try/catch.\n\t *\n\t * @param token - The JWT string to validate\n\t * @returns The decoded {@link TokenPayload} if valid, or null if the token is\n\t * invalid, expired, or missing required claims\n\t */\n\tvalidateToken(token: string): TokenPayload | null {\n\t\t// Try verification with each secret (supports key rotation)\n\t\tlet decoded: Record<string, unknown> | null = null\n\t\tfor (const secret of this.secrets) {\n\t\t\tdecoded = verifyJwt(token, secret)\n\t\t\tif (decoded !== null) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (decoded === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// verifyJwt validates the signature but not expiration;\n\t\t// check the exp claim separately\n\t\tif (isExpired(decoded as { exp?: number })) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Validate that all required base claims are present and correctly typed\n\t\tif (\n\t\t\ttypeof decoded['jti'] !== 'string' ||\n\t\t\ttypeof decoded['sub'] !== 'string' ||\n\t\t\ttypeof decoded['dev'] !== 'string' ||\n\t\t\ttypeof decoded['type'] !== 'string' ||\n\t\t\ttypeof decoded['iat'] !== 'number' ||\n\t\t\ttypeof decoded['exp'] !== 'number'\n\t\t) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst type = decoded['type']\n\t\tif (type !== 'access' && type !== 'refresh' && type !== 'device_credential') {\n\t\t\treturn null\n\t\t}\n\n\t\treturn {\n\t\t\tjti: decoded['jti'] as string,\n\t\t\tsub: decoded['sub'] as string,\n\t\t\tdev: decoded['dev'] as string,\n\t\t\ttype,\n\t\t\tiat: decoded['iat'] as number,\n\t\t\texp: decoded['exp'] as number,\n\t\t}\n\t}\n\n\t/**\n\t * Validate a token and check it against the revocation store.\n\t *\n\t * Like {@link validateToken}, but also checks whether the token's `jti` has been\n\t * revoked. Requires a revocation store to be configured.\n\t *\n\t * @param token - The JWT string to validate\n\t * @returns The decoded {@link TokenPayload} if valid and not revoked, or null otherwise\n\t */\n\tasync validateTokenWithRevocation(token: string): Promise<TokenPayload | null> {\n\t\tconst payload = this.validateToken(token)\n\t\tif (payload === null) {\n\t\t\treturn null\n\t\t}\n\n\t\tif (this.revocationStore) {\n\t\t\tconst revoked = await this.revocationStore.isRevoked(payload.jti)\n\t\t\tif (revoked) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\n\t\treturn payload\n\t}\n\n\t/**\n\t * Revoke a specific token by its JWT ID.\n\t *\n\t * Requires a revocation store to be configured. After revocation, the token\n\t * will be rejected by {@link validateTokenWithRevocation}.\n\t *\n\t * @param jti - The JWT ID of the token to revoke\n\t * @param expiresAt - The token's expiration time (seconds since epoch)\n\t */\n\tasync revokeToken(jti: string, expiresAt: number): Promise<void> {\n\t\tif (this.revocationStore) {\n\t\t\tawait this.revocationStore.revoke(jti, expiresAt)\n\t\t}\n\t}\n\n\t/**\n\t * Revoke all tokens for a specific device.\n\t *\n\t * Called when a device is revoked to ensure all its existing tokens\n\t * (access, refresh, and device credentials) are invalidated.\n\t *\n\t * @param deviceId - The device ID whose tokens should be revoked\n\t */\n\tasync revokeDeviceTokens(deviceId: string): Promise<void> {\n\t\tif (this.revocationStore) {\n\t\t\tawait this.revocationStore.revokeAllForDevice(deviceId)\n\t\t}\n\t}\n\n\t/**\n\t * Refresh an access token using a valid refresh token.\n\t *\n\t * Implements **refresh token rotation with reuse detection**: a new refresh token\n\t * is issued alongside the new access token. The old refresh token's `jti` is\n\t * recorded in the revocation store (if configured). If a previously consumed\n\t * refresh token is presented again, it indicates potential token theft.\n\t *\n\t * Returns null if the provided token is invalid, expired, or not a refresh token.\n\t *\n\t * @param refreshToken - The refresh token JWT string\n\t * @returns A new access/refresh token pair, or null if the refresh token is invalid\n\t */\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t): Promise<{ accessToken: string; refreshToken: string } | null> {\n\t\tconst payload = this.validateToken(refreshToken)\n\n\t\tif (payload === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Only refresh tokens can be used for token refresh.\n\t\t// Accepting access tokens or device credentials here would be a security hole.\n\t\tif (payload.type !== 'refresh') {\n\t\t\treturn null\n\t\t}\n\n\t\t// Check revocation store for replay detection\n\t\tif (this.revocationStore) {\n\t\t\tconst wasRevoked = await this.revocationStore.isRevoked(payload.jti)\n\t\t\tif (wasRevoked) {\n\t\t\t\t// This refresh token was already consumed. This is a potential token theft.\n\t\t\t\t// Revoke all tokens for this device as a safety measure.\n\t\t\t\tawait this.revocationStore.revokeAllForDevice(payload.dev)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Mark this refresh token as consumed (revoked)\n\t\t\tawait this.revocationStore.revoke(payload.jti, payload.exp)\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: this.issueAccessToken(payload.sub, payload.dev),\n\t\t\trefreshToken: this.issueRefreshToken(payload.sub, payload.dev),\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// User types\n// ============================================================================\n\n/**\n * Authenticated user record.\n * Represents the public-facing user identity returned by sign-in and sign-up flows.\n */\nexport interface AuthUser {\n\t/** Unique user identifier (UUID v7) */\n\tid: string\n\t/** User's email address, used as the primary login credential */\n\temail: string\n\t/** Display name */\n\tname: string\n\t/** Timestamp of user creation (milliseconds since epoch) */\n\tcreatedAt: number\n\t/** Timestamp of last profile update (milliseconds since epoch) */\n\tupdatedAt: number\n}\n\n/**\n * A registered device that can operate on behalf of a user.\n * Each device has its own keypair for offline credential verification.\n */\nexport interface AuthDevice {\n\t/** Device identifier, same as the Kora nodeId for this device */\n\tid: string\n\t/** The user this device belongs to */\n\tuserId: string\n\t/** JWK-encoded public key for this device's keypair */\n\tpublicKey: string\n\t/** Human-readable device name (e.g., \"Chrome on MacBook\") */\n\tname: string\n\t/** Timestamp when the device was first registered (milliseconds since epoch) */\n\tregisteredAt: number\n\t/** Timestamp of last activity from this device (milliseconds since epoch) */\n\tlastSeenAt: number\n\t/** Whether this device is allowed to sync */\n\tstatus: DeviceStatus\n}\n\n/** Device status values */\nexport type DeviceStatus = 'active' | 'revoked'\n\n// ============================================================================\n// Token types\n// ============================================================================\n\n/** The three kinds of tokens issued by the auth system */\nexport type TokenType = 'access' | 'refresh' | 'device_credential'\n\n/**\n * Base payload present in all JWT tokens.\n * Fields follow standard JWT claim names.\n */\nexport interface TokenPayload {\n\t/** JWT ID: unique identifier for this specific token (for revocation and replay detection) */\n\tjti: string\n\t/** Subject: the user ID */\n\tsub: string\n\t/** Device ID that this token was issued to */\n\tdev: string\n\t/** Which kind of token this is */\n\ttype: TokenType\n\t/** Issued-at time (seconds since epoch, per JWT spec) */\n\tiat: number\n\t/** Expiration time (seconds since epoch, per JWT spec) */\n\texp: number\n}\n\n/**\n * Short-lived token used to authenticate API requests.\n * Typically lives for 15 minutes.\n */\nexport interface AccessTokenPayload extends TokenPayload {\n\ttype: 'access'\n}\n\n/**\n * Longer-lived token used to obtain new access tokens.\n * Typically lives for 90 days.\n */\nexport interface RefreshTokenPayload extends TokenPayload {\n\ttype: 'refresh'\n}\n\n/**\n * Offline credential stored on the device.\n * Allows the device to continue operating offline, with a mandatory\n * check-in deadline by which it must reconnect to remain authorized.\n */\nexport interface DeviceCredentialPayload extends TokenPayload {\n\ttype: 'device_credential'\n\t/** Device public key thumbprint, binds this credential to a specific device keypair */\n\tdpk: string\n\t/** Timestamp (seconds since epoch) by which the device must check in with the server */\n\tmustCheckinBy: number\n}\n\n/**\n * Bundle of tokens returned after successful authentication.\n */\nexport interface AuthTokens {\n\t/** Short-lived access token */\n\taccessToken: string\n\t/** Long-lived refresh token */\n\trefreshToken: string\n\t/** Optional device credential for offline operation */\n\tdeviceCredential?: string\n}\n\n// ============================================================================\n// Auth configuration\n// ============================================================================\n\n/** Supported auth provider types */\nexport type AuthProviderType = 'built-in' | 'custom'\n\n/** Unlock mechanism for encrypted local storage */\nexport type UnlockMethod = 'biometric' | 'passphrase' | 'both'\n\n/**\n * Encryption settings for locally stored auth credentials.\n */\nexport interface AuthEncryptionConfig {\n\t/** Whether local credential encryption is enabled */\n\tenabled: boolean\n\t/** How the user unlocks encrypted credentials */\n\tunlock: UnlockMethod\n\t/** Milliseconds of inactivity before the app auto-locks. Defaults to 15 minutes. */\n\tautoLockTimeout?: number\n}\n\n/**\n * Custom lifetimes for each token type.\n * All values are in milliseconds.\n */\nexport interface TokenLifetimeConfig {\n\t/** Access token lifetime in ms. Default: 15 minutes. */\n\taccess?: number\n\t/** Refresh token lifetime in ms. Default: 90 days. */\n\trefresh?: number\n\t/** Device credential lifetime in ms. Default: 90 days. */\n\tdeviceCredential?: number\n}\n\n/**\n * Configuration for the Kora auth system.\n * Passed to the auth initializer to control provider, encryption, and token behavior.\n */\nexport interface AuthConfig {\n\t/** Which auth provider to use */\n\tprovider: AuthProviderType\n\t/** Local encryption settings for stored credentials */\n\tencryption?: AuthEncryptionConfig\n\t/** Maximum time a device can operate offline without checking in (ms). Default: 30 days. */\n\tmaxOfflineDuration?: number\n\t/** Custom token lifetimes */\n\ttokenLifetimes?: TokenLifetimeConfig\n}\n\n// ============================================================================\n// Auth state\n// ============================================================================\n\n/** Possible authentication states */\nexport type AuthStatus = 'authenticated' | 'unauthenticated' | 'locked'\n\n/**\n * Current state of the auth system.\n * This is the value exposed to the UI layer for rendering auth-dependent views.\n */\nexport interface AuthState {\n\t/** Current authentication status */\n\tstatus: AuthStatus\n\t/** The authenticated user, or null if unauthenticated/locked */\n\tuser: AuthUser | null\n\t/** The current device's ID, or null if not yet registered */\n\tdeviceId: string | null\n}\n\n// ============================================================================\n// Auth events\n// ============================================================================\n\n/**\n * Events emitted by the auth system.\n * These integrate with Kora's event system for DevTools observability.\n */\nexport type AuthEvent =\n\t| { type: 'auth:signed-in'; user: AuthUser }\n\t| { type: 'auth:signed-out' }\n\t| { type: 'auth:locked' }\n\t| { type: 'auth:unlocked'; user: AuthUser }\n\t| { type: 'auth:token-refreshed' }\n\t| { type: 'auth:device-revoked'; deviceId: string }\n\t| { type: 'auth:permission-changed' }\n\n/** Extract the event type string union from AuthEvent */\nexport type AuthEventType = AuthEvent['type']\n\n/** Extract a specific auth event by its type */\nexport type AuthEventByType<T extends AuthEventType> = Extract<AuthEvent, { type: T }>\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Base error class for authentication-related failures.\n * Extends KoraError to integrate with the framework's error handling patterns.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n/**\n * Thrown when sign-in credentials are invalid (wrong email or password).\n */\nexport class InvalidCredentialsError extends AuthError {\n\tconstructor() {\n\t\tsuper('Invalid email or password.', 'AUTH_INVALID_CREDENTIALS')\n\t\tthis.name = 'InvalidCredentialsError'\n\t}\n}\n\n/**\n * Thrown when a user tries to sign up with an email that already exists.\n */\nexport class EmailAlreadyExistsError extends AuthError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'An account with this email already exists.',\n\t\t\t'AUTH_EMAIL_EXISTS',\n\t\t)\n\t\tthis.name = 'EmailAlreadyExistsError'\n\t}\n}\n\n/**\n * Thrown when a token is expired, malformed, or has an invalid signature.\n */\nexport class TokenError extends AuthError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'AUTH_TOKEN_ERROR', context)\n\t\tthis.name = 'TokenError'\n\t}\n}\n\n/**\n * Thrown when a device's credential has expired or the device has been revoked.\n */\nexport class DeviceRevokedError extends AuthError {\n\tconstructor(deviceId: string) {\n\t\tsuper(\n\t\t\t`Device \"${deviceId}\" has been revoked and can no longer sync.`,\n\t\t\t'AUTH_DEVICE_REVOKED',\n\t\t\t{ deviceId },\n\t\t)\n\t\tthis.name = 'DeviceRevokedError'\n\t}\n}\n\n/**\n * Thrown when the maximum offline duration has been exceeded\n * and the device must reconnect to continue operating.\n */\nexport class OfflineExpiredError extends AuthError {\n\tconstructor(maxDuration: number, lastCheckin: number) {\n\t\tconst daysSinceCheckin = Math.round((Date.now() - lastCheckin) / (24 * 60 * 60 * 1000))\n\t\tsuper(\n\t\t\t`Device has been offline for ${daysSinceCheckin} days, exceeding the maximum offline duration. Reconnect to re-authenticate.`,\n\t\t\t'AUTH_OFFLINE_EXPIRED',\n\t\t\t{ maxDuration, lastCheckin, daysSinceCheckin },\n\t\t)\n\t\tthis.name = 'OfflineExpiredError'\n\t}\n}\n\n// ============================================================================\n// Sign up / sign in params\n// ============================================================================\n\n/**\n * Parameters for creating a new user account.\n */\nexport interface SignUpParams {\n\t/** Email address (used as login credential) */\n\temail: string\n\t/** Password (will be hashed before storage) */\n\tpassword: string\n\t/** Optional display name. Defaults to the local part of the email if omitted. */\n\tname?: string\n}\n\n/**\n * Parameters for signing in to an existing account.\n */\nexport interface SignInParams {\n\t/** Email address */\n\temail: string\n\t/** Password */\n\tpassword: string\n}\n\n// ============================================================================\n// Server-side types for the built-in provider\n// ============================================================================\n\n/**\n * Parameters for creating a user record in the server-side store.\n * The password has already been hashed by the time this is used.\n */\nexport interface CreateUserParams {\n\t/** Email address */\n\temail: string\n\t/** Argon2id or bcrypt hash of the password */\n\tpasswordHash: string\n\t/** Random salt used during hashing */\n\tsalt: string\n\t/** Display name */\n\tname: string\n}\n\n/**\n * Full user record as stored on the server, including sensitive credential fields.\n * This type must NEVER be returned to the client; strip passwordHash and salt first.\n */\nexport interface StoredUser extends AuthUser {\n\t/** Hashed password */\n\tpasswordHash: string\n\t/** Salt used for hashing */\n\tsalt: string\n}\n\n// ============================================================================\n// Auth provider adapter interface\n// ============================================================================\n\n/**\n * Adapter interface for pluggable auth providers.\n * Implement this to integrate Kora auth with a custom identity provider\n * (e.g., Firebase Auth, Auth0, Supabase Auth).\n *\n * The built-in provider implements this interface internally.\n */\nexport interface AuthProviderAdapter {\n\t/** Register a new user and return the user with initial tokens */\n\tsignUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\t/** Authenticate an existing user and return the user with tokens */\n\tsignIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\t/** Exchange a refresh token for a new token set */\n\trefreshToken(refreshToken: string): Promise<AuthTokens>\n\t/** Validate an access token and return its payload, or null if invalid */\n\tvalidateAccessToken(token: string): Promise<TokenPayload | null>\n\t/** Revoke a device, preventing it from syncing */\n\trevokeDevice(deviceId: string): Promise<void>\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default access token lifetime: 15 minutes */\nexport const DEFAULT_ACCESS_TOKEN_LIFETIME = 15 * 60 * 1000\n\n/** Default refresh token lifetime: 90 days */\nexport const DEFAULT_REFRESH_TOKEN_LIFETIME = 90 * 24 * 60 * 60 * 1000\n\n/** Default device credential lifetime: 90 days */\nexport const DEFAULT_DEVICE_CREDENTIAL_LIFETIME = 90 * 24 * 60 * 60 * 1000\n\n/** Default maximum offline duration: 30 days */\nexport const DEFAULT_MAX_OFFLINE_DURATION = 30 * 24 * 60 * 60 * 1000\n\n/** Default auto-lock timeout: 15 minutes */\nexport const DEFAULT_AUTO_LOCK_TIMEOUT = 15 * 60 * 1000\n","import { createHmac } from 'node:crypto'\n\n// ============================================================================\n// Base64url helpers\n// ============================================================================\n\n/**\n * Encodes a UTF-8 string to base64url format (RFC 7515).\n * Base64url uses the URL-safe alphabet (- instead of +, _ instead of /)\n * and strips trailing padding characters.\n *\n * @param input - The UTF-8 string to encode\n * @returns Base64url-encoded string\n */\nexport function base64urlEncode(input: string): string {\n\treturn Buffer.from(input, 'utf-8').toString('base64url')\n}\n\n/**\n * Decodes a base64url-encoded string back to UTF-8.\n *\n * @param input - The base64url-encoded string to decode\n * @returns Decoded UTF-8 string\n */\nexport function base64urlDecode(input: string): string {\n\treturn Buffer.from(input, 'base64url').toString('utf-8')\n}\n\n// ============================================================================\n// Internal signing\n// ============================================================================\n\n/**\n * Computes an HMAC-SHA256 signature and returns it as a base64url string.\n * Uses Node.js crypto module for synchronous signing.\n */\nfunction hmacSha256Base64url(data: string, secret: string): string {\n\treturn createHmac('sha256', secret).update(data).digest('base64url')\n}\n\n// ============================================================================\n// JWT operations\n// ============================================================================\n\n/** Pre-encoded JWT header. Only HS256 is supported; the header never changes. */\nconst ENCODED_HEADER = base64urlEncode(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))\n\n/**\n * Creates a signed JWT (HS256) from a payload and secret.\n *\n * The token is structured as `header.payload.signature` per RFC 7519.\n * Only HMAC-SHA256 is supported. The header is always `{\"alg\":\"HS256\",\"typ\":\"JWT\"}`.\n *\n * @param payload - The claims to include in the token. Must be JSON-serializable.\n * @param secret - The HMAC-SHA256 secret key used for signing.\n * @returns A signed JWT string in the format `header.payload.signature`\n *\n * @example\n * ```typescript\n * const token = encodeJwt(\n * { sub: 'user-123', exp: Math.floor(Date.now() / 1000) + 900 },\n * 'my-secret'\n * )\n * ```\n */\nexport function encodeJwt(payload: Record<string, unknown>, secret: string): string {\n\tconst encodedPayload = base64urlEncode(JSON.stringify(payload))\n\tconst signingInput = `${ENCODED_HEADER}.${encodedPayload}`\n\tconst signature = hmacSha256Base64url(signingInput, secret)\n\treturn `${signingInput}.${signature}`\n}\n\n/**\n * Decodes a JWT without verifying its signature.\n *\n * Use this only when you need to inspect claims (e.g., reading `exp` to decide\n * whether to attempt a refresh) and do NOT need to trust the token's authenticity.\n * For trusted reads, use {@link verifyJwt} instead.\n *\n * @param token - The JWT string to decode\n * @returns The decoded payload as a record, or null if the token is malformed\n *\n * @example\n * ```typescript\n * const claims = decodeJwt(token)\n * if (claims && typeof claims.sub === 'string') {\n * console.log('User:', claims.sub)\n * }\n * ```\n */\nexport function decodeJwt(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\tconst payloadSegment = parts[1]\n\tif (payloadSegment === undefined) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\tconst decoded = base64urlDecode(payloadSegment)\n\t\tconst parsed: unknown = JSON.parse(decoded)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Decodes a JWT and verifies its HMAC-SHA256 signature.\n *\n * Returns the payload only if the signature is valid. Returns null if the token\n * is malformed, has an invalid signature, or cannot be parsed.\n *\n * This function does NOT check expiration. Use {@link isExpired} separately\n * to check the `exp` claim, allowing callers to distinguish between\n * \"invalid signature\" (security issue) and \"expired\" (normal lifecycle).\n *\n * @param token - The JWT string to verify\n * @param secret - The HMAC-SHA256 secret key that was used to sign the token\n * @returns The decoded payload if the signature is valid, or null otherwise\n *\n * @example\n * ```typescript\n * const claims = verifyJwt(token, 'my-secret')\n * if (claims === null) {\n * throw new Error('Invalid token signature')\n * }\n * if (isExpired(claims)) {\n * throw new Error('Token has expired')\n * }\n * ```\n */\nexport function verifyJwt(token: string, secret: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\tconst headerSegment = parts[0]\n\tconst payloadSegment = parts[1]\n\tconst signatureSegment = parts[2]\n\n\tif (\n\t\theaderSegment === undefined\n\t\t|| payloadSegment === undefined\n\t\t|| signatureSegment === undefined\n\t) {\n\t\treturn null\n\t}\n\n\t// Validate the header to prevent algorithm confusion attacks.\n\t// Only HS256 is supported; reject any token claiming a different algorithm.\n\tif (headerSegment !== ENCODED_HEADER) {\n\t\treturn null\n\t}\n\n\t// Recompute signature and compare\n\tconst signingInput = `${headerSegment}.${payloadSegment}`\n\tconst expectedSignature = hmacSha256Base64url(signingInput, secret)\n\n\t// Constant-time comparison to prevent timing attacks.\n\t// Both strings are base64url-encoded HMAC outputs, so they are ASCII-safe\n\t// and we can compare byte-by-byte without early exit.\n\tif (expectedSignature.length !== signatureSegment.length) {\n\t\treturn null\n\t}\n\tlet mismatch = 0\n\tfor (let i = 0; i < expectedSignature.length; i++) {\n\t\t// Bitwise OR accumulates differences without short-circuiting\n\t\tmismatch |= expectedSignature.charCodeAt(i) ^ signatureSegment.charCodeAt(i)\n\t}\n\tif (mismatch !== 0) {\n\t\treturn null\n\t}\n\n\t// Decode the payload\n\ttry {\n\t\tconst decoded = base64urlDecode(payloadSegment)\n\t\tconst parsed: unknown = JSON.parse(decoded)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Clock skew tolerance in seconds. Allows for minor clock differences\n * between servers in multi-server deployments.\n */\nconst CLOCK_SKEW_TOLERANCE_SECONDS = 5\n\n/**\n * Checks whether a token payload has expired based on its `exp` claim.\n *\n * The `exp` claim is expected to be in seconds since the Unix epoch (per JWT spec).\n * Includes a small clock skew tolerance (5 seconds) to handle minor time\n * differences between servers. If the `exp` claim is missing or is not a number,\n * the token is considered non-expiring and this function returns false.\n *\n * @param payload - An object with an optional `exp` field (seconds since epoch)\n * @returns true if the token has expired, false otherwise\n *\n * @example\n * ```typescript\n * const claims = verifyJwt(token, secret)\n * if (claims && isExpired(claims)) {\n * // Token signature is valid but it has expired -- attempt refresh\n * }\n * ```\n */\nexport function isExpired(payload: { exp?: number }): boolean {\n\tif (typeof payload.exp !== 'number') {\n\t\treturn false\n\t}\n\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\treturn nowSeconds >= payload.exp + CLOCK_SKEW_TOLERANCE_SECONDS\n}\n","import { KoraError } from '@korajs/core'\nimport { randomUUID } from 'node:crypto'\n\n/**\n * A user as visible to the application layer.\n * Does not include sensitive fields like password hash or salt.\n */\nexport interface AuthUser {\n\t/** Unique user identifier (UUID v7 or crypto.randomUUID) */\n\tid: string\n\t/** User's email address */\n\temail: string\n\t/** User's display name */\n\tname: string\n\t/** Whether the user's email has been verified */\n\temailVerified: boolean\n\t/** Timestamp when the user was created (milliseconds since epoch) */\n\tcreatedAt: number\n}\n\n/**\n * Internal user record that includes credentials.\n * Extends AuthUser with password hash and salt for verification.\n */\nexport interface StoredUser extends AuthUser {\n\t/** Hex-encoded PBKDF2 derived key */\n\tpasswordHash: string\n\t/** Hex-encoded random salt used during hashing */\n\tsalt: string\n}\n\n/**\n * A device registered to a user.\n */\nexport interface AuthDevice {\n\t/** Unique device identifier */\n\tid: string\n\t/** ID of the user who owns this device */\n\tuserId: string\n\t/** Base64url-encoded public key (or thumbprint) for the device */\n\tpublicKey: string\n\t/** Human-readable device name */\n\tname: string\n\t/** Whether the device has been revoked */\n\trevoked: boolean\n\t/** Timestamp when the device was first registered (milliseconds since epoch) */\n\tcreatedAt: number\n\t/** Timestamp when the device was last seen (milliseconds since epoch) */\n\tlastSeenAt: number\n}\n\n/**\n * Thrown when a user account already exists with the given email.\n */\nexport class DuplicateEmailError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'A user with this email already exists.',\n\t\t\t'DUPLICATE_EMAIL',\n\t\t)\n\t\tthis.name = 'DuplicateEmailError'\n\t}\n}\n\n/**\n * Generic interface for user and device persistence.\n *\n * Implement this interface to provide database-backed user storage for\n * the built-in auth provider. All methods are async to support both\n * synchronous (in-memory, SQLite) and asynchronous (PostgreSQL) backends.\n *\n * Built-in implementations:\n * - {@link InMemoryUserStore} — development and testing (no persistence)\n * - `SqliteUserStore` — SQLite via better-sqlite3 (from `@korajs/auth/server`)\n * - `PostgresUserStore` — PostgreSQL via postgres-js (from `@korajs/auth/server`)\n *\n * @example\n * ```typescript\n * import { BuiltInAuthRoutes, SqliteUserStore } from '@korajs/auth/server'\n *\n * const userStore = await createSqliteUserStore({ filename: './auth.db' })\n * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n * ```\n */\nexport interface UserStore {\n\t/** Create a new user account. Throws DuplicateEmailError if email exists. */\n\tcreateUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser>\n\n\t/** Find a user by email address (case-insensitive). */\n\tfindByEmail(email: string): Promise<StoredUser | null>\n\n\t/** Find a user by ID. */\n\tfindById(id: string): Promise<StoredUser | null>\n\n\t/** Register a device for a user. Idempotent if device already exists and is not revoked. */\n\tregisterDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice>\n\n\t/** Find a device by its ID. */\n\tfindDevice(deviceId: string): Promise<AuthDevice | null>\n\n\t/** List all devices registered for a user (includes revoked). */\n\tlistDevices(userId: string): Promise<AuthDevice[]>\n\n\t/** Soft-revoke a device. No-op if device does not exist. */\n\trevokeDevice(deviceId: string): Promise<void>\n\n\t/** Set a user's email verification status. */\n\tsetEmailVerified(userId: string, verified: boolean): Promise<void>\n\n\t/** Update a user's password hash and salt. */\n\tupdatePassword(userId: string, passwordHash: string, salt: string): Promise<void>\n\n\t/** List all users. For admin/development use. */\n\tlistAll(): Promise<StoredUser[]>\n\n\t/** Update a stored user record. */\n\tupdate(user: StoredUser): Promise<void>\n\n\t/** Delete a user and all associated devices. */\n\tdelete(userId: string): Promise<void>\n\n\t/** Update the last-seen timestamp for a device. No-op if device does not exist. */\n\ttouchDevice(deviceId: string): Promise<void>\n}\n\n/**\n * In-memory user and device store for the built-in auth provider.\n *\n * This is a simple implementation suitable for development and testing.\n * Production applications should use {@link SqliteUserStore} or\n * {@link PostgresUserStore} for persistent storage.\n *\n * @example\n * ```typescript\n * const store = new InMemoryUserStore()\n * const user = await store.createUser({\n * email: 'alice@example.com',\n * passwordHash: 'abc123...',\n * salt: 'def456...',\n * name: 'Alice',\n * })\n * ```\n */\nexport class InMemoryUserStore implements UserStore {\n\t/** Users indexed by ID */\n\tprivate readonly usersById = new Map<string, StoredUser>()\n\n\t/** Users indexed by email (lowercase) for fast lookup */\n\tprivate readonly usersByEmail = new Map<string, StoredUser>()\n\n\t/** Devices indexed by device ID */\n\tprivate readonly devicesById = new Map<string, AuthDevice>()\n\n\t/** Device IDs indexed by user ID for fast listing */\n\tprivate readonly devicesByUserId = new Map<string, Set<string>>()\n\n\t/**\n\t * Create a new user account.\n\t *\n\t * @param params - User creation parameters\n\t * @param params.email - The user's email address (must be unique, case-insensitive)\n\t * @param params.passwordHash - Hex-encoded PBKDF2 derived key\n\t * @param params.salt - Hex-encoded salt used during hashing\n\t * @param params.name - The user's display name\n\t * @returns The created user (without sensitive credential fields)\n\t * @throws {DuplicateEmailError} If a user with the same email already exists\n\t */\n\tasync createUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser> {\n\t\tconst normalizedEmail = params.email.toLowerCase()\n\n\t\tif (this.usersByEmail.has(normalizedEmail)) {\n\t\t\tthrow new DuplicateEmailError()\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst id = randomUUID()\n\n\t\tconst storedUser: StoredUser = {\n\t\t\tid,\n\t\t\temail: normalizedEmail,\n\t\t\tname: params.name,\n\t\t\temailVerified: false,\n\t\t\tcreatedAt: now,\n\t\t\tpasswordHash: params.passwordHash,\n\t\t\tsalt: params.salt,\n\t\t}\n\n\t\tthis.usersById.set(id, storedUser)\n\t\tthis.usersByEmail.set(normalizedEmail, storedUser)\n\n\t\treturn toAuthUser(storedUser)\n\t}\n\n\t/**\n\t * Find a user by email address.\n\t *\n\t * @param email - The email to search for (case-insensitive)\n\t * @returns The stored user record including credentials, or null if not found\n\t */\n\tasync findByEmail(email: string): Promise<StoredUser | null> {\n\t\treturn this.usersByEmail.get(email.toLowerCase()) ?? null\n\t}\n\n\t/**\n\t * Find a user by ID.\n\t *\n\t * @param id - The user ID to search for\n\t * @returns The stored user record including credentials, or null if not found\n\t */\n\tasync findById(id: string): Promise<StoredUser | null> {\n\t\treturn this.usersById.get(id) ?? null\n\t}\n\n\t/**\n\t * Register a device for a user.\n\t *\n\t * If a device with the same ID already exists and is not revoked, it is\n\t * returned as-is (idempotent registration). If it was previously revoked,\n\t * it is re-activated with updated details.\n\t *\n\t * @param params - Device registration parameters\n\t * @param params.id - Unique device identifier\n\t * @param params.userId - ID of the user who owns the device\n\t * @param params.publicKey - Base64url-encoded device public key or thumbprint\n\t * @param params.name - Human-readable device name\n\t * @returns The registered device record\n\t */\n\tasync registerDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice> {\n\t\tconst existing = this.devicesById.get(params.id)\n\t\tif (existing !== undefined && !existing.revoked) {\n\t\t\treturn existing\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst device: AuthDevice = {\n\t\t\tid: params.id,\n\t\t\tuserId: params.userId,\n\t\t\tpublicKey: params.publicKey,\n\t\t\tname: params.name,\n\t\t\trevoked: false,\n\t\t\tcreatedAt: now,\n\t\t\tlastSeenAt: now,\n\t\t}\n\n\t\tthis.devicesById.set(params.id, device)\n\n\t\tlet userDevices = this.devicesByUserId.get(params.userId)\n\t\tif (userDevices === undefined) {\n\t\t\tuserDevices = new Set()\n\t\t\tthis.devicesByUserId.set(params.userId, userDevices)\n\t\t}\n\t\tuserDevices.add(params.id)\n\n\t\treturn device\n\t}\n\n\t/**\n\t * Find a device by its ID.\n\t *\n\t * @param deviceId - The device ID to search for\n\t * @returns The device record, or null if not found\n\t */\n\tasync findDevice(deviceId: string): Promise<AuthDevice | null> {\n\t\treturn this.devicesById.get(deviceId) ?? null\n\t}\n\n\t/**\n\t * List all devices registered for a user.\n\t *\n\t * @param userId - The user ID whose devices to list\n\t * @returns Array of device records (includes revoked devices)\n\t */\n\tasync listDevices(userId: string): Promise<AuthDevice[]> {\n\t\tconst deviceIds = this.devicesByUserId.get(userId)\n\t\tif (deviceIds === undefined) {\n\t\t\treturn []\n\t\t}\n\n\t\tconst devices: AuthDevice[] = []\n\t\tfor (const deviceId of deviceIds) {\n\t\t\tconst device = this.devicesById.get(deviceId)\n\t\t\tif (device !== undefined) {\n\t\t\t\tdevices.push(device)\n\t\t\t}\n\t\t}\n\n\t\treturn devices\n\t}\n\n\t/**\n\t * Revoke a device, preventing it from being used for authentication.\n\t *\n\t * This is a soft revoke — the device record remains but is marked as revoked.\n\t * If the device does not exist, this is a no-op.\n\t *\n\t * @param deviceId - The ID of the device to revoke\n\t */\n\tasync revokeDevice(deviceId: string): Promise<void> {\n\t\tconst device = this.devicesById.get(deviceId)\n\t\tif (device !== undefined) {\n\t\t\tdevice.revoked = true\n\t\t}\n\t}\n\n\t/**\n\t * Set a user's email verification status.\n\t *\n\t * @param userId - The user whose email to verify\n\t * @param verified - Whether the email is verified\n\t */\n\tasync setEmailVerified(userId: string, verified: boolean): Promise<void> {\n\t\tconst user = this.usersById.get(userId)\n\t\tif (!user) return\n\n\t\tconst updated: StoredUser = { ...user, emailVerified: verified }\n\t\tthis.usersById.set(userId, updated)\n\t\tthis.usersByEmail.set(user.email, updated)\n\t}\n\n\t/**\n\t * Update a user's password hash and salt.\n\t *\n\t * @param userId - The user whose password to update\n\t * @param passwordHash - New hex-encoded PBKDF2 derived key\n\t * @param salt - New hex-encoded salt\n\t */\n\tasync updatePassword(userId: string, passwordHash: string, salt: string): Promise<void> {\n\t\tconst user = this.usersById.get(userId)\n\t\tif (!user) return\n\n\t\tconst updated: StoredUser = { ...user, passwordHash, salt }\n\t\tthis.usersById.set(userId, updated)\n\t\tthis.usersByEmail.set(user.email, updated)\n\t}\n\n\t/**\n\t * List all users. For admin/development use.\n\t */\n\tasync listAll(): Promise<StoredUser[]> {\n\t\treturn [...this.usersById.values()]\n\t}\n\n\t/**\n\t * Update a stored user record.\n\t */\n\tasync update(user: StoredUser): Promise<void> {\n\t\tconst existing = this.usersById.get(user.id)\n\t\tif (!existing) return\n\n\t\t// If email changed, update the email index\n\t\tif (existing.email !== user.email) {\n\t\t\tthis.usersByEmail.delete(existing.email)\n\t\t\tthis.usersByEmail.set(user.email, user)\n\t\t} else {\n\t\t\tthis.usersByEmail.set(user.email, user)\n\t\t}\n\t\tthis.usersById.set(user.id, user)\n\t}\n\n\t/**\n\t * Delete a user and all associated devices.\n\t */\n\tasync delete(userId: string): Promise<void> {\n\t\tconst user = this.usersById.get(userId)\n\t\tif (!user) return\n\n\t\tthis.usersById.delete(userId)\n\t\tthis.usersByEmail.delete(user.email)\n\n\t\t// Clean up devices\n\t\tconst deviceIds = this.devicesByUserId.get(userId)\n\t\tif (deviceIds) {\n\t\t\tfor (const deviceId of deviceIds) {\n\t\t\t\tthis.devicesById.delete(deviceId)\n\t\t\t}\n\t\t\tthis.devicesByUserId.delete(userId)\n\t\t}\n\t}\n\n\t/**\n\t * Update the last-seen timestamp for a device.\n\t *\n\t * Called when a device authenticates or syncs to track activity.\n\t * If the device does not exist, this is a no-op.\n\t *\n\t * @param deviceId - The ID of the device to update\n\t */\n\tasync touchDevice(deviceId: string): Promise<void> {\n\t\tconst device = this.devicesById.get(deviceId)\n\t\tif (device !== undefined) {\n\t\t\tdevice.lastSeenAt = Date.now()\n\t\t}\n\t}\n}\n\n/**\n * Strip sensitive fields from a StoredUser to produce an AuthUser.\n * Ensures password hash and salt are never leaked to the application layer.\n */\nfunction toAuthUser(stored: StoredUser): AuthUser {\n\treturn {\n\t\tid: stored.id,\n\t\temail: stored.email,\n\t\tname: stored.name,\n\t\temailVerified: stored.emailVerified,\n\t\tcreatedAt: stored.createdAt,\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport type { UserStore } from './user-store'\nimport { hashPassword } from './password-hash'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A pending password reset request.\n */\nexport interface PasswordResetToken {\n\t/** Cryptographically random single-use token */\n\ttoken: string\n\t/** User ID the token was generated for */\n\tuserId: string\n\t/** Email the reset was requested for */\n\temail: string\n\t/** When the token was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the token expires (ms since epoch) */\n\texpiresAt: number\n\t/** Whether the token has been consumed */\n\tconsumed: boolean\n}\n\n/**\n * Persistence interface for password reset tokens.\n */\nexport interface PasswordResetStore {\n\t/** Store a reset token. */\n\tstore(token: PasswordResetToken): Promise<void>\n\n\t/** Look up a token. Returns null if not found. */\n\tget(token: string): Promise<PasswordResetToken | null>\n\n\t/** Mark a token as consumed. */\n\tconsume(token: string): Promise<void>\n\n\t/** Count active (non-consumed, non-expired) tokens for an email. */\n\tcountActiveForEmail(email: string): Promise<number>\n\n\t/** Remove expired tokens. */\n\tcleanExpired(): Promise<number>\n}\n\n/**\n * Configuration for the password reset flow.\n */\nexport interface PasswordResetConfig {\n\t/** User store for looking up users and updating passwords */\n\tuserStore: UserStore\n\t/** Store for reset tokens. Defaults to InMemoryPasswordResetStore. */\n\tresetStore?: PasswordResetStore\n\t/** Token TTL in milliseconds. Defaults to 1 hour. */\n\ttokenTtlMs?: number\n\t/** Max reset requests per email in the TTL window. Defaults to 3. */\n\tmaxRequestsPerEmail?: number\n\t/**\n\t * Callback invoked when a reset is requested.\n\t * The developer must implement email sending.\n\t * If not provided, the token is returned in the route response (development mode).\n\t */\n\tonResetRequested?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class PasswordResetError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'PasswordResetError'\n\t}\n}\n\nexport class ResetTokenExpiredError extends PasswordResetError {\n\tconstructor() {\n\t\tsuper('Password reset token has expired.', 'RESET_TOKEN_EXPIRED')\n\t}\n}\n\nexport class ResetTokenNotFoundError extends PasswordResetError {\n\tconstructor() {\n\t\tsuper('Password reset token not found or already used.', 'RESET_TOKEN_NOT_FOUND')\n\t}\n}\n\nexport class ResetRateLimitedError extends PasswordResetError {\n\tconstructor() {\n\t\tsuper('Too many password reset requests. Please try again later.', 'RESET_RATE_LIMITED')\n\t}\n}\n\n// ============================================================================\n// InMemoryPasswordResetStore\n// ============================================================================\n\nexport class InMemoryPasswordResetStore implements PasswordResetStore {\n\tprivate tokens = new Map<string, PasswordResetToken>()\n\n\tasync store(token: PasswordResetToken): Promise<void> {\n\t\tthis.tokens.set(token.token, token)\n\t}\n\n\tasync get(token: string): Promise<PasswordResetToken | null> {\n\t\treturn this.tokens.get(token) ?? null\n\t}\n\n\tasync consume(token: string): Promise<void> {\n\t\tconst entry = this.tokens.get(token)\n\t\tif (entry) {\n\t\t\tthis.tokens.set(token, { ...entry, consumed: true })\n\t\t}\n\t}\n\n\tasync countActiveForEmail(email: string): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const token of this.tokens.values()) {\n\t\t\tif (token.email === email && !token.consumed && now < token.expiresAt) {\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [key, token] of this.tokens) {\n\t\t\tif (now > token.expiresAt) {\n\t\t\t\tthis.tokens.delete(key)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// PasswordResetManager\n// ============================================================================\n\n/** Default TTL: 1 hour */\nconst DEFAULT_TOKEN_TTL_MS = 60 * 60 * 1000\n\n/** Default max requests per email per TTL window */\nconst DEFAULT_MAX_REQUESTS = 3\n\n/** Minimum password length (same as auth-routes) */\nconst MIN_PASSWORD_LENGTH = 8\n\n/** Maximum password length (same as auth-routes) */\nconst MAX_PASSWORD_LENGTH = 128\n\n/**\n * Manages the password reset flow.\n *\n * @example\n * ```typescript\n * const resetManager = new PasswordResetManager({\n * userStore,\n * onResetRequested: async (email, token, expiresAt) => {\n * await sendEmail(email, `Reset link: https://app.com/reset?token=${token}`)\n * },\n * })\n *\n * // Request reset (always returns 200 to prevent email enumeration)\n * const response = await resetManager.requestReset('user@example.com')\n *\n * // Consume token and set new password\n * const response = await resetManager.resetPassword(token, 'newPassword123')\n * ```\n */\nexport class PasswordResetManager {\n\tprivate readonly userStore: UserStore\n\tprivate readonly resetStore: PasswordResetStore\n\tprivate readonly tokenTtlMs: number\n\tprivate readonly maxRequestsPerEmail: number\n\tprivate readonly onResetRequested?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n\n\tconstructor(config: PasswordResetConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.resetStore = config.resetStore ?? new InMemoryPasswordResetStore()\n\t\tthis.tokenTtlMs = config.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS\n\t\tthis.maxRequestsPerEmail = config.maxRequestsPerEmail ?? DEFAULT_MAX_REQUESTS\n\t\tthis.onResetRequested = config.onResetRequested\n\t}\n\n\t/**\n\t * Request a password reset for an email.\n\t * Always returns success to prevent email enumeration.\n\t *\n\t * If a callback is configured, invokes it with the token.\n\t * In development mode (no callback), returns the token in the response.\n\t */\n\tasync requestReset(email: string): Promise<{ status: number; body: { data: { message: string; token?: string } } | { error: string } }> {\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\n\t\t// Always return 200 to prevent email enumeration\n\t\tconst successResponse = {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { message: 'If an account with that email exists, a password reset link has been sent.' } } as { data: { message: string; token?: string } },\n\t\t}\n\n\t\t// Look up user\n\t\tconst user = await this.userStore.findByEmail(normalizedEmail)\n\t\tif (!user) {\n\t\t\treturn successResponse\n\t\t}\n\n\t\t// Rate limit\n\t\tconst activeCount = await this.resetStore.countActiveForEmail(normalizedEmail)\n\t\tif (activeCount >= this.maxRequestsPerEmail) {\n\t\t\treturn successResponse // Still 200 to prevent enumeration\n\t\t}\n\n\t\t// Generate token\n\t\tconst token = generateSecureToken()\n\t\tconst now = Date.now()\n\t\tconst resetToken: PasswordResetToken = {\n\t\t\ttoken,\n\t\t\tuserId: user.id,\n\t\t\temail: normalizedEmail,\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + this.tokenTtlMs,\n\t\t\tconsumed: false,\n\t\t}\n\n\t\tawait this.resetStore.store(resetToken)\n\n\t\t// Invoke callback\n\t\tif (this.onResetRequested) {\n\t\t\ttry {\n\t\t\t\tawait this.onResetRequested(normalizedEmail, token, resetToken.expiresAt)\n\t\t\t} catch {\n\t\t\t\t// Don't fail the request if callback errors\n\t\t\t}\n\t\t}\n\n\t\t// In development mode (no callback), return the token\n\t\tif (!this.onResetRequested) {\n\t\t\tsuccessResponse.body = { data: { message: 'Password reset token generated.', token } }\n\t\t}\n\n\t\treturn successResponse\n\t}\n\n\t/**\n\t * Consume a reset token and set a new password.\n\t */\n\tasync resetPassword(\n\t\ttoken: string,\n\t\tnewPassword: string,\n\t): Promise<{ status: number; body: { data: { message: string } } | { error: string } }> {\n\t\t// Validate password\n\t\tif (typeof newPassword !== 'string' || newPassword.length < MIN_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\t\tif (newPassword.length > MAX_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `Password must be at most ${MAX_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\n\t\tconst resetToken = await this.resetStore.get(token)\n\t\tif (!resetToken || resetToken.consumed) {\n\t\t\treturn { status: 404, body: { error: 'Password reset token not found or already used.' } }\n\t\t}\n\n\t\tif (Date.now() > resetToken.expiresAt) {\n\t\t\tawait this.resetStore.consume(token)\n\t\t\treturn { status: 410, body: { error: 'Password reset token has expired.' } }\n\t\t}\n\n\t\t// Consume token\n\t\tawait this.resetStore.consume(token)\n\n\t\t// Update password\n\t\tconst hashed = await hashPassword(newPassword)\n\t\tawait this.userStore.updatePassword(resetToken.userId, hashed.hash, hashed.salt)\n\n\t\treturn { status: 200, body: { data: { message: 'Password has been reset successfully.' } } }\n\t}\n\n\t/**\n\t * Change password for an authenticated user (requires current password verification).\n\t */\n\tasync changePassword(\n\t\tuserId: string,\n\t\tcurrentPassword: string,\n\t\tnewPassword: string,\n\t): Promise<{ status: number; body: { data: { message: string } } | { error: string } }> {\n\t\t// Validate new password\n\t\tif (typeof newPassword !== 'string' || newPassword.length < MIN_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `New password must be at least ${MIN_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\t\tif (newPassword.length > MAX_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `New password must be at most ${MAX_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\treturn { status: 404, body: { error: 'User not found.' } }\n\t\t}\n\n\t\t// Verify current password\n\t\tconst { verifyPassword } = await import('./password-hash')\n\t\tconst isValid = await verifyPassword(currentPassword, user.passwordHash, user.salt)\n\t\tif (!isValid) {\n\t\t\treturn { status: 401, body: { error: 'Current password is incorrect.' } }\n\t\t}\n\n\t\t// Update password\n\t\tconst hashed = await hashPassword(newPassword)\n\t\tawait this.userStore.updatePassword(userId, hashed.hash, hashed.salt)\n\n\t\treturn { status: 200, body: { data: { message: 'Password changed successfully.' } } }\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateSecureToken(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n","import { KoraError } from '@korajs/core'\nimport type { UserStore } from './user-store'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A pending email verification token.\n */\nexport interface EmailVerificationToken {\n\t/** Cryptographically random single-use token */\n\ttoken: string\n\t/** User ID the token was generated for */\n\tuserId: string\n\t/** Email being verified */\n\temail: string\n\t/** When the token was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the token expires (ms since epoch) */\n\texpiresAt: number\n\t/** Whether the token has been consumed */\n\tconsumed: boolean\n}\n\n/**\n * Persistence interface for email verification tokens.\n */\nexport interface EmailVerificationStore {\n\t/** Store a verification token. */\n\tstore(token: EmailVerificationToken): Promise<void>\n\n\t/** Look up a token. Returns null if not found. */\n\tget(token: string): Promise<EmailVerificationToken | null>\n\n\t/** Mark a token as consumed. */\n\tconsume(token: string): Promise<void>\n\n\t/** Count active (non-consumed, non-expired) tokens for a user. */\n\tcountActiveForUser(userId: string): Promise<number>\n\n\t/** Remove expired tokens. */\n\tcleanExpired(): Promise<number>\n}\n\n/**\n * Configuration for the email verification flow.\n */\nexport interface EmailVerificationConfig {\n\t/** User store for looking up users */\n\tuserStore: UserStore\n\t/** Store for verification tokens. Defaults to InMemoryEmailVerificationStore. */\n\tverificationStore?: EmailVerificationStore\n\t/** Token TTL in milliseconds. Defaults to 24 hours. */\n\ttokenTtlMs?: number\n\t/** Max verification requests per user per TTL window. Defaults to 3. */\n\tmaxRequestsPerUser?: number\n\t/**\n\t * Callback invoked when a verification email should be sent.\n\t * The developer must implement email sending.\n\t * If not provided, the token is returned in the route response (development mode).\n\t */\n\tonVerificationRequired?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class EmailVerificationError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'EmailVerificationError'\n\t}\n}\n\nexport class VerificationTokenExpiredError extends EmailVerificationError {\n\tconstructor() {\n\t\tsuper('Email verification token has expired.', 'VERIFICATION_TOKEN_EXPIRED')\n\t}\n}\n\nexport class VerificationTokenNotFoundError extends EmailVerificationError {\n\tconstructor() {\n\t\tsuper('Email verification token not found or already used.', 'VERIFICATION_TOKEN_NOT_FOUND')\n\t}\n}\n\n// ============================================================================\n// InMemoryEmailVerificationStore\n// ============================================================================\n\nexport class InMemoryEmailVerificationStore implements EmailVerificationStore {\n\tprivate tokens = new Map<string, EmailVerificationToken>()\n\n\tasync store(token: EmailVerificationToken): Promise<void> {\n\t\tthis.tokens.set(token.token, token)\n\t}\n\n\tasync get(token: string): Promise<EmailVerificationToken | null> {\n\t\treturn this.tokens.get(token) ?? null\n\t}\n\n\tasync consume(token: string): Promise<void> {\n\t\tconst entry = this.tokens.get(token)\n\t\tif (entry) {\n\t\t\tthis.tokens.set(token, { ...entry, consumed: true })\n\t\t}\n\t}\n\n\tasync countActiveForUser(userId: string): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const token of this.tokens.values()) {\n\t\t\tif (token.userId === userId && !token.consumed && now < token.expiresAt) {\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [key, token] of this.tokens) {\n\t\t\tif (now > token.expiresAt) {\n\t\t\t\tthis.tokens.delete(key)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// EmailVerificationManager\n// ============================================================================\n\n/** Default TTL: 24 hours */\nconst DEFAULT_TOKEN_TTL_MS = 24 * 60 * 60 * 1000\n\n/** Default max requests per user per TTL window */\nconst DEFAULT_MAX_REQUESTS = 3\n\n/**\n * Manages the email verification flow.\n *\n * @example\n * ```typescript\n * const verifier = new EmailVerificationManager({\n * userStore,\n * onVerificationRequired: async (email, token, expiresAt) => {\n * await sendEmail(email, `Verify: https://app.com/verify?token=${token}`)\n * },\n * })\n *\n * // Send verification email\n * await verifier.sendVerification('user-1', 'user@example.com')\n *\n * // Verify email\n * await verifier.verifyEmail(token)\n * ```\n */\nexport class EmailVerificationManager {\n\tprivate readonly userStore: UserStore\n\tprivate readonly verificationStore: EmailVerificationStore\n\tprivate readonly tokenTtlMs: number\n\tprivate readonly maxRequestsPerUser: number\n\tprivate readonly onVerificationRequired?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n\n\tconstructor(config: EmailVerificationConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.verificationStore = config.verificationStore ?? new InMemoryEmailVerificationStore()\n\t\tthis.tokenTtlMs = config.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS\n\t\tthis.maxRequestsPerUser = config.maxRequestsPerUser ?? DEFAULT_MAX_REQUESTS\n\t\tthis.onVerificationRequired = config.onVerificationRequired\n\t}\n\n\t/**\n\t * Send a verification email for a user.\n\t * Rate-limited to prevent abuse.\n\t */\n\tasync sendVerification(\n\t\tuserId: string,\n\t\temail: string,\n\t): Promise<{ status: number; body: { data: { message: string; token?: string } } | { error: string } }> {\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\n\t\t// Rate limit\n\t\tconst activeCount = await this.verificationStore.countActiveForUser(userId)\n\t\tif (activeCount >= this.maxRequestsPerUser) {\n\t\t\treturn { status: 429, body: { error: 'Too many verification requests. Please try again later.' } }\n\t\t}\n\n\t\t// Generate token\n\t\tconst token = generateSecureToken()\n\t\tconst now = Date.now()\n\t\tconst verificationToken: EmailVerificationToken = {\n\t\t\ttoken,\n\t\t\tuserId,\n\t\t\temail: normalizedEmail,\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + this.tokenTtlMs,\n\t\t\tconsumed: false,\n\t\t}\n\n\t\tawait this.verificationStore.store(verificationToken)\n\n\t\t// Invoke callback\n\t\tif (this.onVerificationRequired) {\n\t\t\ttry {\n\t\t\t\tawait this.onVerificationRequired(normalizedEmail, token, verificationToken.expiresAt)\n\t\t\t} catch {\n\t\t\t\t// Don't fail if callback errors\n\t\t\t}\n\t\t}\n\n\t\t// In development mode (no callback), return the token\n\t\tconst responseData: { message: string; token?: string } = {\n\t\t\tmessage: 'Verification email sent.',\n\t\t}\n\t\tif (!this.onVerificationRequired) {\n\t\t\tresponseData.token = token\n\t\t}\n\n\t\treturn { status: 200, body: { data: responseData } }\n\t}\n\n\t/**\n\t * Verify an email using a verification token.\n\t */\n\tasync verifyEmail(\n\t\ttoken: string,\n\t): Promise<{ status: number; body: { data: { message: string; userId: string; email: string } } | { error: string } }> {\n\t\tconst verificationToken = await this.verificationStore.get(token)\n\t\tif (!verificationToken || verificationToken.consumed) {\n\t\t\treturn { status: 404, body: { error: 'Verification token not found or already used.' } }\n\t\t}\n\n\t\tif (Date.now() > verificationToken.expiresAt) {\n\t\t\tawait this.verificationStore.consume(token)\n\t\t\treturn { status: 410, body: { error: 'Verification token has expired.' } }\n\t\t}\n\n\t\t// Consume token\n\t\tawait this.verificationStore.consume(token)\n\n\t\t// Mark user's email as verified\n\t\tawait this.userStore.setEmailVerified(verificationToken.userId, true)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\tdata: {\n\t\t\t\t\tmessage: 'Email verified successfully.',\n\t\t\t\t\tuserId: verificationToken.userId,\n\t\t\t\t\temail: verificationToken.email,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Resend verification email for a user.\n\t * Delegates to sendVerification with rate limiting.\n\t */\n\tasync resendVerification(\n\t\tuserId: string,\n\t): Promise<{ status: number; body: { data: { message: string; token?: string } } | { error: string } }> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\treturn { status: 404, body: { error: 'User not found.' } }\n\t\t}\n\n\t\treturn this.sendVerification(userId, user.email)\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateSecureToken(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n","import { randomUUID } from 'node:crypto'\nimport type { UserStore, AuthUser, StoredUser, AuthDevice } from './user-store'\nimport { DuplicateEmailError } from './user-store'\n\n/**\n * Row shape returned by better-sqlite3 for the auth_users table.\n */\ninterface UserRow {\n\tid: string\n\temail: string\n\tname: string\n\temail_verified: number\n\tcreated_at: number\n\tpassword_hash: string\n\tsalt: string\n}\n\n/**\n * Row shape returned by better-sqlite3 for the auth_devices table.\n */\ninterface DeviceRow {\n\tid: string\n\tuser_id: string\n\tpublic_key: string\n\tname: string\n\trevoked: number\n\tcreated_at: number\n\tlast_seen_at: number\n}\n\n/**\n * Minimal better-sqlite3 subset to avoid a hard dependency on the package.\n * The real Database instance satisfies this at runtime.\n */\ninterface SqliteDatabase {\n\tpragma(source: string): unknown\n\texec(source: string): void\n\tprepare(source: string): {\n\t\trun(...params: unknown[]): unknown\n\t\tget(...params: unknown[]): unknown\n\t\tall(...params: unknown[]): unknown[]\n\t}\n\ttransaction<T>(fn: () => T): () => T\n}\n\n/**\n * SQLite-backed user and device store using better-sqlite3.\n *\n * Provides persistent user storage suitable for single-server deployments,\n * Electron apps, and development environments. Uses WAL mode for concurrent\n * read/write performance.\n *\n * This implementation uses dynamic imports for better-sqlite3 so projects\n * that do not use SQLite server-side do not need to install it.\n *\n * @example\n * ```typescript\n * import { createSqliteUserStore } from '@korajs/auth/server'\n *\n * const userStore = await createSqliteUserStore({ filename: './auth.db' })\n * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n * ```\n */\nexport class SqliteUserStore implements UserStore {\n\tprivate readonly db: SqliteDatabase\n\n\tconstructor(db: SqliteDatabase) {\n\t\tthis.db = db\n\t\tthis.db.pragma('journal_mode = WAL')\n\t\tthis.ensureTables()\n\t}\n\n\tprivate ensureTables(): void {\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS auth_users (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\temail TEXT NOT NULL UNIQUE COLLATE NOCASE,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\temail_verified INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tpassword_hash TEXT NOT NULL,\n\t\t\t\tsalt TEXT NOT NULL\n\t\t\t);\n\n\t\t\tCREATE TABLE IF NOT EXISTS auth_devices (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tuser_id TEXT NOT NULL,\n\t\t\t\tpublic_key TEXT NOT NULL,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\trevoked INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tlast_seen_at INTEGER NOT NULL,\n\t\t\t\tFOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_auth_devices_user_id ON auth_devices(user_id);\n\t\t`)\n\t}\n\n\tasync createUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser> {\n\t\tconst normalizedEmail = params.email.toLowerCase()\n\t\tconst now = Date.now()\n\t\tconst id = randomUUID()\n\n\t\ttry {\n\t\t\tthis.db.prepare(`\n\t\t\t\tINSERT INTO auth_users (id, email, name, email_verified, created_at, password_hash, salt)\n\t\t\t\tVALUES (?, ?, ?, 0, ?, ?, ?)\n\t\t\t`).run(id, normalizedEmail, params.name, now, params.passwordHash, params.salt)\n\t\t} catch (err: unknown) {\n\t\t\tif (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {\n\t\t\t\tthrow new DuplicateEmailError()\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\n\t\treturn { id, email: normalizedEmail, name: params.name, emailVerified: false, createdAt: now }\n\t}\n\n\tasync findByEmail(email: string): Promise<StoredUser | null> {\n\t\tconst row = this.db.prepare(\n\t\t\t'SELECT * FROM auth_users WHERE email = ?',\n\t\t).get(email.toLowerCase()) as UserRow | undefined\n\n\t\treturn row ? rowToStoredUser(row) : null\n\t}\n\n\tasync findById(id: string): Promise<StoredUser | null> {\n\t\tconst row = this.db.prepare(\n\t\t\t'SELECT * FROM auth_users WHERE id = ?',\n\t\t).get(id) as UserRow | undefined\n\n\t\treturn row ? rowToStoredUser(row) : null\n\t}\n\n\tasync registerDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice> {\n\t\tconst existing = this.db.prepare(\n\t\t\t'SELECT * FROM auth_devices WHERE id = ?',\n\t\t).get(params.id) as DeviceRow | undefined\n\n\t\tif (existing && !existing.revoked) {\n\t\t\treturn rowToDevice(existing)\n\t\t}\n\n\t\tconst now = Date.now()\n\n\t\tif (existing) {\n\t\t\t// Re-activate previously revoked device\n\t\t\tthis.db.prepare(`\n\t\t\t\tUPDATE auth_devices SET revoked = 0, public_key = ?, name = ?, last_seen_at = ?\n\t\t\t\tWHERE id = ?\n\t\t\t`).run(params.publicKey, params.name, now, params.id)\n\t\t} else {\n\t\t\tthis.db.prepare(`\n\t\t\t\tINSERT INTO auth_devices (id, user_id, public_key, name, revoked, created_at, last_seen_at)\n\t\t\t\tVALUES (?, ?, ?, ?, 0, ?, ?)\n\t\t\t`).run(params.id, params.userId, params.publicKey, params.name, now, now)\n\t\t}\n\n\t\treturn {\n\t\t\tid: params.id,\n\t\t\tuserId: params.userId,\n\t\t\tpublicKey: params.publicKey,\n\t\t\tname: params.name,\n\t\t\trevoked: false,\n\t\t\tcreatedAt: existing ? existing.created_at : now,\n\t\t\tlastSeenAt: now,\n\t\t}\n\t}\n\n\tasync findDevice(deviceId: string): Promise<AuthDevice | null> {\n\t\tconst row = this.db.prepare(\n\t\t\t'SELECT * FROM auth_devices WHERE id = ?',\n\t\t).get(deviceId) as DeviceRow | undefined\n\n\t\treturn row ? rowToDevice(row) : null\n\t}\n\n\tasync listDevices(userId: string): Promise<AuthDevice[]> {\n\t\tconst rows = this.db.prepare(\n\t\t\t'SELECT * FROM auth_devices WHERE user_id = ?',\n\t\t).all(userId) as DeviceRow[]\n\n\t\treturn rows.map(rowToDevice)\n\t}\n\n\tasync revokeDevice(deviceId: string): Promise<void> {\n\t\tthis.db.prepare('UPDATE auth_devices SET revoked = 1 WHERE id = ?').run(deviceId)\n\t}\n\n\tasync setEmailVerified(userId: string, verified: boolean): Promise<void> {\n\t\tthis.db.prepare(\n\t\t\t'UPDATE auth_users SET email_verified = ? WHERE id = ?',\n\t\t).run(verified ? 1 : 0, userId)\n\t}\n\n\tasync updatePassword(userId: string, passwordHash: string, salt: string): Promise<void> {\n\t\tthis.db.prepare(\n\t\t\t'UPDATE auth_users SET password_hash = ?, salt = ? WHERE id = ?',\n\t\t).run(passwordHash, salt, userId)\n\t}\n\n\tasync listAll(): Promise<StoredUser[]> {\n\t\tconst rows = this.db.prepare('SELECT * FROM auth_users').all() as UserRow[]\n\t\treturn rows.map(rowToStoredUser)\n\t}\n\n\tasync update(user: StoredUser): Promise<void> {\n\t\tthis.db.prepare(`\n\t\t\tUPDATE auth_users\n\t\t\tSET email = ?, name = ?, email_verified = ?, password_hash = ?, salt = ?\n\t\t\tWHERE id = ?\n\t\t`).run(user.email, user.name, user.emailVerified ? 1 : 0, user.passwordHash, user.salt, user.id)\n\t}\n\n\tasync delete(userId: string): Promise<void> {\n\t\tconst deleteInTransaction = this.db.transaction(() => {\n\t\t\tthis.db.prepare('DELETE FROM auth_devices WHERE user_id = ?').run(userId)\n\t\t\tthis.db.prepare('DELETE FROM auth_users WHERE id = ?').run(userId)\n\t\t})\n\t\tdeleteInTransaction()\n\t}\n\n\tasync touchDevice(deviceId: string): Promise<void> {\n\t\tthis.db.prepare(\n\t\t\t'UPDATE auth_devices SET last_seen_at = ? WHERE id = ?',\n\t\t).run(Date.now(), deviceId)\n\t}\n}\n\n/**\n * Creates a SqliteUserStore from a file path.\n *\n * Uses runtime dynamic imports so projects that do not use SQLite server-side\n * do not need to install `better-sqlite3`.\n *\n * @param options.filename - Path to the SQLite database file, or `:memory:` for in-memory\n */\nexport async function createSqliteUserStore(options: {\n\tfilename: string\n}): Promise<SqliteUserStore> {\n\tconst Database = await loadBetterSqlite3()\n\tconst db = new Database(options.filename)\n\treturn new SqliteUserStore(db as unknown as SqliteDatabase)\n}\n\nasync function loadBetterSqlite3(): Promise<new (filename: string) => unknown> {\n\ttry {\n\t\t// Use createRequire for CJS compatibility with better-sqlite3\n\t\tconst { createRequire } = await import('node:module')\n\t\tconst require = createRequire(import.meta.url)\n\t\treturn require('better-sqlite3') as new (filename: string) => unknown\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'SQLite backend requires the \"better-sqlite3\" package. Install it in your project dependencies.',\n\t\t)\n\t}\n}\n\nfunction rowToStoredUser(row: UserRow): StoredUser {\n\treturn {\n\t\tid: row.id,\n\t\temail: row.email,\n\t\tname: row.name,\n\t\temailVerified: Boolean(row.email_verified),\n\t\tcreatedAt: row.created_at,\n\t\tpasswordHash: row.password_hash,\n\t\tsalt: row.salt,\n\t}\n}\n\nfunction rowToDevice(row: DeviceRow): AuthDevice {\n\treturn {\n\t\tid: row.id,\n\t\tuserId: row.user_id,\n\t\tpublicKey: row.public_key,\n\t\tname: row.name,\n\t\trevoked: Boolean(row.revoked),\n\t\tcreatedAt: row.created_at,\n\t\tlastSeenAt: row.last_seen_at,\n\t}\n}\n","import { randomUUID } from 'node:crypto'\nimport type { UserStore, AuthUser, StoredUser, AuthDevice } from './user-store'\nimport { DuplicateEmailError } from './user-store'\n\n/**\n * Minimal typed subset of a postgres-js SQL tag for our queries.\n * Avoids a hard dependency on the postgres package.\n * Uses Record<string, unknown>[] for result type to match postgres-js return shape.\n */\ninterface PostgresClient {\n\tbegin<T>(fn: (sql: PostgresClient) => Promise<T>): Promise<T>\n\t(\n\t\ttemplate: TemplateStringsArray,\n\t\t...args: unknown[]\n\t): Promise<Record<string, unknown>[]>\n}\n\n/**\n * PostgreSQL-backed user and device store using postgres-js.\n *\n * Provides persistent user storage suitable for production multi-server\n * deployments. Uses parameterized queries for SQL injection safety and\n * transactions for atomic operations.\n *\n * This implementation uses dynamic imports for postgres so projects\n * that do not use PostgreSQL do not need to install it.\n *\n * @example\n * ```typescript\n * import { createPostgresUserStore } from '@korajs/auth/server'\n *\n * const userStore = await createPostgresUserStore({\n * connectionString: 'postgres://user:pass@localhost:5432/mydb',\n * })\n * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n * ```\n */\nexport class PostgresUserStore implements UserStore {\n\tprivate readonly sql: PostgresClient\n\tprivate readonly ready: Promise<void>\n\n\tconstructor(sql: PostgresClient) {\n\t\tthis.sql = sql\n\t\tthis.ready = this.ensureTables()\n\t}\n\n\tprivate async ensureTables(): Promise<void> {\n\t\tawait this.sql`\n\t\t\tCREATE TABLE IF NOT EXISTS auth_users (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\temail TEXT NOT NULL UNIQUE,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\temail_verified BOOLEAN NOT NULL DEFAULT FALSE,\n\t\t\t\tcreated_at BIGINT NOT NULL,\n\t\t\t\tpassword_hash TEXT NOT NULL,\n\t\t\t\tsalt TEXT NOT NULL\n\t\t\t)\n\t\t`\n\n\t\tawait this.sql`\n\t\t\tCREATE TABLE IF NOT EXISTS auth_devices (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tuser_id TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE,\n\t\t\t\tpublic_key TEXT NOT NULL,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\trevoked BOOLEAN NOT NULL DEFAULT FALSE,\n\t\t\t\tcreated_at BIGINT NOT NULL,\n\t\t\t\tlast_seen_at BIGINT NOT NULL\n\t\t\t)\n\t\t`\n\n\t\tawait this.sql`\n\t\t\tCREATE INDEX IF NOT EXISTS idx_auth_devices_user_id ON auth_devices(user_id)\n\t\t`\n\n\t\t// Case-insensitive unique index on email for consistent lookups\n\t\tawait this.sql`\n\t\t\tCREATE UNIQUE INDEX IF NOT EXISTS idx_auth_users_email_lower ON auth_users(LOWER(email))\n\t\t`\n\t}\n\n\tasync createUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser> {\n\t\tawait this.ready\n\t\tconst normalizedEmail = params.email.toLowerCase()\n\t\tconst now = Date.now()\n\t\tconst id = randomUUID()\n\n\t\ttry {\n\t\t\tawait this.sql`\n\t\t\t\tINSERT INTO auth_users (id, email, name, email_verified, created_at, password_hash, salt)\n\t\t\t\tVALUES (${id}, ${normalizedEmail}, ${params.name}, FALSE, ${now}, ${params.passwordHash}, ${params.salt})\n\t\t\t`\n\t\t} catch (err: unknown) {\n\t\t\tif (err instanceof Error && (\n\t\t\t\terr.message.includes('unique constraint') ||\n\t\t\t\terr.message.includes('duplicate key')\n\t\t\t)) {\n\t\t\t\tthrow new DuplicateEmailError()\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\n\t\treturn { id, email: normalizedEmail, name: params.name, emailVerified: false, createdAt: now }\n\t}\n\n\tasync findByEmail(email: string): Promise<StoredUser | null> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_users WHERE LOWER(email) = ${email.toLowerCase()}\n\t\t`\n\t\treturn rows.length > 0 ? rowToStoredUser(rows[0] as unknown as UserRow) : null\n\t}\n\n\tasync findById(id: string): Promise<StoredUser | null> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_users WHERE id = ${id}\n\t\t`\n\t\treturn rows.length > 0 ? rowToStoredUser(rows[0] as unknown as UserRow) : null\n\t}\n\n\tasync registerDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice> {\n\t\tawait this.ready\n\t\tconst existingRows = await this.sql`\n\t\t\tSELECT * FROM auth_devices WHERE id = ${params.id}\n\t\t` as unknown as DeviceRow[]\n\n\t\tif (existingRows.length > 0 && !existingRows[0]!.revoked) {\n\t\t\treturn rowToDevice(existingRows[0]!)\n\t\t}\n\n\t\tconst now = Date.now()\n\n\t\tif (existingRows.length > 0) {\n\t\t\t// Re-activate previously revoked device\n\t\t\tawait this.sql`\n\t\t\t\tUPDATE auth_devices SET revoked = FALSE, public_key = ${params.publicKey}, name = ${params.name}, last_seen_at = ${now}\n\t\t\t\tWHERE id = ${params.id}\n\t\t\t`\n\t\t} else {\n\t\t\tawait this.sql`\n\t\t\t\tINSERT INTO auth_devices (id, user_id, public_key, name, revoked, created_at, last_seen_at)\n\t\t\t\tVALUES (${params.id}, ${params.userId}, ${params.publicKey}, ${params.name}, FALSE, ${now}, ${now})\n\t\t\t`\n\t\t}\n\n\t\treturn {\n\t\t\tid: params.id,\n\t\t\tuserId: params.userId,\n\t\t\tpublicKey: params.publicKey,\n\t\t\tname: params.name,\n\t\t\trevoked: false,\n\t\t\tcreatedAt: existingRows.length > 0 ? Number(existingRows[0]!.created_at) : now,\n\t\t\tlastSeenAt: now,\n\t\t}\n\t}\n\n\tasync findDevice(deviceId: string): Promise<AuthDevice | null> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_devices WHERE id = ${deviceId}\n\t\t`\n\t\treturn rows.length > 0 ? rowToDevice(rows[0] as unknown as DeviceRow) : null\n\t}\n\n\tasync listDevices(userId: string): Promise<AuthDevice[]> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_devices WHERE user_id = ${userId}\n\t\t` as unknown as DeviceRow[]\n\t\treturn rows.map(rowToDevice)\n\t}\n\n\tasync revokeDevice(deviceId: string): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`UPDATE auth_devices SET revoked = TRUE WHERE id = ${deviceId}`\n\t}\n\n\tasync setEmailVerified(userId: string, verified: boolean): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`UPDATE auth_users SET email_verified = ${verified} WHERE id = ${userId}`\n\t}\n\n\tasync updatePassword(userId: string, passwordHash: string, salt: string): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`\n\t\t\tUPDATE auth_users SET password_hash = ${passwordHash}, salt = ${salt}\n\t\t\tWHERE id = ${userId}\n\t\t`\n\t}\n\n\tasync listAll(): Promise<StoredUser[]> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`SELECT * FROM auth_users` as unknown as UserRow[]\n\t\treturn rows.map(rowToStoredUser)\n\t}\n\n\tasync update(user: StoredUser): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`\n\t\t\tUPDATE auth_users\n\t\t\tSET email = ${user.email}, name = ${user.name}, email_verified = ${user.emailVerified},\n\t\t\t\tpassword_hash = ${user.passwordHash}, salt = ${user.salt}\n\t\t\tWHERE id = ${user.id}\n\t\t`\n\t}\n\n\tasync delete(userId: string): Promise<void> {\n\t\tawait this.ready\n\t\t// Devices cascade-delete via FK constraint, but be explicit\n\t\tawait this.sql.begin(async (tx) => {\n\t\t\tawait tx`DELETE FROM auth_devices WHERE user_id = ${userId}`\n\t\t\tawait tx`DELETE FROM auth_users WHERE id = ${userId}`\n\t\t})\n\t}\n\n\tasync touchDevice(deviceId: string): Promise<void> {\n\t\tawait this.ready\n\t\tconst now = Date.now()\n\t\tawait this.sql`UPDATE auth_devices SET last_seen_at = ${now} WHERE id = ${deviceId}`\n\t}\n}\n\n/**\n * Creates a PostgresUserStore from a connection string.\n *\n * Uses runtime dynamic imports so projects that do not use PostgreSQL\n * do not need to install `postgres`.\n *\n * @param options.connectionString - PostgreSQL connection URL (e.g., `postgres://user:pass@host:5432/db`)\n */\nexport async function createPostgresUserStore(options: {\n\tconnectionString: string\n}): Promise<PostgresUserStore> {\n\tconst postgresClient = await loadPostgresDeps()\n\tconst sql = postgresClient(options.connectionString) as unknown as PostgresClient\n\treturn new PostgresUserStore(sql)\n}\n\nasync function loadPostgresDeps(): Promise<(connectionString: string) => unknown> {\n\ttry {\n\t\tconst dynamicImport = new Function('specifier', 'return import(specifier)') as (\n\t\t\tspecifier: string,\n\t\t) => Promise<unknown>\n\n\t\tconst postgresMod = (await dynamicImport('postgres')) as { default: (cs: string) => unknown }\n\t\treturn postgresMod.default\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'PostgreSQL backend requires the \"postgres\" package. Install it in your project dependencies.',\n\t\t)\n\t}\n}\n\n/**\n * Row shapes from postgres-js queries. Field names match SQL column names.\n */\ninterface UserRow {\n\tid: string\n\temail: string\n\tname: string\n\temail_verified: boolean\n\tcreated_at: string | number\n\tpassword_hash: string\n\tsalt: string\n}\n\ninterface DeviceRow {\n\tid: string\n\tuser_id: string\n\tpublic_key: string\n\tname: string\n\trevoked: boolean\n\tcreated_at: string | number\n\tlast_seen_at: string | number\n}\n\nfunction rowToStoredUser(row: UserRow): StoredUser {\n\treturn {\n\t\tid: row.id,\n\t\temail: row.email,\n\t\tname: row.name,\n\t\temailVerified: Boolean(row.email_verified),\n\t\tcreatedAt: Number(row.created_at),\n\t\tpasswordHash: row.password_hash,\n\t\tsalt: row.salt,\n\t}\n}\n\nfunction rowToDevice(row: DeviceRow): AuthDevice {\n\treturn {\n\t\tid: row.id,\n\t\tuserId: row.user_id,\n\t\tpublicKey: row.public_key,\n\t\tname: row.name,\n\t\trevoked: Boolean(row.revoked),\n\t\tcreatedAt: Number(row.created_at),\n\t\tlastSeenAt: Number(row.last_seen_at),\n\t}\n}\n","import type { AuthTokens } from '../types'\nimport type { TokenManager } from '../tokens/token-manager'\nimport type { AuthUser, AuthDevice, UserStore } from './built-in/user-store'\nimport { BuiltInAuthRoutes, type AuthRoutesConfig } from './built-in/auth-routes'\n\n/**\n * Parameters for signing up a new user.\n */\nexport interface SignUpParams {\n\t/** The user's email address */\n\temail: string\n\t/** The plaintext password */\n\tpassword: string\n\t/** Optional display name */\n\tname?: string\n\t/** Optional device ID to register with the account */\n\tdeviceId?: string\n\t/** Optional device public key (base64url) */\n\tdevicePublicKey?: string\n}\n\n/**\n * Parameters for signing in an existing user.\n */\nexport interface SignInParams {\n\t/** The user's email address */\n\temail: string\n\t/** The plaintext password */\n\tpassword: string\n\t/** Optional device ID to register or associate */\n\tdeviceId?: string\n\t/** Optional device public key (base64url) */\n\tdevicePublicKey?: string\n}\n\n/**\n * Abstraction for authentication providers.\n *\n * This interface allows swapping between the built-in email/password provider\n * and external providers (OAuth, SAML, custom) without changing application code.\n * Every provider must support the same core operations: sign up, sign in,\n * token refresh, token validation, user lookup, and device management.\n *\n * @example\n * ```typescript\n * // Use the built-in provider\n * const provider: AuthProviderAdapter = new BuiltInProvider({\n * userStore: new InMemoryUserStore(),\n * tokenManager: new TokenManager({ secret: 'my-secret' }),\n * })\n *\n * const { user, tokens } = await provider.signUp({\n * email: 'alice@example.com',\n * password: 'secure-password-123',\n * })\n * ```\n */\nexport interface AuthProviderAdapter {\n\t/**\n\t * Create a new user account and issue authentication tokens.\n\t *\n\t * @param params - Sign-up parameters including email, password, and optional device info\n\t * @returns The created user and tokens\n\t * @throws If the email is already registered or input validation fails\n\t */\n\tsignUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\n\t/**\n\t * Authenticate an existing user and issue tokens.\n\t *\n\t * @param params - Sign-in parameters including email and password\n\t * @returns The authenticated user and tokens\n\t * @throws If the credentials are invalid\n\t */\n\tsignIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\n\t/**\n\t * Exchange a refresh token for new tokens (rotation).\n\t *\n\t * @param refreshToken - The current refresh token\n\t * @returns A new token pair\n\t * @throws If the refresh token is invalid or expired\n\t */\n\trefreshTokens(refreshToken: string): Promise<AuthTokens>\n\n\t/**\n\t * Validate an access token and extract identity claims.\n\t *\n\t * @param token - The JWT access token\n\t * @returns User ID and device ID if valid, or null if invalid/expired\n\t */\n\tvalidateAccessToken(token: string): Promise<{ userId: string; deviceId: string } | null>\n\n\t/**\n\t * Look up a user by ID.\n\t *\n\t * @param userId - The user's ID\n\t * @returns The user profile, or null if not found\n\t */\n\tgetUser(userId: string): Promise<AuthUser | null>\n\n\t/**\n\t * Revoke a device. Requires a valid access token for authorization.\n\t *\n\t * @param accessToken - The caller's access token\n\t * @param deviceId - The ID of the device to revoke\n\t * @throws If the token is invalid or the device does not belong to the caller\n\t */\n\trevokeDevice(accessToken: string, deviceId: string): Promise<void>\n\n\t/**\n\t * List all devices for the authenticated user.\n\t *\n\t * @param accessToken - The caller's access token\n\t * @returns Array of device records\n\t * @throws If the token is invalid\n\t */\n\tlistDevices(accessToken: string): Promise<AuthDevice[]>\n}\n\n/**\n * Error thrown by provider adapter methods when an operation fails.\n * Wraps the HTTP-style status and error message from route handlers\n * into an exception for use in the adapter pattern.\n */\nexport class AuthProviderError extends Error {\n\t/** HTTP-style status code */\n\treadonly status: number\n\n\tconstructor(message: string, status: number) {\n\t\tsuper(message)\n\t\tthis.name = 'AuthProviderError'\n\t\tthis.status = status\n\t}\n}\n\n/**\n * Built-in authentication provider implementing email/password authentication.\n *\n * Wraps {@link BuiltInAuthRoutes} in the {@link AuthProviderAdapter} interface,\n * converting HTTP-style responses into direct return values and exceptions.\n * This is the default provider shipped with Kora and is suitable for\n * applications that want simple email/password auth without external services.\n *\n * @example\n * ```typescript\n * import { BuiltInProvider } from '@korajs/auth'\n * import { InMemoryUserStore } from '@korajs/auth'\n * import { TokenManager } from '@korajs/auth'\n *\n * const provider = new BuiltInProvider({\n * userStore: new InMemoryUserStore(),\n * tokenManager: new TokenManager({ secret: process.env.AUTH_SECRET }),\n * })\n *\n * const { user, tokens } = await provider.signUp({\n * email: 'alice@example.com',\n * password: 'strong-password-123',\n * name: 'Alice',\n * })\n * ```\n */\nexport class BuiltInProvider implements AuthProviderAdapter {\n\tprivate readonly routes: BuiltInAuthRoutes\n\tprivate readonly tokenManager: TokenManager\n\tprivate readonly userStore: UserStore\n\n\tconstructor(config: AuthRoutesConfig) {\n\t\tthis.routes = new BuiltInAuthRoutes(config)\n\t\tthis.tokenManager = config.tokenManager\n\t\tthis.userStore = config.userStore\n\t}\n\n\t/** @inheritdoc */\n\tasync signUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tconst result = await this.routes.handleSignUp(params)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n\n\t/** @inheritdoc */\n\tasync signIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tconst result = await this.routes.handleSignIn(params)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n\n\t/** @inheritdoc */\n\tasync refreshTokens(refreshToken: string): Promise<AuthTokens> {\n\t\tconst result = await this.routes.handleRefresh({ refreshToken })\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n\n\t/** @inheritdoc */\n\tasync validateAccessToken(\n\t\ttoken: string,\n\t): Promise<{ userId: string; deviceId: string } | null> {\n\t\tconst payload = this.tokenManager.validateToken(token)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn null\n\t\t}\n\t\treturn { userId: payload.sub, deviceId: payload.dev }\n\t}\n\n\t/** @inheritdoc */\n\tasync getUser(userId: string): Promise<AuthUser | null> {\n\t\tconst stored = await this.userStore.findById(userId)\n\t\tif (stored === null) {\n\t\t\treturn null\n\t\t}\n\t\treturn {\n\t\t\tid: stored.id,\n\t\t\temail: stored.email,\n\t\t\tname: stored.name,\n\t\t\temailVerified: stored.emailVerified,\n\t\t\tcreatedAt: stored.createdAt,\n\t\t}\n\t}\n\n\t/** @inheritdoc */\n\tasync revokeDevice(accessToken: string, deviceId: string): Promise<void> {\n\t\tconst result = await this.routes.handleRevokeDevice(accessToken, deviceId)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t}\n\n\t/** @inheritdoc */\n\tasync listDevices(accessToken: string): Promise<AuthDevice[]> {\n\t\tconst result = await this.routes.handleListDevices(accessToken)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { verifyJwt, decodeJwt, isExpired } from '../../tokens/jwt'\nimport type {\n\tAuthProviderAdapter,\n\tSignUpParams,\n\tSignInParams,\n} from '../adapter'\nimport type { AuthUser } from '../built-in/user-store'\nimport type { AuthDevice } from '../built-in/user-store'\nimport type { AuthTokens } from '../../types'\n\n// ============================================================================\n// Error classes\n// ============================================================================\n\n/**\n * Thrown when an operation is not supported by external auth providers.\n *\n * External providers delegate user management to the third-party service\n * (Clerk, Auth0, Supabase, etc.). Operations like sign-up and sign-in must\n * be performed through the external provider's SDK or UI, not through Kora.\n */\nexport class ExternalAuthOperationNotSupportedError extends KoraError {\n\tconstructor(operation: string, provider: string) {\n\t\tsuper(\n\t\t\t`The \"${operation}\" operation is not supported by the external auth provider \"${provider}\". ` +\n\t\t\t`Perform this operation through your external auth provider's SDK or dashboard instead.`,\n\t\t\t'AUTH_EXTERNAL_OPERATION_NOT_SUPPORTED',\n\t\t\t{ operation, provider },\n\t\t)\n\t\tthis.name = 'ExternalAuthOperationNotSupportedError'\n\t}\n}\n\n/**\n * Thrown when an external JWT token fails validation.\n */\nexport class ExternalTokenValidationError extends KoraError {\n\tconstructor(reason: string, context?: Record<string, unknown>) {\n\t\tsuper(\n\t\t\t`External token validation failed: ${reason}`,\n\t\t\t'AUTH_EXTERNAL_TOKEN_INVALID',\n\t\t\tcontext,\n\t\t)\n\t\tthis.name = 'ExternalTokenValidationError'\n\t}\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Default claim mapping for external JWT tokens.\n * Maps standard JWT claims to Kora's expected user format.\n */\nfunction defaultMapClaims(claims: Record<string, unknown>): ExternalUserInfo {\n\tconst sub = claims['sub']\n\tif (typeof sub !== 'string' || sub.length === 0) {\n\t\tthrow new ExternalTokenValidationError(\n\t\t\t'JWT is missing a valid \"sub\" (subject) claim. The \"sub\" claim must be a non-empty string identifying the user.',\n\t\t\t{ availableClaims: Object.keys(claims) },\n\t\t)\n\t}\n\n\treturn {\n\t\tuserId: sub,\n\t\temail: typeof claims['email'] === 'string' ? claims['email'] : undefined,\n\t\tname: typeof claims['name'] === 'string' ? claims['name'] : undefined,\n\t\tmetadata: undefined,\n\t}\n}\n\n/**\n * User information extracted from an external JWT token.\n * Returned by the claims mapping function.\n */\nexport interface ExternalUserInfo {\n\t/** Unique user identifier from the external provider */\n\tuserId: string\n\t/** User's email address, if available in the token claims */\n\temail?: string\n\t/** User's display name, if available in the token claims */\n\tname?: string\n\t/** Additional metadata from the token claims */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Configuration for the external JWT authentication provider.\n *\n * Supports two validation modes:\n * 1. **HMAC secret** (`jwtSecret`): For providers that sign tokens with a shared\n * secret (e.g., Supabase). Uses HS256 verification via the existing `verifyJwt` utility.\n * 2. **Custom validator** (`validateToken`): For providers that use asymmetric keys\n * (RS256, ES256) or require custom validation logic (e.g., Clerk with JWKS rotation).\n * The developer provides their own validation function.\n *\n * At least one of `jwtSecret` or `validateToken` must be provided.\n *\n * @example\n * ```typescript\n * // With a shared secret (e.g., Supabase)\n * const provider = new ExternalJwtProvider({\n * providerName: 'supabase',\n * jwtSecret: process.env.SUPABASE_JWT_SECRET,\n * })\n *\n * // With a custom validator (e.g., Clerk JWKS)\n * const provider = new ExternalJwtProvider({\n * providerName: 'clerk',\n * validateToken: async (token) => {\n * const claims = await clerkClient.verifyToken(token)\n * return claims ? { sub: claims.sub, ...claims } : null\n * },\n * })\n * ```\n */\nexport interface ExternalJwtProviderConfig {\n\t/**\n\t * Human-readable name of the external auth provider.\n\t * Used in error messages and DevTools for identification.\n\t * @example 'clerk', 'auth0', 'supabase', 'firebase'\n\t */\n\tproviderName: string\n\n\t/**\n\t * Shared secret for HS256 JWT verification.\n\t * Used when the external provider signs tokens with HMAC-SHA256.\n\t * Mutually exclusive with `validateToken` (if both are provided,\n\t * `validateToken` takes precedence).\n\t */\n\tjwtSecret?: string\n\n\t/**\n\t * Custom token validator function.\n\t * When provided, this is used instead of HS256 HMAC verification.\n\t * Receives the raw JWT string and must return the decoded claims\n\t * (with at least a `sub` field) or null if the token is invalid.\n\t *\n\t * Use this for providers that use asymmetric signing (RS256, ES256)\n\t * or require JWKS-based key rotation.\n\t *\n\t * @param token - The raw JWT string to validate\n\t * @returns The decoded claims object with at least a `sub` field, or null if invalid\n\t */\n\tvalidateToken?: (token: string) => Promise<{ sub: string; [key: string]: unknown } | null>\n\n\t/**\n\t * Map external JWT claims to Kora's expected user format.\n\t *\n\t * The default mapping extracts:\n\t * - `sub` -> `userId` (required)\n\t * - `email` -> `email` (optional)\n\t * - `name` -> `name` (optional)\n\t *\n\t * Override this to extract custom claims from your provider's tokens.\n\t *\n\t * @param claims - The decoded JWT claims object\n\t * @returns Kora-compatible user information\n\t *\n\t * @example\n\t * ```typescript\n\t * mapClaims: (claims) => ({\n\t * userId: claims.sub as string,\n\t * email: claims.email_address as string,\n\t * name: `${claims.first_name} ${claims.last_name}`,\n\t * metadata: { org: claims.org_id },\n\t * })\n\t * ```\n\t */\n\tmapClaims?: (claims: Record<string, unknown>) => ExternalUserInfo\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Authentication provider adapter for external JWT issuers.\n *\n * This adapter validates JWTs issued by third-party auth services (Clerk, Auth0,\n * Supabase, Firebase, or any custom JWT issuer) and maps their claims to Kora's\n * internal auth context. It bridges external identity providers with Kora's\n * sync authentication layer.\n *\n * **How it works:**\n * 1. The client authenticates with the external provider and receives a JWT\n * 2. The client passes this JWT to Kora's sync server\n * 3. This adapter validates the JWT and extracts user identity\n * 4. Kora uses the extracted identity for sync authorization\n *\n * **What it does NOT do:**\n * - Sign up or sign in users (that happens through the external provider)\n * - Issue or refresh tokens (that is the external provider's responsibility)\n * - Manage devices (external providers handle their own device tracking)\n *\n * @example\n * ```typescript\n * import { ExternalJwtProvider } from '@korajs/auth/server'\n *\n * const auth = new ExternalJwtProvider({\n * providerName: 'clerk',\n * validateToken: async (token) => {\n * // Use Clerk's SDK or JWKS endpoint to verify\n * return verifiedClaims\n * },\n * })\n *\n * // Use with Kora sync server\n * const result = await auth.validateAccessToken('eyJhbG...')\n * if (result) {\n * console.log('User:', result.userId)\n * }\n * ```\n */\nexport class ExternalJwtProvider implements AuthProviderAdapter {\n\tprivate readonly providerName: string\n\tprivate readonly jwtSecret: string | undefined\n\tprivate readonly customValidateToken:\n\t\t| ((token: string) => Promise<{ sub: string; [key: string]: unknown } | null>)\n\t\t| undefined\n\tprivate readonly mapClaims: (claims: Record<string, unknown>) => ExternalUserInfo\n\n\tconstructor(config: ExternalJwtProviderConfig) {\n\t\tif (config.validateToken === undefined && config.jwtSecret === undefined) {\n\t\t\tthrow new ExternalTokenValidationError(\n\t\t\t\t'ExternalJwtProvider requires either a \"jwtSecret\" for HS256 verification ' +\n\t\t\t\t'or a custom \"validateToken\" function. Provide at least one.',\n\t\t\t\t{ providerName: config.providerName },\n\t\t\t)\n\t\t}\n\n\t\tthis.providerName = config.providerName\n\t\tthis.jwtSecret = config.jwtSecret\n\t\tthis.customValidateToken = config.validateToken\n\t\tthis.mapClaims = config.mapClaims ?? defaultMapClaims\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * User registration must be performed through the external auth provider's\n\t * SDK or UI. Kora does not manage user accounts for external providers.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync signUp(_params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('signUp', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * User authentication must be performed through the external auth provider's\n\t * SDK or UI. Kora does not manage credentials for external providers.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync signIn(_params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('signIn', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * Token refresh must be performed through the external auth provider's SDK.\n\t * Kora does not manage token lifecycle for external providers.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync refreshTokens(_refreshToken: string): Promise<AuthTokens> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('refreshTokens', this.providerName)\n\t}\n\n\t/**\n\t * Validate an access token from the external auth provider.\n\t *\n\t * Uses either the custom `validateToken` function or HS256 HMAC verification\n\t * (depending on configuration) to validate the JWT. On success, maps the claims\n\t * to Kora's expected format and returns the user ID and device ID.\n\t *\n\t * The device ID for external providers is derived from the user ID with a\n\t * \"external-\" prefix, since external providers typically don't use Kora's\n\t * device identity system.\n\t *\n\t * @param token - The JWT access token issued by the external provider\n\t * @returns User ID and device ID if the token is valid, or null if invalid/expired\n\t */\n\tasync validateAccessToken(\n\t\ttoken: string,\n\t): Promise<{ userId: string; deviceId: string } | null> {\n\t\tconst claims = await this.extractClaims(token)\n\t\tif (claims === null) {\n\t\t\treturn null\n\t\t}\n\n\t\tlet userInfo: ExternalUserInfo\n\t\ttry {\n\t\t\tuserInfo = this.mapClaims(claims)\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\n\t\tif (typeof userInfo.userId !== 'string' || userInfo.userId.length === 0) {\n\t\t\treturn null\n\t\t}\n\n\t\t// External providers don't use Kora's device identity system.\n\t\t// Derive a stable device ID from the user ID so sync authorization works.\n\t\tconst deviceId = `external-${this.providerName}-${userInfo.userId}`\n\n\t\treturn { userId: userInfo.userId, deviceId }\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * User lookup must be performed through the external auth provider's API.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync getUser(_userId: string): Promise<AuthUser | null> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('getUser', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * Device revocation must be managed through the external auth provider\n\t * or by revoking the user's tokens at the provider level.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync revokeDevice(_accessToken: string, _deviceId: string): Promise<void> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('revokeDevice', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * Device listing must be performed through the external auth provider's API.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync listDevices(_accessToken: string): Promise<AuthDevice[]> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('listDevices', this.providerName)\n\t}\n\n\t/**\n\t * Creates a sync server auth provider compatible with `@korajs/server`.\n\t *\n\t * Returns an object with an `authenticate` method that validates the external\n\t * JWT and returns a Kora-compatible auth context. Use this to wire the external\n\t * auth provider into KoraSyncServer.\n\t *\n\t * @returns An object with an `authenticate` method for KoraSyncServer's `auth` config\n\t *\n\t * @example\n\t * ```typescript\n\t * const externalAuth = new ExternalJwtProvider({ ... })\n\t * const syncServer = new KoraSyncServer({\n\t * store,\n\t * auth: externalAuth.toSyncAuthProvider(),\n\t * })\n\t * ```\n\t */\n\ttoSyncAuthProvider(): {\n\t\tauthenticate(token: string): Promise<{\n\t\t\tuserId: string\n\t\t\tscopes?: Record<string, Record<string, unknown>>\n\t\t\tmetadata?: Record<string, unknown>\n\t\t} | null>\n\t} {\n\t\treturn {\n\t\t\tauthenticate: async (token: string) => {\n\t\t\t\tconst claims = await this.extractClaims(token)\n\t\t\t\tif (claims === null) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\tlet userInfo: ExternalUserInfo\n\t\t\t\ttry {\n\t\t\t\t\tuserInfo = this.mapClaims(claims)\n\t\t\t\t} catch {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\tif (typeof userInfo.userId !== 'string' || userInfo.userId.length === 0) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tuserId: userInfo.userId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tprovider: this.providerName,\n\t\t\t\t\t\temail: userInfo.email,\n\t\t\t\t\t\tname: userInfo.name,\n\t\t\t\t\t\t...userInfo.metadata,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Extract and validate claims from a JWT token.\n\t *\n\t * Uses the custom validator if configured, otherwise falls back to\n\t * HS256 HMAC verification with the configured secret.\n\t *\n\t * @param token - The raw JWT string\n\t * @returns The decoded claims object, or null if the token is invalid\n\t */\n\tprivate async extractClaims(token: string): Promise<Record<string, unknown> | null> {\n\t\t// Custom validator takes precedence\n\t\tif (this.customValidateToken !== undefined) {\n\t\t\ttry {\n\t\t\t\tconst result = await this.customValidateToken(token)\n\t\t\t\tif (result === null) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\treturn result as Record<string, unknown>\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to HS256 HMAC verification\n\t\tif (this.jwtSecret !== undefined) {\n\t\t\tconst claims = verifyJwt(token, this.jwtSecret)\n\t\t\tif (claims === null) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Check expiration if the token has an exp claim\n\t\t\tif (isExpired(claims as { exp?: number })) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn claims\n\t\t}\n\n\t\t// Should not reach here due to constructor validation, but handle defensively\n\t\treturn null\n\t}\n}\n","import {\n\tExternalJwtProvider,\n\ttype ExternalJwtProviderConfig,\n\ttype ExternalUserInfo,\n} from './external-jwt-provider'\n\n// ============================================================================\n// Clerk Adapter Configuration\n// ============================================================================\n\n/**\n * Configuration for the Clerk authentication adapter.\n *\n * Clerk uses asymmetric signing (RS256) with JWKS key rotation, so this adapter\n * requires a custom `validateToken` function that handles JWKS-based verification.\n * The adapter provides sensible defaults for mapping Clerk's JWT claims to Kora's\n * expected format.\n *\n * Clerk JWTs typically contain:\n * - `sub`: User ID (e.g., \"user_2abc123...\")\n * - `email`: Primary email address (if configured in session claims)\n * - `first_name`, `last_name`: Name fields (if configured in session claims)\n * - `azp`: Authorized party (your frontend origin)\n * - `org_id`, `org_slug`, `org_role`: Organization claims (if using Clerk orgs)\n *\n * @example\n * ```typescript\n * import { createClerkAdapter } from '@korajs/auth/server'\n *\n * const clerkAuth = createClerkAdapter({\n * validateToken: async (token) => {\n * // Use Clerk's backend SDK or your own JWKS verification\n * const result = await clerkClient.verifyToken(token)\n * return result ? { sub: result.sub, ...result } : null\n * },\n * })\n *\n * // Use with Kora sync server\n * const syncServer = new KoraSyncServer({\n * store,\n * auth: clerkAuth.toSyncAuthProvider(),\n * })\n * ```\n */\nexport interface ClerkAdapterConfig {\n\t/**\n\t * Custom token validator for Clerk JWTs.\n\t *\n\t * Clerk uses RS256 signing with JWKS key rotation, which requires either\n\t * Clerk's backend SDK or a JWKS-based verifier. This function receives\n\t * the raw JWT and should return the decoded claims or null.\n\t *\n\t * @param token - The raw JWT string from the Clerk session\n\t * @returns Decoded claims with at least a `sub` field, or null if invalid\n\t */\n\tvalidateToken: (token: string) => Promise<{ sub: string; [key: string]: unknown } | null>\n\n\t/**\n\t * Custom claim mapping override.\n\t *\n\t * By default, the Clerk adapter maps:\n\t * - `sub` -> `userId`\n\t * - `email` -> `email` (if present)\n\t * - `first_name` + `last_name` -> `name` (concatenated, if present)\n\t * - `org_id`, `org_slug`, `org_role` -> `metadata` (if present)\n\t *\n\t * Override this to customize how Clerk claims are mapped to Kora's format.\n\t */\n\tmapClaims?: ExternalJwtProviderConfig['mapClaims']\n}\n\n// ============================================================================\n// Default Clerk claim mapping\n// ============================================================================\n\n/**\n * Default claim mapping for Clerk JWTs.\n *\n * Extracts user identity from Clerk's standard session claims and maps\n * organization data into Kora metadata when available.\n */\nfunction defaultClerkClaimMapping(claims: Record<string, unknown>): ExternalUserInfo {\n\tconst sub = claims['sub']\n\tif (typeof sub !== 'string' || sub.length === 0) {\n\t\t// Delegate to the base provider's error handling by returning invalid data\n\t\t// The ExternalJwtProvider will catch the missing userId\n\t\treturn { userId: '' }\n\t}\n\n\t// Build display name from first_name and last_name if available\n\tconst firstName = typeof claims['first_name'] === 'string' ? claims['first_name'] : ''\n\tconst lastName = typeof claims['last_name'] === 'string' ? claims['last_name'] : ''\n\tconst fullName = [firstName, lastName].filter(Boolean).join(' ')\n\n\t// Extract email (Clerk may include this in session claims)\n\tconst email = typeof claims['email'] === 'string' ? claims['email'] : undefined\n\n\t// Extract organization metadata if present\n\tconst metadata: Record<string, unknown> = {}\n\tif (typeof claims['org_id'] === 'string') {\n\t\tmetadata['orgId'] = claims['org_id']\n\t}\n\tif (typeof claims['org_slug'] === 'string') {\n\t\tmetadata['orgSlug'] = claims['org_slug']\n\t}\n\tif (typeof claims['org_role'] === 'string') {\n\t\tmetadata['orgRole'] = claims['org_role']\n\t}\n\n\treturn {\n\t\tuserId: sub,\n\t\temail,\n\t\tname: fullName.length > 0 ? fullName : undefined,\n\t\tmetadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n\t}\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Creates an ExternalJwtProvider configured for Clerk authentication.\n *\n * Clerk uses RS256 signing with JWKS key rotation, so a custom `validateToken`\n * function must be provided. This function should use Clerk's backend SDK or\n * a JWKS-based JWT verifier to validate tokens.\n *\n * This adapter does NOT depend on `@clerk/backend` or any Clerk SDK. It only\n * provides sensible defaults for mapping Clerk's JWT claims to Kora's format.\n * The actual token verification is delegated to the provided `validateToken` function.\n *\n * @param config - Clerk adapter configuration\n * @returns An ExternalJwtProvider instance configured for Clerk\n *\n * @example\n * ```typescript\n * import { createClerkAdapter } from '@korajs/auth/server'\n *\n * const clerkAuth = createClerkAdapter({\n * validateToken: async (token) => {\n * // Your JWKS verification logic here\n * const payload = await verifyWithJwks(token, CLERK_JWKS_URL)\n * return payload\n * },\n * })\n *\n * const result = await clerkAuth.validateAccessToken(sessionToken)\n * ```\n */\nexport function createClerkAdapter(config: ClerkAdapterConfig): ExternalJwtProvider {\n\treturn new ExternalJwtProvider({\n\t\tproviderName: 'clerk',\n\t\tvalidateToken: config.validateToken,\n\t\tmapClaims: config.mapClaims ?? defaultClerkClaimMapping,\n\t})\n}\n","import {\n\tExternalJwtProvider,\n\ttype ExternalJwtProviderConfig,\n\ttype ExternalUserInfo,\n} from './external-jwt-provider'\n\n// ============================================================================\n// Supabase Adapter Configuration\n// ============================================================================\n\n/**\n * Configuration for the Supabase authentication adapter.\n *\n * Supabase Auth signs JWTs with HS256 using the project's JWT secret,\n * which is available in your Supabase project settings under\n * Settings -> API -> JWT Secret.\n *\n * Supabase JWTs typically contain:\n * - `sub`: User UUID\n * - `email`: User's email address\n * - `role`: The database role (e.g., \"authenticated\", \"anon\")\n * - `aud`: Audience (usually \"authenticated\")\n * - `app_metadata`: Provider info, roles, etc.\n * - `user_metadata`: Custom user data (name, avatar, etc.)\n *\n * @example\n * ```typescript\n * import { createSupabaseAdapter } from '@korajs/auth/server'\n *\n * const supabaseAuth = createSupabaseAdapter({\n * jwtSecret: process.env.SUPABASE_JWT_SECRET,\n * })\n *\n * // Use with Kora sync server\n * const syncServer = new KoraSyncServer({\n * store,\n * auth: supabaseAuth.toSyncAuthProvider(),\n * })\n * ```\n */\nexport interface SupabaseAdapterConfig {\n\t/**\n\t * Supabase JWT secret from your project settings.\n\t *\n\t * Found in: Supabase Dashboard -> Settings -> API -> JWT Secret\n\t *\n\t * This is the HS256 HMAC secret used to sign and verify Supabase Auth JWTs.\n\t * Keep this secret secure and never expose it in client-side code.\n\t */\n\tjwtSecret: string\n\n\t/**\n\t * Custom claim mapping override.\n\t *\n\t * By default, the Supabase adapter maps:\n\t * - `sub` -> `userId`\n\t * - `email` -> `email`\n\t * - `user_metadata.full_name` or `user_metadata.name` -> `name`\n\t * - `role`, `aud`, `app_metadata` -> `metadata`\n\t *\n\t * Override this to customize how Supabase claims are mapped to Kora's format.\n\t */\n\tmapClaims?: ExternalJwtProviderConfig['mapClaims']\n}\n\n// ============================================================================\n// Default Supabase claim mapping\n// ============================================================================\n\n/**\n * Default claim mapping for Supabase Auth JWTs.\n *\n * Extracts user identity from Supabase's standard JWT claims and maps\n * role/audience information into Kora metadata.\n */\nfunction defaultSupabaseClaimMapping(claims: Record<string, unknown>): ExternalUserInfo {\n\tconst sub = claims['sub']\n\tif (typeof sub !== 'string' || sub.length === 0) {\n\t\treturn { userId: '' }\n\t}\n\n\tconst email = typeof claims['email'] === 'string' ? claims['email'] : undefined\n\n\t// Extract name from user_metadata (Supabase stores user profile data here)\n\tlet name: string | undefined\n\tconst userMetadata = claims['user_metadata']\n\tif (typeof userMetadata === 'object' && userMetadata !== null && !Array.isArray(userMetadata)) {\n\t\tconst meta = userMetadata as Record<string, unknown>\n\t\tif (typeof meta['full_name'] === 'string' && meta['full_name'].length > 0) {\n\t\t\tname = meta['full_name']\n\t\t} else if (typeof meta['name'] === 'string' && meta['name'].length > 0) {\n\t\t\tname = meta['name']\n\t\t}\n\t}\n\n\t// Collect metadata from Supabase-specific claims\n\tconst metadata: Record<string, unknown> = {}\n\tif (typeof claims['role'] === 'string') {\n\t\tmetadata['role'] = claims['role']\n\t}\n\tif (typeof claims['aud'] === 'string') {\n\t\tmetadata['aud'] = claims['aud']\n\t}\n\tif (\n\t\ttypeof claims['app_metadata'] === 'object'\n\t\t&& claims['app_metadata'] !== null\n\t\t&& !Array.isArray(claims['app_metadata'])\n\t) {\n\t\tmetadata['appMetadata'] = claims['app_metadata']\n\t}\n\n\treturn {\n\t\tuserId: sub,\n\t\temail,\n\t\tname,\n\t\tmetadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n\t}\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Creates an ExternalJwtProvider configured for Supabase Auth.\n *\n * Supabase Auth uses HS256 signing with the project's JWT secret, making it\n * compatible with Kora's built-in `verifyJwt` utility. No external SDK is needed.\n *\n * This adapter validates the JWT signature and expiration, then maps Supabase's\n * standard claims (sub, email, role, user_metadata) to Kora's auth context format.\n *\n * @param config - Supabase adapter configuration\n * @returns An ExternalJwtProvider instance configured for Supabase\n *\n * @example\n * ```typescript\n * import { createSupabaseAdapter } from '@korajs/auth/server'\n *\n * const supabaseAuth = createSupabaseAdapter({\n * jwtSecret: process.env.SUPABASE_JWT_SECRET,\n * })\n *\n * // Validate a Supabase access token\n * const result = await supabaseAuth.validateAccessToken(supabaseAccessToken)\n * if (result) {\n * console.log('Supabase user:', result.userId)\n * }\n *\n * // Or use with the sync server\n * const syncServer = new KoraSyncServer({\n * store,\n * auth: supabaseAuth.toSyncAuthProvider(),\n * })\n * ```\n */\nexport function createSupabaseAdapter(config: SupabaseAdapterConfig): ExternalJwtProvider {\n\treturn new ExternalJwtProvider({\n\t\tproviderName: 'supabase',\n\t\tjwtSecret: config.jwtSecret,\n\t\tmapClaims: config.mapClaims ?? defaultSupabaseClaimMapping,\n\t})\n}\n","import { randomBytes } from 'node:crypto'\nimport { KoraError } from '@korajs/core'\nimport { fromBase64Url, toBase64Url } from '../device/device-identity'\nimport { decodeCbor } from './passkey-client'\n\n// ============================================================================\n// Server-side passkey errors\n// ============================================================================\n\n/**\n * Thrown when server-side passkey verification fails.\n */\nexport class PasskeyVerificationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'PASSKEY_VERIFICATION_ERROR', context)\n\t\tthis.name = 'PasskeyVerificationError'\n\t}\n}\n\n// ============================================================================\n// Registration options generation\n// ============================================================================\n\n/** Options returned by generateRegistrationOptions for the client. */\nexport interface RegistrationOptions {\n\t/** Base64url-encoded random challenge (32 bytes) */\n\tchallenge: string\n\t/** Relying party ID (domain) */\n\trpId: string\n\t/** Relying party display name */\n\trpName: string\n\t/** Base64url-encoded user ID */\n\tuserId: string\n\t/** User's email or username */\n\tuserName: string\n\t/** Human-readable display name */\n\tuserDisplayName: string\n\t/** Credential IDs to exclude (prevents re-registration) */\n\texcludeCredentialIds: string[]\n\t/** Authenticator selection criteria */\n\tauthenticatorSelection: {\n\t\tauthenticatorAttachment: 'platform'\n\t\tresidentKey: 'preferred'\n\t\tuserVerification: 'required'\n\t}\n\t/** Timeout in milliseconds */\n\ttimeout: number\n}\n\n/**\n * Generate registration options for creating a new passkey.\n *\n * Creates a cryptographically random challenge and assembles the options\n * object that should be sent to the client for `createPasskeyCredential()`.\n *\n * The server must store the challenge (keyed by user session or similar)\n * for later verification when the client responds.\n *\n * @param params - Registration parameters\n * @param params.rpId - Relying party ID (your domain, e.g. \"example.com\")\n * @param params.rpName - Relying party display name\n * @param params.userId - Unique user identifier\n * @param params.userName - User's email or username\n * @param params.userDisplayName - Human-readable display name\n * @param params.existingCredentialIds - Base64url credential IDs to exclude\n * @returns Registration options to send to the client, including the challenge\n *\n * @example\n * ```typescript\n * const options = generateRegistrationOptions({\n * rpId: 'example.com',\n * rpName: 'My App',\n * userId: user.id,\n * userName: user.email,\n * userDisplayName: user.name,\n * })\n * // Store options.challenge in session for later verification\n * // Send options to client\n * ```\n */\nexport function generateRegistrationOptions(params: {\n\trpId: string\n\trpName: string\n\tuserId: string\n\tuserName: string\n\tuserDisplayName: string\n\texistingCredentialIds?: string[]\n}): RegistrationOptions {\n\t// Generate a 32-byte cryptographically random challenge\n\tconst challengeBytes = randomBytes(32)\n\tconst challenge = toBase64Url(challengeBytes.buffer.slice(\n\t\tchallengeBytes.byteOffset,\n\t\tchallengeBytes.byteOffset + challengeBytes.byteLength,\n\t))\n\n\treturn {\n\t\tchallenge,\n\t\trpId: params.rpId,\n\t\trpName: params.rpName,\n\t\tuserId: params.userId,\n\t\tuserName: params.userName,\n\t\tuserDisplayName: params.userDisplayName,\n\t\texcludeCredentialIds: params.existingCredentialIds ?? [],\n\t\tauthenticatorSelection: {\n\t\t\tauthenticatorAttachment: 'platform',\n\t\t\tresidentKey: 'preferred',\n\t\t\tuserVerification: 'required',\n\t\t},\n\t\ttimeout: 60000,\n\t}\n}\n\n// ============================================================================\n// Registration verification\n// ============================================================================\n\n/** Result of verifying a registration response. */\nexport interface RegistrationVerificationResult {\n\t/** Whether the registration response was verified successfully */\n\tverified: boolean\n\t/** Base64url-encoded credential ID */\n\tcredentialId: string\n\t/** Base64url-encoded COSE public key (store this for future authentication) */\n\tpublicKey: string\n\t/** Initial signature counter from the authenticator */\n\tsignCount: number\n}\n\n/**\n * Verify a registration response from the client.\n *\n * Validates the attestation object and clientDataJSON returned by the browser's\n * `navigator.credentials.create()` call. Extracts and returns the public key\n * and credential ID to store in your database.\n *\n * This implementation supports the \"none\" attestation format, which is the most\n * common and does not require trust in any attestation CA. For higher assurance\n * scenarios, extend this to verify packed/tpm/android attestation formats.\n *\n * @param params - Verification parameters\n * @param params.credential - The credential response from the client\n * @param params.expectedChallenge - The challenge that was sent to the client (base64url)\n * @param params.expectedOrigin - The expected origin (e.g. \"https://example.com\")\n * @param params.expectedRpId - The expected relying party ID (e.g. \"example.com\")\n * @returns Verification result with the credential ID and public key to store\n * @throws {PasskeyVerificationError} If the response is invalid or tampered with\n *\n * @example\n * ```typescript\n * const result = await verifyRegistrationResponse({\n * credential: clientResponse,\n * expectedChallenge: storedChallenge,\n * expectedOrigin: 'https://example.com',\n * expectedRpId: 'example.com',\n * })\n * if (result.verified) {\n * // Store result.credentialId, result.publicKey, result.signCount\n * }\n * ```\n */\nexport async function verifyRegistrationResponse(params: {\n\tcredential: {\n\t\tcredentialId: string\n\t\tpublicKey: string\n\t\tclientDataJSON: string\n\t\tattestationObject: string\n\t}\n\texpectedChallenge: string\n\texpectedOrigin: string\n\texpectedRpId: string\n}): Promise<RegistrationVerificationResult> {\n\tconst { credential, expectedChallenge, expectedOrigin, expectedRpId } = params\n\n\t// Step 1: Decode and verify clientDataJSON\n\tconst clientDataBytes = fromBase64Url(credential.clientDataJSON)\n\tconst clientDataText = new TextDecoder().decode(clientDataBytes)\n\tlet clientData: { type: string; challenge: string; origin: string }\n\ttry {\n\t\tclientData = JSON.parse(clientDataText) as {\n\t\t\ttype: string\n\t\t\tchallenge: string\n\t\t\torigin: string\n\t\t}\n\t} catch {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Failed to parse clientDataJSON. The response may be malformed.',\n\t\t)\n\t}\n\n\t// Verify the type is \"webauthn.create\"\n\tif (clientData.type !== 'webauthn.create') {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Expected clientData.type \"webauthn.create\" but received \"${clientData.type}\".`,\n\t\t\t{ type: clientData.type },\n\t\t)\n\t}\n\n\t// Verify the challenge matches what we sent\n\tif (clientData.challenge !== expectedChallenge) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Challenge mismatch. The response does not match the expected challenge. ' +\n\t\t\t\t'This may indicate a replay attack or session mismatch.',\n\t\t)\n\t}\n\n\t// Verify the origin matches\n\tif (clientData.origin !== expectedOrigin) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Origin mismatch. Expected \"${expectedOrigin}\" but received \"${clientData.origin}\".`,\n\t\t\t{ expected: expectedOrigin, received: clientData.origin },\n\t\t)\n\t}\n\n\t// Step 2: Decode the attestation object (CBOR)\n\tconst attestationBytes = fromBase64Url(credential.attestationObject)\n\tconst attestationResult = decodeCbor(attestationBytes, 0)\n\tconst attestationMap = attestationResult.value as Map<string, unknown>\n\n\t// Verify attestation format\n\tconst fmt = attestationMap.get('fmt')\n\tif (fmt !== 'none') {\n\t\t// For Phase 3, we only support \"none\" attestation.\n\t\t// Other formats (packed, tpm, android-key, etc.) can be added later.\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Unsupported attestation format \"${String(fmt)}\". Only \"none\" attestation is currently supported.`,\n\t\t\t{ format: String(fmt) },\n\t\t)\n\t}\n\n\t// Step 3: Parse the authenticator data\n\tconst authData = attestationMap.get('authData')\n\tif (!(authData instanceof Uint8Array)) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid attestation object: authData is missing or not a byte string.',\n\t\t)\n\t}\n\n\t// Verify the RP ID hash (first 32 bytes of authData)\n\tconst rpIdHash = authData.slice(0, 32)\n\tconst expectedRpIdHash = await sha256(new TextEncoder().encode(expectedRpId))\n\tif (!constantTimeEqual(rpIdHash, new Uint8Array(expectedRpIdHash))) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'RP ID hash mismatch. The authenticator data does not match the expected relying party.',\n\t\t)\n\t}\n\n\t// Parse flags (byte 32)\n\tconst flags = authData[32] as number\n\n\t// Bit 0: User Present (UP) - must be set\n\tif ((flags & 0x01) === 0) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'User Present flag is not set in authenticator data. ' +\n\t\t\t\t'The authenticator did not confirm user presence.',\n\t\t)\n\t}\n\n\t// Bit 6: Attested Credential Data (AT) - must be set for registration\n\tif ((flags & 0x40) === 0) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Attested Credential Data flag is not set. ' +\n\t\t\t\t'The authenticator did not include credential data.',\n\t\t)\n\t}\n\n\t// Parse sign count (bytes 33-36, big-endian uint32)\n\tconst signCount =\n\t\t((authData[33] as number) << 24) |\n\t\t((authData[34] as number) << 16) |\n\t\t((authData[35] as number) << 8) |\n\t\t(authData[36] as number)\n\n\t// Parse attested credential data\n\t// Skip rpIdHash (32) + flags (1) + signCount (4) = 37 bytes\n\tlet offset = 37\n\n\t// aaguid: 16 bytes (we skip it — not needed for \"none\" attestation)\n\toffset += 16\n\n\t// credentialIdLength: 2 bytes, big-endian\n\tconst credentialIdLength =\n\t\t((authData[offset] as number) << 8) | (authData[offset + 1] as number)\n\toffset += 2\n\n\t// credentialId: credentialIdLength bytes\n\tconst credentialIdBytes = authData.slice(offset, offset + credentialIdLength)\n\toffset += credentialIdLength\n\n\t// Verify the credential ID matches what the client sent\n\tconst expectedCredentialId = toBase64Url(credentialIdBytes.buffer as unknown as ArrayBuffer)\n\tif (expectedCredentialId !== credential.credentialId) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Credential ID mismatch between attestation object and client response.',\n\t\t)\n\t}\n\n\t// The remaining bytes are the COSE-encoded public key\n\tconst coseKeyResult = decodeCbor(authData, offset)\n\tconst coseKeyBytes = authData.slice(offset, coseKeyResult.offset)\n\n\t// Verify the public key matches what the client sent\n\tconst publicKeyFromAttestation = toBase64Url(coseKeyBytes.buffer as unknown as ArrayBuffer)\n\tif (publicKeyFromAttestation !== credential.publicKey) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Public key mismatch between attestation object and client response.',\n\t\t)\n\t}\n\n\treturn {\n\t\tverified: true,\n\t\tcredentialId: credential.credentialId,\n\t\tpublicKey: credential.publicKey,\n\t\tsignCount: signCount >>> 0,\n\t}\n}\n\n// ============================================================================\n// Authentication options generation\n// ============================================================================\n\n/** Options returned by generateAuthenticationOptions for the client. */\nexport interface AuthenticationOptions {\n\t/** Base64url-encoded random challenge (32 bytes) */\n\tchallenge: string\n\t/** Relying party ID */\n\trpId: string\n\t/** Credential IDs to allow (limit to specific credentials) */\n\tallowCredentialIds?: string[]\n\t/** User verification requirement */\n\tuserVerification: 'preferred'\n\t/** Timeout in milliseconds */\n\ttimeout: number\n}\n\n/**\n * Generate authentication options for signing in with a passkey.\n *\n * Creates a cryptographically random challenge and assembles the options\n * object that should be sent to the client for `authenticateWithPasskey()`.\n *\n * The server must store the challenge for later verification.\n *\n * @param params - Authentication parameters\n * @param params.rpId - Relying party ID (your domain)\n * @param params.allowCredentialIds - Base64url credential IDs to allow (optional)\n * @returns Authentication options to send to the client\n *\n * @example\n * ```typescript\n * const options = generateAuthenticationOptions({\n * rpId: 'example.com',\n * allowCredentialIds: user.credentialIds,\n * })\n * // Store options.challenge in session\n * // Send options to client\n * ```\n */\nexport function generateAuthenticationOptions(params: {\n\trpId: string\n\tallowCredentialIds?: string[]\n}): AuthenticationOptions {\n\tconst challengeBytes = randomBytes(32)\n\tconst challenge = toBase64Url(challengeBytes.buffer.slice(\n\t\tchallengeBytes.byteOffset,\n\t\tchallengeBytes.byteOffset + challengeBytes.byteLength,\n\t))\n\n\treturn {\n\t\tchallenge,\n\t\trpId: params.rpId,\n\t\tallowCredentialIds: params.allowCredentialIds,\n\t\tuserVerification: 'preferred',\n\t\ttimeout: 60000,\n\t}\n}\n\n// ============================================================================\n// Authentication verification\n// ============================================================================\n\n/** Result of verifying an authentication response. */\nexport interface AuthenticationVerificationResult {\n\t/** Whether the authentication response was verified successfully */\n\tverified: boolean\n\t/** Updated signature counter (store this to detect cloned authenticators) */\n\tnewSignCount: number\n}\n\n/**\n * Verify an authentication response from the client.\n *\n * Validates the signed assertion returned by the browser's\n * `navigator.credentials.get()` call. Checks the signature against the\n * stored public key, verifies the challenge and origin, and validates\n * the signature counter to detect cloned authenticators.\n *\n * This implementation supports ECDSA P-256 (ES256, COSE algorithm -7)\n * signatures, which is the most common algorithm used by platform\n * authenticators (Touch ID, Face ID, Windows Hello).\n *\n * @param params - Verification parameters\n * @param params.assertion - The assertion response from the client\n * @param params.expectedChallenge - The challenge that was sent to the client (base64url)\n * @param params.expectedOrigin - The expected origin (e.g. \"https://example.com\")\n * @param params.expectedRpId - The expected relying party ID\n * @param params.publicKey - The stored COSE public key (base64url, from registration)\n * @param params.previousSignCount - The previously stored signature counter\n * @returns Verification result with the new signature counter\n * @throws {PasskeyVerificationError} If the assertion is invalid\n *\n * @example\n * ```typescript\n * const result = await verifyAuthenticationResponse({\n * assertion: clientAssertion,\n * expectedChallenge: storedChallenge,\n * expectedOrigin: 'https://example.com',\n * expectedRpId: 'example.com',\n * publicKey: storedCredential.publicKey,\n * previousSignCount: storedCredential.signCount,\n * })\n * if (result.verified) {\n * // Update stored sign count: storedCredential.signCount = result.newSignCount\n * // Issue session tokens\n * }\n * ```\n */\nexport async function verifyAuthenticationResponse(params: {\n\tassertion: {\n\t\tcredentialId: string\n\t\tauthenticatorData: string\n\t\tclientDataJSON: string\n\t\tsignature: string\n\t\tuserHandle: string | null\n\t}\n\texpectedChallenge: string\n\texpectedOrigin: string\n\texpectedRpId: string\n\tpublicKey: string\n\tpreviousSignCount: number\n}): Promise<AuthenticationVerificationResult> {\n\tconst {\n\t\tassertion,\n\t\texpectedChallenge,\n\t\texpectedOrigin,\n\t\texpectedRpId,\n\t\tpublicKey,\n\t\tpreviousSignCount,\n\t} = params\n\n\t// Step 1: Decode and verify clientDataJSON\n\tconst clientDataBytes = fromBase64Url(assertion.clientDataJSON)\n\tconst clientDataText = new TextDecoder().decode(clientDataBytes)\n\tlet clientData: { type: string; challenge: string; origin: string }\n\ttry {\n\t\tclientData = JSON.parse(clientDataText) as {\n\t\t\ttype: string\n\t\t\tchallenge: string\n\t\t\torigin: string\n\t\t}\n\t} catch {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Failed to parse clientDataJSON. The assertion may be malformed.',\n\t\t)\n\t}\n\n\t// Verify the type is \"webauthn.get\"\n\tif (clientData.type !== 'webauthn.get') {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Expected clientData.type \"webauthn.get\" but received \"${clientData.type}\".`,\n\t\t\t{ type: clientData.type },\n\t\t)\n\t}\n\n\t// Verify the challenge matches\n\tif (clientData.challenge !== expectedChallenge) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Challenge mismatch. The assertion does not match the expected challenge.',\n\t\t)\n\t}\n\n\t// Verify the origin matches\n\tif (clientData.origin !== expectedOrigin) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Origin mismatch. Expected \"${expectedOrigin}\" but received \"${clientData.origin}\".`,\n\t\t\t{ expected: expectedOrigin, received: clientData.origin },\n\t\t)\n\t}\n\n\t// Step 2: Parse authenticator data\n\tconst authDataBytes = fromBase64Url(assertion.authenticatorData)\n\n\t// Verify RP ID hash (first 32 bytes)\n\tconst rpIdHash = authDataBytes.slice(0, 32)\n\tconst expectedRpIdHash = await sha256(new TextEncoder().encode(expectedRpId))\n\tif (!constantTimeEqual(rpIdHash, new Uint8Array(expectedRpIdHash))) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'RP ID hash mismatch. The authenticator data does not match the expected relying party.',\n\t\t)\n\t}\n\n\t// Parse flags (byte 32)\n\tconst flags = authDataBytes[32] as number\n\n\t// Bit 0: User Present (UP) - must be set\n\tif ((flags & 0x01) === 0) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'User Present flag is not set in authenticator data.',\n\t\t)\n\t}\n\n\t// Parse sign count (bytes 33-36, big-endian uint32)\n\tconst signCount =\n\t\t(((authDataBytes[33] as number) << 24) |\n\t\t\t((authDataBytes[34] as number) << 16) |\n\t\t\t((authDataBytes[35] as number) << 8) |\n\t\t\t(authDataBytes[36] as number)) >>>\n\t\t0\n\n\t// Step 3: Validate sign count to detect cloned authenticators\n\t// If both are 0, the authenticator doesn't support counters — skip check.\n\t// If the new count is not greater than the previous, it may be cloned.\n\tif (previousSignCount > 0 || signCount > 0) {\n\t\tif (signCount <= previousSignCount) {\n\t\t\tthrow new PasskeyVerificationError(\n\t\t\t\t'Signature counter did not increase. This may indicate a cloned authenticator. ' +\n\t\t\t\t\t`Previous count: ${previousSignCount}, received count: ${signCount}.`,\n\t\t\t\t{\n\t\t\t\t\tpreviousSignCount,\n\t\t\t\t\treceivedSignCount: signCount,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\t// Step 4: Verify the signature\n\t// The signature is over: authData || SHA-256(clientDataJSON)\n\tconst clientDataHash = await sha256(clientDataBytes)\n\tconst signedData = new Uint8Array(\n\t\tauthDataBytes.length + clientDataHash.byteLength,\n\t)\n\tsignedData.set(authDataBytes, 0)\n\tsignedData.set(new Uint8Array(clientDataHash), authDataBytes.length)\n\n\t// Decode the COSE public key to get the raw EC key parameters\n\tconst coseKeyBytes = fromBase64Url(publicKey)\n\tconst coseKeyResult = decodeCbor(coseKeyBytes, 0)\n\tconst coseKeyMap = coseKeyResult.value as Map<number, unknown>\n\n\t// COSE key map labels:\n\t// 1: kty (key type) — 2 = EC2\n\t// 3: alg (algorithm) — -7 = ES256\n\t// -1: crv (curve) — 1 = P-256\n\t// -2: x coordinate (byte string, 32 bytes)\n\t// -3: y coordinate (byte string, 32 bytes)\n\n\tconst kty = coseKeyMap.get(1)\n\tconst alg = coseKeyMap.get(3)\n\n\tif (kty !== 2) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Unsupported COSE key type ${String(kty)}. Only EC2 (kty=2) is supported.`,\n\t\t\t{ kty: String(kty) },\n\t\t)\n\t}\n\n\tif (alg !== -7) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Unsupported COSE algorithm ${String(alg)}. Only ES256 (alg=-7) is supported.`,\n\t\t\t{ alg: String(alg) },\n\t\t)\n\t}\n\n\tconst xCoord = coseKeyMap.get(-2) as Uint8Array\n\tconst yCoord = coseKeyMap.get(-3) as Uint8Array\n\n\tif (\n\t\t!(xCoord instanceof Uint8Array) ||\n\t\t!(yCoord instanceof Uint8Array) ||\n\t\txCoord.length !== 32 ||\n\t\tyCoord.length !== 32\n\t) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid COSE public key: x and y coordinates must be 32-byte arrays.',\n\t\t)\n\t}\n\n\t// Import the public key as an ECDSA P-256 key for verification.\n\t// We use the \"raw\" format: 0x04 || x || y (uncompressed point).\n\tconst rawPublicKey = new Uint8Array(65)\n\trawPublicKey[0] = 0x04 // Uncompressed point indicator\n\trawPublicKey.set(xCoord, 1)\n\trawPublicKey.set(yCoord, 33)\n\n\tlet cryptoKey: CryptoKey\n\ttry {\n\t\tcryptoKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\trawPublicKey.buffer as unknown as ArrayBuffer,\n\t\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\t\tfalse,\n\t\t\t['verify'],\n\t\t)\n\t} catch (error) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Failed to import COSE public key for signature verification.',\n\t\t\t{ cause: error instanceof Error ? error.message : String(error) },\n\t\t)\n\t}\n\n\t// The WebAuthn signature is in ASN.1 DER format.\n\t// Web Crypto's ECDSA verify expects the signature in IEEE P1363 format (r || s).\n\t// Convert from DER to P1363.\n\tconst signatureBytes = fromBase64Url(assertion.signature)\n\tconst p1363Signature = derToP1363(signatureBytes, 32)\n\n\tlet verified: boolean\n\ttry {\n\t\tverified = await globalThis.crypto.subtle.verify(\n\t\t\t{ name: 'ECDSA', hash: { name: 'SHA-256' } },\n\t\t\tcryptoKey,\n\t\t\tp1363Signature.buffer as unknown as ArrayBuffer,\n\t\t\tsignedData.buffer as unknown as ArrayBuffer,\n\t\t)\n\t} catch (error) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Signature verification operation failed.',\n\t\t\t{ cause: error instanceof Error ? error.message : String(error) },\n\t\t)\n\t}\n\n\tif (!verified) {\n\t\treturn { verified: false, newSignCount: signCount }\n\t}\n\n\treturn { verified: true, newSignCount: signCount }\n}\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\n/**\n * Compute SHA-256 hash of the given data using Web Crypto API.\n */\nasync function sha256(data: Uint8Array): Promise<ArrayBuffer> {\n\treturn globalThis.crypto.subtle.digest('SHA-256', data as unknown as ArrayBuffer)\n}\n\n/**\n * Constant-time comparison of two byte arrays.\n * Prevents timing attacks when comparing hashes or signatures.\n */\nfunction constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n\tif (a.length !== b.length) {\n\t\treturn false\n\t}\n\tlet result = 0\n\tfor (let i = 0; i < a.length; i++) {\n\t\tresult |= (a[i] as number) ^ (b[i] as number)\n\t}\n\treturn result === 0\n}\n\n/**\n * Convert an ASN.1 DER-encoded ECDSA signature to IEEE P1363 format.\n *\n * DER format: 0x30 <len> 0x02 <r-len> <r> 0x02 <s-len> <s>\n * P1363 format: <r-padded-to-n-bytes> <s-padded-to-n-bytes>\n *\n * This conversion is necessary because WebAuthn authenticators produce\n * DER-encoded signatures, but the Web Crypto API expects P1363 format.\n *\n * @param derSignature - The DER-encoded signature bytes\n * @param componentLength - The expected length of each component (32 for P-256)\n * @returns The P1363-formatted signature\n */\nfunction derToP1363(\n\tderSignature: Uint8Array,\n\tcomponentLength: number,\n): Uint8Array {\n\t// Parse the DER structure\n\tlet offset = 0\n\n\t// SEQUENCE tag (0x30)\n\tif (derSignature[offset] !== 0x30) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid DER signature: expected SEQUENCE tag (0x30).',\n\t\t)\n\t}\n\toffset += 1\n\n\t// SEQUENCE length (may be 1 or 2 bytes)\n\tif ((derSignature[offset] as number) & 0x80) {\n\t\t// Long form: the lower 7 bits give the number of length bytes\n\t\tconst lengthBytes = (derSignature[offset] as number) & 0x7f\n\t\toffset += 1 + lengthBytes\n\t} else {\n\t\toffset += 1\n\t}\n\n\t// First INTEGER (r)\n\tif (derSignature[offset] !== 0x02) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid DER signature: expected INTEGER tag (0x02) for r component.',\n\t\t)\n\t}\n\toffset += 1\n\n\tconst rLength = derSignature[offset] as number\n\toffset += 1\n\n\tconst rBytes = derSignature.slice(offset, offset + rLength)\n\toffset += rLength\n\n\t// Second INTEGER (s)\n\tif (derSignature[offset] !== 0x02) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid DER signature: expected INTEGER tag (0x02) for s component.',\n\t\t)\n\t}\n\toffset += 1\n\n\tconst sLength = derSignature[offset] as number\n\toffset += 1\n\n\tconst sBytes = derSignature.slice(offset, offset + sLength)\n\n\t// Pad or trim r and s to componentLength bytes.\n\t// DER integers may have a leading 0x00 byte to indicate positive sign,\n\t// or may be shorter than componentLength if the leading bytes are zero.\n\tconst result = new Uint8Array(componentLength * 2)\n\tcopyComponentToP1363(rBytes, result, 0, componentLength)\n\tcopyComponentToP1363(sBytes, result, componentLength, componentLength)\n\n\treturn result\n}\n\n/**\n * Copy a DER integer component into a fixed-width P1363 buffer.\n * Handles leading zero padding (DER sign byte) and right-alignment.\n */\nfunction copyComponentToP1363(\n\tcomponent: Uint8Array,\n\ttarget: Uint8Array,\n\ttargetOffset: number,\n\tcomponentLength: number,\n): void {\n\tif (component.length === componentLength) {\n\t\t// Exact fit\n\t\ttarget.set(component, targetOffset)\n\t} else if (component.length > componentLength) {\n\t\t// DER may have a leading 0x00 sign byte — strip it\n\t\tconst excess = component.length - componentLength\n\t\ttarget.set(component.slice(excess), targetOffset)\n\t} else {\n\t\t// Component is shorter — right-align with zero padding\n\t\tconst padding = componentLength - component.length\n\t\ttarget.set(component, targetOffset + padding)\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Organization\n// ============================================================================\n\n/**\n * An organization (workspace/team) that groups users together.\n *\n * Organizations are the fundamental unit of multi-tenancy in Kora.\n * Data is scoped to organizations, and users access data through\n * their organization memberships and roles.\n */\nexport interface Organization {\n\t/** Unique identifier (UUID v7) */\n\tid: string\n\t/** Display name of the organization */\n\tname: string\n\t/** URL-friendly identifier (unique, lowercase, alphanumeric + hyphens) */\n\tslug: string\n\t/** User ID of the organization owner */\n\townerId: string\n\t/** When the organization was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the organization was last updated (ms since epoch) */\n\tupdatedAt: number\n\t/** Arbitrary metadata (plan, billing info, settings, etc.) */\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Parameters for creating a new organization.\n */\nexport interface CreateOrgParams {\n\t/** Display name */\n\tname: string\n\t/** URL-friendly slug (auto-generated from name if omitted) */\n\tslug?: string\n\t/** Optional metadata to attach */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Parameters for updating an existing organization.\n */\nexport interface UpdateOrgParams {\n\t/** New display name */\n\tname?: string\n\t/** New slug */\n\tslug?: string\n\t/** Metadata to merge (shallow merge) */\n\tmetadata?: Record<string, unknown>\n}\n\n// ============================================================================\n// Roles\n// ============================================================================\n\n/**\n * Built-in organization roles, ordered by decreasing privilege.\n *\n * - **owner**: Full control, can delete org, transfer ownership, manage billing\n * - **admin**: Manage members and settings, full data access\n * - **member**: Read + write own data, read shared data\n * - **viewer**: Read-only access to shared data\n * - **billing**: Billing management only, no data access\n */\nexport const ORG_ROLES = ['owner', 'admin', 'member', 'viewer', 'billing'] as const\nexport type OrgRole = (typeof ORG_ROLES)[number]\n\n/**\n * Role hierarchy for permission inheritance.\n * Higher number = more privilege.\n */\nexport const ROLE_HIERARCHY: Record<OrgRole, number> = {\n\tviewer: 10,\n\tbilling: 15,\n\tmember: 20,\n\tadmin: 30,\n\towner: 40,\n} as const\n\n/**\n * Check if one role has at least the privilege level of another.\n *\n * @param userRole - The user's current role\n * @param requiredRole - The minimum role required\n * @returns true if userRole >= requiredRole in the hierarchy\n */\nexport function hasRoleLevel(userRole: OrgRole, requiredRole: OrgRole): boolean {\n\treturn ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole]\n}\n\n// ============================================================================\n// Membership\n// ============================================================================\n\n/**\n * A user's membership in an organization.\n */\nexport interface Membership {\n\t/** Unique identifier for this membership record */\n\tid: string\n\t/** Organization this membership belongs to */\n\torgId: string\n\t/** User who is a member */\n\tuserId: string\n\t/** Role within the organization */\n\trole: OrgRole\n\t/** User who invited this member (null if founder) */\n\tinvitedBy: string | null\n\t/** When the user joined the organization (ms since epoch) */\n\tjoinedAt: number\n\t/** Arbitrary metadata (department, title, etc.) */\n\tmetadata: Record<string, unknown>\n}\n\n// ============================================================================\n// Invitations\n// ============================================================================\n\n/**\n * Invitation status lifecycle.\n */\nexport const INVITATION_STATUSES = ['pending', 'accepted', 'revoked', 'expired'] as const\nexport type InvitationStatus = (typeof INVITATION_STATUSES)[number]\n\n/**\n * An invitation to join an organization.\n *\n * Invitations are sent by email and include a single-use token.\n * They expire after a configurable duration (default: 7 days).\n */\nexport interface OrgInvitation {\n\t/** Unique identifier */\n\tid: string\n\t/** Organization the invitation is for */\n\torgId: string\n\t/** Email address of the invitee */\n\temail: string\n\t/** Role the invitee will receive upon accepting */\n\trole: OrgRole\n\t/** User who created the invitation */\n\tinvitedBy: string\n\t/** Cryptographically random single-use token */\n\ttoken: string\n\t/** When the invitation was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the invitation expires (ms since epoch) */\n\texpiresAt: number\n\t/** Current status */\n\tstatus: InvitationStatus\n}\n\n/**\n * Parameters for creating an invitation.\n */\nexport interface CreateInvitationParams {\n\t/** Email address to invite */\n\temail: string\n\t/** Role to assign when the invitation is accepted */\n\trole: OrgRole\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Base error for organization-related operations.\n */\nexport class OrgError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgError'\n\t}\n}\n\nexport class OrgNotFoundError extends OrgError {\n\tconstructor() {\n\t\tsuper('Organization not found.', 'ORG_NOT_FOUND')\n\t}\n}\n\nexport class OrgSlugTakenError extends OrgError {\n\tconstructor() {\n\t\tsuper('An organization with this slug already exists.', 'ORG_SLUG_TAKEN')\n\t}\n}\n\nexport class MembershipNotFoundError extends OrgError {\n\tconstructor() {\n\t\tsuper('User is not a member of this organization.', 'MEMBERSHIP_NOT_FOUND')\n\t}\n}\n\nexport class MemberAlreadyExistsError extends OrgError {\n\tconstructor() {\n\t\tsuper('User is already a member of this organization.', 'MEMBER_ALREADY_EXISTS')\n\t}\n}\n\nexport class InsufficientRoleError extends OrgError {\n\tconstructor(required: OrgRole) {\n\t\tsuper(\n\t\t\t`This action requires at least the \"${required}\" role.`,\n\t\t\t'INSUFFICIENT_ROLE',\n\t\t\t{ requiredRole: required },\n\t\t)\n\t}\n}\n\nexport class CannotRemoveOwnerError extends OrgError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'The organization owner cannot be removed. Transfer ownership first.',\n\t\t\t'CANNOT_REMOVE_OWNER',\n\t\t)\n\t}\n}\n\nexport class InvitationNotFoundError extends OrgError {\n\tconstructor() {\n\t\tsuper('Invitation not found or has already been used.', 'INVITATION_NOT_FOUND')\n\t}\n}\n\nexport class InvitationExpiredError extends OrgError {\n\tconstructor() {\n\t\tsuper('This invitation has expired.', 'INVITATION_EXPIRED')\n\t}\n}\n","import type {\n\tOrganization,\n\tMembership,\n\tOrgInvitation,\n\tOrgRole,\n\tCreateOrgParams,\n\tUpdateOrgParams,\n} from './org-types'\nimport {\n\tOrgNotFoundError,\n\tOrgSlugTakenError,\n\tMembershipNotFoundError,\n\tMemberAlreadyExistsError,\n\tCannotRemoveOwnerError,\n\tInvitationNotFoundError,\n\tInvitationExpiredError,\n\tInsufficientRoleError,\n\thasRoleLevel,\n} from './org-types'\nimport type { OrgStore } from './org-store'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Response envelope returned by all org route handlers.\n * Mirrors AuthRouteResponse for consistency.\n */\nexport interface OrgRouteResponse<T> {\n\t/** HTTP status code */\n\tstatus: number\n\t/** Either the success payload or an error message */\n\tbody: { data: T } | { error: string }\n}\n\n/**\n * Configuration for the org route handlers.\n */\nexport interface OrgRoutesConfig {\n\t/** The organization store backing all org operations */\n\torgStore: OrgStore\n}\n\n/** Maximum length for org name */\nconst MAX_ORG_NAME_LENGTH = 200\n\n/** Maximum length for org slug */\nconst MAX_SLUG_LENGTH = 100\n\n/** Slug format: lowercase alphanumeric and hyphens, 2-100 chars */\nconst SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,98}[a-z0-9]$/\n\n/**\n * Simple email format validation (same logic as auth-routes).\n */\nfunction isValidEmail(email: string): boolean {\n\tif (email.length === 0 || email.length > 254) return false\n\tconst atIndex = email.indexOf('@')\n\tif (atIndex < 1) return false\n\tconst domain = email.slice(atIndex + 1)\n\tif (domain.length === 0 || !domain.includes('.')) return false\n\tif (email.indexOf('@', atIndex + 1) !== -1) return false\n\tif (email.includes(' ')) return false\n\treturn true\n}\n\n/**\n * Strip control characters from a string.\n */\nfunction sanitize(value: string): string {\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional sanitization\n\treturn value.replace(/[\\x00-\\x1f\\x7f]/g, '').trim()\n}\n\n// ============================================================================\n// OrgRoutes\n// ============================================================================\n\n/**\n * Server-side route handlers for organization management.\n *\n * These handlers enforce authorization (role checks), input validation,\n * and produce transport-agnostic response objects. Wire them to your\n * HTTP framework (Express, Hono, Fastify, etc.):\n *\n * @example\n * ```typescript\n * const orgRoutes = new OrgRoutes({ orgStore: new InMemoryOrgStore() })\n *\n * app.post('/orgs', async (req, res) => {\n * const result = await orgRoutes.createOrg(req.userId, req.body)\n * res.status(result.status).json(result.body)\n * })\n * ```\n */\nexport class OrgRoutes {\n\tprivate readonly store: OrgStore\n\n\tconstructor(config: OrgRoutesConfig) {\n\t\tthis.store = config.orgStore\n\t}\n\n\t// --- Organizations ---\n\n\t/**\n\t * Create a new organization. The authenticated user becomes the owner.\n\t */\n\tasync createOrg(\n\t\tuserId: string,\n\t\tparams: { name?: unknown; slug?: unknown; metadata?: unknown },\n\t): Promise<OrgRouteResponse<Organization>> {\n\t\t// Validate name\n\t\tif (typeof params.name !== 'string' || params.name.trim().length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Organization name is required.' } }\n\t\t}\n\t\tconst name = sanitize(params.name)\n\t\tif (name.length > MAX_ORG_NAME_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `Organization name must be at most ${MAX_ORG_NAME_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\n\t\t// Validate slug (optional)\n\t\tlet slug: string | undefined\n\t\tif (params.slug !== undefined) {\n\t\t\tif (typeof params.slug !== 'string') {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be a string.' } }\n\t\t\t}\n\t\t\tslug = params.slug.toLowerCase().trim()\n\t\t\tif (slug.length < 2) {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be at least 2 characters.' } }\n\t\t\t}\n\t\t\tif (slug.length > MAX_SLUG_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: { error: `Slug must be at most ${MAX_SLUG_LENGTH} characters.` },\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!SLUG_PATTERN.test(slug)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: {\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen.',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate metadata (optional)\n\t\tif (params.metadata !== undefined && (typeof params.metadata !== 'object' || params.metadata === null || Array.isArray(params.metadata))) {\n\t\t\treturn { status: 400, body: { error: 'Metadata must be a plain object.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst createParams: CreateOrgParams = {\n\t\t\t\tname,\n\t\t\t\tslug,\n\t\t\t\tmetadata: params.metadata as Record<string, unknown> | undefined,\n\t\t\t}\n\t\t\tconst org = await this.store.createOrg(userId, createParams)\n\t\t\treturn { status: 201, body: { data: org } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgSlugTakenError) {\n\t\t\t\treturn { status: 409, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Get an organization by ID. Requires membership.\n\t */\n\tasync getOrg(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<Organization>> {\n\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\tif (!membership) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\n\t\tconst org = await this.store.getOrg(orgId)\n\t\tif (!org) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\n\t\treturn { status: 200, body: { data: org } }\n\t}\n\n\t/**\n\t * Update an organization. Requires admin or higher.\n\t */\n\tasync updateOrg(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { name?: unknown; slug?: unknown; metadata?: unknown },\n\t): Promise<OrgRouteResponse<Organization>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tconst updateParams: UpdateOrgParams = {}\n\n\t\t// Validate name\n\t\tif (params.name !== undefined) {\n\t\t\tif (typeof params.name !== 'string' || params.name.trim().length === 0) {\n\t\t\t\treturn { status: 400, body: { error: 'Organization name must be a non-empty string.' } }\n\t\t\t}\n\t\t\tupdateParams.name = sanitize(params.name as string)\n\t\t\tif (updateParams.name.length > MAX_ORG_NAME_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: { error: `Organization name must be at most ${MAX_ORG_NAME_LENGTH} characters.` },\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate slug\n\t\tif (params.slug !== undefined) {\n\t\t\tif (typeof params.slug !== 'string') {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be a string.' } }\n\t\t\t}\n\t\t\tupdateParams.slug = (params.slug as string).toLowerCase().trim()\n\t\t\tif (updateParams.slug.length < 2) {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be at least 2 characters.' } }\n\t\t\t}\n\t\t\tif (updateParams.slug.length > MAX_SLUG_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: { error: `Slug must be at most ${MAX_SLUG_LENGTH} characters.` },\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!SLUG_PATTERN.test(updateParams.slug)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: {\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen.',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate metadata\n\t\tif (params.metadata !== undefined) {\n\t\t\tif (typeof params.metadata !== 'object' || params.metadata === null || Array.isArray(params.metadata)) {\n\t\t\t\treturn { status: 400, body: { error: 'Metadata must be a plain object.' } }\n\t\t\t}\n\t\t\tupdateParams.metadata = params.metadata as Record<string, unknown>\n\t\t}\n\n\t\ttry {\n\t\t\tconst org = await this.store.updateOrg(orgId, updateParams)\n\t\t\treturn { status: 200, body: { data: org } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof OrgSlugTakenError) {\n\t\t\t\treturn { status: 409, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Delete an organization. Requires owner.\n\t */\n\tasync deleteOrg(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<{ deleted: true }>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'owner')\n\t\tif (authResult) return authResult\n\n\t\ttry {\n\t\t\tawait this.store.deleteOrg(orgId)\n\t\t\treturn { status: 200, body: { data: { deleted: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List all organizations the authenticated user belongs to.\n\t */\n\tasync listUserOrgs(userId: string): Promise<OrgRouteResponse<Organization[]>> {\n\t\tconst orgs = await this.store.listUserOrgs(userId)\n\t\treturn { status: 200, body: { data: orgs } }\n\t}\n\n\t// --- Members ---\n\n\t/**\n\t * Add a member to an organization. Requires admin or higher.\n\t */\n\tasync addMember(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { targetUserId?: unknown; role?: unknown },\n\t): Promise<OrgRouteResponse<Membership>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.targetUserId !== 'string' || params.targetUserId.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Target user ID is required.' } }\n\t\t}\n\t\tif (typeof params.role !== 'string' || !isValidRole(params.role)) {\n\t\t\treturn { status: 400, body: { error: 'A valid role is required (admin, member, viewer, billing).' } }\n\t\t}\n\n\t\t// Cannot assign owner role via addMember — use transferOwnership\n\t\tif (params.role === 'owner') {\n\t\t\treturn { status: 400, body: { error: 'Cannot assign owner role directly. Use ownership transfer.' } }\n\t\t}\n\n\t\t// Admins cannot add other admins (only owner can)\n\t\tconst callerMembership = await this.store.getMembership(orgId, userId)\n\t\tif (callerMembership && callerMembership.role !== 'owner' && params.role === 'admin') {\n\t\t\treturn { status: 403, body: { error: 'Only the owner can add admin members.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst membership = await this.store.addMember(orgId, params.targetUserId, params.role as OrgRole, userId)\n\t\t\treturn { status: 201, body: { data: membership } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MemberAlreadyExistsError) {\n\t\t\t\treturn { status: 409, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Remove a member from an organization. Requires admin or higher.\n\t * Members can also remove themselves (leave).\n\t */\n\tasync removeMember(\n\t\tuserId: string,\n\t\torgId: string,\n\t\ttargetUserId: string,\n\t): Promise<OrgRouteResponse<{ removed: true }>> {\n\t\t// Allow self-removal (leaving) for any member\n\t\tconst isSelfRemoval = userId === targetUserId\n\n\t\tif (!isSelfRemoval) {\n\t\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\t\tif (authResult) return authResult\n\t\t} else {\n\t\t\t// Verify caller is a member\n\t\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\t\tif (!membership) {\n\t\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.store.removeMember(orgId, targetUserId)\n\t\t\treturn { status: 200, body: { data: { removed: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof CannotRemoveOwnerError) {\n\t\t\t\treturn { status: 400, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MembershipNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Update a member's role. Requires admin or higher.\n\t */\n\tasync updateMemberRole(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { targetUserId?: unknown; role?: unknown },\n\t): Promise<OrgRouteResponse<Membership>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.targetUserId !== 'string' || params.targetUserId.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Target user ID is required.' } }\n\t\t}\n\t\tif (typeof params.role !== 'string' || !isValidRole(params.role)) {\n\t\t\treturn { status: 400, body: { error: 'A valid role is required (admin, member, viewer, billing).' } }\n\t\t}\n\t\tif (params.role === 'owner') {\n\t\t\treturn { status: 400, body: { error: 'Cannot assign owner role directly. Use ownership transfer.' } }\n\t\t}\n\n\t\t// Admins cannot promote to admin (only owner can)\n\t\tconst callerMembership = await this.store.getMembership(orgId, userId)\n\t\tif (callerMembership && callerMembership.role !== 'owner' && params.role === 'admin') {\n\t\t\treturn { status: 403, body: { error: 'Only the owner can assign admin role.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst membership = await this.store.updateMemberRole(orgId, params.targetUserId, params.role as OrgRole)\n\t\t\treturn { status: 200, body: { data: membership } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof MembershipNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List all members of an organization. Requires membership.\n\t */\n\tasync listMembers(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<Membership[]>> {\n\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\tif (!membership) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst members = await this.store.listMembers(orgId)\n\t\t\treturn { status: 200, body: { data: members } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Transfer ownership to another member. Requires owner.\n\t */\n\tasync transferOwnership(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { newOwnerId?: unknown },\n\t): Promise<OrgRouteResponse<{ transferred: true }>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'owner')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.newOwnerId !== 'string' || params.newOwnerId.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'New owner ID is required.' } }\n\t\t}\n\n\t\tif (params.newOwnerId === userId) {\n\t\t\treturn { status: 400, body: { error: 'You are already the owner.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.store.transferOwnership(orgId, params.newOwnerId)\n\t\t\treturn { status: 200, body: { data: { transferred: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MembershipNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: 'Target user is not a member of this organization.' } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Create an invitation to join the organization. Requires admin or higher.\n\t */\n\tasync createInvitation(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { email?: unknown; role?: unknown },\n\t): Promise<OrgRouteResponse<OrgInvitation>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.email !== 'string' || !isValidEmail(params.email.trim())) {\n\t\t\treturn { status: 400, body: { error: 'A valid email address is required.' } }\n\t\t}\n\t\tif (typeof params.role !== 'string' || !isValidRole(params.role)) {\n\t\t\treturn { status: 400, body: { error: 'A valid role is required (admin, member, viewer, billing).' } }\n\t\t}\n\t\tif (params.role === 'owner') {\n\t\t\treturn { status: 400, body: { error: 'Cannot invite with owner role. Use ownership transfer.' } }\n\t\t}\n\n\t\t// Admins cannot invite admins\n\t\tconst callerMembership = await this.store.getMembership(orgId, userId)\n\t\tif (callerMembership && callerMembership.role !== 'owner' && params.role === 'admin') {\n\t\t\treturn { status: 403, body: { error: 'Only the owner can invite admin members.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst invitation = await this.store.createInvitation(orgId, userId, {\n\t\t\t\temail: params.email.trim(),\n\t\t\t\trole: params.role as OrgRole,\n\t\t\t})\n\t\t\treturn { status: 201, body: { data: invitation } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Accept an invitation by its token. The authenticated user joins the org.\n\t */\n\tasync acceptInvitation(\n\t\tuserId: string,\n\t\tparams: { token?: unknown },\n\t): Promise<OrgRouteResponse<Membership>> {\n\t\tif (typeof params.token !== 'string' || params.token.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Invitation token is required.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst invitation = await this.store.consumeInvitation(params.token)\n\n\t\t\t// Add the user as a member with the invited role\n\t\t\tconst membership = await this.store.addMember(\n\t\t\t\tinvitation.orgId,\n\t\t\t\tuserId,\n\t\t\t\tinvitation.role,\n\t\t\t\tinvitation.invitedBy,\n\t\t\t)\n\t\t\treturn { status: 200, body: { data: membership } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof InvitationNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof InvitationExpiredError) {\n\t\t\t\treturn { status: 410, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MemberAlreadyExistsError) {\n\t\t\t\treturn { status: 409, body: { error: 'You are already a member of this organization.' } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Revoke a pending invitation. Requires admin or higher.\n\t */\n\tasync revokeInvitation(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tinvitationId: string,\n\t): Promise<OrgRouteResponse<{ revoked: true }>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\ttry {\n\t\t\tawait this.store.revokeInvitation(invitationId)\n\t\t\treturn { status: 200, body: { data: { revoked: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof InvitationNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List pending invitations for an organization. Requires admin or higher.\n\t */\n\tasync listPendingInvitations(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<OrgInvitation[]>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\ttry {\n\t\t\tconst invitations = await this.store.listPendingInvitations(orgId)\n\t\t\treturn { status: 200, body: { data: invitations } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List pending invitations for the authenticated user's email.\n\t */\n\tasync listMyInvitations(\n\t\temail: string,\n\t): Promise<OrgRouteResponse<OrgInvitation[]>> {\n\t\tif (!isValidEmail(email)) {\n\t\t\treturn { status: 400, body: { error: 'A valid email address is required.' } }\n\t\t}\n\n\t\tconst invitations = await this.store.listInvitationsForEmail(email)\n\t\treturn { status: 200, body: { data: invitations } }\n\t}\n\n\t// --- Private helpers ---\n\n\t/**\n\t * Check if the caller has the required role in the org.\n\t * Returns an error response if not authorized, or null if authorized.\n\t */\n\tprivate async requireRole(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trequiredRole: OrgRole,\n\t): Promise<OrgRouteResponse<never> | null> {\n\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\tif (!membership) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\t\tif (!hasRoleLevel(membership.role, requiredRole)) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: `This action requires at least the \"${requiredRole}\" role.` },\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst ASSIGNABLE_ROLES = new Set<string>(['admin', 'member', 'viewer', 'billing'])\n\nfunction isValidRole(role: string): boolean {\n\treturn ASSIGNABLE_ROLES.has(role) || role === 'owner'\n}\n","import type {\n\tOrganization,\n\tCreateOrgParams,\n\tUpdateOrgParams,\n\tMembership,\n\tOrgRole,\n\tOrgInvitation,\n\tCreateInvitationParams,\n\tInvitationStatus,\n} from './org-types'\nimport {\n\tOrgNotFoundError,\n\tOrgSlugTakenError,\n\tMembershipNotFoundError,\n\tMemberAlreadyExistsError,\n\tCannotRemoveOwnerError,\n\tInvitationNotFoundError,\n\tInvitationExpiredError,\n} from './org-types'\n\n// ============================================================================\n// OrgStore interface\n// ============================================================================\n\n/**\n * Persistence interface for organizations, memberships, and invitations.\n *\n * Implement this interface to back organizations with any storage:\n * - `InMemoryOrgStore` for development and testing\n * - PostgreSQL/MySQL via Drizzle for production\n * - SQLite for self-hosted or embedded scenarios\n *\n * All methods are async to support any storage backend.\n */\nexport interface OrgStore {\n\t// --- Organizations ---\n\n\t/** Create a new organization. The caller becomes the owner. */\n\tcreateOrg(ownerId: string, params: CreateOrgParams): Promise<Organization>\n\n\t/** Get an organization by ID. Returns null if not found. */\n\tgetOrg(orgId: string): Promise<Organization | null>\n\n\t/** Get an organization by slug. Returns null if not found. */\n\tgetOrgBySlug(slug: string): Promise<Organization | null>\n\n\t/** Update an organization's mutable fields. */\n\tupdateOrg(orgId: string, params: UpdateOrgParams): Promise<Organization>\n\n\t/** Delete an organization and all its memberships and invitations. */\n\tdeleteOrg(orgId: string): Promise<void>\n\n\t/** List all organizations a user is a member of. */\n\tlistUserOrgs(userId: string): Promise<Organization[]>\n\n\t// --- Memberships ---\n\n\t/** Add a user as a member of an organization. */\n\taddMember(orgId: string, userId: string, role: OrgRole, invitedBy: string | null): Promise<Membership>\n\n\t/** Remove a user from an organization. Cannot remove the owner. */\n\tremoveMember(orgId: string, userId: string): Promise<void>\n\n\t/** Update a member's role within an organization. */\n\tupdateMemberRole(orgId: string, userId: string, role: OrgRole): Promise<Membership>\n\n\t/** List all members of an organization. */\n\tlistMembers(orgId: string): Promise<Membership[]>\n\n\t/** Get a specific user's membership in an organization. Returns null if not a member. */\n\tgetMembership(orgId: string, userId: string): Promise<Membership | null>\n\n\t/** Transfer ownership of an organization to another member. */\n\ttransferOwnership(orgId: string, newOwnerId: string): Promise<void>\n\n\t// --- Invitations ---\n\n\t/** Create an invitation to join an organization. */\n\tcreateInvitation(orgId: string, invitedBy: string, params: CreateInvitationParams): Promise<OrgInvitation>\n\n\t/** Look up an invitation by its single-use token. Returns null if not found or already consumed. */\n\tgetInvitationByToken(token: string): Promise<OrgInvitation | null>\n\n\t/** Consume an invitation (mark as accepted). Returns the invitation details. */\n\tconsumeInvitation(token: string): Promise<OrgInvitation>\n\n\t/** Revoke a pending invitation. */\n\trevokeInvitation(invitationId: string): Promise<void>\n\n\t/** List pending invitations for an organization. */\n\tlistPendingInvitations(orgId: string): Promise<OrgInvitation[]>\n\n\t/** List pending invitations for a specific email address. */\n\tlistInvitationsForEmail(email: string): Promise<OrgInvitation[]>\n\n\t/** Remove expired invitations. Returns count of removed invitations. */\n\tcleanExpiredInvitations(): Promise<number>\n}\n\n// ============================================================================\n// InMemoryOrgStore\n// ============================================================================\n\n/**\n * In-memory implementation of OrgStore for development and testing.\n *\n * Data is lost when the process exits. For production, implement OrgStore\n * with a persistent backend (PostgreSQL, MySQL, SQLite via Drizzle).\n *\n * @example\n * ```typescript\n * const orgStore = new InMemoryOrgStore()\n * const org = await orgStore.createOrg('user-1', { name: 'Acme Inc', slug: 'acme' })\n * ```\n */\nexport class InMemoryOrgStore implements OrgStore {\n\tprivate orgs = new Map<string, Organization>()\n\tprivate memberships = new Map<string, Membership>()\n\tprivate invitations = new Map<string, OrgInvitation>()\n\n\t// --- Organizations ---\n\n\tasync createOrg(ownerId: string, params: CreateOrgParams): Promise<Organization> {\n\t\tconst slug = params.slug ?? slugify(params.name)\n\n\t\t// Check slug uniqueness\n\t\tfor (const org of this.orgs.values()) {\n\t\t\tif (org.slug === slug) {\n\t\t\t\tthrow new OrgSlugTakenError()\n\t\t\t}\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst org: Organization = {\n\t\t\tid: generateId(),\n\t\t\tname: params.name,\n\t\t\tslug,\n\t\t\townerId,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t\tmetadata: params.metadata ?? {},\n\t\t}\n\n\t\tthis.orgs.set(org.id, org)\n\n\t\t// Add the creator as owner\n\t\tawait this.addMember(org.id, ownerId, 'owner', null)\n\n\t\treturn org\n\t}\n\n\tasync getOrg(orgId: string): Promise<Organization | null> {\n\t\treturn this.orgs.get(orgId) ?? null\n\t}\n\n\tasync getOrgBySlug(slug: string): Promise<Organization | null> {\n\t\tfor (const org of this.orgs.values()) {\n\t\t\tif (org.slug === slug) return org\n\t\t}\n\t\treturn null\n\t}\n\n\tasync updateOrg(orgId: string, params: UpdateOrgParams): Promise<Organization> {\n\t\tconst org = this.orgs.get(orgId)\n\t\tif (!org) throw new OrgNotFoundError()\n\n\t\tif (params.slug !== undefined && params.slug !== org.slug) {\n\t\t\t// Check slug uniqueness\n\t\t\tfor (const existing of this.orgs.values()) {\n\t\t\t\tif (existing.slug === params.slug && existing.id !== orgId) {\n\t\t\t\t\tthrow new OrgSlugTakenError()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst updated: Organization = {\n\t\t\t...org,\n\t\t\tname: params.name ?? org.name,\n\t\t\tslug: params.slug ?? org.slug,\n\t\t\tupdatedAt: Date.now(),\n\t\t\tmetadata: params.metadata\n\t\t\t\t? { ...org.metadata, ...params.metadata }\n\t\t\t\t: org.metadata,\n\t\t}\n\n\t\tthis.orgs.set(orgId, updated)\n\t\treturn updated\n\t}\n\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\t// Remove all memberships\n\t\tfor (const [id, membership] of this.memberships) {\n\t\t\tif (membership.orgId === orgId) {\n\t\t\t\tthis.memberships.delete(id)\n\t\t\t}\n\t\t}\n\n\t\t// Remove all invitations\n\t\tfor (const [id, invitation] of this.invitations) {\n\t\t\tif (invitation.orgId === orgId) {\n\t\t\t\tthis.invitations.delete(id)\n\t\t\t}\n\t\t}\n\n\t\tthis.orgs.delete(orgId)\n\t}\n\n\tasync listUserOrgs(userId: string): Promise<Organization[]> {\n\t\tconst orgIds = new Set<string>()\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.userId === userId) {\n\t\t\t\torgIds.add(membership.orgId)\n\t\t\t}\n\t\t}\n\n\t\tconst result: Organization[] = []\n\t\tfor (const orgId of orgIds) {\n\t\t\tconst org = this.orgs.get(orgId)\n\t\t\tif (org) result.push(org)\n\t\t}\n\t\treturn result\n\t}\n\n\t// --- Memberships ---\n\n\tasync addMember(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trole: OrgRole,\n\t\tinvitedBy: string | null,\n\t): Promise<Membership> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\t// Check if already a member\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\tthrow new MemberAlreadyExistsError()\n\t\t\t}\n\t\t}\n\n\t\tconst membership: Membership = {\n\t\t\tid: generateId(),\n\t\t\torgId,\n\t\t\tuserId,\n\t\t\trole,\n\t\t\tinvitedBy,\n\t\t\tjoinedAt: Date.now(),\n\t\t\tmetadata: {},\n\t\t}\n\n\t\tthis.memberships.set(membership.id, membership)\n\t\treturn membership\n\t}\n\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tconst org = this.orgs.get(orgId)\n\t\tif (!org) throw new OrgNotFoundError()\n\n\t\tif (org.ownerId === userId) {\n\t\t\tthrow new CannotRemoveOwnerError()\n\t\t}\n\n\t\tlet found = false\n\t\tfor (const [id, membership] of this.memberships) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\tthis.memberships.delete(id)\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (!found) throw new MembershipNotFoundError()\n\t}\n\n\tasync updateMemberRole(orgId: string, userId: string, role: OrgRole): Promise<Membership> {\n\t\tfor (const [id, membership] of this.memberships) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\tconst updated = { ...membership, role }\n\t\t\t\tthis.memberships.set(id, updated)\n\n\t\t\t\t// If promoting to owner, update the org's ownerId and demote previous owner\n\t\t\t\tif (role === 'owner') {\n\t\t\t\t\tconst org = this.orgs.get(orgId)\n\t\t\t\t\tif (org) {\n\t\t\t\t\t\t// Demote previous owner to admin\n\t\t\t\t\t\tfor (const [mId, m] of this.memberships) {\n\t\t\t\t\t\t\tif (m.orgId === orgId && m.userId === org.ownerId && m.userId !== userId) {\n\t\t\t\t\t\t\t\tthis.memberships.set(mId, { ...m, role: 'admin' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.orgs.set(orgId, { ...org, ownerId: userId, updatedAt: Date.now() })\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn updated\n\t\t\t}\n\t\t}\n\n\t\tthrow new MembershipNotFoundError()\n\t}\n\n\tasync listMembers(orgId: string): Promise<Membership[]> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\tconst result: Membership[] = []\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.orgId === orgId) {\n\t\t\t\tresult.push(membership)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tasync getMembership(orgId: string, userId: string): Promise<Membership | null> {\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\treturn membership\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tconst org = this.orgs.get(orgId)\n\t\tif (!org) throw new OrgNotFoundError()\n\n\t\t// Verify new owner is a member\n\t\tconst membership = await this.getMembership(orgId, newOwnerId)\n\t\tif (!membership) throw new MembershipNotFoundError()\n\n\t\t// Demote current owner to admin\n\t\tawait this.updateMemberRole(orgId, org.ownerId, 'admin')\n\n\t\t// Promote new owner\n\t\tawait this.updateMemberRole(orgId, newOwnerId, 'owner')\n\t}\n\n\t// --- Invitations ---\n\n\tasync createInvitation(\n\t\torgId: string,\n\t\tinvitedBy: string,\n\t\tparams: CreateInvitationParams,\n\t): Promise<OrgInvitation> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\tconst now = Date.now()\n\t\tconst invitation: OrgInvitation = {\n\t\t\tid: generateId(),\n\t\t\torgId,\n\t\t\temail: params.email.toLowerCase().trim(),\n\t\t\trole: params.role,\n\t\t\tinvitedBy,\n\t\t\ttoken: generateToken(),\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + 7 * 24 * 60 * 60 * 1000, // 7 days\n\t\t\tstatus: 'pending',\n\t\t}\n\n\t\tthis.invitations.set(invitation.id, invitation)\n\t\treturn invitation\n\t}\n\n\tasync getInvitationByToken(token: string): Promise<OrgInvitation | null> {\n\t\tfor (const invitation of this.invitations.values()) {\n\t\t\tif (invitation.token === token && invitation.status === 'pending') {\n\t\t\t\treturn invitation\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tasync consumeInvitation(token: string): Promise<OrgInvitation> {\n\t\tfor (const [id, invitation] of this.invitations) {\n\t\t\tif (invitation.token === token) {\n\t\t\t\tif (invitation.status !== 'pending') {\n\t\t\t\t\tthrow new InvitationNotFoundError()\n\t\t\t\t}\n\t\t\t\tif (Date.now() > invitation.expiresAt) {\n\t\t\t\t\tthis.invitations.set(id, { ...invitation, status: 'expired' })\n\t\t\t\t\tthrow new InvitationExpiredError()\n\t\t\t\t}\n\n\t\t\t\tconst consumed = { ...invitation, status: 'accepted' as InvitationStatus }\n\t\t\t\tthis.invitations.set(id, consumed)\n\t\t\t\treturn consumed\n\t\t\t}\n\t\t}\n\n\t\tthrow new InvitationNotFoundError()\n\t}\n\n\tasync revokeInvitation(invitationId: string): Promise<void> {\n\t\tconst invitation = this.invitations.get(invitationId)\n\t\tif (!invitation || invitation.status !== 'pending') {\n\t\t\tthrow new InvitationNotFoundError()\n\t\t}\n\t\tthis.invitations.set(invitationId, { ...invitation, status: 'revoked' })\n\t}\n\n\tasync listPendingInvitations(orgId: string): Promise<OrgInvitation[]> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\tconst result: OrgInvitation[] = []\n\t\tconst now = Date.now()\n\t\tfor (const invitation of this.invitations.values()) {\n\t\t\tif (invitation.orgId === orgId && invitation.status === 'pending') {\n\t\t\t\tif (now > invitation.expiresAt) {\n\t\t\t\t\t// Auto-expire\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tresult.push(invitation)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tasync listInvitationsForEmail(email: string): Promise<OrgInvitation[]> {\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\t\tconst result: OrgInvitation[] = []\n\t\tconst now = Date.now()\n\t\tfor (const invitation of this.invitations.values()) {\n\t\t\tif (\n\t\t\t\tinvitation.email === normalizedEmail &&\n\t\t\t\tinvitation.status === 'pending' &&\n\t\t\t\tnow <= invitation.expiresAt\n\t\t\t) {\n\t\t\t\tresult.push(invitation)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tasync cleanExpiredInvitations(): Promise<number> {\n\t\tlet count = 0\n\t\tconst now = Date.now()\n\t\tfor (const [id, invitation] of this.invitations) {\n\t\t\tif (invitation.status === 'pending' && now > invitation.expiresAt) {\n\t\t\t\tthis.invitations.set(id, { ...invitation, status: 'expired' })\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\nfunction generateId(): string {\n\treturn globalThis.crypto.randomUUID()\n}\n\nfunction generateToken(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Convert a name to a URL-friendly slug.\n * Lowercase, replace spaces/special chars with hyphens, collapse multiple hyphens.\n */\nfunction slugify(name: string): string {\n\treturn name\n\t\t.toLowerCase()\n\t\t.trim()\n\t\t.replace(/[^a-z0-9\\s-]/g, '')\n\t\t.replace(/[\\s]+/g, '-')\n\t\t.replace(/-+/g, '-')\n\t\t.replace(/^-|-$/g, '')\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Permissions\n// ============================================================================\n\n/**\n * A permission is a `resource:action` string.\n *\n * Resources are typically collection names or system resources.\n * Actions are the operations allowed on that resource.\n *\n * @example\n * ```typescript\n * const permission: Permission = 'todos:write'\n * const adminPerm: Permission = 'org:manage-members'\n * const wildcard: Permission = 'todos:*' // all actions on todos\n * const superAdmin: Permission = '*:*' // all actions on all resources\n * ```\n */\nexport type Permission = `${string}:${string}`\n\n/**\n * Parse a permission string into its resource and action parts.\n */\nexport function parsePermission(permission: Permission): { resource: string; action: string } {\n\tconst colonIndex = permission.indexOf(':')\n\tif (colonIndex === -1) {\n\t\tthrow new InvalidPermissionError(permission)\n\t}\n\tconst resource = permission.slice(0, colonIndex)\n\tconst action = permission.slice(colonIndex + 1)\n\tif (resource.length === 0 || action.length === 0) {\n\t\tthrow new InvalidPermissionError(permission)\n\t}\n\treturn { resource, action }\n}\n\n/**\n * Check if a granted permission covers a required permission.\n * Supports wildcards: `todos:*` covers `todos:read`, `*:*` covers everything.\n */\nexport function permissionCovers(granted: Permission, required: Permission): boolean {\n\tconst g = parsePermission(granted)\n\tconst r = parsePermission(required)\n\n\tconst resourceMatch = g.resource === '*' || g.resource === r.resource\n\tconst actionMatch = g.action === '*' || g.action === r.action\n\treturn resourceMatch && actionMatch\n}\n\n// ============================================================================\n// Role Definitions\n// ============================================================================\n\n/**\n * A role definition describes a named set of permissions with optional inheritance.\n *\n * Roles form a hierarchy via `inherits`. When evaluating permissions,\n * all inherited roles' permissions are included transitively.\n *\n * @example\n * ```typescript\n * const roles: RoleDefinition[] = [\n * { name: 'viewer', permissions: ['todos:read', 'projects:read'] },\n * { name: 'member', permissions: ['todos:write', 'projects:write'], inherits: ['viewer'] },\n * { name: 'admin', permissions: ['org:manage-members'], inherits: ['member'] },\n * ]\n * ```\n */\nexport interface RoleDefinition {\n\t/** Unique name for this role */\n\tname: string\n\t/** Permissions directly granted to this role */\n\tpermissions: Permission[]\n\t/** Roles this role inherits from (all their permissions are included) */\n\tinherits?: string[]\n}\n\n// ============================================================================\n// Built-in Roles\n// ============================================================================\n\n/**\n * Default built-in roles for organizations.\n *\n * These can be overridden or extended using `defineRoles()`.\n */\nexport const BUILT_IN_ROLES: readonly RoleDefinition[] = [\n\t{\n\t\tname: 'viewer',\n\t\tpermissions: ['*:read'],\n\t},\n\t{\n\t\tname: 'billing',\n\t\tpermissions: ['org:billing'],\n\t},\n\t{\n\t\tname: 'member',\n\t\tpermissions: ['*:write', '*:delete'],\n\t\tinherits: ['viewer'],\n\t},\n\t{\n\t\tname: 'admin',\n\t\tpermissions: ['org:manage-members', 'org:manage-settings', 'org:manage-invitations'],\n\t\tinherits: ['member'],\n\t},\n\t{\n\t\tname: 'owner',\n\t\tpermissions: ['*:*'],\n\t},\n] as const\n\n// ============================================================================\n// RBAC Configuration\n// ============================================================================\n\n/**\n * Configuration for the RBAC engine.\n */\nexport interface RbacConfig {\n\t/** Role definitions. Defaults to BUILT_IN_ROLES if not provided. */\n\troles?: RoleDefinition[]\n}\n\n// ============================================================================\n// Scope Types\n// ============================================================================\n\n/**\n * A sync scope filter for a single collection.\n * The keys are field names and values are the required values.\n * A special `__readonly` key can be set to restrict to read-only access.\n *\n * @example\n * ```typescript\n * // Only sync todos belonging to the user within the org\n * const scope: ScopeFilter = { orgId: 'org-123', userId: 'user-456' }\n *\n * // Read-only scope for viewers\n * const readOnlyScope: ScopeFilter = { orgId: 'org-123', __readonly: true }\n * ```\n */\nexport interface ScopeFilter {\n\t[field: string]: unknown\n}\n\n/**\n * A complete set of sync scopes, keyed by collection name.\n *\n * @example\n * ```typescript\n * const scopes: SyncScopes = {\n * todos: { orgId: 'org-123' },\n * projects: { orgId: 'org-123' },\n * }\n * ```\n */\nexport type SyncScopes = Record<string, ScopeFilter>\n\n/**\n * Custom scope resolver for a specific collection.\n * Given the user context, returns the scope filter for that collection.\n */\nexport type CollectionScopeResolver = (ctx: ScopeContext) => ScopeFilter | null\n\n/**\n * Context passed to scope resolvers.\n */\nexport interface ScopeContext {\n\tuserId: string\n\torgId: string\n\trole: string\n\tpermissions: Permission[]\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class RbacError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'RbacError'\n\t}\n}\n\nexport class InvalidPermissionError extends RbacError {\n\tconstructor(permission: string) {\n\t\tsuper(\n\t\t\t`Invalid permission format: \"${permission}\". Expected \"resource:action\".`,\n\t\t\t'INVALID_PERMISSION',\n\t\t\t{ permission },\n\t\t)\n\t}\n}\n\nexport class RoleNotFoundError extends RbacError {\n\tconstructor(role: string) {\n\t\tsuper(\n\t\t\t`Role \"${role}\" is not defined.`,\n\t\t\t'ROLE_NOT_FOUND',\n\t\t\t{ role },\n\t\t)\n\t}\n}\n\nexport class CircularInheritanceError extends RbacError {\n\tconstructor(chain: string[]) {\n\t\tsuper(\n\t\t\t`Circular role inheritance detected: ${chain.join(' → ')}.`,\n\t\t\t'CIRCULAR_INHERITANCE',\n\t\t\t{ chain },\n\t\t)\n\t}\n}\n","import type { OrgStore } from '../org/org-store'\nimport type {\n\tPermission,\n\tRoleDefinition,\n\tRbacConfig,\n\tSyncScopes,\n\tScopeFilter,\n\tScopeContext,\n\tCollectionScopeResolver,\n} from './rbac-types'\nimport {\n\tBUILT_IN_ROLES,\n\tpermissionCovers,\n\tRoleNotFoundError,\n\tCircularInheritanceError,\n} from './rbac-types'\n\n// ============================================================================\n// RbacEngine\n// ============================================================================\n\n/**\n * Permission evaluation engine for role-based access control.\n *\n * The engine resolves permissions through role inheritance, supports\n * wildcard matching, and integrates with the OrgStore for membership lookups.\n *\n * @example\n * ```typescript\n * const rbac = new RbacEngine({ orgStore })\n *\n * // Check a permission\n * const canWrite = await rbac.hasPermission('user-1', 'org-1', 'todos:write')\n *\n * // Get all permissions for a user in an org\n * const perms = await rbac.getUserPermissions('user-1', 'org-1')\n *\n * // Resolve sync scopes\n * const scopes = await rbac.resolveScopes('user-1', 'org-1')\n * ```\n */\nexport class RbacEngine {\n\tprivate readonly orgStore: OrgStore\n\tprivate readonly roleMap: Map<string, RoleDefinition>\n\tprivate readonly resolvedPermissions = new Map<string, Permission[]>()\n\tprivate readonly collectionResolvers = new Map<string, CollectionScopeResolver>()\n\n\tconstructor(orgStore: OrgStore, config?: RbacConfig) {\n\t\tthis.orgStore = orgStore\n\n\t\tconst roles = config?.roles ?? [...BUILT_IN_ROLES]\n\t\tthis.roleMap = new Map()\n\t\tfor (const role of roles) {\n\t\t\tthis.roleMap.set(role.name, role)\n\t\t}\n\n\t\t// Validate and pre-resolve all role permissions\n\t\tthis.validateRoles()\n\t}\n\n\t// --- Permission Checks ---\n\n\t/**\n\t * Check if a user has a specific permission in an organization.\n\t *\n\t * Resolves the user's role from the org membership, then evaluates\n\t * the role's permissions (including inherited ones) against the required permission.\n\t *\n\t * Returns false (not an error) if the user is not a member.\n\t */\n\tasync hasPermission(userId: string, orgId: string, permission: Permission): Promise<boolean> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return false\n\n\t\tconst rolePerms = this.getRolePermissions(membership.role)\n\t\treturn rolePerms.some((granted) => permissionCovers(granted, permission))\n\t}\n\n\t/**\n\t * Get all effective permissions for a user in an organization.\n\t *\n\t * Returns an empty array if the user is not a member.\n\t */\n\tasync getUserPermissions(userId: string, orgId: string): Promise<Permission[]> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return []\n\n\t\treturn this.getRolePermissions(membership.role)\n\t}\n\n\t/**\n\t * Get all effective permissions for a role name.\n\t * Includes permissions from inherited roles.\n\t *\n\t * @throws {RoleNotFoundError} if the role is not defined\n\t */\n\tgetRolePermissions(roleName: string): Permission[] {\n\t\tconst cached = this.resolvedPermissions.get(roleName)\n\t\tif (cached) return cached\n\n\t\tif (!this.roleMap.has(roleName)) {\n\t\t\tthrow new RoleNotFoundError(roleName)\n\t\t}\n\n\t\tconst perms = this.resolvePermissionsForRole(roleName, new Set())\n\t\tthis.resolvedPermissions.set(roleName, perms)\n\t\treturn perms\n\t}\n\n\t/**\n\t * Check if a role has a specific permission.\n\t */\n\troleHasPermission(roleName: string, permission: Permission): boolean {\n\t\tconst perms = this.getRolePermissions(roleName)\n\t\treturn perms.some((granted) => permissionCovers(granted, permission))\n\t}\n\n\t// --- Scope Resolution ---\n\n\t/**\n\t * Register a custom scope resolver for a collection.\n\t *\n\t * Custom resolvers override the default scope logic for that collection.\n\t */\n\tregisterScopeResolver(collection: string, resolver: CollectionScopeResolver): void {\n\t\tthis.collectionResolvers.set(collection, resolver)\n\t}\n\n\t/**\n\t * Resolve sync scopes for a user in an organization.\n\t *\n\t * The scopes determine what data the user can see and modify during sync.\n\t * - Owner/Admin: all org data\n\t * - Member: all org data (read + write)\n\t * - Viewer: all org data (read-only)\n\t * - Billing: no data scopes (billing-only access)\n\t *\n\t * Custom collection resolvers can override the defaults.\n\t *\n\t * @param collections - List of collection names to resolve scopes for.\n\t * If not provided, only custom-registered collections are included.\n\t */\n\tasync resolveScopes(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollections?: string[],\n\t): Promise<SyncScopes | null> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return null\n\n\t\tconst permissions = this.getRolePermissions(membership.role)\n\t\tconst ctx: ScopeContext = {\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\trole: membership.role,\n\t\t\tpermissions,\n\t\t}\n\n\t\tconst scopes: SyncScopes = {}\n\n\t\t// Check if user has any data permissions at all\n\t\tconst hasAnyRead = permissions.some(\n\t\t\t(p) => permissionCovers(p, '*:read' as Permission),\n\t\t)\n\t\tif (!hasAnyRead) {\n\t\t\t// No data access (e.g., billing-only role)\n\t\t\treturn scopes\n\t\t}\n\n\t\tconst isReadOnly = !permissions.some(\n\t\t\t(p) => permissionCovers(p, '*:write' as Permission),\n\t\t)\n\n\t\tconst collectionsToResolve = collections ?? [...this.collectionResolvers.keys()]\n\n\t\tfor (const collection of collectionsToResolve) {\n\t\t\t// Check custom resolver first\n\t\t\tconst customResolver = this.collectionResolvers.get(collection)\n\t\t\tif (customResolver) {\n\t\t\t\tconst customScope = customResolver(ctx)\n\t\t\t\tif (customScope) {\n\t\t\t\t\tscopes[collection] = customScope\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Default scope: filter by orgId\n\t\t\tconst scope: ScopeFilter = { orgId }\n\t\t\tif (isReadOnly) {\n\t\t\t\tscope.__readonly = true\n\t\t\t}\n\t\t\tscopes[collection] = scope\n\t\t}\n\n\t\treturn scopes\n\t}\n\n\t// --- Role Management ---\n\n\t/**\n\t * Get all defined role names.\n\t */\n\tgetRoleNames(): string[] {\n\t\treturn [...this.roleMap.keys()]\n\t}\n\n\t/**\n\t * Get a role definition by name.\n\t */\n\tgetRoleDefinition(roleName: string): RoleDefinition | null {\n\t\treturn this.roleMap.get(roleName) ?? null\n\t}\n\n\t// --- Private ---\n\n\t/**\n\t * Validate all role definitions for circular inheritance and unknown references.\n\t */\n\tprivate validateRoles(): void {\n\t\tfor (const role of this.roleMap.values()) {\n\t\t\tif (role.inherits) {\n\t\t\t\tfor (const parent of role.inherits) {\n\t\t\t\t\tif (!this.roleMap.has(parent)) {\n\t\t\t\t\t\tthrow new RoleNotFoundError(parent)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check for circular inheritance\n\t\tfor (const role of this.roleMap.values()) {\n\t\t\tthis.detectCircularInheritance(role.name, new Set())\n\t\t}\n\t}\n\n\t/**\n\t * Detect circular inheritance in role hierarchy.\n\t */\n\tprivate detectCircularInheritance(roleName: string, visited: Set<string>): void {\n\t\tif (visited.has(roleName)) {\n\t\t\tthrow new CircularInheritanceError([...visited, roleName])\n\t\t}\n\t\tvisited.add(roleName)\n\n\t\tconst role = this.roleMap.get(roleName)\n\t\tif (role?.inherits) {\n\t\t\tfor (const parent of role.inherits) {\n\t\t\t\tthis.detectCircularInheritance(parent, new Set(visited))\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Recursively resolve all permissions for a role, including inherited permissions.\n\t */\n\tprivate resolvePermissionsForRole(roleName: string, visited: Set<string>): Permission[] {\n\t\tif (visited.has(roleName)) return []\n\t\tvisited.add(roleName)\n\n\t\tconst role = this.roleMap.get(roleName)\n\t\tif (!role) return []\n\n\t\tconst perms = new Set<Permission>(role.permissions)\n\n\t\tif (role.inherits) {\n\t\t\tfor (const parent of role.inherits) {\n\t\t\t\tconst parentPerms = this.resolvePermissionsForRole(parent, visited)\n\t\t\t\tfor (const p of parentPerms) {\n\t\t\t\t\tperms.add(p)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...perms]\n\t}\n}\n\n// ============================================================================\n// defineRoles builder\n// ============================================================================\n\n/**\n * Builder for defining custom roles.\n *\n * @example\n * ```typescript\n * const roles = defineRoles()\n * .role('viewer', ['*:read'])\n * .role('editor', ['*:write'], { inherits: ['viewer'] })\n * .role('admin', ['org:manage-members'], { inherits: ['editor'] })\n * .build()\n * ```\n */\nexport function defineRoles(): RoleBuilder {\n\treturn new RoleBuilder()\n}\n\nclass RoleBuilder {\n\tprivate roles: RoleDefinition[] = []\n\n\t/**\n\t * Add a role definition.\n\t */\n\trole(\n\t\tname: string,\n\t\tpermissions: Permission[],\n\t\toptions?: { inherits?: string[] },\n\t): RoleBuilder {\n\t\tthis.roles.push({\n\t\t\tname,\n\t\t\tpermissions,\n\t\t\tinherits: options?.inherits,\n\t\t})\n\t\treturn this\n\t}\n\n\t/**\n\t * Include the built-in roles as a base.\n\t */\n\twithBuiltInRoles(): RoleBuilder {\n\t\tthis.roles = [...BUILT_IN_ROLES, ...this.roles]\n\t\treturn this\n\t}\n\n\t/**\n\t * Build and return the role definitions array.\n\t */\n\tbuild(): RoleDefinition[] {\n\t\treturn [...this.roles]\n\t}\n}\n","import type { OrgStore } from '../org/org-store'\nimport type { OrgRole } from '../org/org-types'\nimport type {\n\tPermission,\n\tSyncScopes,\n\tScopeFilter,\n\tScopeContext,\n\tCollectionScopeResolver,\n} from './rbac-types'\nimport { permissionCovers } from './rbac-types'\nimport { RbacEngine } from './rbac-engine'\n\n// ============================================================================\n// OrgScopeResolver\n// ============================================================================\n\n/**\n * Resolves sync scopes for org-aware data filtering.\n *\n * Given a user, an organization, and a set of collections, this resolver\n * determines what data the user should receive during sync:\n *\n * - **Owner/Admin**: All data in the org (no field-level filtering)\n * - **Member**: All org data with read/write access\n * - **Viewer**: All org data with read-only flag\n * - **Billing**: No data access (empty scopes)\n *\n * Developers can register per-collection scope resolvers for fine-grained control.\n *\n * @example\n * ```typescript\n * const resolver = new OrgScopeResolver(orgStore, rbacEngine)\n *\n * // Custom scope: members only see their own todos\n * resolver.registerCollectionScope('todos', (ctx) => {\n * if (ctx.role === 'member') {\n * return { orgId: ctx.orgId, userId: ctx.userId }\n * }\n * return { orgId: ctx.orgId } // admins/owners see all\n * })\n *\n * const scopes = await resolver.resolve('user-1', 'org-1', ['todos', 'projects'])\n * // { todos: { orgId: 'org-1', userId: 'user-1' }, projects: { orgId: 'org-1' } }\n * ```\n */\nexport class OrgScopeResolver {\n\tprivate readonly orgStore: OrgStore\n\tprivate readonly rbac: RbacEngine\n\tprivate readonly collectionScopes = new Map<string, CollectionScopeResolver>()\n\n\tconstructor(orgStore: OrgStore, rbac: RbacEngine) {\n\t\tthis.orgStore = orgStore\n\t\tthis.rbac = rbac\n\t}\n\n\t/**\n\t * Register a custom scope resolver for a collection.\n\t * This overrides the default orgId-based filtering for that collection.\n\t */\n\tregisterCollectionScope(collection: string, resolver: CollectionScopeResolver): void {\n\t\tthis.collectionScopes.set(collection, resolver)\n\t}\n\n\t/**\n\t * Resolve sync scopes for all specified collections.\n\t *\n\t * Returns null if the user is not a member of the organization.\n\t * Returns an empty object if the user has no data access (e.g., billing role).\n\t */\n\tasync resolve(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollections: string[],\n\t): Promise<SyncScopes | null> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return null\n\n\t\tconst permissions = this.rbac.getRolePermissions(membership.role)\n\t\tconst ctx: ScopeContext = {\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\trole: membership.role,\n\t\t\tpermissions,\n\t\t}\n\n\t\tconst scopes: SyncScopes = {}\n\n\t\tfor (const collection of collections) {\n\t\t\tconst scope = this.resolveCollectionScope(ctx, collection)\n\t\t\tif (scope) {\n\t\t\t\tscopes[collection] = scope\n\t\t\t}\n\t\t}\n\n\t\treturn scopes\n\t}\n\n\t/**\n\t * Check if a user can write to a specific collection in an org.\n\t */\n\tasync canWrite(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollection: string,\n\t): Promise<boolean> {\n\t\treturn this.rbac.hasPermission(\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\t`${collection}:write` as Permission,\n\t\t)\n\t}\n\n\t/**\n\t * Check if a user can read from a specific collection in an org.\n\t */\n\tasync canRead(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollection: string,\n\t): Promise<boolean> {\n\t\treturn this.rbac.hasPermission(\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\t`${collection}:read` as Permission,\n\t\t)\n\t}\n\n\t// --- Private ---\n\n\tprivate resolveCollectionScope(ctx: ScopeContext, collection: string): ScopeFilter | null {\n\t\t// Check if user has read permission for this collection\n\t\tconst canRead = ctx.permissions.some(\n\t\t\t(p) =>\n\t\t\t\tpermissionCovers(p, `${collection}:read` as Permission) ||\n\t\t\t\tpermissionCovers(p, '*:read' as Permission),\n\t\t)\n\t\tif (!canRead) return null\n\n\t\t// Check custom resolver\n\t\tconst customResolver = this.collectionScopes.get(collection)\n\t\tif (customResolver) {\n\t\t\treturn customResolver(ctx)\n\t\t}\n\n\t\t// Default scope: filter by orgId\n\t\tconst scope: ScopeFilter = { orgId: ctx.orgId }\n\n\t\t// Check if write access is available\n\t\tconst canWrite = ctx.permissions.some(\n\t\t\t(p) =>\n\t\t\t\tpermissionCovers(p, `${collection}:write` as Permission) ||\n\t\t\t\tpermissionCovers(p, '*:write' as Permission),\n\t\t)\n\t\tif (!canWrite) {\n\t\t\tscope.__readonly = true\n\t\t}\n\n\t\treturn scope\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// OAuth Provider Configuration\n// ============================================================================\n\n/**\n * Configuration for an OAuth 2.0 provider.\n */\nexport interface OAuthProviderConfig {\n\t/** Provider identifier (e.g., 'google', 'github', 'microsoft') */\n\tproviderId: string\n\t/** OAuth client ID */\n\tclientId: string\n\t/** OAuth client secret */\n\tclientSecret: string\n\t/** Authorization endpoint URL */\n\tauthorizationUrl: string\n\t/** Token exchange endpoint URL */\n\ttokenUrl: string\n\t/** User info endpoint URL */\n\tuserInfoUrl: string\n\t/** OAuth scopes to request */\n\tscopes: string[]\n\t/** Redirect URI for the callback */\n\tredirectUri: string\n}\n\n// ============================================================================\n// OAuth Tokens\n// ============================================================================\n\n/**\n * Tokens returned by the OAuth provider after code exchange.\n */\nexport interface OAuthTokens {\n\t/** OAuth access token */\n\taccessToken: string\n\t/** Token type (usually 'Bearer') */\n\ttokenType: string\n\t/** Access token expiry in seconds (if provided) */\n\texpiresIn?: number\n\t/** Refresh token (if provided) */\n\trefreshToken?: string\n\t/** ID token (if provided, e.g., OpenID Connect) */\n\tidToken?: string\n\t/** Granted scopes (may differ from requested scopes) */\n\tscope?: string\n}\n\n// ============================================================================\n// OAuth User Info\n// ============================================================================\n\n/**\n * User information from the OAuth provider.\n */\nexport interface OAuthUserInfo {\n\t/** Provider-specific user ID */\n\tproviderId: string\n\t/** Provider name (e.g., 'google', 'github') */\n\tprovider: string\n\t/** User's email address (may be null if not granted) */\n\temail: string | null\n\t/** Whether the email is verified by the provider */\n\temailVerified: boolean\n\t/** User's display name */\n\tname: string | null\n\t/** URL to the user's avatar/profile picture */\n\tavatarUrl: string | null\n\t/** Raw profile data from the provider */\n\trawProfile: Record<string, unknown>\n}\n\n// ============================================================================\n// OAuth State\n// ============================================================================\n\n/**\n * State stored during the OAuth flow for CSRF protection.\n */\nexport interface OAuthState {\n\t/** Random state parameter for CSRF protection */\n\tstate: string\n\t/** Provider ID */\n\tprovider: string\n\t/** Redirect URI used for this flow */\n\tredirectUri: string\n\t/** When this state was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When this state expires (ms since epoch) */\n\texpiresAt: number\n\t/** Optional: user-defined data to pass through the flow */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Store for OAuth state parameters.\n */\nexport interface OAuthStateStore {\n\t/** Store a state parameter for later validation. */\n\tstore(state: OAuthState): Promise<void>\n\t/** Consume a state parameter (single-use). Returns null if not found or expired. */\n\tconsume(stateValue: string): Promise<OAuthState | null>\n\t/** Clean up expired states. */\n\tcleanExpired(): Promise<number>\n}\n\n// ============================================================================\n// Linked Identity\n// ============================================================================\n\n/**\n * A linked OAuth identity for a user.\n * Users can have multiple linked identities (e.g., Google + GitHub).\n */\nexport interface LinkedIdentity {\n\t/** Unique ID of this link */\n\tid: string\n\t/** Kora user ID */\n\tuserId: string\n\t/** OAuth provider name */\n\tprovider: string\n\t/** Provider-specific user ID */\n\tproviderUserId: string\n\t/** Provider email (at time of linking) */\n\temail: string | null\n\t/** When this identity was linked */\n\tlinkedAt: number\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class OAuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OAuthError'\n\t}\n}\n\nexport class OAuthStateMismatchError extends OAuthError {\n\tconstructor() {\n\t\tsuper('OAuth state parameter does not match. Possible CSRF attack.', 'OAUTH_STATE_MISMATCH')\n\t}\n}\n\nexport class OAuthCodeExchangeError extends OAuthError {\n\tconstructor(details?: string) {\n\t\tsuper(\n\t\t\t`Failed to exchange authorization code for tokens.${details ? ` ${details}` : ''}`,\n\t\t\t'OAUTH_CODE_EXCHANGE_FAILED',\n\t\t\tdetails ? { details } : undefined,\n\t\t)\n\t}\n}\n\nexport class OAuthUserInfoError extends OAuthError {\n\tconstructor(details?: string) {\n\t\tsuper(\n\t\t\t`Failed to fetch user info from OAuth provider.${details ? ` ${details}` : ''}`,\n\t\t\t'OAUTH_USER_INFO_FAILED',\n\t\t\tdetails ? { details } : undefined,\n\t\t)\n\t}\n}\n\nexport class OAuthProviderNotFoundError extends OAuthError {\n\tconstructor(provider: string) {\n\t\tsuper(`OAuth provider \"${provider}\" is not configured.`, 'OAUTH_PROVIDER_NOT_FOUND', { provider })\n\t}\n}\n","import type {\n\tOAuthProviderConfig,\n\tOAuthTokens,\n\tOAuthUserInfo,\n\tOAuthState,\n\tOAuthStateStore,\n} from './oauth-types'\nimport {\n\tOAuthStateMismatchError,\n\tOAuthCodeExchangeError,\n\tOAuthUserInfoError,\n\tOAuthProviderNotFoundError,\n} from './oauth-types'\n\n// ============================================================================\n// InMemoryOAuthStateStore\n// ============================================================================\n\n/** Default state TTL: 10 minutes */\nconst DEFAULT_STATE_TTL_MS = 10 * 60 * 1000\n\n/**\n * In-memory OAuth state store for development.\n * Use Redis or a database in production for multi-server deployments.\n */\nexport class InMemoryOAuthStateStore implements OAuthStateStore {\n\tprivate readonly states = new Map<string, OAuthState>()\n\n\tasync store(state: OAuthState): Promise<void> {\n\t\tthis.states.set(state.state, state)\n\t}\n\n\tasync consume(stateValue: string): Promise<OAuthState | null> {\n\t\tconst state = this.states.get(stateValue)\n\t\tif (!state) return null\n\n\t\t// Single-use\n\t\tthis.states.delete(stateValue)\n\n\t\t// Check expiry\n\t\tif (Date.now() > state.expiresAt) return null\n\n\t\treturn state\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [key, state] of this.states) {\n\t\t\tif (now > state.expiresAt) {\n\t\t\t\tthis.states.delete(key)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// OAuthManager\n// ============================================================================\n\n/**\n * Configuration for the OAuth manager.\n */\nexport interface OAuthManagerConfig {\n\t/** Registered OAuth providers */\n\tproviders: OAuthProviderConfig[]\n\t/** State store. Defaults to InMemoryOAuthStateStore. */\n\tstateStore?: OAuthStateStore\n\t/** State TTL in milliseconds. Defaults to 10 minutes. */\n\tstateTtlMs?: number\n\t/**\n\t * Custom fetch function. Defaults to global fetch.\n\t * Useful for testing or custom HTTP clients.\n\t */\n\tfetch?: typeof globalThis.fetch\n}\n\n/**\n * Manages the OAuth 2.0 authorization code flow.\n *\n * Supports any standard OAuth 2.0 / OpenID Connect provider.\n * Pre-built configurations available for Google, GitHub, and Microsoft.\n *\n * @example\n * ```typescript\n * const oauth = new OAuthManager({\n * providers: [\n * googleProvider({ clientId: '...', clientSecret: '...', redirectUri: '...' }),\n * githubProvider({ clientId: '...', clientSecret: '...', redirectUri: '...' }),\n * ],\n * })\n *\n * // Step 1: Generate authorization URL\n * const { url, state } = await oauth.getAuthorizationUrl('google')\n * // Redirect user to url...\n *\n * // Step 2: Handle callback\n * const { tokens, userInfo } = await oauth.handleCallback('google', code, stateParam)\n * ```\n */\nexport class OAuthManager {\n\tprivate readonly providers = new Map<string, OAuthProviderConfig>()\n\tprivate readonly stateStore: OAuthStateStore\n\tprivate readonly stateTtlMs: number\n\tprivate readonly fetchFn: typeof globalThis.fetch\n\n\tconstructor(config: OAuthManagerConfig) {\n\t\tfor (const provider of config.providers) {\n\t\t\tthis.providers.set(provider.providerId, provider)\n\t\t}\n\t\tthis.stateStore = config.stateStore ?? new InMemoryOAuthStateStore()\n\t\tthis.stateTtlMs = config.stateTtlMs ?? DEFAULT_STATE_TTL_MS\n\t\tthis.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis)\n\t}\n\n\t/**\n\t * Generate an authorization URL for the user to visit.\n\t * Returns the URL and the state parameter for CSRF validation.\n\t */\n\tasync getAuthorizationUrl(\n\t\tproviderId: string,\n\t\tmetadata?: Record<string, unknown>,\n\t): Promise<{ url: string; state: string }> {\n\t\tconst provider = this.getProvider(providerId)\n\n\t\tconst state = generateState()\n\t\tconst now = Date.now()\n\n\t\tconst oauthState: OAuthState = {\n\t\t\tstate,\n\t\t\tprovider: providerId,\n\t\t\tredirectUri: provider.redirectUri,\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + this.stateTtlMs,\n\t\t\tmetadata,\n\t\t}\n\n\t\tawait this.stateStore.store(oauthState)\n\n\t\tconst params = new URLSearchParams({\n\t\t\tclient_id: provider.clientId,\n\t\t\tredirect_uri: provider.redirectUri,\n\t\t\tresponse_type: 'code',\n\t\t\tscope: provider.scopes.join(' '),\n\t\t\tstate,\n\t\t})\n\n\t\tconst url = `${provider.authorizationUrl}?${params.toString()}`\n\t\treturn { url, state }\n\t}\n\n\t/**\n\t * Handle the OAuth callback after the user authorizes.\n\t * Validates the state parameter, exchanges the code for tokens,\n\t * and fetches user info.\n\t *\n\t * @param providerId - The OAuth provider\n\t * @param code - The authorization code from the callback\n\t * @param state - The state parameter from the callback\n\t * @returns Tokens and user info from the provider\n\t */\n\tasync handleCallback(\n\t\tproviderId: string,\n\t\tcode: string,\n\t\tstate: string,\n\t): Promise<{ tokens: OAuthTokens; userInfo: OAuthUserInfo; stateMetadata?: Record<string, unknown> }> {\n\t\tconst provider = this.getProvider(providerId)\n\n\t\t// Validate state (CSRF protection)\n\t\tconst oauthState = await this.stateStore.consume(state)\n\t\tif (!oauthState || oauthState.provider !== providerId) {\n\t\t\tthrow new OAuthStateMismatchError()\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tconst tokens = await this.exchangeCodeForTokens(provider, code)\n\n\t\t// Fetch user info\n\t\tconst userInfo = await this.fetchUserInfo(provider, tokens.accessToken)\n\n\t\treturn { tokens, userInfo, stateMetadata: oauthState.metadata }\n\t}\n\n\t/**\n\t * Get a registered provider by ID.\n\t */\n\tgetProvider(providerId: string): OAuthProviderConfig {\n\t\tconst provider = this.providers.get(providerId)\n\t\tif (!provider) {\n\t\t\tthrow new OAuthProviderNotFoundError(providerId)\n\t\t}\n\t\treturn provider\n\t}\n\n\t/**\n\t * List all registered provider IDs.\n\t */\n\tgetProviderIds(): string[] {\n\t\treturn [...this.providers.keys()]\n\t}\n\n\t// --- Private ---\n\n\tprivate async exchangeCodeForTokens(\n\t\tprovider: OAuthProviderConfig,\n\t\tcode: string,\n\t): Promise<OAuthTokens> {\n\t\tconst body = new URLSearchParams({\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tredirect_uri: provider.redirectUri,\n\t\t\tclient_id: provider.clientId,\n\t\t\tclient_secret: provider.clientSecret,\n\t\t})\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(provider.tokenUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t},\n\t\t\t\tbody: body.toString(),\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tthrow new OAuthCodeExchangeError(\n\t\t\t\terr instanceof Error ? err.message : 'Network error',\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet details = `HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst errorBody = await response.text()\n\t\t\t\tdetails += `: ${errorBody}`\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t\tthrow new OAuthCodeExchangeError(details)\n\t\t}\n\n\t\tconst data = (await response.json()) as Record<string, unknown>\n\n\t\treturn {\n\t\t\taccessToken: data['access_token'] as string,\n\t\t\ttokenType: (data['token_type'] as string) ?? 'Bearer',\n\t\t\texpiresIn: data['expires_in'] as number | undefined,\n\t\t\trefreshToken: data['refresh_token'] as string | undefined,\n\t\t\tidToken: data['id_token'] as string | undefined,\n\t\t\tscope: data['scope'] as string | undefined,\n\t\t}\n\t}\n\n\tprivate async fetchUserInfo(\n\t\tprovider: OAuthProviderConfig,\n\t\taccessToken: string,\n\t): Promise<OAuthUserInfo> {\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(provider.userInfoUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t},\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tthrow new OAuthUserInfoError(\n\t\t\t\terr instanceof Error ? err.message : 'Network error',\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tthrow new OAuthUserInfoError(`HTTP ${response.status}`)\n\t\t}\n\n\t\tconst profile = (await response.json()) as Record<string, unknown>\n\n\t\t// Normalize user info based on provider\n\t\treturn normalizeUserInfo(provider.providerId, profile)\n\t}\n}\n\n// ============================================================================\n// User Info Normalization\n// ============================================================================\n\nfunction normalizeUserInfo(providerId: string, profile: Record<string, unknown>): OAuthUserInfo {\n\tswitch (providerId) {\n\t\tcase 'google':\n\t\t\treturn {\n\t\t\t\tproviderId: profile['sub'] as string,\n\t\t\t\tprovider: 'google',\n\t\t\t\temail: (profile['email'] as string) ?? null,\n\t\t\t\temailVerified: (profile['email_verified'] as boolean) ?? false,\n\t\t\t\tname: (profile['name'] as string) ?? null,\n\t\t\t\tavatarUrl: (profile['picture'] as string) ?? null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t\tcase 'github':\n\t\t\treturn {\n\t\t\t\tproviderId: String(profile['id']),\n\t\t\t\tprovider: 'github',\n\t\t\t\temail: (profile['email'] as string) ?? null,\n\t\t\t\temailVerified: false, // GitHub doesn't confirm in the profile response\n\t\t\t\tname: (profile['name'] as string) ?? (profile['login'] as string) ?? null,\n\t\t\t\tavatarUrl: (profile['avatar_url'] as string) ?? null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t\tcase 'microsoft':\n\t\t\treturn {\n\t\t\t\tproviderId: profile['id'] as string,\n\t\t\t\tprovider: 'microsoft',\n\t\t\t\temail: (profile['mail'] as string) ?? (profile['userPrincipalName'] as string) ?? null,\n\t\t\t\temailVerified: false,\n\t\t\t\tname: (profile['displayName'] as string) ?? null,\n\t\t\t\tavatarUrl: null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t\tdefault:\n\t\t\t// Generic normalization\n\t\t\treturn {\n\t\t\t\tproviderId: String(profile['id'] ?? profile['sub'] ?? ''),\n\t\t\t\tprovider: providerId,\n\t\t\t\temail: (profile['email'] as string) ?? null,\n\t\t\t\temailVerified: (profile['email_verified'] as boolean) ?? false,\n\t\t\t\tname: (profile['name'] as string) ?? null,\n\t\t\t\tavatarUrl: (profile['picture'] as string) ?? (profile['avatar_url'] as string) ?? null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t}\n}\n\n// ============================================================================\n// Provider Factories\n// ============================================================================\n\ninterface ProviderFactoryConfig {\n\tclientId: string\n\tclientSecret: string\n\tredirectUri: string\n\tscopes?: string[]\n}\n\n/**\n * Create a Google OAuth provider configuration.\n */\nexport function googleProvider(config: ProviderFactoryConfig): OAuthProviderConfig {\n\treturn {\n\t\tproviderId: 'google',\n\t\tclientId: config.clientId,\n\t\tclientSecret: config.clientSecret,\n\t\tauthorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',\n\t\ttokenUrl: 'https://oauth2.googleapis.com/token',\n\t\tuserInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',\n\t\tscopes: config.scopes ?? ['openid', 'email', 'profile'],\n\t\tredirectUri: config.redirectUri,\n\t}\n}\n\n/**\n * Create a GitHub OAuth provider configuration.\n */\nexport function githubProvider(config: ProviderFactoryConfig): OAuthProviderConfig {\n\treturn {\n\t\tproviderId: 'github',\n\t\tclientId: config.clientId,\n\t\tclientSecret: config.clientSecret,\n\t\tauthorizationUrl: 'https://github.com/login/oauth/authorize',\n\t\ttokenUrl: 'https://github.com/login/oauth/access_token',\n\t\tuserInfoUrl: 'https://api.github.com/user',\n\t\tscopes: config.scopes ?? ['read:user', 'user:email'],\n\t\tredirectUri: config.redirectUri,\n\t}\n}\n\n/**\n * Create a Microsoft OAuth provider configuration.\n */\nexport function microsoftProvider(\n\tconfig: ProviderFactoryConfig & { tenantId?: string },\n): OAuthProviderConfig {\n\tconst tenant = config.tenantId ?? 'common'\n\treturn {\n\t\tproviderId: 'microsoft',\n\t\tclientId: config.clientId,\n\t\tclientSecret: config.clientSecret,\n\t\tauthorizationUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,\n\t\ttokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,\n\t\tuserInfoUrl: 'https://graph.microsoft.com/v1.0/me',\n\t\tscopes: config.scopes ?? ['openid', 'email', 'profile', 'User.Read'],\n\t\tredirectUri: config.redirectUri,\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateState(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Session Types\n// ============================================================================\n\n/**\n * A user session with metadata.\n */\nexport interface Session {\n\t/** Unique session ID */\n\tid: string\n\t/** User ID */\n\tuserId: string\n\t/** Device ID (if available) */\n\tdeviceId: string | null\n\t/** IP address of the client (for display, not for security decisions) */\n\tipAddress: string | null\n\t/** User agent string */\n\tuserAgent: string | null\n\t/** When the session was created */\n\tcreatedAt: number\n\t/** When the session was last active */\n\tlastActiveAt: number\n\t/** When the session expires (absolute expiry) */\n\texpiresAt: number\n\t/** Whether MFA has been completed for this session */\n\tmfaVerified: boolean\n\t/** Custom metadata */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Configuration for the session manager.\n */\nexport interface SessionManagerConfig {\n\t/** Session store implementation */\n\tstore: SessionStore\n\t/** Session TTL in milliseconds. Default: 7 days */\n\tsessionTtlMs?: number\n\t/** Idle timeout in milliseconds. Default: 30 minutes */\n\tidleTimeoutMs?: number\n\t/** Maximum concurrent sessions per user. Default: 10 */\n\tmaxSessionsPerUser?: number\n\t/** Whether to extend session on activity. Default: true */\n\tslidingWindow?: boolean\n}\n\n/**\n * Parameters for creating a new session.\n */\nexport interface CreateSessionParams {\n\tuserId: string\n\tdeviceId?: string\n\tipAddress?: string\n\tuserAgent?: string\n\tmfaVerified?: boolean\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Store for session data.\n */\nexport interface SessionStore {\n\t/** Create a new session */\n\tcreate(session: Session): Promise<void>\n\t/** Get a session by ID */\n\tgetById(sessionId: string): Promise<Session | null>\n\t/** Update a session */\n\tupdate(session: Session): Promise<void>\n\t/** Delete a session */\n\tdelete(sessionId: string): Promise<void>\n\t/** Get all sessions for a user */\n\tlistByUserId(userId: string): Promise<Session[]>\n\t/** Delete all sessions for a user */\n\tdeleteAllForUser(userId: string): Promise<number>\n\t/** Delete all sessions for a user except one */\n\tdeleteAllExcept(userId: string, keepSessionId: string): Promise<number>\n\t/** Clean up expired sessions */\n\tcleanExpired(): Promise<number>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class SessionError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'SessionError'\n\t}\n}\n\nexport class SessionNotFoundError extends SessionError {\n\tconstructor(sessionId: string) {\n\t\tsuper('Session not found or expired.', 'SESSION_NOT_FOUND', { sessionId })\n\t}\n}\n\nexport class SessionExpiredError extends SessionError {\n\tconstructor(sessionId: string) {\n\t\tsuper('Session has expired.', 'SESSION_EXPIRED', { sessionId })\n\t}\n}\n\nexport class SessionLimitExceededError extends SessionError {\n\tconstructor(userId: string, limit: number) {\n\t\tsuper(\n\t\t\t`Maximum concurrent sessions (${limit}) reached. Revoke an existing session first.`,\n\t\t\t'SESSION_LIMIT_EXCEEDED',\n\t\t\t{ userId, limit },\n\t\t)\n\t}\n}\n\nexport class SessionMfaRequiredError extends SessionError {\n\tconstructor(sessionId: string) {\n\t\tsuper('MFA verification is required for this session.', 'SESSION_MFA_REQUIRED', { sessionId })\n\t}\n}\n\n// ============================================================================\n// InMemorySessionStore\n// ============================================================================\n\n/**\n * In-memory session store for development and testing.\n */\nexport class InMemorySessionStore implements SessionStore {\n\tprivate readonly sessions = new Map<string, Session>()\n\n\tasync create(session: Session): Promise<void> {\n\t\tthis.sessions.set(session.id, { ...session })\n\t}\n\n\tasync getById(sessionId: string): Promise<Session | null> {\n\t\tconst session = this.sessions.get(sessionId)\n\t\treturn session ? { ...session } : null\n\t}\n\n\tasync update(session: Session): Promise<void> {\n\t\tthis.sessions.set(session.id, { ...session })\n\t}\n\n\tasync delete(sessionId: string): Promise<void> {\n\t\tthis.sessions.delete(sessionId)\n\t}\n\n\tasync listByUserId(userId: string): Promise<Session[]> {\n\t\tconst results: Session[] = []\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tif (session.userId === userId) {\n\t\t\t\tresults.push({ ...session })\n\t\t\t}\n\t\t}\n\t\treturn results.sort((a, b) => b.lastActiveAt - a.lastActiveAt)\n\t}\n\n\tasync deleteAllForUser(userId: string): Promise<number> {\n\t\tlet count = 0\n\t\tfor (const [id, session] of this.sessions) {\n\t\t\tif (session.userId === userId) {\n\t\t\t\tthis.sessions.delete(id)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync deleteAllExcept(userId: string, keepSessionId: string): Promise<number> {\n\t\tlet count = 0\n\t\tfor (const [id, session] of this.sessions) {\n\t\t\tif (session.userId === userId && id !== keepSessionId) {\n\t\t\t\tthis.sessions.delete(id)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [id, session] of this.sessions) {\n\t\t\tif (now > session.expiresAt) {\n\t\t\t\tthis.sessions.delete(id)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// SessionManager\n// ============================================================================\n\nconst DEFAULT_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\nconst DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes\nconst DEFAULT_MAX_SESSIONS = 10\n\n/**\n * Manages user sessions with support for:\n * - Session creation, validation, and revocation\n * - Sliding window expiry (extends on activity)\n * - Idle timeout detection\n * - Max concurrent sessions per user\n * - MFA verification tracking\n * - \"Sign out everywhere\" support\n *\n * @example\n * ```typescript\n * const sessions = new SessionManager({\n * store: new InMemorySessionStore(),\n * sessionTtlMs: 7 * 24 * 60 * 60 * 1000, // 7 days\n * idleTimeoutMs: 30 * 60 * 1000, // 30 minutes\n * maxSessionsPerUser: 5,\n * })\n *\n * // Create session on login\n * const session = await sessions.create({\n * userId: 'user-123',\n * ipAddress: '192.168.1.1',\n * userAgent: 'Mozilla/5.0...',\n * })\n *\n * // Validate session on each request\n * const valid = await sessions.validate(session.id)\n *\n * // Touch session to extend idle timeout\n * await sessions.touch(session.id)\n * ```\n */\nexport class SessionManager {\n\tprivate readonly store: SessionStore\n\tprivate readonly sessionTtlMs: number\n\tprivate readonly idleTimeoutMs: number\n\tprivate readonly maxSessionsPerUser: number\n\tprivate readonly slidingWindow: boolean\n\n\tconstructor(config: SessionManagerConfig) {\n\t\tthis.store = config.store\n\t\tthis.sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS\n\t\tthis.idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS\n\t\tthis.maxSessionsPerUser = config.maxSessionsPerUser ?? DEFAULT_MAX_SESSIONS\n\t\tthis.slidingWindow = config.slidingWindow ?? true\n\t}\n\n\t/**\n\t * Create a new session for a user.\n\t * Enforces the maximum concurrent sessions limit.\n\t */\n\tasync create(params: CreateSessionParams): Promise<Session> {\n\t\t// Enforce max sessions\n\t\tconst existing = await this.store.listByUserId(params.userId)\n\t\tconst activeSessions = existing.filter((s) => Date.now() <= s.expiresAt)\n\n\t\tif (activeSessions.length >= this.maxSessionsPerUser) {\n\t\t\tthrow new SessionLimitExceededError(params.userId, this.maxSessionsPerUser)\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst session: Session = {\n\t\t\tid: generateSessionId(),\n\t\t\tuserId: params.userId,\n\t\t\tdeviceId: params.deviceId ?? null,\n\t\t\tipAddress: params.ipAddress ?? null,\n\t\t\tuserAgent: params.userAgent ?? null,\n\t\t\tcreatedAt: now,\n\t\t\tlastActiveAt: now,\n\t\t\texpiresAt: now + this.sessionTtlMs,\n\t\t\tmfaVerified: params.mfaVerified ?? false,\n\t\t\tmetadata: params.metadata,\n\t\t}\n\n\t\tawait this.store.create(session)\n\t\treturn session\n\t}\n\n\t/**\n\t * Validate a session.\n\t * Returns the session if valid, throws if not found or expired.\n\t */\n\tasync validate(sessionId: string): Promise<Session> {\n\t\tconst session = await this.store.getById(sessionId)\n\t\tif (!session) {\n\t\t\tthrow new SessionNotFoundError(sessionId)\n\t\t}\n\n\t\tconst now = Date.now()\n\n\t\t// Check absolute expiry\n\t\tif (now > session.expiresAt) {\n\t\t\tawait this.store.delete(sessionId)\n\t\t\tthrow new SessionExpiredError(sessionId)\n\t\t}\n\n\t\t// Check idle timeout\n\t\tif (now - session.lastActiveAt > this.idleTimeoutMs) {\n\t\t\tawait this.store.delete(sessionId)\n\t\t\tthrow new SessionExpiredError(sessionId)\n\t\t}\n\n\t\treturn session\n\t}\n\n\t/**\n\t * Touch a session to update its last activity time.\n\t * If sliding window is enabled, also extends the absolute expiry.\n\t */\n\tasync touch(sessionId: string): Promise<Session> {\n\t\tconst session = await this.validate(sessionId)\n\t\tconst now = Date.now()\n\n\t\tsession.lastActiveAt = now\n\n\t\tif (this.slidingWindow) {\n\t\t\tsession.expiresAt = now + this.sessionTtlMs\n\t\t}\n\n\t\tawait this.store.update(session)\n\t\treturn session\n\t}\n\n\t/**\n\t * Mark a session as MFA-verified.\n\t */\n\tasync markMfaVerified(sessionId: string): Promise<Session> {\n\t\tconst session = await this.validate(sessionId)\n\t\tsession.mfaVerified = true\n\t\tawait this.store.update(session)\n\t\treturn session\n\t}\n\n\t/**\n\t * Require MFA verification on a session.\n\t * Throws SessionMfaRequiredError if MFA is not verified.\n\t */\n\tasync requireMfa(sessionId: string): Promise<Session> {\n\t\tconst session = await this.validate(sessionId)\n\t\tif (!session.mfaVerified) {\n\t\t\tthrow new SessionMfaRequiredError(sessionId)\n\t\t}\n\t\treturn session\n\t}\n\n\t/**\n\t * Revoke (delete) a session.\n\t */\n\tasync revoke(sessionId: string): Promise<void> {\n\t\tawait this.store.delete(sessionId)\n\t}\n\n\t/**\n\t * Revoke all sessions for a user (sign out everywhere).\n\t * Returns the number of sessions revoked.\n\t */\n\tasync revokeAll(userId: string): Promise<number> {\n\t\treturn this.store.deleteAllForUser(userId)\n\t}\n\n\t/**\n\t * Revoke all sessions for a user except the current one.\n\t * Returns the number of sessions revoked.\n\t */\n\tasync revokeOthers(userId: string, currentSessionId: string): Promise<number> {\n\t\treturn this.store.deleteAllExcept(userId, currentSessionId)\n\t}\n\n\t/**\n\t * List all active sessions for a user.\n\t */\n\tasync listSessions(userId: string): Promise<Session[]> {\n\t\tconst sessions = await this.store.listByUserId(userId)\n\t\tconst now = Date.now()\n\t\t// Filter out expired sessions\n\t\treturn sessions.filter((s) => now <= s.expiresAt && now - s.lastActiveAt <= this.idleTimeoutMs)\n\t}\n\n\t/**\n\t * Clean up expired sessions.\n\t * Returns the number of sessions cleaned.\n\t */\n\tasync cleanExpired(): Promise<number> {\n\t\treturn this.store.cleanExpired()\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateSessionId(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// TOTP Types\n// ============================================================================\n\n/**\n * Configuration for TOTP MFA.\n */\nexport interface TotpConfig {\n\t/** Issuer name shown in authenticator apps (e.g., \"MyApp\") */\n\tissuer: string\n\t/** Number of digits in the TOTP code. Default: 6 */\n\tdigits?: number\n\t/** Time step in seconds. Default: 30 */\n\tperiod?: number\n\t/** Hash algorithm. Default: 'SHA-1' (most compatible with authenticator apps) */\n\talgorithm?: 'SHA-1' | 'SHA-256' | 'SHA-512'\n\t/** Number of time windows to check before/after current. Default: 1 */\n\twindow?: number\n\t/** Number of recovery codes to generate. Default: 8 */\n\trecoveryCodes?: number\n}\n\n/**\n * A TOTP secret with metadata for a user.\n */\nexport interface TotpSecret {\n\t/** User ID */\n\tuserId: string\n\t/** Raw secret bytes (base32-encoded for display) */\n\tsecret: string\n\t/** Whether MFA is verified (user has confirmed setup with a valid code) */\n\tverified: boolean\n\t/** Hashed recovery codes (unused ones only) */\n\trecoveryCodes: string[]\n\t/** When this secret was created */\n\tcreatedAt: number\n\t/** When MFA was verified (confirmed with first valid code) */\n\tverifiedAt: number | null\n}\n\n/**\n * Setup result returned when enabling TOTP MFA.\n */\nexport interface TotpSetupResult {\n\t/** The raw secret in base32 encoding (for manual entry) */\n\tsecret: string\n\t/** otpauth:// URI for QR code generation */\n\turi: string\n\t/** Plaintext recovery codes (shown once, then discarded) */\n\trecoveryCodes: string[]\n}\n\n/**\n * Store for TOTP secrets.\n */\nexport interface TotpStore {\n\t/** Save or update a TOTP secret for a user */\n\tsave(secret: TotpSecret): Promise<void>\n\t/** Get a TOTP secret by user ID */\n\tgetByUserId(userId: string): Promise<TotpSecret | null>\n\t/** Delete TOTP secret for a user (disable MFA) */\n\tdelete(userId: string): Promise<void>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class TotpError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'TotpError'\n\t}\n}\n\nexport class TotpInvalidCodeError extends TotpError {\n\tconstructor() {\n\t\tsuper('Invalid TOTP code.', 'TOTP_INVALID_CODE')\n\t}\n}\n\nexport class TotpNotEnabledError extends TotpError {\n\tconstructor(userId: string) {\n\t\tsuper('TOTP MFA is not enabled for this user.', 'TOTP_NOT_ENABLED', { userId })\n\t}\n}\n\nexport class TotpAlreadyEnabledError extends TotpError {\n\tconstructor(userId: string) {\n\t\tsuper('TOTP MFA is already enabled for this user.', 'TOTP_ALREADY_ENABLED', { userId })\n\t}\n}\n\nexport class TotpNotVerifiedError extends TotpError {\n\tconstructor(userId: string) {\n\t\tsuper(\n\t\t\t'TOTP MFA setup is pending verification. Verify with a valid code first.',\n\t\t\t'TOTP_NOT_VERIFIED',\n\t\t\t{ userId },\n\t\t)\n\t}\n}\n\nexport class TotpRecoveryExhaustedError extends TotpError {\n\tconstructor() {\n\t\tsuper('All recovery codes have been used. Please regenerate.', 'TOTP_RECOVERY_EXHAUSTED')\n\t}\n}\n\n// ============================================================================\n// InMemoryTotpStore\n// ============================================================================\n\n/**\n * In-memory TOTP store for development and testing.\n */\nexport class InMemoryTotpStore implements TotpStore {\n\tprivate readonly secrets = new Map<string, TotpSecret>()\n\n\tasync save(secret: TotpSecret): Promise<void> {\n\t\tthis.secrets.set(secret.userId, secret)\n\t}\n\n\tasync getByUserId(userId: string): Promise<TotpSecret | null> {\n\t\treturn this.secrets.get(userId) ?? null\n\t}\n\n\tasync delete(userId: string): Promise<void> {\n\t\tthis.secrets.delete(userId)\n\t}\n}\n\n// ============================================================================\n// TotpManager\n// ============================================================================\n\nconst DEFAULT_DIGITS = 6\nconst DEFAULT_PERIOD = 30\nconst DEFAULT_ALGORITHM = 'SHA-1'\nconst DEFAULT_WINDOW = 1\nconst DEFAULT_RECOVERY_CODES = 8\nconst RECOVERY_CODE_LENGTH = 10\n\n/**\n * Manages TOTP-based Multi-Factor Authentication.\n *\n * Implements RFC 6238 (TOTP) and RFC 4226 (HOTP) with Web Crypto API.\n * Compatible with Google Authenticator, Authy, 1Password, and other\n * TOTP-compatible authenticator apps.\n *\n * @example\n * ```typescript\n * const totp = new TotpManager({\n * issuer: 'MyApp',\n * store: new InMemoryTotpStore(),\n * })\n *\n * // Step 1: Enable MFA (returns QR code URI and recovery codes)\n * const setup = await totp.enable('user-123', 'alice@example.com')\n * // Show setup.uri as QR code, show setup.recoveryCodes once\n *\n * // Step 2: Verify setup with a code from authenticator app\n * await totp.verifySetup('user-123', '123456')\n *\n * // Step 3: On login, verify TOTP code\n * const valid = await totp.verify('user-123', '654321')\n * ```\n */\nexport class TotpManager {\n\tprivate readonly store: TotpStore\n\tprivate readonly issuer: string\n\tprivate readonly digits: number\n\tprivate readonly period: number\n\tprivate readonly algorithm: 'SHA-1' | 'SHA-256' | 'SHA-512'\n\tprivate readonly window: number\n\tprivate readonly recoveryCodeCount: number\n\n\tconstructor(config: TotpConfig & { store: TotpStore }) {\n\t\tthis.store = config.store\n\t\tthis.issuer = config.issuer\n\t\tthis.digits = config.digits ?? DEFAULT_DIGITS\n\t\tthis.period = config.period ?? DEFAULT_PERIOD\n\t\tthis.algorithm = config.algorithm ?? DEFAULT_ALGORITHM\n\t\tthis.window = config.window ?? DEFAULT_WINDOW\n\t\tthis.recoveryCodeCount = config.recoveryCodes ?? DEFAULT_RECOVERY_CODES\n\t}\n\n\t/**\n\t * Enable TOTP MFA for a user.\n\t * Returns the secret URI (for QR code) and recovery codes.\n\t * The user must verify setup with a valid code before MFA is active.\n\t */\n\tasync enable(userId: string, accountName: string): Promise<TotpSetupResult> {\n\t\tconst existing = await this.store.getByUserId(userId)\n\t\tif (existing?.verified) {\n\t\t\tthrow new TotpAlreadyEnabledError(userId)\n\t\t}\n\n\t\tconst secretBytes = generateSecret(20)\n\t\tconst secret = base32Encode(secretBytes)\n\t\tconst recoveryCodes = generateRecoveryCodes(this.recoveryCodeCount, RECOVERY_CODE_LENGTH)\n\t\tconst hashedCodes = await Promise.all(recoveryCodes.map((c) => hashRecoveryCode(c)))\n\n\t\tconst totpSecret: TotpSecret = {\n\t\t\tuserId,\n\t\t\tsecret,\n\t\t\tverified: false,\n\t\t\trecoveryCodes: hashedCodes,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tverifiedAt: null,\n\t\t}\n\n\t\tawait this.store.save(totpSecret)\n\n\t\tconst uri = buildOtpauthUri({\n\t\t\tissuer: this.issuer,\n\t\t\taccountName,\n\t\t\tsecret,\n\t\t\talgorithm: this.algorithm,\n\t\t\tdigits: this.digits,\n\t\t\tperiod: this.period,\n\t\t})\n\n\t\treturn { secret, uri, recoveryCodes }\n\t}\n\n\t/**\n\t * Verify the TOTP setup by confirming the user can generate a valid code.\n\t * Must be called after `enable()` and before MFA is enforced.\n\t */\n\tasync verifySetup(userId: string, code: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (stored.verified) {\n\t\t\t// Already verified, just validate the code\n\t\t\treturn this.validateCode(stored.secret, code)\n\t\t}\n\n\t\tconst valid = this.validateCode(stored.secret, code)\n\t\tif (!valid) {\n\t\t\tthrow new TotpInvalidCodeError()\n\t\t}\n\n\t\t// Mark as verified\n\t\tstored.verified = true\n\t\tstored.verifiedAt = Date.now()\n\t\tawait this.store.save(stored)\n\n\t\treturn true\n\t}\n\n\t/**\n\t * Verify a TOTP code during login.\n\t * Returns true if the code is valid, false otherwise.\n\t */\n\tasync verify(userId: string, code: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (!stored.verified) {\n\t\t\tthrow new TotpNotVerifiedError(userId)\n\t\t}\n\n\t\treturn this.validateCode(stored.secret, code)\n\t}\n\n\t/**\n\t * Verify a recovery code as an alternative to TOTP.\n\t * Recovery codes are single-use.\n\t */\n\tasync verifyRecoveryCode(userId: string, recoveryCode: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (!stored.verified) {\n\t\t\tthrow new TotpNotVerifiedError(userId)\n\t\t}\n\n\t\tconst hashed = await hashRecoveryCode(recoveryCode.trim())\n\n\t\tconst index = stored.recoveryCodes.indexOf(hashed)\n\t\tif (index === -1) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Consume the recovery code (single-use)\n\t\tstored.recoveryCodes.splice(index, 1)\n\t\tawait this.store.save(stored)\n\n\t\treturn true\n\t}\n\n\t/**\n\t * Regenerate recovery codes. Requires a valid TOTP code for authorization.\n\t * Replaces all existing recovery codes.\n\t */\n\tasync regenerateRecoveryCodes(userId: string, totpCode: string): Promise<string[]> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (!stored.verified) {\n\t\t\tthrow new TotpNotVerifiedError(userId)\n\t\t}\n\n\t\tconst valid = this.validateCode(stored.secret, totpCode)\n\t\tif (!valid) {\n\t\t\tthrow new TotpInvalidCodeError()\n\t\t}\n\n\t\tconst recoveryCodes = generateRecoveryCodes(this.recoveryCodeCount, RECOVERY_CODE_LENGTH)\n\t\tconst hashedCodes = await Promise.all(recoveryCodes.map((c) => hashRecoveryCode(c)))\n\n\t\tstored.recoveryCodes = hashedCodes\n\t\tawait this.store.save(stored)\n\n\t\treturn recoveryCodes\n\t}\n\n\t/**\n\t * Disable TOTP MFA for a user.\n\t * Requires a valid TOTP code or recovery code for authorization.\n\t */\n\tasync disable(userId: string, code: string): Promise<void> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\t// Accept either a TOTP code or a recovery code\n\t\tlet authorized = false\n\n\t\tif (stored.verified) {\n\t\t\tauthorized = this.validateCode(stored.secret, code)\n\t\t}\n\n\t\tif (!authorized) {\n\t\t\tconst hashed = await hashRecoveryCode(code.trim())\n\t\t\tauthorized = stored.recoveryCodes.includes(hashed)\n\t\t}\n\n\t\tif (!authorized) {\n\t\t\tthrow new TotpInvalidCodeError()\n\t\t}\n\n\t\tawait this.store.delete(userId)\n\t}\n\n\t/**\n\t * Check if a user has TOTP MFA enabled and verified.\n\t */\n\tasync isEnabled(userId: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\treturn stored !== null && stored.verified\n\t}\n\n\t/**\n\t * Get the number of remaining recovery codes for a user.\n\t */\n\tasync remainingRecoveryCodes(userId: string): Promise<number> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored || !stored.verified) return 0\n\t\treturn stored.recoveryCodes.length\n\t}\n\n\t// --- Private ---\n\n\tprivate validateCode(base32Secret: string, code: string): boolean {\n\t\tconst secretBytes = base32Decode(base32Secret)\n\t\tconst now = Math.floor(Date.now() / 1000)\n\n\t\t// Check current window and adjacent windows\n\t\tfor (let offset = -this.window; offset <= this.window; offset++) {\n\t\t\tconst timeCounter = Math.floor((now + offset * this.period) / this.period)\n\t\t\tconst expected = generateTotpCode(secretBytes, timeCounter, this.digits, this.algorithm)\n\t\t\tif (timingSafeEqual(code, expected)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t}\n}\n\n// ============================================================================\n// TOTP Core (RFC 6238 / RFC 4226)\n// ============================================================================\n\n/**\n * Generate a TOTP code for a given time counter.\n * Implements HOTP (RFC 4226) with a time-based counter (RFC 6238).\n */\nfunction generateTotpCode(\n\tsecret: Uint8Array,\n\tcounter: number,\n\tdigits: number,\n\talgorithm: string,\n): string {\n\t// Counter as 8-byte big-endian\n\tconst counterBytes = new Uint8Array(8)\n\tlet c = counter\n\tfor (let i = 7; i >= 0; i--) {\n\t\tcounterBytes[i] = c & 0xff\n\t\tc = Math.floor(c / 256)\n\t}\n\n\t// HMAC-SHA1 (or SHA-256/SHA-512)\n\tconst hash = hmacSha(algorithm, secret, counterBytes)\n\n\t// Dynamic truncation (RFC 4226 section 5.4)\n\tconst offset = hash[hash.length - 1]! & 0x0f\n\tconst binary =\n\t\t((hash[offset]! & 0x7f) << 24) |\n\t\t((hash[offset + 1]! & 0xff) << 16) |\n\t\t((hash[offset + 2]! & 0xff) << 8) |\n\t\t(hash[offset + 3]! & 0xff)\n\n\tconst otp = binary % Math.pow(10, digits)\n\treturn otp.toString().padStart(digits, '0')\n}\n\n/**\n * Synchronous HMAC using a simplified implementation.\n * TOTP only needs HMAC-SHA1 which we implement directly\n * to avoid async Web Crypto for the hot path (validation).\n */\nfunction hmacSha(algorithm: string, key: Uint8Array, message: Uint8Array): Uint8Array {\n\t// Use the appropriate block size and hash\n\tconst blockSize = algorithm === 'SHA-512' ? 128 : 64\n\n\t// Pad or hash the key\n\tlet keyPad = key\n\tif (keyPad.length > blockSize) {\n\t\tkeyPad = sha1(keyPad)\n\t}\n\n\t// Create padded key\n\tconst ipad = new Uint8Array(blockSize)\n\tconst opad = new Uint8Array(blockSize)\n\tfor (let i = 0; i < blockSize; i++) {\n\t\tconst k = i < keyPad.length ? keyPad[i]! : 0\n\t\tipad[i] = k ^ 0x36\n\t\topad[i] = k ^ 0x5c\n\t}\n\n\t// Inner hash: H(key XOR ipad || message)\n\tconst innerData = new Uint8Array(blockSize + message.length)\n\tinnerData.set(ipad)\n\tinnerData.set(message, blockSize)\n\tconst innerHash = sha1(innerData)\n\n\t// Outer hash: H(key XOR opad || inner_hash)\n\tconst outerData = new Uint8Array(blockSize + innerHash.length)\n\touterData.set(opad)\n\touterData.set(innerHash, blockSize)\n\treturn sha1(outerData)\n}\n\n/**\n * SHA-1 implementation for TOTP HMAC.\n * SHA-1 is the standard for TOTP (RFC 6238) and is NOT used for security\n * hashing here — it's used as a PRF inside HMAC which is still secure.\n */\nfunction sha1(data: Uint8Array): Uint8Array {\n\tlet h0 = 0x67452301\n\tlet h1 = 0xefcdab89\n\tlet h2 = 0x98badcfe\n\tlet h3 = 0x10325476\n\tlet h4 = 0xc3d2e1f0\n\n\tconst bitLength = data.length * 8\n\n\t// Pre-processing: add padding\n\t// message + 1 bit + zeros + 64-bit length\n\tconst paddedLength = Math.ceil((data.length + 9) / 64) * 64\n\tconst padded = new Uint8Array(paddedLength)\n\tpadded.set(data)\n\tpadded[data.length] = 0x80\n\n\t// Append length as 64-bit big-endian\n\tconst view = new DataView(padded.buffer, padded.byteOffset)\n\t// For messages < 2^32 bits, high 32 bits are 0\n\tview.setUint32(paddedLength - 4, bitLength, false)\n\n\t// Process each 512-bit (64-byte) block\n\tconst w = new Int32Array(80)\n\n\tfor (let offset = 0; offset < paddedLength; offset += 64) {\n\t\tfor (let i = 0; i < 16; i++) {\n\t\t\tw[i] = view.getInt32(offset + i * 4, false)\n\t\t}\n\n\t\tfor (let i = 16; i < 80; i++) {\n\t\t\tw[i] = rotl32((w[i - 3]! ^ w[i - 8]! ^ w[i - 14]! ^ w[i - 16]!) | 0, 1)\n\t\t}\n\n\t\tlet a = h0\n\t\tlet b = h1\n\t\tlet c = h2\n\t\tlet d = h3\n\t\tlet e = h4\n\n\t\tfor (let i = 0; i < 80; i++) {\n\t\t\tlet f: number\n\t\t\tlet k: number\n\n\t\t\tif (i < 20) {\n\t\t\t\tf = (b & c) | (~b & d)\n\t\t\t\tk = 0x5a827999\n\t\t\t} else if (i < 40) {\n\t\t\t\tf = b ^ c ^ d\n\t\t\t\tk = 0x6ed9eba1\n\t\t\t} else if (i < 60) {\n\t\t\t\tf = (b & c) | (b & d) | (c & d)\n\t\t\t\tk = 0x8f1bbcdc\n\t\t\t} else {\n\t\t\t\tf = b ^ c ^ d\n\t\t\t\tk = 0xca62c1d6\n\t\t\t}\n\n\t\t\tconst temp = (rotl32(a, 5) + f + e + k + w[i]!) | 0\n\t\t\te = d\n\t\t\td = c\n\t\t\tc = rotl32(b, 30)\n\t\t\tb = a\n\t\t\ta = temp\n\t\t}\n\n\t\th0 = (h0 + a) | 0\n\t\th1 = (h1 + b) | 0\n\t\th2 = (h2 + c) | 0\n\t\th3 = (h3 + d) | 0\n\t\th4 = (h4 + e) | 0\n\t}\n\n\tconst result = new Uint8Array(20)\n\tconst rv = new DataView(result.buffer)\n\trv.setInt32(0, h0, false)\n\trv.setInt32(4, h1, false)\n\trv.setInt32(8, h2, false)\n\trv.setInt32(12, h3, false)\n\trv.setInt32(16, h4, false)\n\n\treturn result\n}\n\nfunction rotl32(value: number, shift: number): number {\n\treturn ((value << shift) | (value >>> (32 - shift))) | 0\n}\n\n// ============================================================================\n// Base32 Encoding/Decoding (RFC 4648)\n// ============================================================================\n\nconst BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n\n/**\n * Encode bytes to base32 string (RFC 4648, no padding).\n */\nexport function base32Encode(data: Uint8Array): string {\n\tlet result = ''\n\tlet bits = 0\n\tlet buffer = 0\n\n\tfor (let i = 0; i < data.length; i++) {\n\t\tbuffer = (buffer << 8) | data[i]!\n\t\tbits += 8\n\t\twhile (bits >= 5) {\n\t\t\tbits -= 5\n\t\t\tresult += BASE32_ALPHABET[(buffer >>> bits) & 0x1f]\n\t\t}\n\t}\n\n\tif (bits > 0) {\n\t\tresult += BASE32_ALPHABET[(buffer << (5 - bits)) & 0x1f]\n\t}\n\n\treturn result\n}\n\n/**\n * Decode a base32 string to bytes (RFC 4648).\n */\nexport function base32Decode(encoded: string): Uint8Array {\n\tconst cleaned = encoded.replace(/=+$/, '').toUpperCase()\n\tconst output: number[] = []\n\tlet bits = 0\n\tlet buffer = 0\n\n\tfor (let i = 0; i < cleaned.length; i++) {\n\t\tconst char = cleaned[i]!\n\t\tconst value = BASE32_ALPHABET.indexOf(char)\n\t\tif (value === -1) continue // skip invalid chars\n\n\t\tbuffer = (buffer << 5) | value\n\t\tbits += 5\n\n\t\tif (bits >= 8) {\n\t\t\tbits -= 8\n\t\t\toutput.push((buffer >>> bits) & 0xff)\n\t\t}\n\t}\n\n\treturn new Uint8Array(output)\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Generate a random secret of the given byte length.\n */\nfunction generateSecret(byteLength: number): Uint8Array {\n\tconst bytes = new Uint8Array(byteLength)\n\tglobalThis.crypto.getRandomValues(bytes)\n\treturn bytes\n}\n\n/**\n * Generate random recovery codes.\n */\nfunction generateRecoveryCodes(count: number, length: number): string[] {\n\tconst codes: string[] = []\n\tconst chars = 'abcdefghijklmnopqrstuvwxyz0123456789'\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst bytes = new Uint8Array(length)\n\t\tglobalThis.crypto.getRandomValues(bytes)\n\t\tlet code = ''\n\t\tfor (let j = 0; j < length; j++) {\n\t\t\tcode += chars[bytes[j]! % chars.length]\n\t\t}\n\t\t// Format as xxxxx-xxxxx for readability\n\t\tcodes.push(`${code.slice(0, 5)}-${code.slice(5)}`)\n\t}\n\n\treturn codes\n}\n\n/**\n * Hash a recovery code for storage (SHA-256).\n */\nasync function hashRecoveryCode(code: string): Promise<string> {\n\tconst encoded = new TextEncoder().encode(code.toLowerCase().replace(/[-\\s]/g, ''))\n\tconst hash = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\tconst bytes = new Uint8Array(hash)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n\n/**\n * Build an otpauth:// URI for QR code generation.\n */\nfunction buildOtpauthUri(params: {\n\tissuer: string\n\taccountName: string\n\tsecret: string\n\talgorithm: string\n\tdigits: number\n\tperiod: number\n}): string {\n\tconst label = `${encodeURIComponent(params.issuer)}:${encodeURIComponent(params.accountName)}`\n\tconst query = new URLSearchParams({\n\t\tsecret: params.secret,\n\t\tissuer: params.issuer,\n\t\talgorithm: params.algorithm.replace('-', ''),\n\t\tdigits: params.digits.toString(),\n\t\tperiod: params.period.toString(),\n\t})\n\treturn `otpauth://totp/${label}?${query.toString()}`\n}\n\n/**\n * Timing-safe string comparison to prevent timing attacks.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n\tif (a.length !== b.length) return false\n\n\tlet result = 0\n\tfor (let i = 0; i < a.length; i++) {\n\t\tresult |= a.charCodeAt(i) ^ b.charCodeAt(i)\n\t}\n\n\treturn result === 0\n}\n","import { KoraError } from '@korajs/core'\nimport type { AuthUser, StoredUser, UserStore } from '../provider/built-in/user-store'\nimport type { Session, SessionStore } from '../session/session'\nimport type { AuditLogger, AuditAction } from './audit-log'\n\n// ============================================================================\n// Admin API Types\n// ============================================================================\n\n/**\n * Configuration for the Admin API.\n */\nexport interface AdminApiConfig {\n\t/** User store for managing users */\n\tuserStore: UserStore\n\t/** Session store for managing sessions (optional) */\n\tsessionStore?: SessionStore\n\t/** Audit logger (optional) */\n\tauditLogger?: AuditLogger\n}\n\n/**\n * Paginated result set.\n */\nexport interface PaginatedResult<T> {\n\tdata: T[]\n\ttotal: number\n\tlimit: number\n\toffset: number\n}\n\n/**\n * User list query parameters.\n */\nexport interface UserListQuery {\n\t/** Search by email (substring match) */\n\temail?: string\n\t/** Filter by email verified status */\n\temailVerified?: boolean\n\t/** Maximum number of results */\n\tlimit?: number\n\t/** Offset for pagination */\n\toffset?: number\n}\n\n/**\n * Admin-level user update (different from self-update).\n */\nexport interface AdminUserUpdate {\n\t/** Update display name */\n\tname?: string\n\t/** Set email verified status */\n\temailVerified?: boolean\n\t/** Update email (admin override) */\n\temail?: string\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class AdminApiError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AdminApiError'\n\t}\n}\n\nexport class AdminUserNotFoundError extends AdminApiError {\n\tconstructor(userId: string) {\n\t\tsuper(`User \"${userId}\" not found.`, 'ADMIN_USER_NOT_FOUND', { userId })\n\t}\n}\n\nexport class AdminUnauthorizedError extends AdminApiError {\n\tconstructor() {\n\t\tsuper('Admin privileges required.', 'ADMIN_UNAUTHORIZED')\n\t}\n}\n\n// ============================================================================\n// AdminApi\n// ============================================================================\n\n/**\n * Administrative API for managing users, sessions, and system configuration.\n *\n * Provides elevated operations that should only be accessible to administrators.\n * All operations are audit-logged when an AuditLogger is configured.\n *\n * @example\n * ```typescript\n * const admin = new AdminApi({\n * userStore: myUserStore,\n * sessionStore: mySessionStore,\n * auditLogger: myAuditLogger,\n * })\n *\n * // List users\n * const { data, total } = await admin.listUsers({ limit: 20 })\n *\n * // Suspend a user (revokes all sessions)\n * await admin.suspendUser('admin-user-id', 'target-user-id', 'Policy violation')\n * ```\n */\nexport class AdminApi {\n\tprivate readonly userStore: UserStore\n\tprivate readonly sessionStore: SessionStore | null\n\tprivate readonly auditLogger: AuditLogger | null\n\n\tconstructor(config: AdminApiConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.sessionStore = config.sessionStore ?? null\n\t\tthis.auditLogger = config.auditLogger ?? null\n\t}\n\n\t/**\n\t * Get a user by ID with full details.\n\t */\n\tasync getUser(adminId: string, userId: string): Promise<AuthUser> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\tthrow new AdminUserNotFoundError(userId)\n\t\t}\n\n\t\tawait this.audit('admin.user_lookup', adminId, userId, 'user')\n\n\t\treturn toAuthUser(user)\n\t}\n\n\t/**\n\t * List users with optional filtering and pagination.\n\t */\n\tasync listUsers(query: UserListQuery = {}): Promise<PaginatedResult<AuthUser>> {\n\t\tconst limit = query.limit ?? 50\n\t\tconst offset = query.offset ?? 0\n\n\t\t// Get all users (InMemoryUserStore doesn't have a list method, so we use findByEmail with patterns)\n\t\t// For now, we expose a listing helper\n\t\tconst allUsers = await this.userStore.listAll()\n\n\t\tlet filtered = allUsers\n\n\t\tif (query.email) {\n\t\t\tconst searchEmail = query.email.toLowerCase()\n\t\t\tfiltered = filtered.filter((u) => u.email.toLowerCase().includes(searchEmail))\n\t\t}\n\n\t\tif (query.emailVerified !== undefined) {\n\t\t\tfiltered = filtered.filter((u) => u.emailVerified === query.emailVerified)\n\t\t}\n\n\t\tconst total = filtered.length\n\t\tconst data = filtered.slice(offset, offset + limit).map(toAuthUser)\n\n\t\treturn { data, total, limit, offset }\n\t}\n\n\t/**\n\t * Update a user's profile (admin-level).\n\t */\n\tasync updateUser(adminId: string, userId: string, updates: AdminUserUpdate): Promise<AuthUser> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\tthrow new AdminUserNotFoundError(userId)\n\t\t}\n\n\t\tif (updates.name !== undefined) {\n\t\t\tuser.name = updates.name\n\t\t}\n\t\tif (updates.emailVerified !== undefined) {\n\t\t\tuser.emailVerified = updates.emailVerified\n\t\t}\n\t\tif (updates.email !== undefined) {\n\t\t\tuser.email = updates.email.toLowerCase().trim()\n\t\t}\n\n\t\tawait this.userStore.update(user)\n\n\t\tawait this.audit('user.update', adminId, userId, 'user', { updates })\n\n\t\treturn toAuthUser(user)\n\t}\n\n\t/**\n\t * Delete a user and all associated sessions.\n\t */\n\tasync deleteUser(adminId: string, userId: string): Promise<void> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\tthrow new AdminUserNotFoundError(userId)\n\t\t}\n\n\t\t// Revoke all sessions first\n\t\tif (this.sessionStore) {\n\t\t\tawait this.sessionStore.deleteAllForUser(userId)\n\t\t}\n\n\t\tawait this.userStore.delete(userId)\n\n\t\tawait this.audit('user.delete', adminId, userId, 'user')\n\t}\n\n\t/**\n\t * Get all active sessions for a user.\n\t */\n\tasync getUserSessions(userId: string): Promise<Session[]> {\n\t\tif (!this.sessionStore) return []\n\t\treturn this.sessionStore.listByUserId(userId)\n\t}\n\n\t/**\n\t * Revoke all sessions for a user.\n\t */\n\tasync revokeUserSessions(adminId: string, userId: string): Promise<number> {\n\t\tif (!this.sessionStore) return 0\n\n\t\tconst count = await this.sessionStore.deleteAllForUser(userId)\n\n\t\tawait this.audit('session.revoke_all', adminId, userId, 'user', { sessionsRevoked: count })\n\n\t\treturn count\n\t}\n\n\t/**\n\t * Revoke a specific session.\n\t */\n\tasync revokeSession(adminId: string, sessionId: string): Promise<void> {\n\t\tif (!this.sessionStore) return\n\n\t\tawait this.sessionStore.delete(sessionId)\n\n\t\tawait this.audit('session.revoke', adminId, sessionId, 'session')\n\t}\n\n\t/**\n\t * Get system statistics.\n\t */\n\tasync getStats(): Promise<{\n\t\ttotalUsers: number\n\t\tverifiedUsers: number\n\t\tunverifiedUsers: number\n\t}> {\n\t\tconst allUsers = await this.userStore.listAll()\n\t\tconst verified = allUsers.filter((u) => u.emailVerified).length\n\n\t\treturn {\n\t\t\ttotalUsers: allUsers.length,\n\t\t\tverifiedUsers: verified,\n\t\t\tunverifiedUsers: allUsers.length - verified,\n\t\t}\n\t}\n\n\t// --- Private ---\n\n\tprivate async audit(\n\t\taction: AuditAction,\n\t\tactorId: string,\n\t\ttargetId: string,\n\t\ttargetType: string,\n\t\tmetadata?: Record<string, unknown>,\n\t): Promise<void> {\n\t\tif (!this.auditLogger) return\n\t\tawait this.auditLogger.log({\n\t\t\taction,\n\t\t\tactorId,\n\t\t\tactorType: 'admin',\n\t\t\ttargetId,\n\t\t\ttargetType,\n\t\t\tmetadata,\n\t\t})\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction toAuthUser(stored: StoredUser): AuthUser {\n\treturn {\n\t\tid: stored.id,\n\t\temail: stored.email,\n\t\tname: stored.name,\n\t\temailVerified: stored.emailVerified,\n\t\tcreatedAt: stored.createdAt,\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Audit Log Types\n// ============================================================================\n\n/**\n * Actions that can be audited.\n */\nconst AUDIT_ACTIONS = [\n\t// Auth\n\t'user.signup',\n\t'user.signin',\n\t'user.signout',\n\t'user.token_refresh',\n\t'user.password_change',\n\t'user.password_reset_request',\n\t'user.password_reset',\n\t'user.email_verify',\n\t// MFA\n\t'mfa.enable',\n\t'mfa.verify_setup',\n\t'mfa.verify',\n\t'mfa.recovery_used',\n\t'mfa.recovery_regenerate',\n\t'mfa.disable',\n\t// Session\n\t'session.create',\n\t'session.revoke',\n\t'session.revoke_all',\n\t'session.expired',\n\t// OAuth\n\t'oauth.authorize',\n\t'oauth.callback',\n\t'oauth.link',\n\t'oauth.unlink',\n\t// User management\n\t'user.create',\n\t'user.update',\n\t'user.delete',\n\t'user.suspend',\n\t'user.unsuspend',\n\t// Org management\n\t'org.create',\n\t'org.update',\n\t'org.delete',\n\t'org.member_add',\n\t'org.member_remove',\n\t'org.member_role_change',\n\t'org.ownership_transfer',\n\t'org.invitation_create',\n\t'org.invitation_accept',\n\t'org.invitation_revoke',\n\t// Admin\n\t'admin.user_lookup',\n\t'admin.impersonate',\n\t'admin.config_change',\n] as const\n\nexport type AuditAction = (typeof AUDIT_ACTIONS)[number]\n\n/**\n * An audit log entry.\n */\nexport interface AuditEntry {\n\t/** Unique entry ID */\n\tid: string\n\t/** When this event occurred */\n\ttimestamp: number\n\t/** The action that was performed */\n\taction: AuditAction\n\t/** Who performed the action (user ID, \"system\", or \"anonymous\") */\n\tactorId: string\n\t/** The type of actor */\n\tactorType: 'user' | 'admin' | 'system'\n\t/** The target of the action (e.g., user ID, org ID, session ID) */\n\ttargetId: string | null\n\t/** The type of target */\n\ttargetType: string | null\n\t/** IP address of the actor */\n\tipAddress: string | null\n\t/** User agent */\n\tuserAgent: string | null\n\t/** Whether the action succeeded */\n\tsuccess: boolean\n\t/** Error message if the action failed */\n\terrorMessage: string | null\n\t/** Additional structured context */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Query parameters for searching audit logs.\n */\nexport interface AuditLogQuery {\n\t/** Filter by actor ID */\n\tactorId?: string\n\t/** Filter by target ID */\n\ttargetId?: string\n\t/** Filter by action(s) */\n\tactions?: AuditAction[]\n\t/** Filter by success/failure */\n\tsuccess?: boolean\n\t/** Start time (inclusive) */\n\tstartTime?: number\n\t/** End time (inclusive) */\n\tendTime?: number\n\t/** Maximum number of entries to return */\n\tlimit?: number\n\t/** Offset for pagination */\n\toffset?: number\n}\n\n/**\n * Store for audit log entries.\n */\nexport interface AuditLogStore {\n\t/** Append an audit entry */\n\tappend(entry: AuditEntry): Promise<void>\n\t/** Query audit entries */\n\tquery(params: AuditLogQuery): Promise<AuditEntry[]>\n\t/** Count matching entries */\n\tcount(params: AuditLogQuery): Promise<number>\n\t/** Delete entries older than a given timestamp */\n\tpurgeOlderThan(timestamp: number): Promise<number>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class AuditLogError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuditLogError'\n\t}\n}\n\n// ============================================================================\n// InMemoryAuditLogStore\n// ============================================================================\n\n/**\n * In-memory audit log store for development and testing.\n */\nexport class InMemoryAuditLogStore implements AuditLogStore {\n\tprivate readonly entries: AuditEntry[] = []\n\n\tasync append(entry: AuditEntry): Promise<void> {\n\t\tthis.entries.push({ ...entry })\n\t}\n\n\tasync query(params: AuditLogQuery): Promise<AuditEntry[]> {\n\t\tlet results = this.filterEntries(params)\n\n\t\t// Sort by timestamp descending (newest first)\n\t\tresults.sort((a, b) => b.timestamp - a.timestamp)\n\n\t\t// Pagination\n\t\tconst offset = params.offset ?? 0\n\t\tconst limit = params.limit ?? 100\n\t\tresults = results.slice(offset, offset + limit)\n\n\t\treturn results.map((e) => ({ ...e }))\n\t}\n\n\tasync count(params: AuditLogQuery): Promise<number> {\n\t\treturn this.filterEntries(params).length\n\t}\n\n\tasync purgeOlderThan(timestamp: number): Promise<number> {\n\t\tconst initialLength = this.entries.length\n\t\tlet writeIndex = 0\n\t\tfor (let i = 0; i < this.entries.length; i++) {\n\t\t\tif (this.entries[i]!.timestamp >= timestamp) {\n\t\t\t\tthis.entries[writeIndex] = this.entries[i]!\n\t\t\t\twriteIndex++\n\t\t\t}\n\t\t}\n\t\tthis.entries.length = writeIndex\n\t\treturn initialLength - writeIndex\n\t}\n\n\tprivate filterEntries(params: AuditLogQuery): AuditEntry[] {\n\t\treturn this.entries.filter((e) => {\n\t\t\tif (params.actorId !== undefined && e.actorId !== params.actorId) return false\n\t\t\tif (params.targetId !== undefined && e.targetId !== params.targetId) return false\n\t\t\tif (params.actions !== undefined && !params.actions.includes(e.action)) return false\n\t\t\tif (params.success !== undefined && e.success !== params.success) return false\n\t\t\tif (params.startTime !== undefined && e.timestamp < params.startTime) return false\n\t\t\tif (params.endTime !== undefined && e.timestamp > params.endTime) return false\n\t\t\treturn true\n\t\t})\n\t}\n}\n\n// ============================================================================\n// AuditLogger\n// ============================================================================\n\n/**\n * Structured audit logger for authentication events.\n *\n * Records all security-relevant actions for compliance and debugging.\n * Designed to be plugged into the auth system at key decision points.\n *\n * @example\n * ```typescript\n * const auditLog = new AuditLogger({\n * store: new InMemoryAuditLogStore(),\n * })\n *\n * await auditLog.log({\n * action: 'user.signin',\n * actorId: 'user-123',\n * actorType: 'user',\n * success: true,\n * ipAddress: '192.168.1.1',\n * })\n *\n * const entries = await auditLog.query({ actorId: 'user-123', limit: 50 })\n * ```\n */\nexport class AuditLogger {\n\tprivate readonly store: AuditLogStore\n\tprivate readonly retentionMs: number | null\n\n\tconstructor(config: { store: AuditLogStore; retentionDays?: number }) {\n\t\tthis.store = config.store\n\t\tthis.retentionMs = config.retentionDays\n\t\t\t? config.retentionDays * 24 * 60 * 60 * 1000\n\t\t\t: null\n\t}\n\n\t/**\n\t * Log an audit event.\n\t */\n\tasync log(params: {\n\t\taction: AuditAction\n\t\tactorId: string\n\t\tactorType?: 'user' | 'admin' | 'system'\n\t\ttargetId?: string\n\t\ttargetType?: string\n\t\tipAddress?: string\n\t\tuserAgent?: string\n\t\tsuccess?: boolean\n\t\terrorMessage?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<AuditEntry> {\n\t\tconst entry: AuditEntry = {\n\t\t\tid: generateAuditId(),\n\t\t\ttimestamp: Date.now(),\n\t\t\taction: params.action,\n\t\t\tactorId: params.actorId,\n\t\t\tactorType: params.actorType ?? 'user',\n\t\t\ttargetId: params.targetId ?? null,\n\t\t\ttargetType: params.targetType ?? null,\n\t\t\tipAddress: params.ipAddress ?? null,\n\t\t\tuserAgent: params.userAgent ?? null,\n\t\t\tsuccess: params.success ?? true,\n\t\t\terrorMessage: params.errorMessage ?? null,\n\t\t\tmetadata: params.metadata,\n\t\t}\n\n\t\tawait this.store.append(entry)\n\t\treturn entry\n\t}\n\n\t/**\n\t * Query audit log entries.\n\t */\n\tasync query(params: AuditLogQuery): Promise<AuditEntry[]> {\n\t\treturn this.store.query(params)\n\t}\n\n\t/**\n\t * Count matching audit entries.\n\t */\n\tasync count(params: AuditLogQuery): Promise<number> {\n\t\treturn this.store.count(params)\n\t}\n\n\t/**\n\t * Purge old entries based on retention policy.\n\t * Returns the number of entries purged.\n\t */\n\tasync purge(): Promise<number> {\n\t\tif (this.retentionMs === null) return 0\n\t\tconst cutoff = Date.now() - this.retentionMs\n\t\treturn this.store.purgeOlderThan(cutoff)\n\t}\n\n\t/**\n\t * Get recent activity for a user.\n\t */\n\tasync getUserActivity(userId: string, limit: number = 50): Promise<AuditEntry[]> {\n\t\treturn this.store.query({ actorId: userId, limit })\n\t}\n\n\t/**\n\t * Get failed login attempts for a user within a time window.\n\t */\n\tasync getFailedLogins(userId: string, windowMs: number): Promise<AuditEntry[]> {\n\t\treturn this.store.query({\n\t\t\ttargetId: userId,\n\t\t\tactions: ['user.signin'],\n\t\t\tsuccess: false,\n\t\t\tstartTime: Date.now() - windowMs,\n\t\t})\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateAuditId(): string {\n\tconst bytes = new Uint8Array(16)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Webhook Types\n// ============================================================================\n\n/**\n * Events that can trigger webhooks.\n */\nconst WEBHOOK_EVENTS = [\n\t'user.created',\n\t'user.updated',\n\t'user.deleted',\n\t'user.signin',\n\t'user.signout',\n\t'user.password_changed',\n\t'user.email_verified',\n\t'mfa.enabled',\n\t'mfa.disabled',\n\t'session.created',\n\t'session.revoked',\n\t'org.created',\n\t'org.updated',\n\t'org.deleted',\n\t'org.member_added',\n\t'org.member_removed',\n\t'org.invitation_created',\n\t'org.invitation_accepted',\n] as const\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number]\n\n/**\n * A webhook endpoint configuration.\n */\nexport interface WebhookEndpoint {\n\t/** Unique endpoint ID */\n\tid: string\n\t/** Destination URL */\n\turl: string\n\t/** Events this endpoint subscribes to */\n\tevents: WebhookEvent[]\n\t/** Signing secret for HMAC verification */\n\tsecret: string\n\t/** Whether this endpoint is active */\n\tactive: boolean\n\t/** When this endpoint was created */\n\tcreatedAt: number\n\t/** Custom metadata */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * A webhook delivery attempt.\n */\nexport interface WebhookDelivery {\n\t/** Unique delivery ID */\n\tid: string\n\t/** Endpoint ID */\n\tendpointId: string\n\t/** Event that triggered this delivery */\n\tevent: WebhookEvent\n\t/** Request payload (JSON) */\n\tpayload: string\n\t/** HTTP response status (null if network error) */\n\tresponseStatus: number | null\n\t/** Whether the delivery was successful (2xx response) */\n\tsuccess: boolean\n\t/** Error message if delivery failed */\n\terror: string | null\n\t/** Number of attempts made */\n\tattempts: number\n\t/** When the delivery was first attempted */\n\tcreatedAt: number\n\t/** When the delivery was last attempted */\n\tlastAttemptAt: number\n}\n\n/**\n * Payload sent to webhook endpoints.\n */\nexport interface WebhookPayload {\n\t/** Unique event ID */\n\tid: string\n\t/** Event type */\n\tevent: WebhookEvent\n\t/** When the event occurred */\n\ttimestamp: number\n\t/** Event data */\n\tdata: Record<string, unknown>\n}\n\n/**\n * Store for webhook endpoints and deliveries.\n */\nexport interface WebhookStore {\n\t/** Save a webhook endpoint */\n\tsaveEndpoint(endpoint: WebhookEndpoint): Promise<void>\n\t/** Get an endpoint by ID */\n\tgetEndpoint(id: string): Promise<WebhookEndpoint | null>\n\t/** List all endpoints */\n\tlistEndpoints(): Promise<WebhookEndpoint[]>\n\t/** Delete an endpoint */\n\tdeleteEndpoint(id: string): Promise<void>\n\t/** Save a delivery record */\n\tsaveDelivery(delivery: WebhookDelivery): Promise<void>\n\t/** List recent deliveries for an endpoint */\n\tlistDeliveries(endpointId: string, limit?: number): Promise<WebhookDelivery[]>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class WebhookError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'WebhookError'\n\t}\n}\n\nexport class WebhookEndpointNotFoundError extends WebhookError {\n\tconstructor(id: string) {\n\t\tsuper(`Webhook endpoint \"${id}\" not found.`, 'WEBHOOK_ENDPOINT_NOT_FOUND', { id })\n\t}\n}\n\n// ============================================================================\n// InMemoryWebhookStore\n// ============================================================================\n\nexport class InMemoryWebhookStore implements WebhookStore {\n\tprivate readonly endpoints = new Map<string, WebhookEndpoint>()\n\tprivate readonly deliveries: WebhookDelivery[] = []\n\n\tasync saveEndpoint(endpoint: WebhookEndpoint): Promise<void> {\n\t\tthis.endpoints.set(endpoint.id, { ...endpoint })\n\t}\n\n\tasync getEndpoint(id: string): Promise<WebhookEndpoint | null> {\n\t\tconst ep = this.endpoints.get(id)\n\t\treturn ep ? { ...ep } : null\n\t}\n\n\tasync listEndpoints(): Promise<WebhookEndpoint[]> {\n\t\treturn [...this.endpoints.values()].map((e) => ({ ...e }))\n\t}\n\n\tasync deleteEndpoint(id: string): Promise<void> {\n\t\tthis.endpoints.delete(id)\n\t}\n\n\tasync saveDelivery(delivery: WebhookDelivery): Promise<void> {\n\t\tconst existing = this.deliveries.findIndex((d) => d.id === delivery.id)\n\t\tif (existing >= 0) {\n\t\t\tthis.deliveries[existing] = { ...delivery }\n\t\t} else {\n\t\t\tthis.deliveries.push({ ...delivery })\n\t\t}\n\t}\n\n\tasync listDeliveries(endpointId: string, limit: number = 50): Promise<WebhookDelivery[]> {\n\t\treturn this.deliveries\n\t\t\t.filter((d) => d.endpointId === endpointId)\n\t\t\t.sort((a, b) => b.createdAt - a.createdAt)\n\t\t\t.slice(0, limit)\n\t\t\t.map((d) => ({ ...d }))\n\t}\n}\n\n// ============================================================================\n// WebhookManager\n// ============================================================================\n\nconst MAX_RETRIES = 3\nconst RETRY_DELAYS_MS = [1000, 5000, 30000] // 1s, 5s, 30s\n\n/**\n * Manages webhook registrations and event delivery.\n *\n * Sends HTTP POST requests to registered endpoints when auth events occur.\n * Includes HMAC-SHA256 signature verification and retry logic.\n *\n * @example\n * ```typescript\n * const webhooks = new WebhookManager({\n * store: new InMemoryWebhookStore(),\n * })\n *\n * // Register an endpoint\n * const endpoint = await webhooks.register({\n * url: 'https://myapp.com/webhooks/auth',\n * events: ['user.created', 'user.signin'],\n * })\n *\n * // Dispatch an event (usually called internally by auth system)\n * await webhooks.dispatch('user.created', { userId: 'user-123', email: 'alice@example.com' })\n * ```\n */\nexport class WebhookManager {\n\tprivate readonly store: WebhookStore\n\tprivate readonly fetchFn: typeof globalThis.fetch\n\n\tconstructor(config: { store: WebhookStore; fetch?: typeof globalThis.fetch }) {\n\t\tthis.store = config.store\n\t\tthis.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis)\n\t}\n\n\t/**\n\t * Register a new webhook endpoint.\n\t */\n\tasync register(params: {\n\t\turl: string\n\t\tevents: WebhookEvent[]\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<WebhookEndpoint> {\n\t\tconst endpoint: WebhookEndpoint = {\n\t\t\tid: generateId(),\n\t\t\turl: params.url,\n\t\t\tevents: [...params.events],\n\t\t\tsecret: generateSecret(),\n\t\t\tactive: true,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tmetadata: params.metadata,\n\t\t}\n\n\t\tawait this.store.saveEndpoint(endpoint)\n\t\treturn endpoint\n\t}\n\n\t/**\n\t * Update a webhook endpoint.\n\t */\n\tasync update(\n\t\tid: string,\n\t\tupdates: { url?: string; events?: WebhookEvent[]; active?: boolean },\n\t): Promise<WebhookEndpoint> {\n\t\tconst endpoint = await this.store.getEndpoint(id)\n\t\tif (!endpoint) {\n\t\t\tthrow new WebhookEndpointNotFoundError(id)\n\t\t}\n\n\t\tif (updates.url !== undefined) endpoint.url = updates.url\n\t\tif (updates.events !== undefined) endpoint.events = [...updates.events]\n\t\tif (updates.active !== undefined) endpoint.active = updates.active\n\n\t\tawait this.store.saveEndpoint(endpoint)\n\t\treturn endpoint\n\t}\n\n\t/**\n\t * Delete a webhook endpoint.\n\t */\n\tasync remove(id: string): Promise<void> {\n\t\tawait this.store.deleteEndpoint(id)\n\t}\n\n\t/**\n\t * List all webhook endpoints.\n\t */\n\tasync list(): Promise<WebhookEndpoint[]> {\n\t\treturn this.store.listEndpoints()\n\t}\n\n\t/**\n\t * Get a specific endpoint.\n\t */\n\tasync get(id: string): Promise<WebhookEndpoint> {\n\t\tconst endpoint = await this.store.getEndpoint(id)\n\t\tif (!endpoint) {\n\t\t\tthrow new WebhookEndpointNotFoundError(id)\n\t\t}\n\t\treturn endpoint\n\t}\n\n\t/**\n\t * Get recent deliveries for an endpoint.\n\t */\n\tasync getDeliveries(endpointId: string, limit?: number): Promise<WebhookDelivery[]> {\n\t\treturn this.store.listDeliveries(endpointId, limit)\n\t}\n\n\t/**\n\t * Dispatch an event to all matching webhook endpoints.\n\t * Delivery is best-effort with retries.\n\t */\n\tasync dispatch(\n\t\tevent: WebhookEvent,\n\t\tdata: Record<string, unknown>,\n\t): Promise<void> {\n\t\tconst endpoints = await this.store.listEndpoints()\n\t\tconst matching = endpoints.filter(\n\t\t\t(ep) => ep.active && ep.events.includes(event),\n\t\t)\n\n\t\tconst payload: WebhookPayload = {\n\t\t\tid: generateId(),\n\t\t\tevent,\n\t\t\ttimestamp: Date.now(),\n\t\t\tdata,\n\t\t}\n\n\t\tconst payloadJson = JSON.stringify(payload)\n\n\t\tawait Promise.allSettled(\n\t\t\tmatching.map((ep) => this.deliverToEndpoint(ep, payloadJson, event)),\n\t\t)\n\t}\n\n\t// --- Private ---\n\n\tprivate async deliverToEndpoint(\n\t\tendpoint: WebhookEndpoint,\n\t\tpayloadJson: string,\n\t\tevent: WebhookEvent,\n\t): Promise<void> {\n\t\tconst signature = await signPayload(payloadJson, endpoint.secret)\n\n\t\tconst delivery: WebhookDelivery = {\n\t\t\tid: generateId(),\n\t\t\tendpointId: endpoint.id,\n\t\t\tevent,\n\t\t\tpayload: payloadJson,\n\t\t\tresponseStatus: null,\n\t\t\tsuccess: false,\n\t\t\terror: null,\n\t\t\tattempts: 0,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tlastAttemptAt: Date.now(),\n\t\t}\n\n\t\tfor (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n\t\t\tdelivery.attempts = attempt + 1\n\t\t\tdelivery.lastAttemptAt = Date.now()\n\n\t\t\ttry {\n\t\t\t\tconst response = await this.fetchFn(endpoint.url, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'X-Webhook-Signature': signature,\n\t\t\t\t\t\t'X-Webhook-Event': event,\n\t\t\t\t\t\t'X-Webhook-Delivery': delivery.id,\n\t\t\t\t\t},\n\t\t\t\t\tbody: payloadJson,\n\t\t\t\t})\n\n\t\t\t\tdelivery.responseStatus = response.status\n\t\t\t\tdelivery.success = response.ok\n\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.store.saveDelivery(delivery)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tdelivery.error = `HTTP ${response.status}`\n\t\t\t} catch (err) {\n\t\t\t\tdelivery.error = err instanceof Error ? err.message : 'Network error'\n\t\t\t}\n\n\t\t\t// Wait before retrying (skip wait on last attempt)\n\t\t\tif (attempt < MAX_RETRIES - 1) {\n\t\t\t\tawait delay(RETRY_DELAYS_MS[attempt] ?? 1000)\n\t\t\t}\n\t\t}\n\n\t\t// All retries exhausted\n\t\tawait this.store.saveDelivery(delivery)\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateId(): string {\n\tconst bytes = new Uint8Array(16)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n\nfunction generateSecret(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn `whsec_${hex}`\n}\n\n/**\n * Sign a payload with HMAC-SHA256 for webhook verification.\n */\nasync function signPayload(payload: string, secret: string): Promise<string> {\n\tconst encoder = new TextEncoder()\n\tconst key = await globalThis.crypto.subtle.importKey(\n\t\t'raw',\n\t\tencoder.encode(secret),\n\t\t{ name: 'HMAC', hash: 'SHA-256' },\n\t\tfalse,\n\t\t['sign'],\n\t)\n\tconst signature = await globalThis.crypto.subtle.sign(\n\t\t'HMAC',\n\t\tkey,\n\t\tencoder.encode(payload),\n\t)\n\tconst bytes = new Uint8Array(signature)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn `sha256=${hex}`\n}\n\n/**\n * Verify a webhook payload signature.\n * Useful for consumers of webhooks to verify authenticity.\n */\nexport async function verifyWebhookSignature(\n\tpayload: string,\n\tsignature: string,\n\tsecret: string,\n): Promise<boolean> {\n\tconst expected = await signPayload(payload, secret)\n\t// Timing-safe comparison\n\tif (expected.length !== signature.length) return false\n\tlet result = 0\n\tfor (let i = 0; i < expected.length; i++) {\n\t\tresult |= expected.charCodeAt(i) ^ signature.charCodeAt(i)\n\t}\n\treturn result === 0\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],"mappings":"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","names":["randomBytes","KoraError","randomUUID","randomBytes","KoraError","KoraError","MIN_PASSWORD_LENGTH","MAX_PASSWORD_LENGTH","verifyPassword","KoraError","DEFAULT_TOKEN_TTL_MS","DEFAULT_MAX_REQUESTS","generateSecureToken","randomUUID","randomUUID","require","randomUUID","randomUUID","rowToStoredUser","rowToDevice","KoraError","KoraError","randomBytes","KoraError","KoraError","randomBytes","KoraError","isValidEmail","org","membership","KoraError","KoraError","KoraError","KoraError","KoraError","toAuthUser","KoraError","KoraError","KoraError","KoraError","generateId","generateSecret"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider/built-in/auth-routes.ts","../src/tokens/token-manager.ts","../src/types.ts","../src/tokens/jwt.ts","../src/provider/built-in/user-store.ts","../src/provider/built-in/password-reset.ts","../src/provider/built-in/email-verification.ts","../src/provider/built-in/sqlite-user-store.ts","../src/provider/built-in/postgres-user-store.ts","../src/provider/adapter.ts","../src/provider/external/external-jwt-provider.ts","../src/provider/external/clerk-adapter.ts","../src/provider/external/supabase-adapter.ts","../src/passkey/passkey-server.ts","../src/org/org-types.ts","../src/org/org-routes.ts","../src/org/org-store.ts","../src/rbac/rbac-types.ts","../src/rbac/rbac-engine.ts","../src/rbac/scope-resolver.ts","../src/provider/oauth/oauth-types.ts","../src/provider/oauth/oauth-flow.ts","../src/session/session.ts","../src/mfa/totp.ts","../src/admin/admin-api.ts","../src/admin/audit-log.ts","../src/admin/webhooks.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport type { AuthTokens } from '../../types'\nimport { TokenManager } from '../../tokens/token-manager'\nimport {\n\tcomputePublicKeyThumbprint,\n\tverifyChallenge,\n} from '../../device/device-identity'\nimport { hashPassword, verifyPassword } from './password-hash'\nimport {\n\ttype UserStore,\n\ttype AuthUser,\n\ttype AuthDevice,\n} from './user-store'\n\n// ============================================================================\n// Challenge Store\n// ============================================================================\n\n/**\n * Interface for server-side challenge storage.\n *\n * Challenges must be stored server-side with expiry and single-use semantics\n * to prevent replay attacks on device verification.\n */\nexport interface ChallengeStore {\n\t/**\n\t * Store a challenge for later verification.\n\t * @param challenge - The challenge string\n\t * @param deviceId - The device this challenge is intended for\n\t * @param expiresAt - Timestamp (ms since epoch) when this challenge expires\n\t */\n\tstore(challenge: string, deviceId: string, expiresAt: number): Promise<void>\n\n\t/**\n\t * Consume a challenge (single-use). Returns the associated device ID if the\n\t * challenge is valid and not expired, or null if it doesn't exist, has expired,\n\t * or was already consumed.\n\t */\n\tconsume(challenge: string): Promise<{ deviceId: string } | null>\n}\n\n/**\n * In-memory challenge store with expiry and single-use semantics.\n * Suitable for development and testing. Use Redis or a database in production.\n */\nexport class InMemoryChallengeStore implements ChallengeStore {\n\tprivate readonly challenges = new Map<string, { deviceId: string; expiresAt: number }>()\n\n\tasync store(challenge: string, deviceId: string, expiresAt: number): Promise<void> {\n\t\tthis.challenges.set(challenge, { deviceId, expiresAt })\n\t}\n\n\tasync consume(challenge: string): Promise<{ deviceId: string } | null> {\n\t\tconst entry = this.challenges.get(challenge)\n\t\tif (entry === undefined) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Always delete (single-use)\n\t\tthis.challenges.delete(challenge)\n\n\t\t// Check expiry\n\t\tif (Date.now() > entry.expiresAt) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn { deviceId: entry.deviceId }\n\t}\n\n\t/**\n\t * Remove expired challenges to prevent unbounded memory growth.\n\t */\n\tcleanup(): void {\n\t\tconst now = Date.now()\n\t\tfor (const [challenge, entry] of this.challenges) {\n\t\t\tif (now > entry.expiresAt) {\n\t\t\t\tthis.challenges.delete(challenge)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Rate Limiter\n// ============================================================================\n\n/**\n * Interface for rate limiting auth endpoints.\n *\n * Rate limiting is critical for preventing brute-force password guessing\n * and credential stuffing attacks.\n */\nexport interface RateLimiter {\n\t/**\n\t * Check if an action is allowed for the given key.\n\t * @param key - Rate limit key (e.g., IP address, email, or composite key)\n\t * @returns true if the action is allowed, false if rate limited\n\t */\n\tisAllowed(key: string): Promise<boolean>\n\n\t/**\n\t * Record that an action was performed for the given key.\n\t * Call this after each authentication attempt.\n\t */\n\trecord(key: string): Promise<void>\n\n\t/**\n\t * Reset the rate limit for a key (e.g., after a successful login).\n\t */\n\treset(key: string): Promise<void>\n}\n\n/**\n * In-memory sliding window rate limiter.\n * Suitable for development and single-server deployments.\n * Use Redis-based rate limiting for multi-server production deployments.\n */\nexport class InMemoryRateLimiter implements RateLimiter {\n\tprivate readonly attempts = new Map<string, number[]>()\n\tprivate readonly maxAttempts: number\n\tprivate readonly windowMs: number\n\n\t/**\n\t * @param maxAttempts - Maximum number of attempts within the time window (default: 10)\n\t * @param windowMs - Time window in milliseconds (default: 60,000 = 1 minute)\n\t */\n\tconstructor(maxAttempts = 10, windowMs = 60_000) {\n\t\tthis.maxAttempts = maxAttempts\n\t\tthis.windowMs = windowMs\n\t}\n\n\tasync isAllowed(key: string): Promise<boolean> {\n\t\tconst now = Date.now()\n\t\tconst attempts = this.attempts.get(key) ?? []\n\t\tconst recentAttempts = attempts.filter((t) => now - t < this.windowMs)\n\t\treturn recentAttempts.length < this.maxAttempts\n\t}\n\n\tasync record(key: string): Promise<void> {\n\t\tconst now = Date.now()\n\t\tconst attempts = this.attempts.get(key) ?? []\n\t\t// Keep only recent attempts to bound memory\n\t\tconst recentAttempts = attempts.filter((t) => now - t < this.windowMs)\n\t\trecentAttempts.push(now)\n\t\tthis.attempts.set(key, recentAttempts)\n\t}\n\n\tasync reset(key: string): Promise<void> {\n\t\tthis.attempts.delete(key)\n\t}\n}\n\n// ============================================================================\n// Auth Routes Configuration\n// ============================================================================\n\n/**\n * Configuration for building the built-in auth routes.\n */\nexport interface AuthRoutesConfig {\n\t/** The user/device store backing the auth routes */\n\tuserStore: UserStore\n\t/** The token manager for issuing and validating JWTs */\n\ttokenManager: TokenManager\n\t/**\n\t * Optional challenge store for device verification.\n\t * Required for secure device proof-of-possession verification.\n\t * If not provided, an in-memory store is created automatically.\n\t */\n\tchallengeStore?: ChallengeStore\n\t/**\n\t * Optional rate limiter for authentication endpoints.\n\t * If not provided, an in-memory rate limiter is created with defaults\n\t * (10 attempts per minute).\n\t */\n\trateLimiter?: RateLimiter\n}\n\n/**\n * Response envelope returned by all auth route handlers.\n *\n * Successful responses include a `data` field; failures include an `error` string.\n * The `status` field maps directly to an HTTP status code.\n */\nexport interface AuthRouteResponse<T> {\n\t/** HTTP status code */\n\tstatus: number\n\t/** Either the success payload or an error message */\n\tbody: { data: T } | { error: string }\n}\n\n/** Minimum password length enforced at sign-up. */\nconst MIN_PASSWORD_LENGTH = 8\n\n/** Maximum password length to prevent hash-DoS attacks via extremely long passwords. */\nconst MAX_PASSWORD_LENGTH = 128\n\n/** Maximum length for user/device name fields. */\nconst MAX_NAME_LENGTH = 200\n\n/** Challenge validity window in milliseconds (60 seconds). */\nconst CHALLENGE_TTL_MS = 60_000\n\n/**\n * Simple email format validation.\n * Checks for the presence of exactly one @ with non-empty local and domain parts,\n * and at least one dot in the domain. This is intentionally lenient — real email\n * validation happens by sending a confirmation email, not by regex.\n */\nfunction isValidEmail(email: string): boolean {\n\tif (email.length === 0 || email.length > 254) {\n\t\treturn false\n\t}\n\tconst atIndex = email.indexOf('@')\n\tif (atIndex < 1) {\n\t\treturn false\n\t}\n\tconst domain = email.slice(atIndex + 1)\n\tif (domain.length === 0 || !domain.includes('.')) {\n\t\treturn false\n\t}\n\t// No double-@ or spaces\n\tif (email.indexOf('@', atIndex + 1) !== -1) {\n\t\treturn false\n\t}\n\tif (email.includes(' ')) {\n\t\treturn false\n\t}\n\treturn true\n}\n\n/**\n * Sanitize and limit a name string.\n * Trims whitespace, enforces max length, and strips control characters.\n */\nfunction sanitizeName(name: string): string {\n\t// Strip ASCII control characters (0x00-0x1F, 0x7F)\n\tconst cleaned = name.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\tconst trimmed = cleaned.trim()\n\tif (trimmed.length > MAX_NAME_LENGTH) {\n\t\treturn trimmed.slice(0, MAX_NAME_LENGTH)\n\t}\n\treturn trimmed\n}\n\n/**\n * HTTP route handlers for the built-in Kora auth provider.\n *\n * These are framework-agnostic functions that accept parsed request bodies and return\n * structured responses. The server package is responsible for wiring them into its\n * HTTP server (e.g., mapping `POST /auth/signup` to `handleSignUp`).\n *\n * All handlers follow the same pattern:\n * - Validate input\n * - Perform the operation\n * - Return `{ status, body: { data } }` on success\n * - Return `{ status, body: { error } }` on failure\n *\n * Security features:\n * - Rate limiting on sign-in/sign-up to prevent brute-force attacks\n * - Server-side challenge store for device verification (single-use, time-limited)\n * - Token revocation on sign-out and device revocation\n * - Input sanitization on all name fields\n * - Maximum password length to prevent hash-DoS\n *\n * @example\n * ```typescript\n * const routes = new BuiltInAuthRoutes({\n * userStore: new InMemoryUserStore(),\n * tokenManager: new TokenManager({ secret: TokenManager.generateSecret() }),\n * })\n *\n * // Wire into an HTTP server:\n * app.post('/auth/signup', async (req, res) => {\n * const result = await routes.handleSignUp(req.body)\n * res.status(result.status).json(result.body)\n * })\n * ```\n */\nexport class BuiltInAuthRoutes {\n\tprivate readonly userStore: UserStore\n\tprivate readonly tokenManager: TokenManager\n\tprivate readonly challengeStore: ChallengeStore\n\tprivate readonly rateLimiter: RateLimiter\n\n\tconstructor(config: AuthRoutesConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.tokenManager = config.tokenManager\n\t\tthis.challengeStore = config.challengeStore ?? new InMemoryChallengeStore()\n\t\tthis.rateLimiter = config.rateLimiter ?? new InMemoryRateLimiter()\n\t}\n\n\t/**\n\t * Handle user sign-up (POST /auth/signup).\n\t *\n\t * Validates email format and password length, hashes the password,\n\t * creates the user, optionally registers a device, and issues tokens.\n\t *\n\t * @param body - Sign-up request body\n\t * @param body.email - The user's email address\n\t * @param body.password - The plaintext password (8-128 characters)\n\t * @param body.name - Optional display name (defaults to email local part)\n\t * @param body.deviceId - Optional device ID to register\n\t * @param body.devicePublicKey - Optional device public key (base64url)\n\t * @param clientIp - Optional client IP for rate limiting\n\t * @returns Auth response with the created user and tokens, or an error\n\t */\n\tasync handleSignUp(body: {\n\t\temail: string\n\t\tpassword: string\n\t\tname?: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}, clientIp?: string): Promise<AuthRouteResponse<{ user: AuthUser; tokens: AuthTokens }>> {\n\t\t// Rate limiting\n\t\tconst rateLimitKey = clientIp ?? 'global'\n\t\tif (!(await this.rateLimiter.isAllowed(rateLimitKey))) {\n\t\t\treturn {\n\t\t\t\tstatus: 429,\n\t\t\t\tbody: { error: 'Too many requests. Please try again later.' },\n\t\t\t}\n\t\t}\n\t\tawait this.rateLimiter.record(rateLimitKey)\n\n\t\t// Validate email format\n\t\tif (!isValidEmail(body.email)) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: {\n\t\t\t\t\terror: 'Invalid email address. Please provide a valid email in the format user@domain.com.',\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// Validate password length (min and max)\n\t\tif (body.password.length < MIN_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: {\n\t\t\t\t\terror: `Password must be at least ${MIN_PASSWORD_LENGTH} characters long.`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tif (body.password.length > MAX_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: {\n\t\t\t\t\terror: `Password must be at most ${MAX_PASSWORD_LENGTH} characters long.`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// Hash the password\n\t\tconst { hash, salt } = await hashPassword(body.password)\n\n\t\t// Sanitize the display name\n\t\tconst rawName = body.name ?? body.email.split('@')[0] ?? body.email\n\t\tconst name = sanitizeName(rawName)\n\n\t\t// Create the user — may throw DuplicateEmailError\n\t\tlet user: AuthUser\n\t\ttry {\n\t\t\tuser = await this.userStore.createUser({\n\t\t\t\temail: body.email,\n\t\t\t\tpasswordHash: hash,\n\t\t\t\tsalt,\n\t\t\t\tname,\n\t\t\t})\n\t\t} catch (err: unknown) {\n\t\t\tif (err instanceof Error && err.name === 'DuplicateEmailError') {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 409,\n\t\t\t\t\tbody: { error: 'An account with this email already exists.' },\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\n\t\t// Use provided deviceId or generate a placeholder\n\t\tconst deviceId = body.deviceId ?? `device-${user.id}`\n\n\t\t// Always register a device — use provided info or create a basic record\n\t\tawait this.userStore.registerDevice({\n\t\t\tid: deviceId,\n\t\t\tuserId: user.id,\n\t\t\tpublicKey: body.devicePublicKey ?? '',\n\t\t\tname: body.deviceId ? 'Primary Device' : 'Browser',\n\t\t})\n\n\t\t// Issue tokens\n\t\tconst tokens = this.tokenManager.issueTokens(user.id, deviceId)\n\n\t\treturn {\n\t\t\tstatus: 201,\n\t\t\tbody: { data: { user, tokens } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle user sign-in (POST /auth/signin).\n\t *\n\t * Looks up the user by email, verifies the password, optionally registers\n\t * a new device, and issues tokens.\n\t *\n\t * @param body - Sign-in request body\n\t * @param body.email - The user's email address\n\t * @param body.password - The plaintext password\n\t * @param body.deviceId - Optional device ID to register\n\t * @param body.devicePublicKey - Optional device public key (base64url)\n\t * @param clientIp - Optional client IP for rate limiting\n\t * @returns Auth response with the user and tokens, or an error\n\t */\n\tasync handleSignIn(body: {\n\t\temail: string\n\t\tpassword: string\n\t\tdeviceId?: string\n\t\tdevicePublicKey?: string\n\t}, clientIp?: string): Promise<AuthRouteResponse<{ user: AuthUser; tokens: AuthTokens }>> {\n\t\t// Rate limiting (use email + IP composite key for per-account protection)\n\t\tconst rateLimitKey = clientIp\n\t\t\t? `signin:${body.email.toLowerCase()}:${clientIp}`\n\t\t\t: `signin:${body.email.toLowerCase()}`\n\n\t\tif (!(await this.rateLimiter.isAllowed(rateLimitKey))) {\n\t\t\treturn {\n\t\t\t\tstatus: 429,\n\t\t\t\tbody: { error: 'Too many sign-in attempts. Please try again later.' },\n\t\t\t}\n\t\t}\n\t\tawait this.rateLimiter.record(rateLimitKey)\n\n\t\tconst storedUser = await this.userStore.findByEmail(body.email)\n\t\tif (storedUser === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid email or password.' },\n\t\t\t}\n\t\t}\n\n\t\tconst passwordValid = await verifyPassword(\n\t\t\tbody.password,\n\t\t\tstoredUser.passwordHash,\n\t\t\tstoredUser.salt,\n\t\t)\n\t\tif (!passwordValid) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid email or password.' },\n\t\t\t}\n\t\t}\n\n\t\t// Successful login — reset rate limit for this key\n\t\tawait this.rateLimiter.reset(rateLimitKey)\n\n\t\t// Use provided deviceId or generate a placeholder\n\t\tconst deviceId = body.deviceId ?? `device-${storedUser.id}`\n\n\t\t// Always register a device — use provided info or create a basic record\n\t\tawait this.userStore.registerDevice({\n\t\t\tid: deviceId,\n\t\t\tuserId: storedUser.id,\n\t\t\tpublicKey: body.devicePublicKey ?? '',\n\t\t\tname: body.deviceId ? 'Device' : 'Browser',\n\t\t})\n\n\t\tconst tokens = this.tokenManager.issueTokens(storedUser.id, deviceId)\n\n\t\tconst user: AuthUser = {\n\t\t\tid: storedUser.id,\n\t\t\temail: storedUser.email,\n\t\t\tname: storedUser.name,\n\t\t\temailVerified: storedUser.emailVerified,\n\t\t\tcreatedAt: storedUser.createdAt,\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { user, tokens } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle token refresh (POST /auth/refresh).\n\t *\n\t * Validates the provided refresh token and issues a new token pair\n\t * (refresh token rotation with reuse detection). The old refresh token\n\t * is marked as consumed in the revocation store.\n\t *\n\t * @param body - Refresh request body\n\t * @param body.refreshToken - The current refresh token\n\t * @returns Auth response with new tokens, or an error\n\t */\n\tasync handleRefresh(body: {\n\t\trefreshToken: string\n\t}): Promise<AuthRouteResponse<AuthTokens>> {\n\t\tconst result = await this.tokenManager.refreshAccessToken(body.refreshToken)\n\n\t\tif (result === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired refresh token.' },\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: result },\n\t\t}\n\t}\n\n\t/**\n\t * Handle sign-out (POST /auth/signout).\n\t *\n\t * Validates the access token and revokes the current refresh token\n\t * (if a revocation store is configured). This ensures that stolen\n\t * refresh tokens cannot be used after the user signs out.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param body - Sign-out request body\n\t * @param body.refreshToken - The current refresh token to revoke\n\t * @returns Auth response with success flag, or an error\n\t */\n\tasync handleSignOut(\n\t\taccessToken: string,\n\t\tbody: { refreshToken?: string },\n\t): Promise<AuthRouteResponse<{ success: boolean }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Revoke the access token itself\n\t\tawait this.tokenManager.revokeToken(payload.jti, payload.exp)\n\n\t\t// Revoke the refresh token if provided\n\t\tif (body.refreshToken) {\n\t\t\tconst refreshPayload = this.tokenManager.validateToken(body.refreshToken)\n\t\t\tif (refreshPayload !== null && refreshPayload.type === 'refresh') {\n\t\t\t\tawait this.tokenManager.revokeToken(refreshPayload.jti, refreshPayload.exp)\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { success: true } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle get-current-user (GET /auth/me).\n\t *\n\t * Validates the access token and returns the authenticated user's profile.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @returns Auth response with the user profile, or an error\n\t */\n\tasync handleGetMe(accessToken: string): Promise<AuthRouteResponse<AuthUser>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\tconst storedUser = await this.userStore.findById(payload.sub)\n\t\tif (storedUser === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'User not found.' },\n\t\t\t}\n\t\t}\n\n\t\tconst user: AuthUser = {\n\t\t\tid: storedUser.id,\n\t\t\temail: storedUser.email,\n\t\t\tname: storedUser.name,\n\t\t\temailVerified: storedUser.emailVerified,\n\t\t\tcreatedAt: storedUser.createdAt,\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: user },\n\t\t}\n\t}\n\n\t/**\n\t * Handle list-devices (GET /auth/devices).\n\t *\n\t * Validates the access token and returns all devices registered for the user.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @returns Auth response with the device list, or an error\n\t */\n\tasync handleListDevices(\n\t\taccessToken: string,\n\t): Promise<AuthRouteResponse<AuthDevice[]>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\tconst devices = await this.userStore.listDevices(payload.sub)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: devices },\n\t\t}\n\t}\n\n\t/**\n\t * Handle device revocation (DELETE /auth/device/:id).\n\t *\n\t * Validates the access token, revokes the specified device, and invalidates\n\t * all tokens issued to that device. Only the device's owner can revoke it.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param deviceId - The ID of the device to revoke\n\t * @returns Auth response with success flag, or an error\n\t */\n\tasync handleRevokeDevice(\n\t\taccessToken: string,\n\t\tdeviceId: string,\n\t): Promise<AuthRouteResponse<{ success: boolean }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the device belongs to the authenticated user\n\t\tconst device = await this.userStore.findDevice(deviceId)\n\t\tif (device === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'Device not found.' },\n\t\t\t}\n\t\t}\n\n\t\tif (device.userId !== payload.sub) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: 'You can only revoke your own devices.' },\n\t\t\t}\n\t\t}\n\n\t\tawait this.userStore.revokeDevice(deviceId)\n\n\t\t// Invalidate all tokens for this device\n\t\tawait this.tokenManager.revokeDeviceTokens(deviceId)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { success: true } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle device registration (POST /auth/device/register).\n\t *\n\t * Requires a valid access token. Registers a new device for the authenticated\n\t * user and issues a device credential token bound to the device's public key.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param body - Device registration request body\n\t * @param body.deviceId - Unique identifier for the device\n\t * @param body.publicKey - The device's public key as a JWK JSON string\n\t * @param body.name - Human-readable device name (e.g., \"Chrome on MacBook\")\n\t * @returns Auth response with the registered device and device credential, or an error\n\t */\n\tasync handleDeviceRegister(\n\t\taccessToken: string,\n\t\tbody: {\n\t\t\tdeviceId: string\n\t\t\tpublicKey: string\n\t\t\tname: string\n\t\t},\n\t): Promise<AuthRouteResponse<{ device: AuthDevice; deviceCredential: string }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Sanitize the device name\n\t\tconst deviceName = sanitizeName(body.name)\n\t\tif (deviceName.length === 0) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Device name must not be empty.' },\n\t\t\t}\n\t\t}\n\n\t\t// Parse the JWK JSON string into a JsonWebKey object\n\t\tlet publicKeyJwk: JsonWebKey\n\t\ttry {\n\t\t\tpublicKeyJwk = JSON.parse(body.publicKey) as JsonWebKey\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Invalid public key format. Expected a JSON-encoded JWK string.' },\n\t\t\t}\n\t\t}\n\n\t\t// Compute the SHA-256 thumbprint of the public key for binding to the credential\n\t\tlet thumbprint: string\n\t\ttry {\n\t\t\tthumbprint = await computePublicKeyThumbprint(publicKeyJwk)\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Failed to compute public key thumbprint. Ensure the key is a valid EC P-256 JWK.' },\n\t\t\t}\n\t\t}\n\n\t\t// Register the device in the user store\n\t\tconst device = await this.userStore.registerDevice({\n\t\t\tid: body.deviceId,\n\t\t\tuserId: payload.sub,\n\t\t\tpublicKey: body.publicKey,\n\t\t\tname: deviceName,\n\t\t})\n\n\t\t// Issue a device credential token bound to the public key thumbprint\n\t\tconst deviceCredential = this.tokenManager.issueDeviceCredential(\n\t\t\tpayload.sub,\n\t\t\tbody.deviceId,\n\t\t\tthumbprint,\n\t\t)\n\n\t\treturn {\n\t\t\tstatus: 201,\n\t\t\tbody: { data: { device, deviceCredential } },\n\t\t}\n\t}\n\n\t/**\n\t * Generate a challenge for device proof-of-possession verification.\n\t *\n\t * Creates a cryptographically random challenge, stores it server-side with\n\t * a 60-second TTL and the target device ID, and returns the challenge string.\n\t * The client signs this challenge with its private key and submits it via\n\t * {@link handleDeviceVerify}.\n\t *\n\t * @param accessToken - The JWT access token (without \"Bearer \" prefix)\n\t * @param deviceId - The device this challenge is intended for\n\t * @returns Auth response with the challenge string, or an error\n\t */\n\tasync handleDeviceChallenge(\n\t\taccessToken: string,\n\t\tdeviceId: string,\n\t): Promise<AuthRouteResponse<{ challenge: string }>> {\n\t\tconst payload = this.tokenManager.validateToken(accessToken)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired access token.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the device exists and belongs to this user\n\t\tconst device = await this.userStore.findDevice(deviceId)\n\t\tif (device === null || device.userId !== payload.sub) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'Device not found.' },\n\t\t\t}\n\t\t}\n\n\t\tif (device.revoked) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: 'Device has been revoked.' },\n\t\t\t}\n\t\t}\n\n\t\tconst challenge = randomBytes(32).toString('hex')\n\t\tconst expiresAt = Date.now() + CHALLENGE_TTL_MS\n\n\t\tawait this.challengeStore.store(challenge, deviceId, expiresAt)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { challenge } },\n\t\t}\n\t}\n\n\t/**\n\t * Handle device proof-of-possession verification (POST /auth/device/verify).\n\t *\n\t * Verifies that the device holds the private key corresponding to its registered\n\t * public key by checking a signed challenge. The challenge must have been previously\n\t * issued via {@link handleDeviceChallenge} and is single-use.\n\t *\n\t * On success, issues fresh tokens for the device.\n\t *\n\t * @param body - Device verification request body\n\t * @param body.deviceId - The ID of the device to verify\n\t * @param body.challenge - The challenge string (from handleDeviceChallenge)\n\t * @param body.signature - The base64url-encoded ECDSA signature of the challenge\n\t * @returns Auth response with fresh tokens on success, or an error\n\t */\n\tasync handleDeviceVerify(body: {\n\t\tdeviceId: string\n\t\tchallenge: string\n\t\tsignature: string\n\t}): Promise<AuthRouteResponse<{ tokens: AuthTokens }>> {\n\t\t// Consume the challenge (single-use, time-limited)\n\t\tconst challengeEntry = await this.challengeStore.consume(body.challenge)\n\t\tif (challengeEntry === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid or expired challenge. Request a new challenge and try again.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the challenge was issued for this device\n\t\tif (challengeEntry.deviceId !== body.deviceId) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Challenge was not issued for this device.' },\n\t\t\t}\n\t\t}\n\n\t\t// Look up the device in the store\n\t\tconst device = await this.userStore.findDevice(body.deviceId)\n\t\tif (device === null) {\n\t\t\treturn {\n\t\t\t\tstatus: 404,\n\t\t\t\tbody: { error: 'Device not found.' },\n\t\t\t}\n\t\t}\n\n\t\t// Revoked devices cannot verify\n\t\tif (device.revoked) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: 'Device has been revoked and cannot authenticate.' },\n\t\t\t}\n\t\t}\n\n\t\t// Parse the stored public key JWK\n\t\tlet publicKeyJwk: JsonWebKey\n\t\ttry {\n\t\t\tpublicKeyJwk = JSON.parse(device.publicKey) as JsonWebKey\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 500,\n\t\t\t\tbody: { error: 'Device has an invalid stored public key.' },\n\t\t\t}\n\t\t}\n\n\t\t// Verify the signature against the challenge using the device's public key\n\t\tlet isValid: boolean\n\t\ttry {\n\t\t\tisValid = await verifyChallenge(publicKeyJwk, body.challenge, body.signature)\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: 'Signature verification failed. The signature or public key format may be invalid.' },\n\t\t\t}\n\t\t}\n\n\t\tif (!isValid) {\n\t\t\treturn {\n\t\t\t\tstatus: 401,\n\t\t\t\tbody: { error: 'Invalid signature. Proof-of-possession verification failed.' },\n\t\t\t}\n\t\t}\n\n\t\t// Compute thumbprint for the device credential\n\t\tlet thumbprint: string\n\t\ttry {\n\t\t\tthumbprint = await computePublicKeyThumbprint(publicKeyJwk)\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tstatus: 500,\n\t\t\t\tbody: { error: 'Failed to compute public key thumbprint.' },\n\t\t\t}\n\t\t}\n\n\t\t// Issue fresh tokens for this device\n\t\tconst tokens = this.tokenManager.issueTokens(device.userId, device.id, thumbprint)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { tokens } },\n\t\t}\n\t}\n\n\t/**\n\t * Generates a random challenge string for proof-of-possession verification.\n\t *\n\t * **Deprecated:** Use {@link handleDeviceChallenge} instead, which stores\n\t * the challenge server-side with expiry and single-use semantics.\n\t *\n\t * @returns A 64-character hex string (32 random bytes)\n\t */\n\tstatic generateChallenge(): string {\n\t\treturn randomBytes(32).toString('hex')\n\t}\n\n\t/**\n\t * Creates a sync server auth provider compatible with `@korajs/server`.\n\t *\n\t * The returned object implements the `AuthProvider` interface from\n\t * `@korajs/server`, validating access tokens and returning an auth\n\t * context containing the user ID and device metadata. This bridges\n\t * the built-in auth system with the sync server's authentication layer.\n\t *\n\t * Also checks device revocation status during authentication, ensuring\n\t * that revoked devices are rejected even if their tokens haven't expired.\n\t *\n\t * @returns An object with an `authenticate` method suitable for KoraSyncServer's `auth` config\n\t *\n\t * @example\n\t * ```typescript\n\t * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n\t * const syncServer = new KoraSyncServer({\n\t * store,\n\t * auth: routes.toSyncAuthProvider(),\n\t * })\n\t * ```\n\t */\n\ttoSyncAuthProvider(): {\n\t\tauthenticate(token: string): Promise<{\n\t\t\tuserId: string\n\t\t\tscopes?: Record<string, Record<string, unknown>>\n\t\t\tmetadata?: Record<string, unknown>\n\t\t} | null>\n\t} {\n\t\tconst tokenManager = this.tokenManager\n\t\tconst userStore = this.userStore\n\n\t\treturn {\n\t\t\tasync authenticate(token: string) {\n\t\t\t\tconst payload = tokenManager.validateToken(token)\n\t\t\t\tif (payload === null || payload.type !== 'access') {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\t// Verify the user still exists\n\t\t\t\tconst user = await userStore.findById(payload.sub)\n\t\t\t\tif (user === null) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\t// Check device revocation status\n\t\t\t\tconst device = await userStore.findDevice(payload.dev)\n\t\t\t\tif (device !== null && device.revoked) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\t// Touch the device to update last-seen timestamp\n\t\t\t\tawait userStore.touchDevice(payload.dev)\n\n\t\t\t\treturn {\n\t\t\t\t\tuserId: payload.sub,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tdeviceId: payload.dev,\n\t\t\t\t\t\temail: user.email,\n\t\t\t\t\t\tname: user.name,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n","import { randomBytes, randomUUID } from 'node:crypto'\nimport type {\n\tAuthTokens,\n\tDeviceCredentialPayload,\n\tTokenPayload,\n} from '../types'\nimport {\n\tDEFAULT_ACCESS_TOKEN_LIFETIME,\n\tDEFAULT_DEVICE_CREDENTIAL_LIFETIME,\n\tDEFAULT_REFRESH_TOKEN_LIFETIME,\n} from '../types'\nimport { encodeJwt, isExpired, verifyJwt } from './jwt'\n\n/**\n * Minimum HMAC secret length in bytes. A 256-bit key provides full security\n * for HMAC-SHA256 (NIST SP 800-107). Shorter keys weaken the MAC and are\n * vulnerable to brute-force attacks.\n */\nconst MIN_SECRET_LENGTH = 32\n\n/**\n * Interface for server-side token revocation storage.\n *\n * Implementing this interface allows the TokenManager to:\n * - Revoke individual tokens by their `jti`\n * - Detect refresh token reuse (potential theft indicator)\n * - Invalidate all tokens for a specific device on revocation\n *\n * The in-memory implementation ({@link InMemoryTokenRevocationStore}) is suitable\n * for development. Production deployments should use a persistent store (Redis, database).\n */\nexport interface TokenRevocationStore {\n\t/**\n\t * Check whether a token has been revoked.\n\t * @param jti - The JWT ID to check\n\t * @returns true if the token has been revoked\n\t */\n\tisRevoked(jti: string): Promise<boolean>\n\n\t/**\n\t * Revoke a specific token by its JWT ID.\n\t * @param jti - The JWT ID to revoke\n\t * @param expiresAt - The token's original expiration time (seconds since epoch).\n\t * The store may use this to auto-purge expired revocations.\n\t */\n\trevoke(jti: string, expiresAt: number): Promise<void>\n\n\t/**\n\t * Revoke all tokens associated with a specific device.\n\t * Called when a device is revoked to invalidate all its tokens.\n\t * @param deviceId - The device ID whose tokens should be revoked\n\t */\n\trevokeAllForDevice(deviceId: string): Promise<void>\n}\n\n/**\n * In-memory token revocation store.\n *\n * Suitable for development and testing. Revoked tokens are stored in a Set\n * and automatically cleaned up when they would have expired naturally.\n *\n * **Not suitable for production**: revocations are lost on server restart\n * and not shared across server instances. Use a Redis or database-backed\n * store in production.\n */\nexport class InMemoryTokenRevocationStore implements TokenRevocationStore {\n\tprivate readonly revokedTokens = new Map<string, number>()\n\tprivate readonly revokedDevices = new Set<string>()\n\n\tasync isRevoked(jti: string): Promise<boolean> {\n\t\treturn this.revokedTokens.has(jti)\n\t}\n\n\tasync revoke(jti: string, expiresAt: number): Promise<void> {\n\t\tthis.revokedTokens.set(jti, expiresAt)\n\t}\n\n\tasync revokeAllForDevice(deviceId: string): Promise<void> {\n\t\tthis.revokedDevices.add(deviceId)\n\t}\n\n\t/**\n\t * Check if a device has been revoked.\n\t */\n\tisDeviceRevoked(deviceId: string): boolean {\n\t\treturn this.revokedDevices.has(deviceId)\n\t}\n\n\t/**\n\t * Remove expired revocations to prevent unbounded memory growth.\n\t * Call periodically (e.g., every hour) in long-running servers.\n\t */\n\tcleanup(): void {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tfor (const [jti, expiresAt] of this.revokedTokens) {\n\t\t\tif (nowSeconds > expiresAt) {\n\t\t\t\tthis.revokedTokens.delete(jti)\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Configuration for the server-side TokenManager.\n */\nexport interface TokenManagerConfig {\n\t/**\n\t * Secret key for signing JWTs (HMAC-SHA256).\n\t *\n\t * Must be at least 32 characters (256 bits). Use {@link TokenManager.generateSecret}\n\t * to create a cryptographically random secret.\n\t *\n\t * For key rotation, provide an array of secrets. The first secret is used for\n\t * signing new tokens; all secrets are tried during verification (newest first).\n\t * This allows graceful rotation: add the new secret at index 0, then remove\n\t * the old secret after all tokens signed with it have expired.\n\t */\n\tsecret: string | string[]\n\n\t/** Access token lifetime in milliseconds (default: 15 minutes) */\n\taccessTokenLifetime?: number\n\n\t/** Refresh token lifetime in milliseconds (default: 90 days) */\n\trefreshTokenLifetime?: number\n\n\t/** Device credential lifetime in milliseconds (default: 90 days) */\n\tdeviceCredentialLifetime?: number\n\n\t/**\n\t * Optional token revocation store. When provided, enables:\n\t * - Individual token revocation via `revokeToken()`\n\t * - Refresh token reuse detection (consumed tokens are tracked)\n\t * - Device-level token invalidation via `revokeDeviceTokens()`\n\t *\n\t * Without a revocation store, tokens are valid until they expire.\n\t */\n\trevocationStore?: TokenRevocationStore\n}\n\n/**\n * Server-side token manager responsible for issuing, refreshing, and validating\n * Kora authentication tokens.\n *\n * Uses HMAC-SHA256 signed JWTs with unique `jti` identifiers for every token.\n * Supports key rotation (multiple secrets), token revocation, and refresh token\n * reuse detection.\n *\n * @example\n * ```typescript\n * const tokenManager = new TokenManager({\n * secret: TokenManager.generateSecret(),\n * revocationStore: new InMemoryTokenRevocationStore(),\n * })\n *\n * // Issue all tokens at once\n * const tokens = tokenManager.issueTokens('user-123', 'device-456')\n *\n * // Validate an access token\n * const payload = tokenManager.validateToken(tokens.accessToken)\n *\n * // Refresh when the access token expires\n * const newTokens = await tokenManager.refreshAccessToken(tokens.refreshToken)\n * ```\n */\nexport class TokenManager {\n\t/** All signing/verification secrets (index 0 = current signing key) */\n\tprivate readonly secrets: string[]\n\tprivate readonly accessTokenLifetime: number\n\tprivate readonly refreshTokenLifetime: number\n\tprivate readonly deviceCredentialLifetime: number\n\tprivate readonly revocationStore: TokenRevocationStore | undefined\n\n\tconstructor(config: TokenManagerConfig) {\n\t\tconst secrets = Array.isArray(config.secret) ? config.secret : [config.secret]\n\n\t\tif (secrets.length === 0) {\n\t\t\tthrow new Error('TokenManager requires at least one secret.')\n\t\t}\n\n\t\tfor (const secret of secrets) {\n\t\t\tif (secret.length < MIN_SECRET_LENGTH) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`JWT secret must be at least ${MIN_SECRET_LENGTH} characters (256 bits) for HMAC-SHA256 security. ` +\n\t\t\t\t\t`Received ${secret.length} characters. Use TokenManager.generateSecret() to generate a secure secret.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tthis.secrets = secrets\n\t\tthis.accessTokenLifetime = config.accessTokenLifetime ?? DEFAULT_ACCESS_TOKEN_LIFETIME\n\t\tthis.refreshTokenLifetime = config.refreshTokenLifetime ?? DEFAULT_REFRESH_TOKEN_LIFETIME\n\t\tthis.deviceCredentialLifetime =\n\t\t\tconfig.deviceCredentialLifetime ?? DEFAULT_DEVICE_CREDENTIAL_LIFETIME\n\t\tthis.revocationStore = config.revocationStore\n\t}\n\n\t/**\n\t * Generate a cryptographically random secret suitable for HMAC-SHA256 signing.\n\t *\n\t * Returns a 64-character hex string (32 bytes / 256 bits of entropy).\n\t * Store this securely (environment variable, secrets manager) — never in source code.\n\t *\n\t * @returns A random 256-bit hex-encoded secret\n\t */\n\tstatic generateSecret(): string {\n\t\treturn randomBytes(32).toString('hex')\n\t}\n\n\t/**\n\t * Issue a signed JWT access token.\n\t *\n\t * Access tokens are short-lived (default 15 minutes) and used to authorize\n\t * API requests. When expired, use {@link refreshAccessToken} with a valid\n\t * refresh token to obtain a new one.\n\t *\n\t * @param userId - The subject (user ID) to encode in the token\n\t * @param deviceId - The device ID of the requesting device\n\t * @returns A signed JWT string with type 'access'\n\t */\n\tissueAccessToken(userId: string, deviceId: string): string {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tconst payload: TokenPayload = {\n\t\t\tjti: randomUUID(),\n\t\t\tsub: userId,\n\t\t\tdev: deviceId,\n\t\t\ttype: 'access',\n\t\t\tiat: nowSeconds,\n\t\t\texp: nowSeconds + Math.floor(this.accessTokenLifetime / 1000),\n\t\t}\n\t\treturn encodeJwt(payload as unknown as Record<string, unknown>, this.secrets[0] as string)\n\t}\n\n\t/**\n\t * Issue a signed JWT refresh token.\n\t *\n\t * Refresh tokens are longer-lived (default 90 days) and used exclusively\n\t * to obtain new access tokens via {@link refreshAccessToken}. They should\n\t * be stored securely and never sent to resource APIs.\n\t *\n\t * @param userId - The subject (user ID) to encode in the token\n\t * @param deviceId - The device ID of the requesting device\n\t * @returns A signed JWT string with type 'refresh'\n\t */\n\tissueRefreshToken(userId: string, deviceId: string): string {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tconst payload: TokenPayload = {\n\t\t\tjti: randomUUID(),\n\t\t\tsub: userId,\n\t\t\tdev: deviceId,\n\t\t\ttype: 'refresh',\n\t\t\tiat: nowSeconds,\n\t\t\texp: nowSeconds + Math.floor(this.refreshTokenLifetime / 1000),\n\t\t}\n\t\treturn encodeJwt(payload as unknown as Record<string, unknown>, this.secrets[0] as string)\n\t}\n\n\t/**\n\t * Issue a signed device credential token.\n\t *\n\t * Device credentials are long-lived tokens bound to a device's public key.\n\t * They include a `mustCheckinBy` deadline; if the device does not check in\n\t * before this deadline, the credential should be treated as revoked.\n\t *\n\t * @param userId - The subject (user ID) to encode in the token\n\t * @param deviceId - The device ID of the requesting device\n\t * @param publicKeyThumbprint - SHA-256 thumbprint of the device's public key\n\t * @returns A signed JWT string with type 'device_credential'\n\t */\n\tissueDeviceCredential(\n\t\tuserId: string,\n\t\tdeviceId: string,\n\t\tpublicKeyThumbprint: string,\n\t): string {\n\t\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\t\tconst lifetimeSeconds = Math.floor(this.deviceCredentialLifetime / 1000)\n\t\tconst payload: DeviceCredentialPayload = {\n\t\t\tjti: randomUUID(),\n\t\t\tsub: userId,\n\t\t\tdev: deviceId,\n\t\t\ttype: 'device_credential',\n\t\t\tiat: nowSeconds,\n\t\t\texp: nowSeconds + lifetimeSeconds,\n\t\t\tdpk: publicKeyThumbprint,\n\t\t\tmustCheckinBy: nowSeconds + lifetimeSeconds,\n\t\t}\n\t\treturn encodeJwt(payload as unknown as Record<string, unknown>, this.secrets[0] as string)\n\t}\n\n\t/**\n\t * Issue a complete set of authentication tokens.\n\t *\n\t * Always issues an access token and refresh token. If a `publicKeyThumbprint`\n\t * is provided, also issues a device credential.\n\t *\n\t * @param userId - The subject (user ID) to encode in the tokens\n\t * @param deviceId - The device ID of the requesting device\n\t * @param publicKeyThumbprint - Optional SHA-256 thumbprint of the device's public key.\n\t * When provided, a device credential is included in the returned tokens.\n\t * @returns An {@link AuthTokens} object containing the issued tokens\n\t */\n\tissueTokens(\n\t\tuserId: string,\n\t\tdeviceId: string,\n\t\tpublicKeyThumbprint?: string,\n\t): AuthTokens {\n\t\tconst tokens: AuthTokens = {\n\t\t\taccessToken: this.issueAccessToken(userId, deviceId),\n\t\t\trefreshToken: this.issueRefreshToken(userId, deviceId),\n\t\t}\n\n\t\tif (publicKeyThumbprint !== undefined) {\n\t\t\ttokens.deviceCredential = this.issueDeviceCredential(\n\t\t\t\tuserId,\n\t\t\t\tdeviceId,\n\t\t\t\tpublicKeyThumbprint,\n\t\t\t)\n\t\t}\n\n\t\treturn tokens\n\t}\n\n\t/**\n\t * Validate and decode a token.\n\t *\n\t * Verifies the HMAC-SHA256 signature (trying all configured secrets for key rotation),\n\t * checks that the token has not expired, and validates all required claims.\n\t * Returns null (rather than throwing) for invalid or expired tokens, so callers\n\t * can handle authentication failure without try/catch.\n\t *\n\t * @param token - The JWT string to validate\n\t * @returns The decoded {@link TokenPayload} if valid, or null if the token is\n\t * invalid, expired, or missing required claims\n\t */\n\tvalidateToken(token: string): TokenPayload | null {\n\t\t// Try verification with each secret (supports key rotation)\n\t\tlet decoded: Record<string, unknown> | null = null\n\t\tfor (const secret of this.secrets) {\n\t\t\tdecoded = verifyJwt(token, secret)\n\t\t\tif (decoded !== null) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (decoded === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// verifyJwt validates the signature but not expiration;\n\t\t// check the exp claim separately\n\t\tif (isExpired(decoded as { exp?: number })) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Validate that all required base claims are present and correctly typed\n\t\tif (\n\t\t\ttypeof decoded['jti'] !== 'string' ||\n\t\t\ttypeof decoded['sub'] !== 'string' ||\n\t\t\ttypeof decoded['dev'] !== 'string' ||\n\t\t\ttypeof decoded['type'] !== 'string' ||\n\t\t\ttypeof decoded['iat'] !== 'number' ||\n\t\t\ttypeof decoded['exp'] !== 'number'\n\t\t) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst type = decoded['type']\n\t\tif (type !== 'access' && type !== 'refresh' && type !== 'device_credential') {\n\t\t\treturn null\n\t\t}\n\n\t\treturn {\n\t\t\tjti: decoded['jti'] as string,\n\t\t\tsub: decoded['sub'] as string,\n\t\t\tdev: decoded['dev'] as string,\n\t\t\ttype,\n\t\t\tiat: decoded['iat'] as number,\n\t\t\texp: decoded['exp'] as number,\n\t\t}\n\t}\n\n\t/**\n\t * Validate a token and check it against the revocation store.\n\t *\n\t * Like {@link validateToken}, but also checks whether the token's `jti` has been\n\t * revoked. Requires a revocation store to be configured.\n\t *\n\t * @param token - The JWT string to validate\n\t * @returns The decoded {@link TokenPayload} if valid and not revoked, or null otherwise\n\t */\n\tasync validateTokenWithRevocation(token: string): Promise<TokenPayload | null> {\n\t\tconst payload = this.validateToken(token)\n\t\tif (payload === null) {\n\t\t\treturn null\n\t\t}\n\n\t\tif (this.revocationStore) {\n\t\t\tconst revoked = await this.revocationStore.isRevoked(payload.jti)\n\t\t\tif (revoked) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\n\t\treturn payload\n\t}\n\n\t/**\n\t * Revoke a specific token by its JWT ID.\n\t *\n\t * Requires a revocation store to be configured. After revocation, the token\n\t * will be rejected by {@link validateTokenWithRevocation}.\n\t *\n\t * @param jti - The JWT ID of the token to revoke\n\t * @param expiresAt - The token's expiration time (seconds since epoch)\n\t */\n\tasync revokeToken(jti: string, expiresAt: number): Promise<void> {\n\t\tif (this.revocationStore) {\n\t\t\tawait this.revocationStore.revoke(jti, expiresAt)\n\t\t}\n\t}\n\n\t/**\n\t * Revoke all tokens for a specific device.\n\t *\n\t * Called when a device is revoked to ensure all its existing tokens\n\t * (access, refresh, and device credentials) are invalidated.\n\t *\n\t * @param deviceId - The device ID whose tokens should be revoked\n\t */\n\tasync revokeDeviceTokens(deviceId: string): Promise<void> {\n\t\tif (this.revocationStore) {\n\t\t\tawait this.revocationStore.revokeAllForDevice(deviceId)\n\t\t}\n\t}\n\n\t/**\n\t * Refresh an access token using a valid refresh token.\n\t *\n\t * Implements **refresh token rotation with reuse detection**: a new refresh token\n\t * is issued alongside the new access token. The old refresh token's `jti` is\n\t * recorded in the revocation store (if configured). If a previously consumed\n\t * refresh token is presented again, it indicates potential token theft.\n\t *\n\t * Returns null if the provided token is invalid, expired, or not a refresh token.\n\t *\n\t * @param refreshToken - The refresh token JWT string\n\t * @returns A new access/refresh token pair, or null if the refresh token is invalid\n\t */\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t): Promise<{ accessToken: string; refreshToken: string } | null> {\n\t\tconst payload = this.validateToken(refreshToken)\n\n\t\tif (payload === null) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Only refresh tokens can be used for token refresh.\n\t\t// Accepting access tokens or device credentials here would be a security hole.\n\t\tif (payload.type !== 'refresh') {\n\t\t\treturn null\n\t\t}\n\n\t\t// Check revocation store for replay detection\n\t\tif (this.revocationStore) {\n\t\t\tconst wasRevoked = await this.revocationStore.isRevoked(payload.jti)\n\t\t\tif (wasRevoked) {\n\t\t\t\t// This refresh token was already consumed. This is a potential token theft.\n\t\t\t\t// Revoke all tokens for this device as a safety measure.\n\t\t\t\tawait this.revocationStore.revokeAllForDevice(payload.dev)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Mark this refresh token as consumed (revoked)\n\t\t\tawait this.revocationStore.revoke(payload.jti, payload.exp)\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: this.issueAccessToken(payload.sub, payload.dev),\n\t\t\trefreshToken: this.issueRefreshToken(payload.sub, payload.dev),\n\t\t}\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// User types\n// ============================================================================\n\n/**\n * Authenticated user record.\n * Represents the public-facing user identity returned by sign-in and sign-up flows.\n */\nexport interface AuthUser {\n\t/** Unique user identifier (UUID v7) */\n\tid: string\n\t/** User's email address, used as the primary login credential */\n\temail: string\n\t/** Display name */\n\tname: string\n\t/** Timestamp of user creation (milliseconds since epoch) */\n\tcreatedAt: number\n\t/** Timestamp of last profile update (milliseconds since epoch) */\n\tupdatedAt: number\n}\n\n/**\n * A registered device that can operate on behalf of a user.\n * Each device has its own keypair for offline credential verification.\n */\nexport interface AuthDevice {\n\t/** Device identifier, same as the Kora nodeId for this device */\n\tid: string\n\t/** The user this device belongs to */\n\tuserId: string\n\t/** JWK-encoded public key for this device's keypair */\n\tpublicKey: string\n\t/** Human-readable device name (e.g., \"Chrome on MacBook\") */\n\tname: string\n\t/** Timestamp when the device was first registered (milliseconds since epoch) */\n\tregisteredAt: number\n\t/** Timestamp of last activity from this device (milliseconds since epoch) */\n\tlastSeenAt: number\n\t/** Whether this device is allowed to sync */\n\tstatus: DeviceStatus\n}\n\n/** Device status values */\nexport type DeviceStatus = 'active' | 'revoked'\n\n// ============================================================================\n// Token types\n// ============================================================================\n\n/** The three kinds of tokens issued by the auth system */\nexport type TokenType = 'access' | 'refresh' | 'device_credential'\n\n/**\n * Base payload present in all JWT tokens.\n * Fields follow standard JWT claim names.\n */\nexport interface TokenPayload {\n\t/** JWT ID: unique identifier for this specific token (for revocation and replay detection) */\n\tjti: string\n\t/** Subject: the user ID */\n\tsub: string\n\t/** Device ID that this token was issued to */\n\tdev: string\n\t/** Which kind of token this is */\n\ttype: TokenType\n\t/** Issued-at time (seconds since epoch, per JWT spec) */\n\tiat: number\n\t/** Expiration time (seconds since epoch, per JWT spec) */\n\texp: number\n}\n\n/**\n * Short-lived token used to authenticate API requests.\n * Typically lives for 15 minutes.\n */\nexport interface AccessTokenPayload extends TokenPayload {\n\ttype: 'access'\n}\n\n/**\n * Longer-lived token used to obtain new access tokens.\n * Typically lives for 90 days.\n */\nexport interface RefreshTokenPayload extends TokenPayload {\n\ttype: 'refresh'\n}\n\n/**\n * Offline credential stored on the device.\n * Allows the device to continue operating offline, with a mandatory\n * check-in deadline by which it must reconnect to remain authorized.\n */\nexport interface DeviceCredentialPayload extends TokenPayload {\n\ttype: 'device_credential'\n\t/** Device public key thumbprint, binds this credential to a specific device keypair */\n\tdpk: string\n\t/** Timestamp (seconds since epoch) by which the device must check in with the server */\n\tmustCheckinBy: number\n}\n\n/**\n * Bundle of tokens returned after successful authentication.\n */\nexport interface AuthTokens {\n\t/** Short-lived access token */\n\taccessToken: string\n\t/** Long-lived refresh token */\n\trefreshToken: string\n\t/** Optional device credential for offline operation */\n\tdeviceCredential?: string\n}\n\n// ============================================================================\n// Auth configuration\n// ============================================================================\n\n/** Supported auth provider types */\nexport type AuthProviderType = 'built-in' | 'custom'\n\n/** Unlock mechanism for encrypted local storage */\nexport type UnlockMethod = 'biometric' | 'passphrase' | 'both'\n\n/**\n * Encryption settings for locally stored auth credentials.\n */\nexport interface AuthEncryptionConfig {\n\t/** Whether local credential encryption is enabled */\n\tenabled: boolean\n\t/** How the user unlocks encrypted credentials */\n\tunlock: UnlockMethod\n\t/** Milliseconds of inactivity before the app auto-locks. Defaults to 15 minutes. */\n\tautoLockTimeout?: number\n}\n\n/**\n * Custom lifetimes for each token type.\n * All values are in milliseconds.\n */\nexport interface TokenLifetimeConfig {\n\t/** Access token lifetime in ms. Default: 15 minutes. */\n\taccess?: number\n\t/** Refresh token lifetime in ms. Default: 90 days. */\n\trefresh?: number\n\t/** Device credential lifetime in ms. Default: 90 days. */\n\tdeviceCredential?: number\n}\n\n/**\n * Configuration for the Kora auth system.\n * Passed to the auth initializer to control provider, encryption, and token behavior.\n */\nexport interface AuthConfig {\n\t/** Which auth provider to use */\n\tprovider: AuthProviderType\n\t/** Local encryption settings for stored credentials */\n\tencryption?: AuthEncryptionConfig\n\t/** Maximum time a device can operate offline without checking in (ms). Default: 30 days. */\n\tmaxOfflineDuration?: number\n\t/** Custom token lifetimes */\n\ttokenLifetimes?: TokenLifetimeConfig\n}\n\n// ============================================================================\n// Auth state\n// ============================================================================\n\n/** Possible authentication states */\nexport type AuthStatus = 'authenticated' | 'unauthenticated' | 'locked'\n\n/**\n * Current state of the auth system.\n * This is the value exposed to the UI layer for rendering auth-dependent views.\n */\nexport interface AuthState {\n\t/** Current authentication status */\n\tstatus: AuthStatus\n\t/** The authenticated user, or null if unauthenticated/locked */\n\tuser: AuthUser | null\n\t/** The current device's ID, or null if not yet registered */\n\tdeviceId: string | null\n}\n\n// ============================================================================\n// Auth events\n// ============================================================================\n\n/**\n * Events emitted by the auth system.\n * These integrate with Kora's event system for DevTools observability.\n */\nexport type AuthEvent =\n\t| { type: 'auth:signed-in'; user: AuthUser }\n\t| { type: 'auth:signed-out' }\n\t| { type: 'auth:locked' }\n\t| { type: 'auth:unlocked'; user: AuthUser }\n\t| { type: 'auth:token-refreshed' }\n\t| { type: 'auth:device-revoked'; deviceId: string }\n\t| { type: 'auth:permission-changed' }\n\n/** Extract the event type string union from AuthEvent */\nexport type AuthEventType = AuthEvent['type']\n\n/** Extract a specific auth event by its type */\nexport type AuthEventByType<T extends AuthEventType> = Extract<AuthEvent, { type: T }>\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Base error class for authentication-related failures.\n * Extends KoraError to integrate with the framework's error handling patterns.\n */\nexport class AuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuthError'\n\t}\n}\n\n/**\n * Thrown when sign-in credentials are invalid (wrong email or password).\n */\nexport class InvalidCredentialsError extends AuthError {\n\tconstructor() {\n\t\tsuper('Invalid email or password.', 'AUTH_INVALID_CREDENTIALS')\n\t\tthis.name = 'InvalidCredentialsError'\n\t}\n}\n\n/**\n * Thrown when a user tries to sign up with an email that already exists.\n */\nexport class EmailAlreadyExistsError extends AuthError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'An account with this email already exists.',\n\t\t\t'AUTH_EMAIL_EXISTS',\n\t\t)\n\t\tthis.name = 'EmailAlreadyExistsError'\n\t}\n}\n\n/**\n * Thrown when a token is expired, malformed, or has an invalid signature.\n */\nexport class TokenError extends AuthError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'AUTH_TOKEN_ERROR', context)\n\t\tthis.name = 'TokenError'\n\t}\n}\n\n/**\n * Thrown when a device's credential has expired or the device has been revoked.\n */\nexport class DeviceRevokedError extends AuthError {\n\tconstructor(deviceId: string) {\n\t\tsuper(\n\t\t\t`Device \"${deviceId}\" has been revoked and can no longer sync.`,\n\t\t\t'AUTH_DEVICE_REVOKED',\n\t\t\t{ deviceId },\n\t\t)\n\t\tthis.name = 'DeviceRevokedError'\n\t}\n}\n\n/**\n * Thrown when the maximum offline duration has been exceeded\n * and the device must reconnect to continue operating.\n */\nexport class OfflineExpiredError extends AuthError {\n\tconstructor(maxDuration: number, lastCheckin: number) {\n\t\tconst daysSinceCheckin = Math.round((Date.now() - lastCheckin) / (24 * 60 * 60 * 1000))\n\t\tsuper(\n\t\t\t`Device has been offline for ${daysSinceCheckin} days, exceeding the maximum offline duration. Reconnect to re-authenticate.`,\n\t\t\t'AUTH_OFFLINE_EXPIRED',\n\t\t\t{ maxDuration, lastCheckin, daysSinceCheckin },\n\t\t)\n\t\tthis.name = 'OfflineExpiredError'\n\t}\n}\n\n// ============================================================================\n// Sign up / sign in params\n// ============================================================================\n\n/**\n * Parameters for creating a new user account.\n */\nexport interface SignUpParams {\n\t/** Email address (used as login credential) */\n\temail: string\n\t/** Password (will be hashed before storage) */\n\tpassword: string\n\t/** Optional display name. Defaults to the local part of the email if omitted. */\n\tname?: string\n}\n\n/**\n * Parameters for signing in to an existing account.\n */\nexport interface SignInParams {\n\t/** Email address */\n\temail: string\n\t/** Password */\n\tpassword: string\n}\n\n// ============================================================================\n// Server-side types for the built-in provider\n// ============================================================================\n\n/**\n * Parameters for creating a user record in the server-side store.\n * The password has already been hashed by the time this is used.\n */\nexport interface CreateUserParams {\n\t/** Email address */\n\temail: string\n\t/** Argon2id or bcrypt hash of the password */\n\tpasswordHash: string\n\t/** Random salt used during hashing */\n\tsalt: string\n\t/** Display name */\n\tname: string\n}\n\n/**\n * Full user record as stored on the server, including sensitive credential fields.\n * This type must NEVER be returned to the client; strip passwordHash and salt first.\n */\nexport interface StoredUser extends AuthUser {\n\t/** Hashed password */\n\tpasswordHash: string\n\t/** Salt used for hashing */\n\tsalt: string\n}\n\n// ============================================================================\n// Auth provider adapter interface\n// ============================================================================\n\n/**\n * Adapter interface for pluggable auth providers.\n * Implement this to integrate Kora auth with a custom identity provider\n * (e.g., Firebase Auth, Auth0, Supabase Auth).\n *\n * The built-in provider implements this interface internally.\n */\nexport interface AuthProviderAdapter {\n\t/** Register a new user and return the user with initial tokens */\n\tsignUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\t/** Authenticate an existing user and return the user with tokens */\n\tsignIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\t/** Exchange a refresh token for a new token set */\n\trefreshToken(refreshToken: string): Promise<AuthTokens>\n\t/** Validate an access token and return its payload, or null if invalid */\n\tvalidateAccessToken(token: string): Promise<TokenPayload | null>\n\t/** Revoke a device, preventing it from syncing */\n\trevokeDevice(deviceId: string): Promise<void>\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default access token lifetime: 15 minutes */\nexport const DEFAULT_ACCESS_TOKEN_LIFETIME = 15 * 60 * 1000\n\n/** Default refresh token lifetime: 90 days */\nexport const DEFAULT_REFRESH_TOKEN_LIFETIME = 90 * 24 * 60 * 60 * 1000\n\n/** Default device credential lifetime: 90 days */\nexport const DEFAULT_DEVICE_CREDENTIAL_LIFETIME = 90 * 24 * 60 * 60 * 1000\n\n/** Default maximum offline duration: 30 days */\nexport const DEFAULT_MAX_OFFLINE_DURATION = 30 * 24 * 60 * 60 * 1000\n\n/** Default auto-lock timeout: 15 minutes */\nexport const DEFAULT_AUTO_LOCK_TIMEOUT = 15 * 60 * 1000\n","import { createHmac } from 'node:crypto'\n\n// ============================================================================\n// Base64url helpers\n// ============================================================================\n\n/**\n * Encodes a UTF-8 string to base64url format (RFC 7515).\n * Base64url uses the URL-safe alphabet (- instead of +, _ instead of /)\n * and strips trailing padding characters.\n *\n * @param input - The UTF-8 string to encode\n * @returns Base64url-encoded string\n */\nexport function base64urlEncode(input: string): string {\n\treturn Buffer.from(input, 'utf-8').toString('base64url')\n}\n\n/**\n * Decodes a base64url-encoded string back to UTF-8.\n *\n * @param input - The base64url-encoded string to decode\n * @returns Decoded UTF-8 string\n */\nexport function base64urlDecode(input: string): string {\n\treturn Buffer.from(input, 'base64url').toString('utf-8')\n}\n\n// ============================================================================\n// Internal signing\n// ============================================================================\n\n/**\n * Computes an HMAC-SHA256 signature and returns it as a base64url string.\n * Uses Node.js crypto module for synchronous signing.\n */\nfunction hmacSha256Base64url(data: string, secret: string): string {\n\treturn createHmac('sha256', secret).update(data).digest('base64url')\n}\n\n// ============================================================================\n// JWT operations\n// ============================================================================\n\n/** Pre-encoded JWT header. Only HS256 is supported; the header never changes. */\nconst ENCODED_HEADER = base64urlEncode(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))\n\n/**\n * Creates a signed JWT (HS256) from a payload and secret.\n *\n * The token is structured as `header.payload.signature` per RFC 7519.\n * Only HMAC-SHA256 is supported. The header is always `{\"alg\":\"HS256\",\"typ\":\"JWT\"}`.\n *\n * @param payload - The claims to include in the token. Must be JSON-serializable.\n * @param secret - The HMAC-SHA256 secret key used for signing.\n * @returns A signed JWT string in the format `header.payload.signature`\n *\n * @example\n * ```typescript\n * const token = encodeJwt(\n * { sub: 'user-123', exp: Math.floor(Date.now() / 1000) + 900 },\n * 'my-secret'\n * )\n * ```\n */\nexport function encodeJwt(payload: Record<string, unknown>, secret: string): string {\n\tconst encodedPayload = base64urlEncode(JSON.stringify(payload))\n\tconst signingInput = `${ENCODED_HEADER}.${encodedPayload}`\n\tconst signature = hmacSha256Base64url(signingInput, secret)\n\treturn `${signingInput}.${signature}`\n}\n\n/**\n * Decodes a JWT without verifying its signature.\n *\n * Use this only when you need to inspect claims (e.g., reading `exp` to decide\n * whether to attempt a refresh) and do NOT need to trust the token's authenticity.\n * For trusted reads, use {@link verifyJwt} instead.\n *\n * @param token - The JWT string to decode\n * @returns The decoded payload as a record, or null if the token is malformed\n *\n * @example\n * ```typescript\n * const claims = decodeJwt(token)\n * if (claims && typeof claims.sub === 'string') {\n * console.log('User:', claims.sub)\n * }\n * ```\n */\nexport function decodeJwt(token: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\tconst payloadSegment = parts[1]\n\tif (payloadSegment === undefined) {\n\t\treturn null\n\t}\n\n\ttry {\n\t\tconst decoded = base64urlDecode(payloadSegment)\n\t\tconst parsed: unknown = JSON.parse(decoded)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Decodes a JWT and verifies its HMAC-SHA256 signature.\n *\n * Returns the payload only if the signature is valid. Returns null if the token\n * is malformed, has an invalid signature, or cannot be parsed.\n *\n * This function does NOT check expiration. Use {@link isExpired} separately\n * to check the `exp` claim, allowing callers to distinguish between\n * \"invalid signature\" (security issue) and \"expired\" (normal lifecycle).\n *\n * @param token - The JWT string to verify\n * @param secret - The HMAC-SHA256 secret key that was used to sign the token\n * @returns The decoded payload if the signature is valid, or null otherwise\n *\n * @example\n * ```typescript\n * const claims = verifyJwt(token, 'my-secret')\n * if (claims === null) {\n * throw new Error('Invalid token signature')\n * }\n * if (isExpired(claims)) {\n * throw new Error('Token has expired')\n * }\n * ```\n */\nexport function verifyJwt(token: string, secret: string): Record<string, unknown> | null {\n\tconst parts = token.split('.')\n\tif (parts.length !== 3) {\n\t\treturn null\n\t}\n\n\tconst headerSegment = parts[0]\n\tconst payloadSegment = parts[1]\n\tconst signatureSegment = parts[2]\n\n\tif (\n\t\theaderSegment === undefined\n\t\t|| payloadSegment === undefined\n\t\t|| signatureSegment === undefined\n\t) {\n\t\treturn null\n\t}\n\n\t// Validate the header to prevent algorithm confusion attacks.\n\t// Only HS256 is supported; reject any token claiming a different algorithm.\n\tif (headerSegment !== ENCODED_HEADER) {\n\t\treturn null\n\t}\n\n\t// Recompute signature and compare\n\tconst signingInput = `${headerSegment}.${payloadSegment}`\n\tconst expectedSignature = hmacSha256Base64url(signingInput, secret)\n\n\t// Constant-time comparison to prevent timing attacks.\n\t// Both strings are base64url-encoded HMAC outputs, so they are ASCII-safe\n\t// and we can compare byte-by-byte without early exit.\n\tif (expectedSignature.length !== signatureSegment.length) {\n\t\treturn null\n\t}\n\tlet mismatch = 0\n\tfor (let i = 0; i < expectedSignature.length; i++) {\n\t\t// Bitwise OR accumulates differences without short-circuiting\n\t\tmismatch |= expectedSignature.charCodeAt(i) ^ signatureSegment.charCodeAt(i)\n\t}\n\tif (mismatch !== 0) {\n\t\treturn null\n\t}\n\n\t// Decode the payload\n\ttry {\n\t\tconst decoded = base64urlDecode(payloadSegment)\n\t\tconst parsed: unknown = JSON.parse(decoded)\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn null\n\t\t}\n\t\treturn parsed as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Clock skew tolerance in seconds. Allows for minor clock differences\n * between servers in multi-server deployments.\n */\nconst CLOCK_SKEW_TOLERANCE_SECONDS = 5\n\n/**\n * Checks whether a token payload has expired based on its `exp` claim.\n *\n * The `exp` claim is expected to be in seconds since the Unix epoch (per JWT spec).\n * Includes a small clock skew tolerance (5 seconds) to handle minor time\n * differences between servers. If the `exp` claim is missing or is not a number,\n * the token is considered non-expiring and this function returns false.\n *\n * @param payload - An object with an optional `exp` field (seconds since epoch)\n * @returns true if the token has expired, false otherwise\n *\n * @example\n * ```typescript\n * const claims = verifyJwt(token, secret)\n * if (claims && isExpired(claims)) {\n * // Token signature is valid but it has expired -- attempt refresh\n * }\n * ```\n */\nexport function isExpired(payload: { exp?: number }): boolean {\n\tif (typeof payload.exp !== 'number') {\n\t\treturn false\n\t}\n\tconst nowSeconds = Math.floor(Date.now() / 1000)\n\treturn nowSeconds >= payload.exp + CLOCK_SKEW_TOLERANCE_SECONDS\n}\n","import { KoraError } from '@korajs/core'\nimport { randomUUID } from 'node:crypto'\n\n/**\n * A user as visible to the application layer.\n * Does not include sensitive fields like password hash or salt.\n */\nexport interface AuthUser {\n\t/** Unique user identifier (UUID v7 or crypto.randomUUID) */\n\tid: string\n\t/** User's email address */\n\temail: string\n\t/** User's display name */\n\tname: string\n\t/** Whether the user's email has been verified */\n\temailVerified: boolean\n\t/** Timestamp when the user was created (milliseconds since epoch) */\n\tcreatedAt: number\n}\n\n/**\n * Internal user record that includes credentials.\n * Extends AuthUser with password hash and salt for verification.\n */\nexport interface StoredUser extends AuthUser {\n\t/** Hex-encoded PBKDF2 derived key */\n\tpasswordHash: string\n\t/** Hex-encoded random salt used during hashing */\n\tsalt: string\n}\n\n/**\n * A device registered to a user.\n */\nexport interface AuthDevice {\n\t/** Unique device identifier */\n\tid: string\n\t/** ID of the user who owns this device */\n\tuserId: string\n\t/** Base64url-encoded public key (or thumbprint) for the device */\n\tpublicKey: string\n\t/** Human-readable device name */\n\tname: string\n\t/** Whether the device has been revoked */\n\trevoked: boolean\n\t/** Timestamp when the device was first registered (milliseconds since epoch) */\n\tcreatedAt: number\n\t/** Timestamp when the device was last seen (milliseconds since epoch) */\n\tlastSeenAt: number\n}\n\n/**\n * Thrown when a user account already exists with the given email.\n */\nexport class DuplicateEmailError extends KoraError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'A user with this email already exists.',\n\t\t\t'DUPLICATE_EMAIL',\n\t\t)\n\t\tthis.name = 'DuplicateEmailError'\n\t}\n}\n\n/**\n * Generic interface for user and device persistence.\n *\n * Implement this interface to provide database-backed user storage for\n * the built-in auth provider. All methods are async to support both\n * synchronous (in-memory, SQLite) and asynchronous (PostgreSQL) backends.\n *\n * Built-in implementations:\n * - {@link InMemoryUserStore} — development and testing (no persistence)\n * - `SqliteUserStore` — SQLite via better-sqlite3 (from `@korajs/auth/server`)\n * - `PostgresUserStore` — PostgreSQL via postgres-js (from `@korajs/auth/server`)\n *\n * @example\n * ```typescript\n * import { BuiltInAuthRoutes, SqliteUserStore } from '@korajs/auth/server'\n *\n * const userStore = await createSqliteUserStore({ filename: './auth.db' })\n * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n * ```\n */\nexport interface UserStore {\n\t/** Create a new user account. Throws DuplicateEmailError if email exists. */\n\tcreateUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser>\n\n\t/** Find a user by email address (case-insensitive). */\n\tfindByEmail(email: string): Promise<StoredUser | null>\n\n\t/** Find a user by ID. */\n\tfindById(id: string): Promise<StoredUser | null>\n\n\t/** Register a device for a user. Idempotent if device already exists and is not revoked. */\n\tregisterDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice>\n\n\t/** Find a device by its ID. */\n\tfindDevice(deviceId: string): Promise<AuthDevice | null>\n\n\t/** List all devices registered for a user (includes revoked). */\n\tlistDevices(userId: string): Promise<AuthDevice[]>\n\n\t/** Soft-revoke a device. No-op if device does not exist. */\n\trevokeDevice(deviceId: string): Promise<void>\n\n\t/** Set a user's email verification status. */\n\tsetEmailVerified(userId: string, verified: boolean): Promise<void>\n\n\t/** Update a user's password hash and salt. */\n\tupdatePassword(userId: string, passwordHash: string, salt: string): Promise<void>\n\n\t/** List all users. For admin/development use. */\n\tlistAll(): Promise<StoredUser[]>\n\n\t/** Update a stored user record. */\n\tupdate(user: StoredUser): Promise<void>\n\n\t/** Delete a user and all associated devices. */\n\tdelete(userId: string): Promise<void>\n\n\t/** Update the last-seen timestamp for a device. No-op if device does not exist. */\n\ttouchDevice(deviceId: string): Promise<void>\n}\n\n/**\n * In-memory user and device store for the built-in auth provider.\n *\n * This is a simple implementation suitable for development and testing.\n * Production applications should use {@link SqliteUserStore} or\n * {@link PostgresUserStore} for persistent storage.\n *\n * @example\n * ```typescript\n * const store = new InMemoryUserStore()\n * const user = await store.createUser({\n * email: 'alice@example.com',\n * passwordHash: 'abc123...',\n * salt: 'def456...',\n * name: 'Alice',\n * })\n * ```\n */\nexport class InMemoryUserStore implements UserStore {\n\t/** Users indexed by ID */\n\tprivate readonly usersById = new Map<string, StoredUser>()\n\n\t/** Users indexed by email (lowercase) for fast lookup */\n\tprivate readonly usersByEmail = new Map<string, StoredUser>()\n\n\t/** Devices indexed by device ID */\n\tprivate readonly devicesById = new Map<string, AuthDevice>()\n\n\t/** Device IDs indexed by user ID for fast listing */\n\tprivate readonly devicesByUserId = new Map<string, Set<string>>()\n\n\t/**\n\t * Create a new user account.\n\t *\n\t * @param params - User creation parameters\n\t * @param params.email - The user's email address (must be unique, case-insensitive)\n\t * @param params.passwordHash - Hex-encoded PBKDF2 derived key\n\t * @param params.salt - Hex-encoded salt used during hashing\n\t * @param params.name - The user's display name\n\t * @returns The created user (without sensitive credential fields)\n\t * @throws {DuplicateEmailError} If a user with the same email already exists\n\t */\n\tasync createUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser> {\n\t\tconst normalizedEmail = params.email.toLowerCase()\n\n\t\tif (this.usersByEmail.has(normalizedEmail)) {\n\t\t\tthrow new DuplicateEmailError()\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst id = randomUUID()\n\n\t\tconst storedUser: StoredUser = {\n\t\t\tid,\n\t\t\temail: normalizedEmail,\n\t\t\tname: params.name,\n\t\t\temailVerified: false,\n\t\t\tcreatedAt: now,\n\t\t\tpasswordHash: params.passwordHash,\n\t\t\tsalt: params.salt,\n\t\t}\n\n\t\tthis.usersById.set(id, storedUser)\n\t\tthis.usersByEmail.set(normalizedEmail, storedUser)\n\n\t\treturn toAuthUser(storedUser)\n\t}\n\n\t/**\n\t * Find a user by email address.\n\t *\n\t * @param email - The email to search for (case-insensitive)\n\t * @returns The stored user record including credentials, or null if not found\n\t */\n\tasync findByEmail(email: string): Promise<StoredUser | null> {\n\t\treturn this.usersByEmail.get(email.toLowerCase()) ?? null\n\t}\n\n\t/**\n\t * Find a user by ID.\n\t *\n\t * @param id - The user ID to search for\n\t * @returns The stored user record including credentials, or null if not found\n\t */\n\tasync findById(id: string): Promise<StoredUser | null> {\n\t\treturn this.usersById.get(id) ?? null\n\t}\n\n\t/**\n\t * Register a device for a user.\n\t *\n\t * If a device with the same ID already exists and is not revoked, it is\n\t * returned as-is (idempotent registration). If it was previously revoked,\n\t * it is re-activated with updated details.\n\t *\n\t * @param params - Device registration parameters\n\t * @param params.id - Unique device identifier\n\t * @param params.userId - ID of the user who owns the device\n\t * @param params.publicKey - Base64url-encoded device public key or thumbprint\n\t * @param params.name - Human-readable device name\n\t * @returns The registered device record\n\t */\n\tasync registerDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice> {\n\t\tconst existing = this.devicesById.get(params.id)\n\t\tif (existing !== undefined && !existing.revoked) {\n\t\t\treturn existing\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst device: AuthDevice = {\n\t\t\tid: params.id,\n\t\t\tuserId: params.userId,\n\t\t\tpublicKey: params.publicKey,\n\t\t\tname: params.name,\n\t\t\trevoked: false,\n\t\t\tcreatedAt: now,\n\t\t\tlastSeenAt: now,\n\t\t}\n\n\t\tthis.devicesById.set(params.id, device)\n\n\t\tlet userDevices = this.devicesByUserId.get(params.userId)\n\t\tif (userDevices === undefined) {\n\t\t\tuserDevices = new Set()\n\t\t\tthis.devicesByUserId.set(params.userId, userDevices)\n\t\t}\n\t\tuserDevices.add(params.id)\n\n\t\treturn device\n\t}\n\n\t/**\n\t * Find a device by its ID.\n\t *\n\t * @param deviceId - The device ID to search for\n\t * @returns The device record, or null if not found\n\t */\n\tasync findDevice(deviceId: string): Promise<AuthDevice | null> {\n\t\treturn this.devicesById.get(deviceId) ?? null\n\t}\n\n\t/**\n\t * List all devices registered for a user.\n\t *\n\t * @param userId - The user ID whose devices to list\n\t * @returns Array of device records (includes revoked devices)\n\t */\n\tasync listDevices(userId: string): Promise<AuthDevice[]> {\n\t\tconst deviceIds = this.devicesByUserId.get(userId)\n\t\tif (deviceIds === undefined) {\n\t\t\treturn []\n\t\t}\n\n\t\tconst devices: AuthDevice[] = []\n\t\tfor (const deviceId of deviceIds) {\n\t\t\tconst device = this.devicesById.get(deviceId)\n\t\t\tif (device !== undefined) {\n\t\t\t\tdevices.push(device)\n\t\t\t}\n\t\t}\n\n\t\treturn devices\n\t}\n\n\t/**\n\t * Revoke a device, preventing it from being used for authentication.\n\t *\n\t * This is a soft revoke — the device record remains but is marked as revoked.\n\t * If the device does not exist, this is a no-op.\n\t *\n\t * @param deviceId - The ID of the device to revoke\n\t */\n\tasync revokeDevice(deviceId: string): Promise<void> {\n\t\tconst device = this.devicesById.get(deviceId)\n\t\tif (device !== undefined) {\n\t\t\tdevice.revoked = true\n\t\t}\n\t}\n\n\t/**\n\t * Set a user's email verification status.\n\t *\n\t * @param userId - The user whose email to verify\n\t * @param verified - Whether the email is verified\n\t */\n\tasync setEmailVerified(userId: string, verified: boolean): Promise<void> {\n\t\tconst user = this.usersById.get(userId)\n\t\tif (!user) return\n\n\t\tconst updated: StoredUser = { ...user, emailVerified: verified }\n\t\tthis.usersById.set(userId, updated)\n\t\tthis.usersByEmail.set(user.email, updated)\n\t}\n\n\t/**\n\t * Update a user's password hash and salt.\n\t *\n\t * @param userId - The user whose password to update\n\t * @param passwordHash - New hex-encoded PBKDF2 derived key\n\t * @param salt - New hex-encoded salt\n\t */\n\tasync updatePassword(userId: string, passwordHash: string, salt: string): Promise<void> {\n\t\tconst user = this.usersById.get(userId)\n\t\tif (!user) return\n\n\t\tconst updated: StoredUser = { ...user, passwordHash, salt }\n\t\tthis.usersById.set(userId, updated)\n\t\tthis.usersByEmail.set(user.email, updated)\n\t}\n\n\t/**\n\t * List all users. For admin/development use.\n\t */\n\tasync listAll(): Promise<StoredUser[]> {\n\t\treturn [...this.usersById.values()]\n\t}\n\n\t/**\n\t * Update a stored user record.\n\t */\n\tasync update(user: StoredUser): Promise<void> {\n\t\tconst existing = this.usersById.get(user.id)\n\t\tif (!existing) return\n\n\t\t// If email changed, update the email index\n\t\tif (existing.email !== user.email) {\n\t\t\tthis.usersByEmail.delete(existing.email)\n\t\t\tthis.usersByEmail.set(user.email, user)\n\t\t} else {\n\t\t\tthis.usersByEmail.set(user.email, user)\n\t\t}\n\t\tthis.usersById.set(user.id, user)\n\t}\n\n\t/**\n\t * Delete a user and all associated devices.\n\t */\n\tasync delete(userId: string): Promise<void> {\n\t\tconst user = this.usersById.get(userId)\n\t\tif (!user) return\n\n\t\tthis.usersById.delete(userId)\n\t\tthis.usersByEmail.delete(user.email)\n\n\t\t// Clean up devices\n\t\tconst deviceIds = this.devicesByUserId.get(userId)\n\t\tif (deviceIds) {\n\t\t\tfor (const deviceId of deviceIds) {\n\t\t\t\tthis.devicesById.delete(deviceId)\n\t\t\t}\n\t\t\tthis.devicesByUserId.delete(userId)\n\t\t}\n\t}\n\n\t/**\n\t * Update the last-seen timestamp for a device.\n\t *\n\t * Called when a device authenticates or syncs to track activity.\n\t * If the device does not exist, this is a no-op.\n\t *\n\t * @param deviceId - The ID of the device to update\n\t */\n\tasync touchDevice(deviceId: string): Promise<void> {\n\t\tconst device = this.devicesById.get(deviceId)\n\t\tif (device !== undefined) {\n\t\t\tdevice.lastSeenAt = Date.now()\n\t\t}\n\t}\n}\n\n/**\n * Strip sensitive fields from a StoredUser to produce an AuthUser.\n * Ensures password hash and salt are never leaked to the application layer.\n */\nfunction toAuthUser(stored: StoredUser): AuthUser {\n\treturn {\n\t\tid: stored.id,\n\t\temail: stored.email,\n\t\tname: stored.name,\n\t\temailVerified: stored.emailVerified,\n\t\tcreatedAt: stored.createdAt,\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport type { UserStore } from './user-store'\nimport { hashPassword } from './password-hash'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A pending password reset request.\n */\nexport interface PasswordResetToken {\n\t/** Cryptographically random single-use token */\n\ttoken: string\n\t/** User ID the token was generated for */\n\tuserId: string\n\t/** Email the reset was requested for */\n\temail: string\n\t/** When the token was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the token expires (ms since epoch) */\n\texpiresAt: number\n\t/** Whether the token has been consumed */\n\tconsumed: boolean\n}\n\n/**\n * Persistence interface for password reset tokens.\n */\nexport interface PasswordResetStore {\n\t/** Store a reset token. */\n\tstore(token: PasswordResetToken): Promise<void>\n\n\t/** Look up a token. Returns null if not found. */\n\tget(token: string): Promise<PasswordResetToken | null>\n\n\t/** Mark a token as consumed. */\n\tconsume(token: string): Promise<void>\n\n\t/** Count active (non-consumed, non-expired) tokens for an email. */\n\tcountActiveForEmail(email: string): Promise<number>\n\n\t/** Remove expired tokens. */\n\tcleanExpired(): Promise<number>\n}\n\n/**\n * Configuration for the password reset flow.\n */\nexport interface PasswordResetConfig {\n\t/** User store for looking up users and updating passwords */\n\tuserStore: UserStore\n\t/** Store for reset tokens. Defaults to InMemoryPasswordResetStore. */\n\tresetStore?: PasswordResetStore\n\t/** Token TTL in milliseconds. Defaults to 1 hour. */\n\ttokenTtlMs?: number\n\t/** Max reset requests per email in the TTL window. Defaults to 3. */\n\tmaxRequestsPerEmail?: number\n\t/**\n\t * Callback invoked when a reset is requested.\n\t * The developer must implement email sending.\n\t * If not provided, the token is returned in the route response (development mode).\n\t */\n\tonResetRequested?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class PasswordResetError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'PasswordResetError'\n\t}\n}\n\nexport class ResetTokenExpiredError extends PasswordResetError {\n\tconstructor() {\n\t\tsuper('Password reset token has expired.', 'RESET_TOKEN_EXPIRED')\n\t}\n}\n\nexport class ResetTokenNotFoundError extends PasswordResetError {\n\tconstructor() {\n\t\tsuper('Password reset token not found or already used.', 'RESET_TOKEN_NOT_FOUND')\n\t}\n}\n\nexport class ResetRateLimitedError extends PasswordResetError {\n\tconstructor() {\n\t\tsuper('Too many password reset requests. Please try again later.', 'RESET_RATE_LIMITED')\n\t}\n}\n\n// ============================================================================\n// InMemoryPasswordResetStore\n// ============================================================================\n\nexport class InMemoryPasswordResetStore implements PasswordResetStore {\n\tprivate tokens = new Map<string, PasswordResetToken>()\n\n\tasync store(token: PasswordResetToken): Promise<void> {\n\t\tthis.tokens.set(token.token, token)\n\t}\n\n\tasync get(token: string): Promise<PasswordResetToken | null> {\n\t\treturn this.tokens.get(token) ?? null\n\t}\n\n\tasync consume(token: string): Promise<void> {\n\t\tconst entry = this.tokens.get(token)\n\t\tif (entry) {\n\t\t\tthis.tokens.set(token, { ...entry, consumed: true })\n\t\t}\n\t}\n\n\tasync countActiveForEmail(email: string): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const token of this.tokens.values()) {\n\t\t\tif (token.email === email && !token.consumed && now < token.expiresAt) {\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [key, token] of this.tokens) {\n\t\t\tif (now > token.expiresAt) {\n\t\t\t\tthis.tokens.delete(key)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// PasswordResetManager\n// ============================================================================\n\n/** Default TTL: 1 hour */\nconst DEFAULT_TOKEN_TTL_MS = 60 * 60 * 1000\n\n/** Default max requests per email per TTL window */\nconst DEFAULT_MAX_REQUESTS = 3\n\n/** Minimum password length (same as auth-routes) */\nconst MIN_PASSWORD_LENGTH = 8\n\n/** Maximum password length (same as auth-routes) */\nconst MAX_PASSWORD_LENGTH = 128\n\n/**\n * Manages the password reset flow.\n *\n * @example\n * ```typescript\n * const resetManager = new PasswordResetManager({\n * userStore,\n * onResetRequested: async (email, token, expiresAt) => {\n * await sendEmail(email, `Reset link: https://app.com/reset?token=${token}`)\n * },\n * })\n *\n * // Request reset (always returns 200 to prevent email enumeration)\n * const response = await resetManager.requestReset('user@example.com')\n *\n * // Consume token and set new password\n * const response = await resetManager.resetPassword(token, 'newPassword123')\n * ```\n */\nexport class PasswordResetManager {\n\tprivate readonly userStore: UserStore\n\tprivate readonly resetStore: PasswordResetStore\n\tprivate readonly tokenTtlMs: number\n\tprivate readonly maxRequestsPerEmail: number\n\tprivate readonly onResetRequested?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n\n\tconstructor(config: PasswordResetConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.resetStore = config.resetStore ?? new InMemoryPasswordResetStore()\n\t\tthis.tokenTtlMs = config.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS\n\t\tthis.maxRequestsPerEmail = config.maxRequestsPerEmail ?? DEFAULT_MAX_REQUESTS\n\t\tthis.onResetRequested = config.onResetRequested\n\t}\n\n\t/**\n\t * Request a password reset for an email.\n\t * Always returns success to prevent email enumeration.\n\t *\n\t * If a callback is configured, invokes it with the token.\n\t * In development mode (no callback), returns the token in the response.\n\t */\n\tasync requestReset(email: string): Promise<{ status: number; body: { data: { message: string; token?: string } } | { error: string } }> {\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\n\t\t// Always return 200 to prevent email enumeration\n\t\tconst successResponse = {\n\t\t\tstatus: 200,\n\t\t\tbody: { data: { message: 'If an account with that email exists, a password reset link has been sent.' } } as { data: { message: string; token?: string } },\n\t\t}\n\n\t\t// Look up user\n\t\tconst user = await this.userStore.findByEmail(normalizedEmail)\n\t\tif (!user) {\n\t\t\treturn successResponse\n\t\t}\n\n\t\t// Rate limit\n\t\tconst activeCount = await this.resetStore.countActiveForEmail(normalizedEmail)\n\t\tif (activeCount >= this.maxRequestsPerEmail) {\n\t\t\treturn successResponse // Still 200 to prevent enumeration\n\t\t}\n\n\t\t// Generate token\n\t\tconst token = generateSecureToken()\n\t\tconst now = Date.now()\n\t\tconst resetToken: PasswordResetToken = {\n\t\t\ttoken,\n\t\t\tuserId: user.id,\n\t\t\temail: normalizedEmail,\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + this.tokenTtlMs,\n\t\t\tconsumed: false,\n\t\t}\n\n\t\tawait this.resetStore.store(resetToken)\n\n\t\t// Invoke callback\n\t\tif (this.onResetRequested) {\n\t\t\ttry {\n\t\t\t\tawait this.onResetRequested(normalizedEmail, token, resetToken.expiresAt)\n\t\t\t} catch {\n\t\t\t\t// Don't fail the request if callback errors\n\t\t\t}\n\t\t}\n\n\t\t// In development mode (no callback), return the token\n\t\tif (!this.onResetRequested) {\n\t\t\tsuccessResponse.body = { data: { message: 'Password reset token generated.', token } }\n\t\t}\n\n\t\treturn successResponse\n\t}\n\n\t/**\n\t * Consume a reset token and set a new password.\n\t */\n\tasync resetPassword(\n\t\ttoken: string,\n\t\tnewPassword: string,\n\t): Promise<{ status: number; body: { data: { message: string } } | { error: string } }> {\n\t\t// Validate password\n\t\tif (typeof newPassword !== 'string' || newPassword.length < MIN_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\t\tif (newPassword.length > MAX_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `Password must be at most ${MAX_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\n\t\tconst resetToken = await this.resetStore.get(token)\n\t\tif (!resetToken || resetToken.consumed) {\n\t\t\treturn { status: 404, body: { error: 'Password reset token not found or already used.' } }\n\t\t}\n\n\t\tif (Date.now() > resetToken.expiresAt) {\n\t\t\tawait this.resetStore.consume(token)\n\t\t\treturn { status: 410, body: { error: 'Password reset token has expired.' } }\n\t\t}\n\n\t\t// Consume token\n\t\tawait this.resetStore.consume(token)\n\n\t\t// Update password\n\t\tconst hashed = await hashPassword(newPassword)\n\t\tawait this.userStore.updatePassword(resetToken.userId, hashed.hash, hashed.salt)\n\n\t\treturn { status: 200, body: { data: { message: 'Password has been reset successfully.' } } }\n\t}\n\n\t/**\n\t * Change password for an authenticated user (requires current password verification).\n\t */\n\tasync changePassword(\n\t\tuserId: string,\n\t\tcurrentPassword: string,\n\t\tnewPassword: string,\n\t): Promise<{ status: number; body: { data: { message: string } } | { error: string } }> {\n\t\t// Validate new password\n\t\tif (typeof newPassword !== 'string' || newPassword.length < MIN_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `New password must be at least ${MIN_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\t\tif (newPassword.length > MAX_PASSWORD_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `New password must be at most ${MAX_PASSWORD_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\treturn { status: 404, body: { error: 'User not found.' } }\n\t\t}\n\n\t\t// Verify current password\n\t\tconst { verifyPassword } = await import('./password-hash')\n\t\tconst isValid = await verifyPassword(currentPassword, user.passwordHash, user.salt)\n\t\tif (!isValid) {\n\t\t\treturn { status: 401, body: { error: 'Current password is incorrect.' } }\n\t\t}\n\n\t\t// Update password\n\t\tconst hashed = await hashPassword(newPassword)\n\t\tawait this.userStore.updatePassword(userId, hashed.hash, hashed.salt)\n\n\t\treturn { status: 200, body: { data: { message: 'Password changed successfully.' } } }\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateSecureToken(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n","import { KoraError } from '@korajs/core'\nimport type { UserStore } from './user-store'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A pending email verification token.\n */\nexport interface EmailVerificationToken {\n\t/** Cryptographically random single-use token */\n\ttoken: string\n\t/** User ID the token was generated for */\n\tuserId: string\n\t/** Email being verified */\n\temail: string\n\t/** When the token was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the token expires (ms since epoch) */\n\texpiresAt: number\n\t/** Whether the token has been consumed */\n\tconsumed: boolean\n}\n\n/**\n * Persistence interface for email verification tokens.\n */\nexport interface EmailVerificationStore {\n\t/** Store a verification token. */\n\tstore(token: EmailVerificationToken): Promise<void>\n\n\t/** Look up a token. Returns null if not found. */\n\tget(token: string): Promise<EmailVerificationToken | null>\n\n\t/** Mark a token as consumed. */\n\tconsume(token: string): Promise<void>\n\n\t/** Count active (non-consumed, non-expired) tokens for a user. */\n\tcountActiveForUser(userId: string): Promise<number>\n\n\t/** Remove expired tokens. */\n\tcleanExpired(): Promise<number>\n}\n\n/**\n * Configuration for the email verification flow.\n */\nexport interface EmailVerificationConfig {\n\t/** User store for looking up users */\n\tuserStore: UserStore\n\t/** Store for verification tokens. Defaults to InMemoryEmailVerificationStore. */\n\tverificationStore?: EmailVerificationStore\n\t/** Token TTL in milliseconds. Defaults to 24 hours. */\n\ttokenTtlMs?: number\n\t/** Max verification requests per user per TTL window. Defaults to 3. */\n\tmaxRequestsPerUser?: number\n\t/**\n\t * Callback invoked when a verification email should be sent.\n\t * The developer must implement email sending.\n\t * If not provided, the token is returned in the route response (development mode).\n\t */\n\tonVerificationRequired?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class EmailVerificationError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'EmailVerificationError'\n\t}\n}\n\nexport class VerificationTokenExpiredError extends EmailVerificationError {\n\tconstructor() {\n\t\tsuper('Email verification token has expired.', 'VERIFICATION_TOKEN_EXPIRED')\n\t}\n}\n\nexport class VerificationTokenNotFoundError extends EmailVerificationError {\n\tconstructor() {\n\t\tsuper('Email verification token not found or already used.', 'VERIFICATION_TOKEN_NOT_FOUND')\n\t}\n}\n\n// ============================================================================\n// InMemoryEmailVerificationStore\n// ============================================================================\n\nexport class InMemoryEmailVerificationStore implements EmailVerificationStore {\n\tprivate tokens = new Map<string, EmailVerificationToken>()\n\n\tasync store(token: EmailVerificationToken): Promise<void> {\n\t\tthis.tokens.set(token.token, token)\n\t}\n\n\tasync get(token: string): Promise<EmailVerificationToken | null> {\n\t\treturn this.tokens.get(token) ?? null\n\t}\n\n\tasync consume(token: string): Promise<void> {\n\t\tconst entry = this.tokens.get(token)\n\t\tif (entry) {\n\t\t\tthis.tokens.set(token, { ...entry, consumed: true })\n\t\t}\n\t}\n\n\tasync countActiveForUser(userId: string): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const token of this.tokens.values()) {\n\t\t\tif (token.userId === userId && !token.consumed && now < token.expiresAt) {\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [key, token] of this.tokens) {\n\t\t\tif (now > token.expiresAt) {\n\t\t\t\tthis.tokens.delete(key)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// EmailVerificationManager\n// ============================================================================\n\n/** Default TTL: 24 hours */\nconst DEFAULT_TOKEN_TTL_MS = 24 * 60 * 60 * 1000\n\n/** Default max requests per user per TTL window */\nconst DEFAULT_MAX_REQUESTS = 3\n\n/**\n * Manages the email verification flow.\n *\n * @example\n * ```typescript\n * const verifier = new EmailVerificationManager({\n * userStore,\n * onVerificationRequired: async (email, token, expiresAt) => {\n * await sendEmail(email, `Verify: https://app.com/verify?token=${token}`)\n * },\n * })\n *\n * // Send verification email\n * await verifier.sendVerification('user-1', 'user@example.com')\n *\n * // Verify email\n * await verifier.verifyEmail(token)\n * ```\n */\nexport class EmailVerificationManager {\n\tprivate readonly userStore: UserStore\n\tprivate readonly verificationStore: EmailVerificationStore\n\tprivate readonly tokenTtlMs: number\n\tprivate readonly maxRequestsPerUser: number\n\tprivate readonly onVerificationRequired?: (email: string, token: string, expiresAt: number) => void | Promise<void>\n\n\tconstructor(config: EmailVerificationConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.verificationStore = config.verificationStore ?? new InMemoryEmailVerificationStore()\n\t\tthis.tokenTtlMs = config.tokenTtlMs ?? DEFAULT_TOKEN_TTL_MS\n\t\tthis.maxRequestsPerUser = config.maxRequestsPerUser ?? DEFAULT_MAX_REQUESTS\n\t\tthis.onVerificationRequired = config.onVerificationRequired\n\t}\n\n\t/**\n\t * Send a verification email for a user.\n\t * Rate-limited to prevent abuse.\n\t */\n\tasync sendVerification(\n\t\tuserId: string,\n\t\temail: string,\n\t): Promise<{ status: number; body: { data: { message: string; token?: string } } | { error: string } }> {\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\n\t\t// Rate limit\n\t\tconst activeCount = await this.verificationStore.countActiveForUser(userId)\n\t\tif (activeCount >= this.maxRequestsPerUser) {\n\t\t\treturn { status: 429, body: { error: 'Too many verification requests. Please try again later.' } }\n\t\t}\n\n\t\t// Generate token\n\t\tconst token = generateSecureToken()\n\t\tconst now = Date.now()\n\t\tconst verificationToken: EmailVerificationToken = {\n\t\t\ttoken,\n\t\t\tuserId,\n\t\t\temail: normalizedEmail,\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + this.tokenTtlMs,\n\t\t\tconsumed: false,\n\t\t}\n\n\t\tawait this.verificationStore.store(verificationToken)\n\n\t\t// Invoke callback\n\t\tif (this.onVerificationRequired) {\n\t\t\ttry {\n\t\t\t\tawait this.onVerificationRequired(normalizedEmail, token, verificationToken.expiresAt)\n\t\t\t} catch {\n\t\t\t\t// Don't fail if callback errors\n\t\t\t}\n\t\t}\n\n\t\t// In development mode (no callback), return the token\n\t\tconst responseData: { message: string; token?: string } = {\n\t\t\tmessage: 'Verification email sent.',\n\t\t}\n\t\tif (!this.onVerificationRequired) {\n\t\t\tresponseData.token = token\n\t\t}\n\n\t\treturn { status: 200, body: { data: responseData } }\n\t}\n\n\t/**\n\t * Verify an email using a verification token.\n\t */\n\tasync verifyEmail(\n\t\ttoken: string,\n\t): Promise<{ status: number; body: { data: { message: string; userId: string; email: string } } | { error: string } }> {\n\t\tconst verificationToken = await this.verificationStore.get(token)\n\t\tif (!verificationToken || verificationToken.consumed) {\n\t\t\treturn { status: 404, body: { error: 'Verification token not found or already used.' } }\n\t\t}\n\n\t\tif (Date.now() > verificationToken.expiresAt) {\n\t\t\tawait this.verificationStore.consume(token)\n\t\t\treturn { status: 410, body: { error: 'Verification token has expired.' } }\n\t\t}\n\n\t\t// Consume token\n\t\tawait this.verificationStore.consume(token)\n\n\t\t// Mark user's email as verified\n\t\tawait this.userStore.setEmailVerified(verificationToken.userId, true)\n\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\tdata: {\n\t\t\t\t\tmessage: 'Email verified successfully.',\n\t\t\t\t\tuserId: verificationToken.userId,\n\t\t\t\t\temail: verificationToken.email,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Resend verification email for a user.\n\t * Delegates to sendVerification with rate limiting.\n\t */\n\tasync resendVerification(\n\t\tuserId: string,\n\t): Promise<{ status: number; body: { data: { message: string; token?: string } } | { error: string } }> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\treturn { status: 404, body: { error: 'User not found.' } }\n\t\t}\n\n\t\treturn this.sendVerification(userId, user.email)\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateSecureToken(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n","import { randomUUID } from 'node:crypto'\nimport type { UserStore, AuthUser, StoredUser, AuthDevice } from './user-store'\nimport { DuplicateEmailError } from './user-store'\n\n/**\n * Row shape returned by better-sqlite3 for the auth_users table.\n */\ninterface UserRow {\n\tid: string\n\temail: string\n\tname: string\n\temail_verified: number\n\tcreated_at: number\n\tpassword_hash: string\n\tsalt: string\n}\n\n/**\n * Row shape returned by better-sqlite3 for the auth_devices table.\n */\ninterface DeviceRow {\n\tid: string\n\tuser_id: string\n\tpublic_key: string\n\tname: string\n\trevoked: number\n\tcreated_at: number\n\tlast_seen_at: number\n}\n\n/**\n * Minimal better-sqlite3 subset to avoid a hard dependency on the package.\n * The real Database instance satisfies this at runtime.\n */\ninterface SqliteDatabase {\n\tpragma(source: string): unknown\n\texec(source: string): void\n\tprepare(source: string): {\n\t\trun(...params: unknown[]): unknown\n\t\tget(...params: unknown[]): unknown\n\t\tall(...params: unknown[]): unknown[]\n\t}\n\ttransaction<T>(fn: () => T): () => T\n}\n\n/**\n * SQLite-backed user and device store using better-sqlite3.\n *\n * Provides persistent user storage suitable for single-server deployments,\n * Electron apps, and development environments. Uses WAL mode for concurrent\n * read/write performance.\n *\n * This implementation uses dynamic imports for better-sqlite3 so projects\n * that do not use SQLite server-side do not need to install it.\n *\n * @example\n * ```typescript\n * import { createSqliteUserStore } from '@korajs/auth/server'\n *\n * const userStore = await createSqliteUserStore({ filename: './auth.db' })\n * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n * ```\n */\nexport class SqliteUserStore implements UserStore {\n\tprivate readonly db: SqliteDatabase\n\n\tconstructor(db: SqliteDatabase) {\n\t\tthis.db = db\n\t\tthis.db.pragma('journal_mode = WAL')\n\t\tthis.ensureTables()\n\t}\n\n\tprivate ensureTables(): void {\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS auth_users (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\temail TEXT NOT NULL UNIQUE COLLATE NOCASE,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\temail_verified INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tpassword_hash TEXT NOT NULL,\n\t\t\t\tsalt TEXT NOT NULL\n\t\t\t);\n\n\t\t\tCREATE TABLE IF NOT EXISTS auth_devices (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tuser_id TEXT NOT NULL,\n\t\t\t\tpublic_key TEXT NOT NULL,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\trevoked INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tlast_seen_at INTEGER NOT NULL,\n\t\t\t\tFOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE\n\t\t\t);\n\n\t\t\tCREATE INDEX IF NOT EXISTS idx_auth_devices_user_id ON auth_devices(user_id);\n\t\t`)\n\t}\n\n\tasync createUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser> {\n\t\tconst normalizedEmail = params.email.toLowerCase()\n\t\tconst now = Date.now()\n\t\tconst id = randomUUID()\n\n\t\ttry {\n\t\t\tthis.db.prepare(`\n\t\t\t\tINSERT INTO auth_users (id, email, name, email_verified, created_at, password_hash, salt)\n\t\t\t\tVALUES (?, ?, ?, 0, ?, ?, ?)\n\t\t\t`).run(id, normalizedEmail, params.name, now, params.passwordHash, params.salt)\n\t\t} catch (err: unknown) {\n\t\t\tif (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {\n\t\t\t\tthrow new DuplicateEmailError()\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\n\t\treturn { id, email: normalizedEmail, name: params.name, emailVerified: false, createdAt: now }\n\t}\n\n\tasync findByEmail(email: string): Promise<StoredUser | null> {\n\t\tconst row = this.db.prepare(\n\t\t\t'SELECT * FROM auth_users WHERE email = ?',\n\t\t).get(email.toLowerCase()) as UserRow | undefined\n\n\t\treturn row ? rowToStoredUser(row) : null\n\t}\n\n\tasync findById(id: string): Promise<StoredUser | null> {\n\t\tconst row = this.db.prepare(\n\t\t\t'SELECT * FROM auth_users WHERE id = ?',\n\t\t).get(id) as UserRow | undefined\n\n\t\treturn row ? rowToStoredUser(row) : null\n\t}\n\n\tasync registerDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice> {\n\t\tconst existing = this.db.prepare(\n\t\t\t'SELECT * FROM auth_devices WHERE id = ?',\n\t\t).get(params.id) as DeviceRow | undefined\n\n\t\tif (existing && !existing.revoked) {\n\t\t\treturn rowToDevice(existing)\n\t\t}\n\n\t\tconst now = Date.now()\n\n\t\tif (existing) {\n\t\t\t// Re-activate previously revoked device\n\t\t\tthis.db.prepare(`\n\t\t\t\tUPDATE auth_devices SET revoked = 0, public_key = ?, name = ?, last_seen_at = ?\n\t\t\t\tWHERE id = ?\n\t\t\t`).run(params.publicKey, params.name, now, params.id)\n\t\t} else {\n\t\t\tthis.db.prepare(`\n\t\t\t\tINSERT INTO auth_devices (id, user_id, public_key, name, revoked, created_at, last_seen_at)\n\t\t\t\tVALUES (?, ?, ?, ?, 0, ?, ?)\n\t\t\t`).run(params.id, params.userId, params.publicKey, params.name, now, now)\n\t\t}\n\n\t\treturn {\n\t\t\tid: params.id,\n\t\t\tuserId: params.userId,\n\t\t\tpublicKey: params.publicKey,\n\t\t\tname: params.name,\n\t\t\trevoked: false,\n\t\t\tcreatedAt: existing ? existing.created_at : now,\n\t\t\tlastSeenAt: now,\n\t\t}\n\t}\n\n\tasync findDevice(deviceId: string): Promise<AuthDevice | null> {\n\t\tconst row = this.db.prepare(\n\t\t\t'SELECT * FROM auth_devices WHERE id = ?',\n\t\t).get(deviceId) as DeviceRow | undefined\n\n\t\treturn row ? rowToDevice(row) : null\n\t}\n\n\tasync listDevices(userId: string): Promise<AuthDevice[]> {\n\t\tconst rows = this.db.prepare(\n\t\t\t'SELECT * FROM auth_devices WHERE user_id = ?',\n\t\t).all(userId) as DeviceRow[]\n\n\t\treturn rows.map(rowToDevice)\n\t}\n\n\tasync revokeDevice(deviceId: string): Promise<void> {\n\t\tthis.db.prepare('UPDATE auth_devices SET revoked = 1 WHERE id = ?').run(deviceId)\n\t}\n\n\tasync setEmailVerified(userId: string, verified: boolean): Promise<void> {\n\t\tthis.db.prepare(\n\t\t\t'UPDATE auth_users SET email_verified = ? WHERE id = ?',\n\t\t).run(verified ? 1 : 0, userId)\n\t}\n\n\tasync updatePassword(userId: string, passwordHash: string, salt: string): Promise<void> {\n\t\tthis.db.prepare(\n\t\t\t'UPDATE auth_users SET password_hash = ?, salt = ? WHERE id = ?',\n\t\t).run(passwordHash, salt, userId)\n\t}\n\n\tasync listAll(): Promise<StoredUser[]> {\n\t\tconst rows = this.db.prepare('SELECT * FROM auth_users').all() as UserRow[]\n\t\treturn rows.map(rowToStoredUser)\n\t}\n\n\tasync update(user: StoredUser): Promise<void> {\n\t\tthis.db.prepare(`\n\t\t\tUPDATE auth_users\n\t\t\tSET email = ?, name = ?, email_verified = ?, password_hash = ?, salt = ?\n\t\t\tWHERE id = ?\n\t\t`).run(user.email, user.name, user.emailVerified ? 1 : 0, user.passwordHash, user.salt, user.id)\n\t}\n\n\tasync delete(userId: string): Promise<void> {\n\t\tconst deleteInTransaction = this.db.transaction(() => {\n\t\t\tthis.db.prepare('DELETE FROM auth_devices WHERE user_id = ?').run(userId)\n\t\t\tthis.db.prepare('DELETE FROM auth_users WHERE id = ?').run(userId)\n\t\t})\n\t\tdeleteInTransaction()\n\t}\n\n\tasync touchDevice(deviceId: string): Promise<void> {\n\t\tthis.db.prepare(\n\t\t\t'UPDATE auth_devices SET last_seen_at = ? WHERE id = ?',\n\t\t).run(Date.now(), deviceId)\n\t}\n}\n\n/**\n * Creates a SqliteUserStore from a file path.\n *\n * Uses runtime dynamic imports so projects that do not use SQLite server-side\n * do not need to install `better-sqlite3`.\n *\n * @param options.filename - Path to the SQLite database file, or `:memory:` for in-memory\n */\nexport async function createSqliteUserStore(options: {\n\tfilename: string\n}): Promise<SqliteUserStore> {\n\tconst Database = await loadBetterSqlite3()\n\tconst db = new Database(options.filename)\n\treturn new SqliteUserStore(db as unknown as SqliteDatabase)\n}\n\nasync function loadBetterSqlite3(): Promise<new (filename: string) => unknown> {\n\ttry {\n\t\t// Use createRequire for CJS compatibility with better-sqlite3\n\t\tconst { createRequire } = await import('node:module')\n\t\tconst require = createRequire(import.meta.url)\n\t\treturn require('better-sqlite3') as new (filename: string) => unknown\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'SQLite backend requires the \"better-sqlite3\" package. Install it in your project dependencies.',\n\t\t)\n\t}\n}\n\nfunction rowToStoredUser(row: UserRow): StoredUser {\n\treturn {\n\t\tid: row.id,\n\t\temail: row.email,\n\t\tname: row.name,\n\t\temailVerified: Boolean(row.email_verified),\n\t\tcreatedAt: row.created_at,\n\t\tpasswordHash: row.password_hash,\n\t\tsalt: row.salt,\n\t}\n}\n\nfunction rowToDevice(row: DeviceRow): AuthDevice {\n\treturn {\n\t\tid: row.id,\n\t\tuserId: row.user_id,\n\t\tpublicKey: row.public_key,\n\t\tname: row.name,\n\t\trevoked: Boolean(row.revoked),\n\t\tcreatedAt: row.created_at,\n\t\tlastSeenAt: row.last_seen_at,\n\t}\n}\n","import { randomUUID } from 'node:crypto'\nimport type { UserStore, AuthUser, StoredUser, AuthDevice } from './user-store'\nimport { DuplicateEmailError } from './user-store'\n\n/**\n * Minimal typed subset of a postgres-js SQL tag for our queries.\n * Avoids a hard dependency on the postgres package.\n * Uses Record<string, unknown>[] for result type to match postgres-js return shape.\n */\ninterface PostgresClient {\n\tbegin<T>(fn: (sql: PostgresClient) => Promise<T>): Promise<T>\n\t(\n\t\ttemplate: TemplateStringsArray,\n\t\t...args: unknown[]\n\t): Promise<Record<string, unknown>[]>\n}\n\n/**\n * PostgreSQL-backed user and device store using postgres-js.\n *\n * Provides persistent user storage suitable for production multi-server\n * deployments. Uses parameterized queries for SQL injection safety and\n * transactions for atomic operations.\n *\n * This implementation uses dynamic imports for postgres so projects\n * that do not use PostgreSQL do not need to install it.\n *\n * @example\n * ```typescript\n * import { createPostgresUserStore } from '@korajs/auth/server'\n *\n * const userStore = await createPostgresUserStore({\n * connectionString: 'postgres://user:pass@localhost:5432/mydb',\n * })\n * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })\n * ```\n */\nexport class PostgresUserStore implements UserStore {\n\tprivate readonly sql: PostgresClient\n\tprivate readonly ready: Promise<void>\n\n\tconstructor(sql: PostgresClient) {\n\t\tthis.sql = sql\n\t\tthis.ready = this.ensureTables()\n\t}\n\n\tprivate async ensureTables(): Promise<void> {\n\t\tawait this.sql`\n\t\t\tCREATE TABLE IF NOT EXISTS auth_users (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\temail TEXT NOT NULL UNIQUE,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\temail_verified BOOLEAN NOT NULL DEFAULT FALSE,\n\t\t\t\tcreated_at BIGINT NOT NULL,\n\t\t\t\tpassword_hash TEXT NOT NULL,\n\t\t\t\tsalt TEXT NOT NULL\n\t\t\t)\n\t\t`\n\n\t\tawait this.sql`\n\t\t\tCREATE TABLE IF NOT EXISTS auth_devices (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tuser_id TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE,\n\t\t\t\tpublic_key TEXT NOT NULL,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\trevoked BOOLEAN NOT NULL DEFAULT FALSE,\n\t\t\t\tcreated_at BIGINT NOT NULL,\n\t\t\t\tlast_seen_at BIGINT NOT NULL\n\t\t\t)\n\t\t`\n\n\t\tawait this.sql`\n\t\t\tCREATE INDEX IF NOT EXISTS idx_auth_devices_user_id ON auth_devices(user_id)\n\t\t`\n\n\t\t// Case-insensitive unique index on email for consistent lookups\n\t\tawait this.sql`\n\t\t\tCREATE UNIQUE INDEX IF NOT EXISTS idx_auth_users_email_lower ON auth_users(LOWER(email))\n\t\t`\n\t}\n\n\tasync createUser(params: {\n\t\temail: string\n\t\tpasswordHash: string\n\t\tsalt: string\n\t\tname: string\n\t}): Promise<AuthUser> {\n\t\tawait this.ready\n\t\tconst normalizedEmail = params.email.toLowerCase()\n\t\tconst now = Date.now()\n\t\tconst id = randomUUID()\n\n\t\ttry {\n\t\t\tawait this.sql`\n\t\t\t\tINSERT INTO auth_users (id, email, name, email_verified, created_at, password_hash, salt)\n\t\t\t\tVALUES (${id}, ${normalizedEmail}, ${params.name}, FALSE, ${now}, ${params.passwordHash}, ${params.salt})\n\t\t\t`\n\t\t} catch (err: unknown) {\n\t\t\tif (err instanceof Error && (\n\t\t\t\terr.message.includes('unique constraint') ||\n\t\t\t\terr.message.includes('duplicate key')\n\t\t\t)) {\n\t\t\t\tthrow new DuplicateEmailError()\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\n\t\treturn { id, email: normalizedEmail, name: params.name, emailVerified: false, createdAt: now }\n\t}\n\n\tasync findByEmail(email: string): Promise<StoredUser | null> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_users WHERE LOWER(email) = ${email.toLowerCase()}\n\t\t`\n\t\treturn rows.length > 0 ? rowToStoredUser(rows[0] as unknown as UserRow) : null\n\t}\n\n\tasync findById(id: string): Promise<StoredUser | null> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_users WHERE id = ${id}\n\t\t`\n\t\treturn rows.length > 0 ? rowToStoredUser(rows[0] as unknown as UserRow) : null\n\t}\n\n\tasync registerDevice(params: {\n\t\tid: string\n\t\tuserId: string\n\t\tpublicKey: string\n\t\tname: string\n\t}): Promise<AuthDevice> {\n\t\tawait this.ready\n\t\tconst existingRows = await this.sql`\n\t\t\tSELECT * FROM auth_devices WHERE id = ${params.id}\n\t\t` as unknown as DeviceRow[]\n\n\t\tif (existingRows.length > 0 && !existingRows[0]!.revoked) {\n\t\t\treturn rowToDevice(existingRows[0]!)\n\t\t}\n\n\t\tconst now = Date.now()\n\n\t\tif (existingRows.length > 0) {\n\t\t\t// Re-activate previously revoked device\n\t\t\tawait this.sql`\n\t\t\t\tUPDATE auth_devices SET revoked = FALSE, public_key = ${params.publicKey}, name = ${params.name}, last_seen_at = ${now}\n\t\t\t\tWHERE id = ${params.id}\n\t\t\t`\n\t\t} else {\n\t\t\tawait this.sql`\n\t\t\t\tINSERT INTO auth_devices (id, user_id, public_key, name, revoked, created_at, last_seen_at)\n\t\t\t\tVALUES (${params.id}, ${params.userId}, ${params.publicKey}, ${params.name}, FALSE, ${now}, ${now})\n\t\t\t`\n\t\t}\n\n\t\treturn {\n\t\t\tid: params.id,\n\t\t\tuserId: params.userId,\n\t\t\tpublicKey: params.publicKey,\n\t\t\tname: params.name,\n\t\t\trevoked: false,\n\t\t\tcreatedAt: existingRows.length > 0 ? Number(existingRows[0]!.created_at) : now,\n\t\t\tlastSeenAt: now,\n\t\t}\n\t}\n\n\tasync findDevice(deviceId: string): Promise<AuthDevice | null> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_devices WHERE id = ${deviceId}\n\t\t`\n\t\treturn rows.length > 0 ? rowToDevice(rows[0] as unknown as DeviceRow) : null\n\t}\n\n\tasync listDevices(userId: string): Promise<AuthDevice[]> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`\n\t\t\tSELECT * FROM auth_devices WHERE user_id = ${userId}\n\t\t` as unknown as DeviceRow[]\n\t\treturn rows.map(rowToDevice)\n\t}\n\n\tasync revokeDevice(deviceId: string): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`UPDATE auth_devices SET revoked = TRUE WHERE id = ${deviceId}`\n\t}\n\n\tasync setEmailVerified(userId: string, verified: boolean): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`UPDATE auth_users SET email_verified = ${verified} WHERE id = ${userId}`\n\t}\n\n\tasync updatePassword(userId: string, passwordHash: string, salt: string): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`\n\t\t\tUPDATE auth_users SET password_hash = ${passwordHash}, salt = ${salt}\n\t\t\tWHERE id = ${userId}\n\t\t`\n\t}\n\n\tasync listAll(): Promise<StoredUser[]> {\n\t\tawait this.ready\n\t\tconst rows = await this.sql`SELECT * FROM auth_users` as unknown as UserRow[]\n\t\treturn rows.map(rowToStoredUser)\n\t}\n\n\tasync update(user: StoredUser): Promise<void> {\n\t\tawait this.ready\n\t\tawait this.sql`\n\t\t\tUPDATE auth_users\n\t\t\tSET email = ${user.email}, name = ${user.name}, email_verified = ${user.emailVerified},\n\t\t\t\tpassword_hash = ${user.passwordHash}, salt = ${user.salt}\n\t\t\tWHERE id = ${user.id}\n\t\t`\n\t}\n\n\tasync delete(userId: string): Promise<void> {\n\t\tawait this.ready\n\t\t// Devices cascade-delete via FK constraint, but be explicit\n\t\tawait this.sql.begin(async (tx) => {\n\t\t\tawait tx`DELETE FROM auth_devices WHERE user_id = ${userId}`\n\t\t\tawait tx`DELETE FROM auth_users WHERE id = ${userId}`\n\t\t})\n\t}\n\n\tasync touchDevice(deviceId: string): Promise<void> {\n\t\tawait this.ready\n\t\tconst now = Date.now()\n\t\tawait this.sql`UPDATE auth_devices SET last_seen_at = ${now} WHERE id = ${deviceId}`\n\t}\n}\n\n/**\n * Creates a PostgresUserStore from a connection string.\n *\n * Uses runtime dynamic imports so projects that do not use PostgreSQL\n * do not need to install `postgres`.\n *\n * @param options.connectionString - PostgreSQL connection URL (e.g., `postgres://user:pass@host:5432/db`)\n */\nexport async function createPostgresUserStore(options: {\n\tconnectionString: string\n}): Promise<PostgresUserStore> {\n\tconst postgresClient = await loadPostgresDeps()\n\tconst sql = postgresClient(options.connectionString) as unknown as PostgresClient\n\treturn new PostgresUserStore(sql)\n}\n\nasync function loadPostgresDeps(): Promise<(connectionString: string) => unknown> {\n\ttry {\n\t\tconst dynamicImport = new Function('specifier', 'return import(specifier)') as (\n\t\t\tspecifier: string,\n\t\t) => Promise<unknown>\n\n\t\tconst postgresMod = (await dynamicImport('postgres')) as { default: (cs: string) => unknown }\n\t\treturn postgresMod.default\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'PostgreSQL backend requires the \"postgres\" package. Install it in your project dependencies.',\n\t\t)\n\t}\n}\n\n/**\n * Row shapes from postgres-js queries. Field names match SQL column names.\n */\ninterface UserRow {\n\tid: string\n\temail: string\n\tname: string\n\temail_verified: boolean\n\tcreated_at: string | number\n\tpassword_hash: string\n\tsalt: string\n}\n\ninterface DeviceRow {\n\tid: string\n\tuser_id: string\n\tpublic_key: string\n\tname: string\n\trevoked: boolean\n\tcreated_at: string | number\n\tlast_seen_at: string | number\n}\n\nfunction rowToStoredUser(row: UserRow): StoredUser {\n\treturn {\n\t\tid: row.id,\n\t\temail: row.email,\n\t\tname: row.name,\n\t\temailVerified: Boolean(row.email_verified),\n\t\tcreatedAt: Number(row.created_at),\n\t\tpasswordHash: row.password_hash,\n\t\tsalt: row.salt,\n\t}\n}\n\nfunction rowToDevice(row: DeviceRow): AuthDevice {\n\treturn {\n\t\tid: row.id,\n\t\tuserId: row.user_id,\n\t\tpublicKey: row.public_key,\n\t\tname: row.name,\n\t\trevoked: Boolean(row.revoked),\n\t\tcreatedAt: Number(row.created_at),\n\t\tlastSeenAt: Number(row.last_seen_at),\n\t}\n}\n","import type { AuthTokens } from '../types'\nimport type { TokenManager } from '../tokens/token-manager'\nimport type { AuthUser, AuthDevice, UserStore } from './built-in/user-store'\nimport { BuiltInAuthRoutes, type AuthRoutesConfig } from './built-in/auth-routes'\n\n/**\n * Parameters for signing up a new user.\n */\nexport interface SignUpParams {\n\t/** The user's email address */\n\temail: string\n\t/** The plaintext password */\n\tpassword: string\n\t/** Optional display name */\n\tname?: string\n\t/** Optional device ID to register with the account */\n\tdeviceId?: string\n\t/** Optional device public key (base64url) */\n\tdevicePublicKey?: string\n}\n\n/**\n * Parameters for signing in an existing user.\n */\nexport interface SignInParams {\n\t/** The user's email address */\n\temail: string\n\t/** The plaintext password */\n\tpassword: string\n\t/** Optional device ID to register or associate */\n\tdeviceId?: string\n\t/** Optional device public key (base64url) */\n\tdevicePublicKey?: string\n}\n\n/**\n * Abstraction for authentication providers.\n *\n * This interface allows swapping between the built-in email/password provider\n * and external providers (OAuth, SAML, custom) without changing application code.\n * Every provider must support the same core operations: sign up, sign in,\n * token refresh, token validation, user lookup, and device management.\n *\n * @example\n * ```typescript\n * // Use the built-in provider\n * const provider: AuthProviderAdapter = new BuiltInProvider({\n * userStore: new InMemoryUserStore(),\n * tokenManager: new TokenManager({ secret: 'my-secret' }),\n * })\n *\n * const { user, tokens } = await provider.signUp({\n * email: 'alice@example.com',\n * password: 'secure-password-123',\n * })\n * ```\n */\nexport interface AuthProviderAdapter {\n\t/**\n\t * Create a new user account and issue authentication tokens.\n\t *\n\t * @param params - Sign-up parameters including email, password, and optional device info\n\t * @returns The created user and tokens\n\t * @throws If the email is already registered or input validation fails\n\t */\n\tsignUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\n\t/**\n\t * Authenticate an existing user and issue tokens.\n\t *\n\t * @param params - Sign-in parameters including email and password\n\t * @returns The authenticated user and tokens\n\t * @throws If the credentials are invalid\n\t */\n\tsignIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }>\n\n\t/**\n\t * Exchange a refresh token for new tokens (rotation).\n\t *\n\t * @param refreshToken - The current refresh token\n\t * @returns A new token pair\n\t * @throws If the refresh token is invalid or expired\n\t */\n\trefreshTokens(refreshToken: string): Promise<AuthTokens>\n\n\t/**\n\t * Validate an access token and extract identity claims.\n\t *\n\t * @param token - The JWT access token\n\t * @returns User ID and device ID if valid, or null if invalid/expired\n\t */\n\tvalidateAccessToken(token: string): Promise<{ userId: string; deviceId: string } | null>\n\n\t/**\n\t * Look up a user by ID.\n\t *\n\t * @param userId - The user's ID\n\t * @returns The user profile, or null if not found\n\t */\n\tgetUser(userId: string): Promise<AuthUser | null>\n\n\t/**\n\t * Revoke a device. Requires a valid access token for authorization.\n\t *\n\t * @param accessToken - The caller's access token\n\t * @param deviceId - The ID of the device to revoke\n\t * @throws If the token is invalid or the device does not belong to the caller\n\t */\n\trevokeDevice(accessToken: string, deviceId: string): Promise<void>\n\n\t/**\n\t * List all devices for the authenticated user.\n\t *\n\t * @param accessToken - The caller's access token\n\t * @returns Array of device records\n\t * @throws If the token is invalid\n\t */\n\tlistDevices(accessToken: string): Promise<AuthDevice[]>\n}\n\n/**\n * Error thrown by provider adapter methods when an operation fails.\n * Wraps the HTTP-style status and error message from route handlers\n * into an exception for use in the adapter pattern.\n */\nexport class AuthProviderError extends Error {\n\t/** HTTP-style status code */\n\treadonly status: number\n\n\tconstructor(message: string, status: number) {\n\t\tsuper(message)\n\t\tthis.name = 'AuthProviderError'\n\t\tthis.status = status\n\t}\n}\n\n/**\n * Built-in authentication provider implementing email/password authentication.\n *\n * Wraps {@link BuiltInAuthRoutes} in the {@link AuthProviderAdapter} interface,\n * converting HTTP-style responses into direct return values and exceptions.\n * This is the default provider shipped with Kora and is suitable for\n * applications that want simple email/password auth without external services.\n *\n * @example\n * ```typescript\n * import { BuiltInProvider } from '@korajs/auth'\n * import { InMemoryUserStore } from '@korajs/auth'\n * import { TokenManager } from '@korajs/auth'\n *\n * const provider = new BuiltInProvider({\n * userStore: new InMemoryUserStore(),\n * tokenManager: new TokenManager({ secret: process.env.AUTH_SECRET }),\n * })\n *\n * const { user, tokens } = await provider.signUp({\n * email: 'alice@example.com',\n * password: 'strong-password-123',\n * name: 'Alice',\n * })\n * ```\n */\nexport class BuiltInProvider implements AuthProviderAdapter {\n\tprivate readonly routes: BuiltInAuthRoutes\n\tprivate readonly tokenManager: TokenManager\n\tprivate readonly userStore: UserStore\n\n\tconstructor(config: AuthRoutesConfig) {\n\t\tthis.routes = new BuiltInAuthRoutes(config)\n\t\tthis.tokenManager = config.tokenManager\n\t\tthis.userStore = config.userStore\n\t}\n\n\t/** @inheritdoc */\n\tasync signUp(params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tconst result = await this.routes.handleSignUp(params)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n\n\t/** @inheritdoc */\n\tasync signIn(params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tconst result = await this.routes.handleSignIn(params)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n\n\t/** @inheritdoc */\n\tasync refreshTokens(refreshToken: string): Promise<AuthTokens> {\n\t\tconst result = await this.routes.handleRefresh({ refreshToken })\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n\n\t/** @inheritdoc */\n\tasync validateAccessToken(\n\t\ttoken: string,\n\t): Promise<{ userId: string; deviceId: string } | null> {\n\t\tconst payload = this.tokenManager.validateToken(token)\n\t\tif (payload === null || payload.type !== 'access') {\n\t\t\treturn null\n\t\t}\n\t\treturn { userId: payload.sub, deviceId: payload.dev }\n\t}\n\n\t/** @inheritdoc */\n\tasync getUser(userId: string): Promise<AuthUser | null> {\n\t\tconst stored = await this.userStore.findById(userId)\n\t\tif (stored === null) {\n\t\t\treturn null\n\t\t}\n\t\treturn {\n\t\t\tid: stored.id,\n\t\t\temail: stored.email,\n\t\t\tname: stored.name,\n\t\t\temailVerified: stored.emailVerified,\n\t\t\tcreatedAt: stored.createdAt,\n\t\t}\n\t}\n\n\t/** @inheritdoc */\n\tasync revokeDevice(accessToken: string, deviceId: string): Promise<void> {\n\t\tconst result = await this.routes.handleRevokeDevice(accessToken, deviceId)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t}\n\n\t/** @inheritdoc */\n\tasync listDevices(accessToken: string): Promise<AuthDevice[]> {\n\t\tconst result = await this.routes.handleListDevices(accessToken)\n\t\tif ('error' in result.body) {\n\t\t\tthrow new AuthProviderError(result.body.error, result.status)\n\t\t}\n\t\treturn result.body.data\n\t}\n}\n","import { KoraError } from '@korajs/core'\nimport { verifyJwt, decodeJwt, isExpired } from '../../tokens/jwt'\nimport type {\n\tAuthProviderAdapter,\n\tSignUpParams,\n\tSignInParams,\n} from '../adapter'\nimport type { AuthUser } from '../built-in/user-store'\nimport type { AuthDevice } from '../built-in/user-store'\nimport type { AuthTokens } from '../../types'\n\n// ============================================================================\n// Error classes\n// ============================================================================\n\n/**\n * Thrown when an operation is not supported by external auth providers.\n *\n * External providers delegate user management to the third-party service\n * (Clerk, Auth0, Supabase, etc.). Operations like sign-up and sign-in must\n * be performed through the external provider's SDK or UI, not through Kora.\n */\nexport class ExternalAuthOperationNotSupportedError extends KoraError {\n\tconstructor(operation: string, provider: string) {\n\t\tsuper(\n\t\t\t`The \"${operation}\" operation is not supported by the external auth provider \"${provider}\". ` +\n\t\t\t`Perform this operation through your external auth provider's SDK or dashboard instead.`,\n\t\t\t'AUTH_EXTERNAL_OPERATION_NOT_SUPPORTED',\n\t\t\t{ operation, provider },\n\t\t)\n\t\tthis.name = 'ExternalAuthOperationNotSupportedError'\n\t}\n}\n\n/**\n * Thrown when an external JWT token fails validation.\n */\nexport class ExternalTokenValidationError extends KoraError {\n\tconstructor(reason: string, context?: Record<string, unknown>) {\n\t\tsuper(\n\t\t\t`External token validation failed: ${reason}`,\n\t\t\t'AUTH_EXTERNAL_TOKEN_INVALID',\n\t\t\tcontext,\n\t\t)\n\t\tthis.name = 'ExternalTokenValidationError'\n\t}\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Default claim mapping for external JWT tokens.\n * Maps standard JWT claims to Kora's expected user format.\n */\nfunction defaultMapClaims(claims: Record<string, unknown>): ExternalUserInfo {\n\tconst sub = claims['sub']\n\tif (typeof sub !== 'string' || sub.length === 0) {\n\t\tthrow new ExternalTokenValidationError(\n\t\t\t'JWT is missing a valid \"sub\" (subject) claim. The \"sub\" claim must be a non-empty string identifying the user.',\n\t\t\t{ availableClaims: Object.keys(claims) },\n\t\t)\n\t}\n\n\treturn {\n\t\tuserId: sub,\n\t\temail: typeof claims['email'] === 'string' ? claims['email'] : undefined,\n\t\tname: typeof claims['name'] === 'string' ? claims['name'] : undefined,\n\t\tmetadata: undefined,\n\t}\n}\n\n/**\n * User information extracted from an external JWT token.\n * Returned by the claims mapping function.\n */\nexport interface ExternalUserInfo {\n\t/** Unique user identifier from the external provider */\n\tuserId: string\n\t/** User's email address, if available in the token claims */\n\temail?: string\n\t/** User's display name, if available in the token claims */\n\tname?: string\n\t/** Additional metadata from the token claims */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Configuration for the external JWT authentication provider.\n *\n * Supports two validation modes:\n * 1. **HMAC secret** (`jwtSecret`): For providers that sign tokens with a shared\n * secret (e.g., Supabase). Uses HS256 verification via the existing `verifyJwt` utility.\n * 2. **Custom validator** (`validateToken`): For providers that use asymmetric keys\n * (RS256, ES256) or require custom validation logic (e.g., Clerk with JWKS rotation).\n * The developer provides their own validation function.\n *\n * At least one of `jwtSecret` or `validateToken` must be provided.\n *\n * @example\n * ```typescript\n * // With a shared secret (e.g., Supabase)\n * const provider = new ExternalJwtProvider({\n * providerName: 'supabase',\n * jwtSecret: process.env.SUPABASE_JWT_SECRET,\n * })\n *\n * // With a custom validator (e.g., Clerk JWKS)\n * const provider = new ExternalJwtProvider({\n * providerName: 'clerk',\n * validateToken: async (token) => {\n * const claims = await clerkClient.verifyToken(token)\n * return claims ? { sub: claims.sub, ...claims } : null\n * },\n * })\n * ```\n */\nexport interface ExternalJwtProviderConfig {\n\t/**\n\t * Human-readable name of the external auth provider.\n\t * Used in error messages and DevTools for identification.\n\t * @example 'clerk', 'auth0', 'supabase', 'firebase'\n\t */\n\tproviderName: string\n\n\t/**\n\t * Shared secret for HS256 JWT verification.\n\t * Used when the external provider signs tokens with HMAC-SHA256.\n\t * Mutually exclusive with `validateToken` (if both are provided,\n\t * `validateToken` takes precedence).\n\t */\n\tjwtSecret?: string\n\n\t/**\n\t * Custom token validator function.\n\t * When provided, this is used instead of HS256 HMAC verification.\n\t * Receives the raw JWT string and must return the decoded claims\n\t * (with at least a `sub` field) or null if the token is invalid.\n\t *\n\t * Use this for providers that use asymmetric signing (RS256, ES256)\n\t * or require JWKS-based key rotation.\n\t *\n\t * @param token - The raw JWT string to validate\n\t * @returns The decoded claims object with at least a `sub` field, or null if invalid\n\t */\n\tvalidateToken?: (token: string) => Promise<{ sub: string; [key: string]: unknown } | null>\n\n\t/**\n\t * Map external JWT claims to Kora's expected user format.\n\t *\n\t * The default mapping extracts:\n\t * - `sub` -> `userId` (required)\n\t * - `email` -> `email` (optional)\n\t * - `name` -> `name` (optional)\n\t *\n\t * Override this to extract custom claims from your provider's tokens.\n\t *\n\t * @param claims - The decoded JWT claims object\n\t * @returns Kora-compatible user information\n\t *\n\t * @example\n\t * ```typescript\n\t * mapClaims: (claims) => ({\n\t * userId: claims.sub as string,\n\t * email: claims.email_address as string,\n\t * name: `${claims.first_name} ${claims.last_name}`,\n\t * metadata: { org: claims.org_id },\n\t * })\n\t * ```\n\t */\n\tmapClaims?: (claims: Record<string, unknown>) => ExternalUserInfo\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Authentication provider adapter for external JWT issuers.\n *\n * This adapter validates JWTs issued by third-party auth services (Clerk, Auth0,\n * Supabase, Firebase, or any custom JWT issuer) and maps their claims to Kora's\n * internal auth context. It bridges external identity providers with Kora's\n * sync authentication layer.\n *\n * **How it works:**\n * 1. The client authenticates with the external provider and receives a JWT\n * 2. The client passes this JWT to Kora's sync server\n * 3. This adapter validates the JWT and extracts user identity\n * 4. Kora uses the extracted identity for sync authorization\n *\n * **What it does NOT do:**\n * - Sign up or sign in users (that happens through the external provider)\n * - Issue or refresh tokens (that is the external provider's responsibility)\n * - Manage devices (external providers handle their own device tracking)\n *\n * @example\n * ```typescript\n * import { ExternalJwtProvider } from '@korajs/auth/server'\n *\n * const auth = new ExternalJwtProvider({\n * providerName: 'clerk',\n * validateToken: async (token) => {\n * // Use Clerk's SDK or JWKS endpoint to verify\n * return verifiedClaims\n * },\n * })\n *\n * // Use with Kora sync server\n * const result = await auth.validateAccessToken('eyJhbG...')\n * if (result) {\n * console.log('User:', result.userId)\n * }\n * ```\n */\nexport class ExternalJwtProvider implements AuthProviderAdapter {\n\tprivate readonly providerName: string\n\tprivate readonly jwtSecret: string | undefined\n\tprivate readonly customValidateToken:\n\t\t| ((token: string) => Promise<{ sub: string; [key: string]: unknown } | null>)\n\t\t| undefined\n\tprivate readonly mapClaims: (claims: Record<string, unknown>) => ExternalUserInfo\n\n\tconstructor(config: ExternalJwtProviderConfig) {\n\t\tif (config.validateToken === undefined && config.jwtSecret === undefined) {\n\t\t\tthrow new ExternalTokenValidationError(\n\t\t\t\t'ExternalJwtProvider requires either a \"jwtSecret\" for HS256 verification ' +\n\t\t\t\t'or a custom \"validateToken\" function. Provide at least one.',\n\t\t\t\t{ providerName: config.providerName },\n\t\t\t)\n\t\t}\n\n\t\tthis.providerName = config.providerName\n\t\tthis.jwtSecret = config.jwtSecret\n\t\tthis.customValidateToken = config.validateToken\n\t\tthis.mapClaims = config.mapClaims ?? defaultMapClaims\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * User registration must be performed through the external auth provider's\n\t * SDK or UI. Kora does not manage user accounts for external providers.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync signUp(_params: SignUpParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('signUp', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * User authentication must be performed through the external auth provider's\n\t * SDK or UI. Kora does not manage credentials for external providers.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync signIn(_params: SignInParams): Promise<{ user: AuthUser; tokens: AuthTokens }> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('signIn', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * Token refresh must be performed through the external auth provider's SDK.\n\t * Kora does not manage token lifecycle for external providers.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync refreshTokens(_refreshToken: string): Promise<AuthTokens> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('refreshTokens', this.providerName)\n\t}\n\n\t/**\n\t * Validate an access token from the external auth provider.\n\t *\n\t * Uses either the custom `validateToken` function or HS256 HMAC verification\n\t * (depending on configuration) to validate the JWT. On success, maps the claims\n\t * to Kora's expected format and returns the user ID and device ID.\n\t *\n\t * The device ID for external providers is derived from the user ID with a\n\t * \"external-\" prefix, since external providers typically don't use Kora's\n\t * device identity system.\n\t *\n\t * @param token - The JWT access token issued by the external provider\n\t * @returns User ID and device ID if the token is valid, or null if invalid/expired\n\t */\n\tasync validateAccessToken(\n\t\ttoken: string,\n\t): Promise<{ userId: string; deviceId: string } | null> {\n\t\tconst claims = await this.extractClaims(token)\n\t\tif (claims === null) {\n\t\t\treturn null\n\t\t}\n\n\t\tlet userInfo: ExternalUserInfo\n\t\ttry {\n\t\t\tuserInfo = this.mapClaims(claims)\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\n\t\tif (typeof userInfo.userId !== 'string' || userInfo.userId.length === 0) {\n\t\t\treturn null\n\t\t}\n\n\t\t// External providers don't use Kora's device identity system.\n\t\t// Derive a stable device ID from the user ID so sync authorization works.\n\t\tconst deviceId = `external-${this.providerName}-${userInfo.userId}`\n\n\t\treturn { userId: userInfo.userId, deviceId }\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * User lookup must be performed through the external auth provider's API.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync getUser(_userId: string): Promise<AuthUser | null> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('getUser', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * Device revocation must be managed through the external auth provider\n\t * or by revoking the user's tokens at the provider level.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync revokeDevice(_accessToken: string, _deviceId: string): Promise<void> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('revokeDevice', this.providerName)\n\t}\n\n\t/**\n\t * Not supported for external providers.\n\t *\n\t * Device listing must be performed through the external auth provider's API.\n\t *\n\t * @throws {ExternalAuthOperationNotSupportedError} Always\n\t */\n\tasync listDevices(_accessToken: string): Promise<AuthDevice[]> {\n\t\tthrow new ExternalAuthOperationNotSupportedError('listDevices', this.providerName)\n\t}\n\n\t/**\n\t * Creates a sync server auth provider compatible with `@korajs/server`.\n\t *\n\t * Returns an object with an `authenticate` method that validates the external\n\t * JWT and returns a Kora-compatible auth context. Use this to wire the external\n\t * auth provider into KoraSyncServer.\n\t *\n\t * @returns An object with an `authenticate` method for KoraSyncServer's `auth` config\n\t *\n\t * @example\n\t * ```typescript\n\t * const externalAuth = new ExternalJwtProvider({ ... })\n\t * const syncServer = new KoraSyncServer({\n\t * store,\n\t * auth: externalAuth.toSyncAuthProvider(),\n\t * })\n\t * ```\n\t */\n\ttoSyncAuthProvider(): {\n\t\tauthenticate(token: string): Promise<{\n\t\t\tuserId: string\n\t\t\tscopes?: Record<string, Record<string, unknown>>\n\t\t\tmetadata?: Record<string, unknown>\n\t\t} | null>\n\t} {\n\t\treturn {\n\t\t\tauthenticate: async (token: string) => {\n\t\t\t\tconst claims = await this.extractClaims(token)\n\t\t\t\tif (claims === null) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\tlet userInfo: ExternalUserInfo\n\t\t\t\ttry {\n\t\t\t\t\tuserInfo = this.mapClaims(claims)\n\t\t\t\t} catch {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\tif (typeof userInfo.userId !== 'string' || userInfo.userId.length === 0) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tuserId: userInfo.userId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tprovider: this.providerName,\n\t\t\t\t\t\temail: userInfo.email,\n\t\t\t\t\t\tname: userInfo.name,\n\t\t\t\t\t\t...userInfo.metadata,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Extract and validate claims from a JWT token.\n\t *\n\t * Uses the custom validator if configured, otherwise falls back to\n\t * HS256 HMAC verification with the configured secret.\n\t *\n\t * @param token - The raw JWT string\n\t * @returns The decoded claims object, or null if the token is invalid\n\t */\n\tprivate async extractClaims(token: string): Promise<Record<string, unknown> | null> {\n\t\t// Custom validator takes precedence\n\t\tif (this.customValidateToken !== undefined) {\n\t\t\ttry {\n\t\t\t\tconst result = await this.customValidateToken(token)\n\t\t\t\tif (result === null) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\treturn result as Record<string, unknown>\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to HS256 HMAC verification\n\t\tif (this.jwtSecret !== undefined) {\n\t\t\tconst claims = verifyJwt(token, this.jwtSecret)\n\t\t\tif (claims === null) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Check expiration if the token has an exp claim\n\t\t\tif (isExpired(claims as { exp?: number })) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn claims\n\t\t}\n\n\t\t// Should not reach here due to constructor validation, but handle defensively\n\t\treturn null\n\t}\n}\n","import {\n\tExternalJwtProvider,\n\ttype ExternalJwtProviderConfig,\n\ttype ExternalUserInfo,\n} from './external-jwt-provider'\n\n// ============================================================================\n// Clerk Adapter Configuration\n// ============================================================================\n\n/**\n * Configuration for the Clerk authentication adapter.\n *\n * Clerk uses asymmetric signing (RS256) with JWKS key rotation, so this adapter\n * requires a custom `validateToken` function that handles JWKS-based verification.\n * The adapter provides sensible defaults for mapping Clerk's JWT claims to Kora's\n * expected format.\n *\n * Clerk JWTs typically contain:\n * - `sub`: User ID (e.g., \"user_2abc123...\")\n * - `email`: Primary email address (if configured in session claims)\n * - `first_name`, `last_name`: Name fields (if configured in session claims)\n * - `azp`: Authorized party (your frontend origin)\n * - `org_id`, `org_slug`, `org_role`: Organization claims (if using Clerk orgs)\n *\n * @example\n * ```typescript\n * import { createClerkAdapter } from '@korajs/auth/server'\n *\n * const clerkAuth = createClerkAdapter({\n * validateToken: async (token) => {\n * // Use Clerk's backend SDK or your own JWKS verification\n * const result = await clerkClient.verifyToken(token)\n * return result ? { sub: result.sub, ...result } : null\n * },\n * })\n *\n * // Use with Kora sync server\n * const syncServer = new KoraSyncServer({\n * store,\n * auth: clerkAuth.toSyncAuthProvider(),\n * })\n * ```\n */\nexport interface ClerkAdapterConfig {\n\t/**\n\t * Custom token validator for Clerk JWTs.\n\t *\n\t * Clerk uses RS256 signing with JWKS key rotation, which requires either\n\t * Clerk's backend SDK or a JWKS-based verifier. This function receives\n\t * the raw JWT and should return the decoded claims or null.\n\t *\n\t * @param token - The raw JWT string from the Clerk session\n\t * @returns Decoded claims with at least a `sub` field, or null if invalid\n\t */\n\tvalidateToken: (token: string) => Promise<{ sub: string; [key: string]: unknown } | null>\n\n\t/**\n\t * Custom claim mapping override.\n\t *\n\t * By default, the Clerk adapter maps:\n\t * - `sub` -> `userId`\n\t * - `email` -> `email` (if present)\n\t * - `first_name` + `last_name` -> `name` (concatenated, if present)\n\t * - `org_id`, `org_slug`, `org_role` -> `metadata` (if present)\n\t *\n\t * Override this to customize how Clerk claims are mapped to Kora's format.\n\t */\n\tmapClaims?: ExternalJwtProviderConfig['mapClaims']\n}\n\n// ============================================================================\n// Default Clerk claim mapping\n// ============================================================================\n\n/**\n * Default claim mapping for Clerk JWTs.\n *\n * Extracts user identity from Clerk's standard session claims and maps\n * organization data into Kora metadata when available.\n */\nfunction defaultClerkClaimMapping(claims: Record<string, unknown>): ExternalUserInfo {\n\tconst sub = claims['sub']\n\tif (typeof sub !== 'string' || sub.length === 0) {\n\t\t// Delegate to the base provider's error handling by returning invalid data\n\t\t// The ExternalJwtProvider will catch the missing userId\n\t\treturn { userId: '' }\n\t}\n\n\t// Build display name from first_name and last_name if available\n\tconst firstName = typeof claims['first_name'] === 'string' ? claims['first_name'] : ''\n\tconst lastName = typeof claims['last_name'] === 'string' ? claims['last_name'] : ''\n\tconst fullName = [firstName, lastName].filter(Boolean).join(' ')\n\n\t// Extract email (Clerk may include this in session claims)\n\tconst email = typeof claims['email'] === 'string' ? claims['email'] : undefined\n\n\t// Extract organization metadata if present\n\tconst metadata: Record<string, unknown> = {}\n\tif (typeof claims['org_id'] === 'string') {\n\t\tmetadata['orgId'] = claims['org_id']\n\t}\n\tif (typeof claims['org_slug'] === 'string') {\n\t\tmetadata['orgSlug'] = claims['org_slug']\n\t}\n\tif (typeof claims['org_role'] === 'string') {\n\t\tmetadata['orgRole'] = claims['org_role']\n\t}\n\n\treturn {\n\t\tuserId: sub,\n\t\temail,\n\t\tname: fullName.length > 0 ? fullName : undefined,\n\t\tmetadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n\t}\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Creates an ExternalJwtProvider configured for Clerk authentication.\n *\n * Clerk uses RS256 signing with JWKS key rotation, so a custom `validateToken`\n * function must be provided. This function should use Clerk's backend SDK or\n * a JWKS-based JWT verifier to validate tokens.\n *\n * This adapter does NOT depend on `@clerk/backend` or any Clerk SDK. It only\n * provides sensible defaults for mapping Clerk's JWT claims to Kora's format.\n * The actual token verification is delegated to the provided `validateToken` function.\n *\n * @param config - Clerk adapter configuration\n * @returns An ExternalJwtProvider instance configured for Clerk\n *\n * @example\n * ```typescript\n * import { createClerkAdapter } from '@korajs/auth/server'\n *\n * const clerkAuth = createClerkAdapter({\n * validateToken: async (token) => {\n * // Your JWKS verification logic here\n * const payload = await verifyWithJwks(token, CLERK_JWKS_URL)\n * return payload\n * },\n * })\n *\n * const result = await clerkAuth.validateAccessToken(sessionToken)\n * ```\n */\nexport function createClerkAdapter(config: ClerkAdapterConfig): ExternalJwtProvider {\n\treturn new ExternalJwtProvider({\n\t\tproviderName: 'clerk',\n\t\tvalidateToken: config.validateToken,\n\t\tmapClaims: config.mapClaims ?? defaultClerkClaimMapping,\n\t})\n}\n","import {\n\tExternalJwtProvider,\n\ttype ExternalJwtProviderConfig,\n\ttype ExternalUserInfo,\n} from './external-jwt-provider'\n\n// ============================================================================\n// Supabase Adapter Configuration\n// ============================================================================\n\n/**\n * Configuration for the Supabase authentication adapter.\n *\n * Supabase Auth signs JWTs with HS256 using the project's JWT secret,\n * which is available in your Supabase project settings under\n * Settings -> API -> JWT Secret.\n *\n * Supabase JWTs typically contain:\n * - `sub`: User UUID\n * - `email`: User's email address\n * - `role`: The database role (e.g., \"authenticated\", \"anon\")\n * - `aud`: Audience (usually \"authenticated\")\n * - `app_metadata`: Provider info, roles, etc.\n * - `user_metadata`: Custom user data (name, avatar, etc.)\n *\n * @example\n * ```typescript\n * import { createSupabaseAdapter } from '@korajs/auth/server'\n *\n * const supabaseAuth = createSupabaseAdapter({\n * jwtSecret: process.env.SUPABASE_JWT_SECRET,\n * })\n *\n * // Use with Kora sync server\n * const syncServer = new KoraSyncServer({\n * store,\n * auth: supabaseAuth.toSyncAuthProvider(),\n * })\n * ```\n */\nexport interface SupabaseAdapterConfig {\n\t/**\n\t * Supabase JWT secret from your project settings.\n\t *\n\t * Found in: Supabase Dashboard -> Settings -> API -> JWT Secret\n\t *\n\t * This is the HS256 HMAC secret used to sign and verify Supabase Auth JWTs.\n\t * Keep this secret secure and never expose it in client-side code.\n\t */\n\tjwtSecret: string\n\n\t/**\n\t * Custom claim mapping override.\n\t *\n\t * By default, the Supabase adapter maps:\n\t * - `sub` -> `userId`\n\t * - `email` -> `email`\n\t * - `user_metadata.full_name` or `user_metadata.name` -> `name`\n\t * - `role`, `aud`, `app_metadata` -> `metadata`\n\t *\n\t * Override this to customize how Supabase claims are mapped to Kora's format.\n\t */\n\tmapClaims?: ExternalJwtProviderConfig['mapClaims']\n}\n\n// ============================================================================\n// Default Supabase claim mapping\n// ============================================================================\n\n/**\n * Default claim mapping for Supabase Auth JWTs.\n *\n * Extracts user identity from Supabase's standard JWT claims and maps\n * role/audience information into Kora metadata.\n */\nfunction defaultSupabaseClaimMapping(claims: Record<string, unknown>): ExternalUserInfo {\n\tconst sub = claims['sub']\n\tif (typeof sub !== 'string' || sub.length === 0) {\n\t\treturn { userId: '' }\n\t}\n\n\tconst email = typeof claims['email'] === 'string' ? claims['email'] : undefined\n\n\t// Extract name from user_metadata (Supabase stores user profile data here)\n\tlet name: string | undefined\n\tconst userMetadata = claims['user_metadata']\n\tif (typeof userMetadata === 'object' && userMetadata !== null && !Array.isArray(userMetadata)) {\n\t\tconst meta = userMetadata as Record<string, unknown>\n\t\tif (typeof meta['full_name'] === 'string' && meta['full_name'].length > 0) {\n\t\t\tname = meta['full_name']\n\t\t} else if (typeof meta['name'] === 'string' && meta['name'].length > 0) {\n\t\t\tname = meta['name']\n\t\t}\n\t}\n\n\t// Collect metadata from Supabase-specific claims\n\tconst metadata: Record<string, unknown> = {}\n\tif (typeof claims['role'] === 'string') {\n\t\tmetadata['role'] = claims['role']\n\t}\n\tif (typeof claims['aud'] === 'string') {\n\t\tmetadata['aud'] = claims['aud']\n\t}\n\tif (\n\t\ttypeof claims['app_metadata'] === 'object'\n\t\t&& claims['app_metadata'] !== null\n\t\t&& !Array.isArray(claims['app_metadata'])\n\t) {\n\t\tmetadata['appMetadata'] = claims['app_metadata']\n\t}\n\n\treturn {\n\t\tuserId: sub,\n\t\temail,\n\t\tname,\n\t\tmetadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n\t}\n}\n\n// ============================================================================\n// Factory function\n// ============================================================================\n\n/**\n * Creates an ExternalJwtProvider configured for Supabase Auth.\n *\n * Supabase Auth uses HS256 signing with the project's JWT secret, making it\n * compatible with Kora's built-in `verifyJwt` utility. No external SDK is needed.\n *\n * This adapter validates the JWT signature and expiration, then maps Supabase's\n * standard claims (sub, email, role, user_metadata) to Kora's auth context format.\n *\n * @param config - Supabase adapter configuration\n * @returns An ExternalJwtProvider instance configured for Supabase\n *\n * @example\n * ```typescript\n * import { createSupabaseAdapter } from '@korajs/auth/server'\n *\n * const supabaseAuth = createSupabaseAdapter({\n * jwtSecret: process.env.SUPABASE_JWT_SECRET,\n * })\n *\n * // Validate a Supabase access token\n * const result = await supabaseAuth.validateAccessToken(supabaseAccessToken)\n * if (result) {\n * console.log('Supabase user:', result.userId)\n * }\n *\n * // Or use with the sync server\n * const syncServer = new KoraSyncServer({\n * store,\n * auth: supabaseAuth.toSyncAuthProvider(),\n * })\n * ```\n */\nexport function createSupabaseAdapter(config: SupabaseAdapterConfig): ExternalJwtProvider {\n\treturn new ExternalJwtProvider({\n\t\tproviderName: 'supabase',\n\t\tjwtSecret: config.jwtSecret,\n\t\tmapClaims: config.mapClaims ?? defaultSupabaseClaimMapping,\n\t})\n}\n","import { randomBytes } from 'node:crypto'\nimport { KoraError } from '@korajs/core'\nimport { fromBase64Url, toBase64Url } from '../device/device-identity'\nimport { decodeCbor } from './passkey-client'\n\n// ============================================================================\n// Server-side passkey errors\n// ============================================================================\n\n/**\n * Thrown when server-side passkey verification fails.\n */\nexport class PasskeyVerificationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'PASSKEY_VERIFICATION_ERROR', context)\n\t\tthis.name = 'PasskeyVerificationError'\n\t}\n}\n\n// ============================================================================\n// Registration options generation\n// ============================================================================\n\n/** Options returned by generateRegistrationOptions for the client. */\nexport interface RegistrationOptions {\n\t/** Base64url-encoded random challenge (32 bytes) */\n\tchallenge: string\n\t/** Relying party ID (domain) */\n\trpId: string\n\t/** Relying party display name */\n\trpName: string\n\t/** Base64url-encoded user ID */\n\tuserId: string\n\t/** User's email or username */\n\tuserName: string\n\t/** Human-readable display name */\n\tuserDisplayName: string\n\t/** Credential IDs to exclude (prevents re-registration) */\n\texcludeCredentialIds: string[]\n\t/** Authenticator selection criteria */\n\tauthenticatorSelection: {\n\t\tauthenticatorAttachment: 'platform'\n\t\tresidentKey: 'preferred'\n\t\tuserVerification: 'required'\n\t}\n\t/** Timeout in milliseconds */\n\ttimeout: number\n}\n\n/**\n * Generate registration options for creating a new passkey.\n *\n * Creates a cryptographically random challenge and assembles the options\n * object that should be sent to the client for `createPasskeyCredential()`.\n *\n * The server must store the challenge (keyed by user session or similar)\n * for later verification when the client responds.\n *\n * @param params - Registration parameters\n * @param params.rpId - Relying party ID (your domain, e.g. \"example.com\")\n * @param params.rpName - Relying party display name\n * @param params.userId - Unique user identifier\n * @param params.userName - User's email or username\n * @param params.userDisplayName - Human-readable display name\n * @param params.existingCredentialIds - Base64url credential IDs to exclude\n * @returns Registration options to send to the client, including the challenge\n *\n * @example\n * ```typescript\n * const options = generateRegistrationOptions({\n * rpId: 'example.com',\n * rpName: 'My App',\n * userId: user.id,\n * userName: user.email,\n * userDisplayName: user.name,\n * })\n * // Store options.challenge in session for later verification\n * // Send options to client\n * ```\n */\nexport function generateRegistrationOptions(params: {\n\trpId: string\n\trpName: string\n\tuserId: string\n\tuserName: string\n\tuserDisplayName: string\n\texistingCredentialIds?: string[]\n}): RegistrationOptions {\n\t// Generate a 32-byte cryptographically random challenge\n\tconst challengeBytes = randomBytes(32)\n\tconst challenge = toBase64Url(challengeBytes.buffer.slice(\n\t\tchallengeBytes.byteOffset,\n\t\tchallengeBytes.byteOffset + challengeBytes.byteLength,\n\t))\n\n\treturn {\n\t\tchallenge,\n\t\trpId: params.rpId,\n\t\trpName: params.rpName,\n\t\tuserId: params.userId,\n\t\tuserName: params.userName,\n\t\tuserDisplayName: params.userDisplayName,\n\t\texcludeCredentialIds: params.existingCredentialIds ?? [],\n\t\tauthenticatorSelection: {\n\t\t\tauthenticatorAttachment: 'platform',\n\t\t\tresidentKey: 'preferred',\n\t\t\tuserVerification: 'required',\n\t\t},\n\t\ttimeout: 60000,\n\t}\n}\n\n// ============================================================================\n// Registration verification\n// ============================================================================\n\n/** Result of verifying a registration response. */\nexport interface RegistrationVerificationResult {\n\t/** Whether the registration response was verified successfully */\n\tverified: boolean\n\t/** Base64url-encoded credential ID */\n\tcredentialId: string\n\t/** Base64url-encoded COSE public key (store this for future authentication) */\n\tpublicKey: string\n\t/** Initial signature counter from the authenticator */\n\tsignCount: number\n}\n\n/**\n * Verify a registration response from the client.\n *\n * Validates the attestation object and clientDataJSON returned by the browser's\n * `navigator.credentials.create()` call. Extracts and returns the public key\n * and credential ID to store in your database.\n *\n * This implementation supports the \"none\" attestation format, which is the most\n * common and does not require trust in any attestation CA. For higher assurance\n * scenarios, extend this to verify packed/tpm/android attestation formats.\n *\n * @param params - Verification parameters\n * @param params.credential - The credential response from the client\n * @param params.expectedChallenge - The challenge that was sent to the client (base64url)\n * @param params.expectedOrigin - The expected origin (e.g. \"https://example.com\")\n * @param params.expectedRpId - The expected relying party ID (e.g. \"example.com\")\n * @returns Verification result with the credential ID and public key to store\n * @throws {PasskeyVerificationError} If the response is invalid or tampered with\n *\n * @example\n * ```typescript\n * const result = await verifyRegistrationResponse({\n * credential: clientResponse,\n * expectedChallenge: storedChallenge,\n * expectedOrigin: 'https://example.com',\n * expectedRpId: 'example.com',\n * })\n * if (result.verified) {\n * // Store result.credentialId, result.publicKey, result.signCount\n * }\n * ```\n */\nexport async function verifyRegistrationResponse(params: {\n\tcredential: {\n\t\tcredentialId: string\n\t\tpublicKey: string\n\t\tclientDataJSON: string\n\t\tattestationObject: string\n\t}\n\texpectedChallenge: string\n\texpectedOrigin: string\n\texpectedRpId: string\n}): Promise<RegistrationVerificationResult> {\n\tconst { credential, expectedChallenge, expectedOrigin, expectedRpId } = params\n\n\t// Step 1: Decode and verify clientDataJSON\n\tconst clientDataBytes = fromBase64Url(credential.clientDataJSON)\n\tconst clientDataText = new TextDecoder().decode(clientDataBytes)\n\tlet clientData: { type: string; challenge: string; origin: string }\n\ttry {\n\t\tclientData = JSON.parse(clientDataText) as {\n\t\t\ttype: string\n\t\t\tchallenge: string\n\t\t\torigin: string\n\t\t}\n\t} catch {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Failed to parse clientDataJSON. The response may be malformed.',\n\t\t)\n\t}\n\n\t// Verify the type is \"webauthn.create\"\n\tif (clientData.type !== 'webauthn.create') {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Expected clientData.type \"webauthn.create\" but received \"${clientData.type}\".`,\n\t\t\t{ type: clientData.type },\n\t\t)\n\t}\n\n\t// Verify the challenge matches what we sent\n\tif (clientData.challenge !== expectedChallenge) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Challenge mismatch. The response does not match the expected challenge. ' +\n\t\t\t\t'This may indicate a replay attack or session mismatch.',\n\t\t)\n\t}\n\n\t// Verify the origin matches\n\tif (clientData.origin !== expectedOrigin) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Origin mismatch. Expected \"${expectedOrigin}\" but received \"${clientData.origin}\".`,\n\t\t\t{ expected: expectedOrigin, received: clientData.origin },\n\t\t)\n\t}\n\n\t// Step 2: Decode the attestation object (CBOR)\n\tconst attestationBytes = fromBase64Url(credential.attestationObject)\n\tconst attestationResult = decodeCbor(attestationBytes, 0)\n\tconst attestationMap = attestationResult.value as Map<string, unknown>\n\n\t// Verify attestation format\n\tconst fmt = attestationMap.get('fmt')\n\tif (fmt !== 'none') {\n\t\t// For Phase 3, we only support \"none\" attestation.\n\t\t// Other formats (packed, tpm, android-key, etc.) can be added later.\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Unsupported attestation format \"${String(fmt)}\". Only \"none\" attestation is currently supported.`,\n\t\t\t{ format: String(fmt) },\n\t\t)\n\t}\n\n\t// Step 3: Parse the authenticator data\n\tconst authData = attestationMap.get('authData')\n\tif (!(authData instanceof Uint8Array)) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid attestation object: authData is missing or not a byte string.',\n\t\t)\n\t}\n\n\t// Verify the RP ID hash (first 32 bytes of authData)\n\tconst rpIdHash = authData.slice(0, 32)\n\tconst expectedRpIdHash = await sha256(new TextEncoder().encode(expectedRpId))\n\tif (!constantTimeEqual(rpIdHash, new Uint8Array(expectedRpIdHash))) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'RP ID hash mismatch. The authenticator data does not match the expected relying party.',\n\t\t)\n\t}\n\n\t// Parse flags (byte 32)\n\tconst flags = authData[32] as number\n\n\t// Bit 0: User Present (UP) - must be set\n\tif ((flags & 0x01) === 0) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'User Present flag is not set in authenticator data. ' +\n\t\t\t\t'The authenticator did not confirm user presence.',\n\t\t)\n\t}\n\n\t// Bit 6: Attested Credential Data (AT) - must be set for registration\n\tif ((flags & 0x40) === 0) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Attested Credential Data flag is not set. ' +\n\t\t\t\t'The authenticator did not include credential data.',\n\t\t)\n\t}\n\n\t// Parse sign count (bytes 33-36, big-endian uint32)\n\tconst signCount =\n\t\t((authData[33] as number) << 24) |\n\t\t((authData[34] as number) << 16) |\n\t\t((authData[35] as number) << 8) |\n\t\t(authData[36] as number)\n\n\t// Parse attested credential data\n\t// Skip rpIdHash (32) + flags (1) + signCount (4) = 37 bytes\n\tlet offset = 37\n\n\t// aaguid: 16 bytes (we skip it — not needed for \"none\" attestation)\n\toffset += 16\n\n\t// credentialIdLength: 2 bytes, big-endian\n\tconst credentialIdLength =\n\t\t((authData[offset] as number) << 8) | (authData[offset + 1] as number)\n\toffset += 2\n\n\t// credentialId: credentialIdLength bytes\n\tconst credentialIdBytes = authData.slice(offset, offset + credentialIdLength)\n\toffset += credentialIdLength\n\n\t// Verify the credential ID matches what the client sent\n\tconst expectedCredentialId = toBase64Url(credentialIdBytes.buffer as unknown as ArrayBuffer)\n\tif (expectedCredentialId !== credential.credentialId) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Credential ID mismatch between attestation object and client response.',\n\t\t)\n\t}\n\n\t// The remaining bytes are the COSE-encoded public key\n\tconst coseKeyResult = decodeCbor(authData, offset)\n\tconst coseKeyBytes = authData.slice(offset, coseKeyResult.offset)\n\n\t// Verify the public key matches what the client sent\n\tconst publicKeyFromAttestation = toBase64Url(coseKeyBytes.buffer as unknown as ArrayBuffer)\n\tif (publicKeyFromAttestation !== credential.publicKey) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Public key mismatch between attestation object and client response.',\n\t\t)\n\t}\n\n\treturn {\n\t\tverified: true,\n\t\tcredentialId: credential.credentialId,\n\t\tpublicKey: credential.publicKey,\n\t\tsignCount: signCount >>> 0,\n\t}\n}\n\n// ============================================================================\n// Authentication options generation\n// ============================================================================\n\n/** Options returned by generateAuthenticationOptions for the client. */\nexport interface AuthenticationOptions {\n\t/** Base64url-encoded random challenge (32 bytes) */\n\tchallenge: string\n\t/** Relying party ID */\n\trpId: string\n\t/** Credential IDs to allow (limit to specific credentials) */\n\tallowCredentialIds?: string[]\n\t/** User verification requirement */\n\tuserVerification: 'preferred'\n\t/** Timeout in milliseconds */\n\ttimeout: number\n}\n\n/**\n * Generate authentication options for signing in with a passkey.\n *\n * Creates a cryptographically random challenge and assembles the options\n * object that should be sent to the client for `authenticateWithPasskey()`.\n *\n * The server must store the challenge for later verification.\n *\n * @param params - Authentication parameters\n * @param params.rpId - Relying party ID (your domain)\n * @param params.allowCredentialIds - Base64url credential IDs to allow (optional)\n * @returns Authentication options to send to the client\n *\n * @example\n * ```typescript\n * const options = generateAuthenticationOptions({\n * rpId: 'example.com',\n * allowCredentialIds: user.credentialIds,\n * })\n * // Store options.challenge in session\n * // Send options to client\n * ```\n */\nexport function generateAuthenticationOptions(params: {\n\trpId: string\n\tallowCredentialIds?: string[]\n}): AuthenticationOptions {\n\tconst challengeBytes = randomBytes(32)\n\tconst challenge = toBase64Url(challengeBytes.buffer.slice(\n\t\tchallengeBytes.byteOffset,\n\t\tchallengeBytes.byteOffset + challengeBytes.byteLength,\n\t))\n\n\treturn {\n\t\tchallenge,\n\t\trpId: params.rpId,\n\t\tallowCredentialIds: params.allowCredentialIds,\n\t\tuserVerification: 'preferred',\n\t\ttimeout: 60000,\n\t}\n}\n\n// ============================================================================\n// Authentication verification\n// ============================================================================\n\n/** Result of verifying an authentication response. */\nexport interface AuthenticationVerificationResult {\n\t/** Whether the authentication response was verified successfully */\n\tverified: boolean\n\t/** Updated signature counter (store this to detect cloned authenticators) */\n\tnewSignCount: number\n}\n\n/**\n * Verify an authentication response from the client.\n *\n * Validates the signed assertion returned by the browser's\n * `navigator.credentials.get()` call. Checks the signature against the\n * stored public key, verifies the challenge and origin, and validates\n * the signature counter to detect cloned authenticators.\n *\n * This implementation supports ECDSA P-256 (ES256, COSE algorithm -7)\n * signatures, which is the most common algorithm used by platform\n * authenticators (Touch ID, Face ID, Windows Hello).\n *\n * @param params - Verification parameters\n * @param params.assertion - The assertion response from the client\n * @param params.expectedChallenge - The challenge that was sent to the client (base64url)\n * @param params.expectedOrigin - The expected origin (e.g. \"https://example.com\")\n * @param params.expectedRpId - The expected relying party ID\n * @param params.publicKey - The stored COSE public key (base64url, from registration)\n * @param params.previousSignCount - The previously stored signature counter\n * @returns Verification result with the new signature counter\n * @throws {PasskeyVerificationError} If the assertion is invalid\n *\n * @example\n * ```typescript\n * const result = await verifyAuthenticationResponse({\n * assertion: clientAssertion,\n * expectedChallenge: storedChallenge,\n * expectedOrigin: 'https://example.com',\n * expectedRpId: 'example.com',\n * publicKey: storedCredential.publicKey,\n * previousSignCount: storedCredential.signCount,\n * })\n * if (result.verified) {\n * // Update stored sign count: storedCredential.signCount = result.newSignCount\n * // Issue session tokens\n * }\n * ```\n */\nexport async function verifyAuthenticationResponse(params: {\n\tassertion: {\n\t\tcredentialId: string\n\t\tauthenticatorData: string\n\t\tclientDataJSON: string\n\t\tsignature: string\n\t\tuserHandle: string | null\n\t}\n\texpectedChallenge: string\n\texpectedOrigin: string\n\texpectedRpId: string\n\tpublicKey: string\n\tpreviousSignCount: number\n}): Promise<AuthenticationVerificationResult> {\n\tconst {\n\t\tassertion,\n\t\texpectedChallenge,\n\t\texpectedOrigin,\n\t\texpectedRpId,\n\t\tpublicKey,\n\t\tpreviousSignCount,\n\t} = params\n\n\t// Step 1: Decode and verify clientDataJSON\n\tconst clientDataBytes = fromBase64Url(assertion.clientDataJSON)\n\tconst clientDataText = new TextDecoder().decode(clientDataBytes)\n\tlet clientData: { type: string; challenge: string; origin: string }\n\ttry {\n\t\tclientData = JSON.parse(clientDataText) as {\n\t\t\ttype: string\n\t\t\tchallenge: string\n\t\t\torigin: string\n\t\t}\n\t} catch {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Failed to parse clientDataJSON. The assertion may be malformed.',\n\t\t)\n\t}\n\n\t// Verify the type is \"webauthn.get\"\n\tif (clientData.type !== 'webauthn.get') {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Expected clientData.type \"webauthn.get\" but received \"${clientData.type}\".`,\n\t\t\t{ type: clientData.type },\n\t\t)\n\t}\n\n\t// Verify the challenge matches\n\tif (clientData.challenge !== expectedChallenge) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Challenge mismatch. The assertion does not match the expected challenge.',\n\t\t)\n\t}\n\n\t// Verify the origin matches\n\tif (clientData.origin !== expectedOrigin) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Origin mismatch. Expected \"${expectedOrigin}\" but received \"${clientData.origin}\".`,\n\t\t\t{ expected: expectedOrigin, received: clientData.origin },\n\t\t)\n\t}\n\n\t// Step 2: Parse authenticator data\n\tconst authDataBytes = fromBase64Url(assertion.authenticatorData)\n\n\t// Verify RP ID hash (first 32 bytes)\n\tconst rpIdHash = authDataBytes.slice(0, 32)\n\tconst expectedRpIdHash = await sha256(new TextEncoder().encode(expectedRpId))\n\tif (!constantTimeEqual(rpIdHash, new Uint8Array(expectedRpIdHash))) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'RP ID hash mismatch. The authenticator data does not match the expected relying party.',\n\t\t)\n\t}\n\n\t// Parse flags (byte 32)\n\tconst flags = authDataBytes[32] as number\n\n\t// Bit 0: User Present (UP) - must be set\n\tif ((flags & 0x01) === 0) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'User Present flag is not set in authenticator data.',\n\t\t)\n\t}\n\n\t// Parse sign count (bytes 33-36, big-endian uint32)\n\tconst signCount =\n\t\t(((authDataBytes[33] as number) << 24) |\n\t\t\t((authDataBytes[34] as number) << 16) |\n\t\t\t((authDataBytes[35] as number) << 8) |\n\t\t\t(authDataBytes[36] as number)) >>>\n\t\t0\n\n\t// Step 3: Validate sign count to detect cloned authenticators\n\t// If both are 0, the authenticator doesn't support counters — skip check.\n\t// If the new count is not greater than the previous, it may be cloned.\n\tif (previousSignCount > 0 || signCount > 0) {\n\t\tif (signCount <= previousSignCount) {\n\t\t\tthrow new PasskeyVerificationError(\n\t\t\t\t'Signature counter did not increase. This may indicate a cloned authenticator. ' +\n\t\t\t\t\t`Previous count: ${previousSignCount}, received count: ${signCount}.`,\n\t\t\t\t{\n\t\t\t\t\tpreviousSignCount,\n\t\t\t\t\treceivedSignCount: signCount,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\t// Step 4: Verify the signature\n\t// The signature is over: authData || SHA-256(clientDataJSON)\n\tconst clientDataHash = await sha256(clientDataBytes)\n\tconst signedData = new Uint8Array(\n\t\tauthDataBytes.length + clientDataHash.byteLength,\n\t)\n\tsignedData.set(authDataBytes, 0)\n\tsignedData.set(new Uint8Array(clientDataHash), authDataBytes.length)\n\n\t// Decode the COSE public key to get the raw EC key parameters\n\tconst coseKeyBytes = fromBase64Url(publicKey)\n\tconst coseKeyResult = decodeCbor(coseKeyBytes, 0)\n\tconst coseKeyMap = coseKeyResult.value as Map<number, unknown>\n\n\t// COSE key map labels:\n\t// 1: kty (key type) — 2 = EC2\n\t// 3: alg (algorithm) — -7 = ES256\n\t// -1: crv (curve) — 1 = P-256\n\t// -2: x coordinate (byte string, 32 bytes)\n\t// -3: y coordinate (byte string, 32 bytes)\n\n\tconst kty = coseKeyMap.get(1)\n\tconst alg = coseKeyMap.get(3)\n\n\tif (kty !== 2) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Unsupported COSE key type ${String(kty)}. Only EC2 (kty=2) is supported.`,\n\t\t\t{ kty: String(kty) },\n\t\t)\n\t}\n\n\tif (alg !== -7) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t`Unsupported COSE algorithm ${String(alg)}. Only ES256 (alg=-7) is supported.`,\n\t\t\t{ alg: String(alg) },\n\t\t)\n\t}\n\n\tconst xCoord = coseKeyMap.get(-2) as Uint8Array\n\tconst yCoord = coseKeyMap.get(-3) as Uint8Array\n\n\tif (\n\t\t!(xCoord instanceof Uint8Array) ||\n\t\t!(yCoord instanceof Uint8Array) ||\n\t\txCoord.length !== 32 ||\n\t\tyCoord.length !== 32\n\t) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid COSE public key: x and y coordinates must be 32-byte arrays.',\n\t\t)\n\t}\n\n\t// Import the public key as an ECDSA P-256 key for verification.\n\t// We use the \"raw\" format: 0x04 || x || y (uncompressed point).\n\tconst rawPublicKey = new Uint8Array(65)\n\trawPublicKey[0] = 0x04 // Uncompressed point indicator\n\trawPublicKey.set(xCoord, 1)\n\trawPublicKey.set(yCoord, 33)\n\n\tlet cryptoKey: CryptoKey\n\ttry {\n\t\tcryptoKey = await globalThis.crypto.subtle.importKey(\n\t\t\t'raw',\n\t\t\trawPublicKey.buffer as unknown as ArrayBuffer,\n\t\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\t\tfalse,\n\t\t\t['verify'],\n\t\t)\n\t} catch (error) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Failed to import COSE public key for signature verification.',\n\t\t\t{ cause: error instanceof Error ? error.message : String(error) },\n\t\t)\n\t}\n\n\t// The WebAuthn signature is in ASN.1 DER format.\n\t// Web Crypto's ECDSA verify expects the signature in IEEE P1363 format (r || s).\n\t// Convert from DER to P1363.\n\tconst signatureBytes = fromBase64Url(assertion.signature)\n\tconst p1363Signature = derToP1363(signatureBytes, 32)\n\n\tlet verified: boolean\n\ttry {\n\t\tverified = await globalThis.crypto.subtle.verify(\n\t\t\t{ name: 'ECDSA', hash: { name: 'SHA-256' } },\n\t\t\tcryptoKey,\n\t\t\tp1363Signature.buffer as unknown as ArrayBuffer,\n\t\t\tsignedData.buffer as unknown as ArrayBuffer,\n\t\t)\n\t} catch (error) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Signature verification operation failed.',\n\t\t\t{ cause: error instanceof Error ? error.message : String(error) },\n\t\t)\n\t}\n\n\tif (!verified) {\n\t\treturn { verified: false, newSignCount: signCount }\n\t}\n\n\treturn { verified: true, newSignCount: signCount }\n}\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\n/**\n * Compute SHA-256 hash of the given data using Web Crypto API.\n */\nasync function sha256(data: Uint8Array): Promise<ArrayBuffer> {\n\treturn globalThis.crypto.subtle.digest('SHA-256', data as unknown as ArrayBuffer)\n}\n\n/**\n * Constant-time comparison of two byte arrays.\n * Prevents timing attacks when comparing hashes or signatures.\n */\nfunction constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n\tif (a.length !== b.length) {\n\t\treturn false\n\t}\n\tlet result = 0\n\tfor (let i = 0; i < a.length; i++) {\n\t\tresult |= (a[i] as number) ^ (b[i] as number)\n\t}\n\treturn result === 0\n}\n\n/**\n * Convert an ASN.1 DER-encoded ECDSA signature to IEEE P1363 format.\n *\n * DER format: 0x30 <len> 0x02 <r-len> <r> 0x02 <s-len> <s>\n * P1363 format: <r-padded-to-n-bytes> <s-padded-to-n-bytes>\n *\n * This conversion is necessary because WebAuthn authenticators produce\n * DER-encoded signatures, but the Web Crypto API expects P1363 format.\n *\n * @param derSignature - The DER-encoded signature bytes\n * @param componentLength - The expected length of each component (32 for P-256)\n * @returns The P1363-formatted signature\n */\nfunction derToP1363(\n\tderSignature: Uint8Array,\n\tcomponentLength: number,\n): Uint8Array {\n\t// Parse the DER structure\n\tlet offset = 0\n\n\t// SEQUENCE tag (0x30)\n\tif (derSignature[offset] !== 0x30) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid DER signature: expected SEQUENCE tag (0x30).',\n\t\t)\n\t}\n\toffset += 1\n\n\t// SEQUENCE length (may be 1 or 2 bytes)\n\tif ((derSignature[offset] as number) & 0x80) {\n\t\t// Long form: the lower 7 bits give the number of length bytes\n\t\tconst lengthBytes = (derSignature[offset] as number) & 0x7f\n\t\toffset += 1 + lengthBytes\n\t} else {\n\t\toffset += 1\n\t}\n\n\t// First INTEGER (r)\n\tif (derSignature[offset] !== 0x02) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid DER signature: expected INTEGER tag (0x02) for r component.',\n\t\t)\n\t}\n\toffset += 1\n\n\tconst rLength = derSignature[offset] as number\n\toffset += 1\n\n\tconst rBytes = derSignature.slice(offset, offset + rLength)\n\toffset += rLength\n\n\t// Second INTEGER (s)\n\tif (derSignature[offset] !== 0x02) {\n\t\tthrow new PasskeyVerificationError(\n\t\t\t'Invalid DER signature: expected INTEGER tag (0x02) for s component.',\n\t\t)\n\t}\n\toffset += 1\n\n\tconst sLength = derSignature[offset] as number\n\toffset += 1\n\n\tconst sBytes = derSignature.slice(offset, offset + sLength)\n\n\t// Pad or trim r and s to componentLength bytes.\n\t// DER integers may have a leading 0x00 byte to indicate positive sign,\n\t// or may be shorter than componentLength if the leading bytes are zero.\n\tconst result = new Uint8Array(componentLength * 2)\n\tcopyComponentToP1363(rBytes, result, 0, componentLength)\n\tcopyComponentToP1363(sBytes, result, componentLength, componentLength)\n\n\treturn result\n}\n\n/**\n * Copy a DER integer component into a fixed-width P1363 buffer.\n * Handles leading zero padding (DER sign byte) and right-alignment.\n */\nfunction copyComponentToP1363(\n\tcomponent: Uint8Array,\n\ttarget: Uint8Array,\n\ttargetOffset: number,\n\tcomponentLength: number,\n): void {\n\tif (component.length === componentLength) {\n\t\t// Exact fit\n\t\ttarget.set(component, targetOffset)\n\t} else if (component.length > componentLength) {\n\t\t// DER may have a leading 0x00 sign byte — strip it\n\t\tconst excess = component.length - componentLength\n\t\ttarget.set(component.slice(excess), targetOffset)\n\t} else {\n\t\t// Component is shorter — right-align with zero padding\n\t\tconst padding = componentLength - component.length\n\t\ttarget.set(component, targetOffset + padding)\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Organization\n// ============================================================================\n\n/**\n * An organization (workspace/team) that groups users together.\n *\n * Organizations are the fundamental unit of multi-tenancy in Kora.\n * Data is scoped to organizations, and users access data through\n * their organization memberships and roles.\n */\nexport interface Organization {\n\t/** Unique identifier (UUID v7) */\n\tid: string\n\t/** Display name of the organization */\n\tname: string\n\t/** URL-friendly identifier (unique, lowercase, alphanumeric + hyphens) */\n\tslug: string\n\t/** User ID of the organization owner */\n\townerId: string\n\t/** When the organization was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the organization was last updated (ms since epoch) */\n\tupdatedAt: number\n\t/** Arbitrary metadata (plan, billing info, settings, etc.) */\n\tmetadata: Record<string, unknown>\n}\n\n/**\n * Parameters for creating a new organization.\n */\nexport interface CreateOrgParams {\n\t/** Display name */\n\tname: string\n\t/** URL-friendly slug (auto-generated from name if omitted) */\n\tslug?: string\n\t/** Optional metadata to attach */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Parameters for updating an existing organization.\n */\nexport interface UpdateOrgParams {\n\t/** New display name */\n\tname?: string\n\t/** New slug */\n\tslug?: string\n\t/** Metadata to merge (shallow merge) */\n\tmetadata?: Record<string, unknown>\n}\n\n// ============================================================================\n// Roles\n// ============================================================================\n\n/**\n * Built-in organization roles, ordered by decreasing privilege.\n *\n * - **owner**: Full control, can delete org, transfer ownership, manage billing\n * - **admin**: Manage members and settings, full data access\n * - **member**: Read + write own data, read shared data\n * - **viewer**: Read-only access to shared data\n * - **billing**: Billing management only, no data access\n */\nexport const ORG_ROLES = ['owner', 'admin', 'member', 'viewer', 'billing'] as const\nexport type OrgRole = (typeof ORG_ROLES)[number]\n\n/**\n * Role hierarchy for permission inheritance.\n * Higher number = more privilege.\n */\nexport const ROLE_HIERARCHY: Record<OrgRole, number> = {\n\tviewer: 10,\n\tbilling: 15,\n\tmember: 20,\n\tadmin: 30,\n\towner: 40,\n} as const\n\n/**\n * Check if one role has at least the privilege level of another.\n *\n * @param userRole - The user's current role\n * @param requiredRole - The minimum role required\n * @returns true if userRole >= requiredRole in the hierarchy\n */\nexport function hasRoleLevel(userRole: OrgRole, requiredRole: OrgRole): boolean {\n\treturn ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole]\n}\n\n// ============================================================================\n// Membership\n// ============================================================================\n\n/**\n * A user's membership in an organization.\n */\nexport interface Membership {\n\t/** Unique identifier for this membership record */\n\tid: string\n\t/** Organization this membership belongs to */\n\torgId: string\n\t/** User who is a member */\n\tuserId: string\n\t/** Role within the organization */\n\trole: OrgRole\n\t/** User who invited this member (null if founder) */\n\tinvitedBy: string | null\n\t/** When the user joined the organization (ms since epoch) */\n\tjoinedAt: number\n\t/** Arbitrary metadata (department, title, etc.) */\n\tmetadata: Record<string, unknown>\n}\n\n// ============================================================================\n// Invitations\n// ============================================================================\n\n/**\n * Invitation status lifecycle.\n */\nexport const INVITATION_STATUSES = ['pending', 'accepted', 'revoked', 'expired'] as const\nexport type InvitationStatus = (typeof INVITATION_STATUSES)[number]\n\n/**\n * An invitation to join an organization.\n *\n * Invitations are sent by email and include a single-use token.\n * They expire after a configurable duration (default: 7 days).\n */\nexport interface OrgInvitation {\n\t/** Unique identifier */\n\tid: string\n\t/** Organization the invitation is for */\n\torgId: string\n\t/** Email address of the invitee */\n\temail: string\n\t/** Role the invitee will receive upon accepting */\n\trole: OrgRole\n\t/** User who created the invitation */\n\tinvitedBy: string\n\t/** Cryptographically random single-use token */\n\ttoken: string\n\t/** When the invitation was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When the invitation expires (ms since epoch) */\n\texpiresAt: number\n\t/** Current status */\n\tstatus: InvitationStatus\n}\n\n/**\n * Parameters for creating an invitation.\n */\nexport interface CreateInvitationParams {\n\t/** Email address to invite */\n\temail: string\n\t/** Role to assign when the invitation is accepted */\n\trole: OrgRole\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Base error for organization-related operations.\n */\nexport class OrgError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OrgError'\n\t}\n}\n\nexport class OrgNotFoundError extends OrgError {\n\tconstructor() {\n\t\tsuper('Organization not found.', 'ORG_NOT_FOUND')\n\t}\n}\n\nexport class OrgSlugTakenError extends OrgError {\n\tconstructor() {\n\t\tsuper('An organization with this slug already exists.', 'ORG_SLUG_TAKEN')\n\t}\n}\n\nexport class MembershipNotFoundError extends OrgError {\n\tconstructor() {\n\t\tsuper('User is not a member of this organization.', 'MEMBERSHIP_NOT_FOUND')\n\t}\n}\n\nexport class MemberAlreadyExistsError extends OrgError {\n\tconstructor() {\n\t\tsuper('User is already a member of this organization.', 'MEMBER_ALREADY_EXISTS')\n\t}\n}\n\nexport class InsufficientRoleError extends OrgError {\n\tconstructor(required: OrgRole) {\n\t\tsuper(\n\t\t\t`This action requires at least the \"${required}\" role.`,\n\t\t\t'INSUFFICIENT_ROLE',\n\t\t\t{ requiredRole: required },\n\t\t)\n\t}\n}\n\nexport class CannotRemoveOwnerError extends OrgError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'The organization owner cannot be removed. Transfer ownership first.',\n\t\t\t'CANNOT_REMOVE_OWNER',\n\t\t)\n\t}\n}\n\nexport class InvitationNotFoundError extends OrgError {\n\tconstructor() {\n\t\tsuper('Invitation not found or has already been used.', 'INVITATION_NOT_FOUND')\n\t}\n}\n\nexport class InvitationExpiredError extends OrgError {\n\tconstructor() {\n\t\tsuper('This invitation has expired.', 'INVITATION_EXPIRED')\n\t}\n}\n","import type {\n\tOrganization,\n\tMembership,\n\tOrgInvitation,\n\tOrgRole,\n\tCreateOrgParams,\n\tUpdateOrgParams,\n} from './org-types'\nimport {\n\tOrgNotFoundError,\n\tOrgSlugTakenError,\n\tMembershipNotFoundError,\n\tMemberAlreadyExistsError,\n\tCannotRemoveOwnerError,\n\tInvitationNotFoundError,\n\tInvitationExpiredError,\n\tInsufficientRoleError,\n\thasRoleLevel,\n} from './org-types'\nimport type { OrgStore } from './org-store'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Response envelope returned by all org route handlers.\n * Mirrors AuthRouteResponse for consistency.\n */\nexport interface OrgRouteResponse<T> {\n\t/** HTTP status code */\n\tstatus: number\n\t/** Either the success payload or an error message */\n\tbody: { data: T } | { error: string }\n}\n\n/**\n * Configuration for the org route handlers.\n */\nexport interface OrgRoutesConfig {\n\t/** The organization store backing all org operations */\n\torgStore: OrgStore\n}\n\n/** Maximum length for org name */\nconst MAX_ORG_NAME_LENGTH = 200\n\n/** Maximum length for org slug */\nconst MAX_SLUG_LENGTH = 100\n\n/** Slug format: lowercase alphanumeric and hyphens, 2-100 chars */\nconst SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,98}[a-z0-9]$/\n\n/**\n * Simple email format validation (same logic as auth-routes).\n */\nfunction isValidEmail(email: string): boolean {\n\tif (email.length === 0 || email.length > 254) return false\n\tconst atIndex = email.indexOf('@')\n\tif (atIndex < 1) return false\n\tconst domain = email.slice(atIndex + 1)\n\tif (domain.length === 0 || !domain.includes('.')) return false\n\tif (email.indexOf('@', atIndex + 1) !== -1) return false\n\tif (email.includes(' ')) return false\n\treturn true\n}\n\n/**\n * Strip control characters from a string.\n */\nfunction sanitize(value: string): string {\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional sanitization\n\treturn value.replace(/[\\x00-\\x1f\\x7f]/g, '').trim()\n}\n\n// ============================================================================\n// OrgRoutes\n// ============================================================================\n\n/**\n * Server-side route handlers for organization management.\n *\n * These handlers enforce authorization (role checks), input validation,\n * and produce transport-agnostic response objects. Wire them to your\n * HTTP framework (Express, Hono, Fastify, etc.):\n *\n * @example\n * ```typescript\n * const orgRoutes = new OrgRoutes({ orgStore: new InMemoryOrgStore() })\n *\n * app.post('/orgs', async (req, res) => {\n * const result = await orgRoutes.createOrg(req.userId, req.body)\n * res.status(result.status).json(result.body)\n * })\n * ```\n */\nexport class OrgRoutes {\n\tprivate readonly store: OrgStore\n\n\tconstructor(config: OrgRoutesConfig) {\n\t\tthis.store = config.orgStore\n\t}\n\n\t// --- Organizations ---\n\n\t/**\n\t * Create a new organization. The authenticated user becomes the owner.\n\t */\n\tasync createOrg(\n\t\tuserId: string,\n\t\tparams: { name?: unknown; slug?: unknown; metadata?: unknown },\n\t): Promise<OrgRouteResponse<Organization>> {\n\t\t// Validate name\n\t\tif (typeof params.name !== 'string' || params.name.trim().length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Organization name is required.' } }\n\t\t}\n\t\tconst name = sanitize(params.name)\n\t\tif (name.length > MAX_ORG_NAME_LENGTH) {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { error: `Organization name must be at most ${MAX_ORG_NAME_LENGTH} characters.` },\n\t\t\t}\n\t\t}\n\n\t\t// Validate slug (optional)\n\t\tlet slug: string | undefined\n\t\tif (params.slug !== undefined) {\n\t\t\tif (typeof params.slug !== 'string') {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be a string.' } }\n\t\t\t}\n\t\t\tslug = params.slug.toLowerCase().trim()\n\t\t\tif (slug.length < 2) {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be at least 2 characters.' } }\n\t\t\t}\n\t\t\tif (slug.length > MAX_SLUG_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: { error: `Slug must be at most ${MAX_SLUG_LENGTH} characters.` },\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!SLUG_PATTERN.test(slug)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: {\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen.',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate metadata (optional)\n\t\tif (params.metadata !== undefined && (typeof params.metadata !== 'object' || params.metadata === null || Array.isArray(params.metadata))) {\n\t\t\treturn { status: 400, body: { error: 'Metadata must be a plain object.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst createParams: CreateOrgParams = {\n\t\t\t\tname,\n\t\t\t\tslug,\n\t\t\t\tmetadata: params.metadata as Record<string, unknown> | undefined,\n\t\t\t}\n\t\t\tconst org = await this.store.createOrg(userId, createParams)\n\t\t\treturn { status: 201, body: { data: org } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgSlugTakenError) {\n\t\t\t\treturn { status: 409, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Get an organization by ID. Requires membership.\n\t */\n\tasync getOrg(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<Organization>> {\n\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\tif (!membership) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\n\t\tconst org = await this.store.getOrg(orgId)\n\t\tif (!org) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\n\t\treturn { status: 200, body: { data: org } }\n\t}\n\n\t/**\n\t * Update an organization. Requires admin or higher.\n\t */\n\tasync updateOrg(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { name?: unknown; slug?: unknown; metadata?: unknown },\n\t): Promise<OrgRouteResponse<Organization>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tconst updateParams: UpdateOrgParams = {}\n\n\t\t// Validate name\n\t\tif (params.name !== undefined) {\n\t\t\tif (typeof params.name !== 'string' || params.name.trim().length === 0) {\n\t\t\t\treturn { status: 400, body: { error: 'Organization name must be a non-empty string.' } }\n\t\t\t}\n\t\t\tupdateParams.name = sanitize(params.name as string)\n\t\t\tif (updateParams.name.length > MAX_ORG_NAME_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: { error: `Organization name must be at most ${MAX_ORG_NAME_LENGTH} characters.` },\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate slug\n\t\tif (params.slug !== undefined) {\n\t\t\tif (typeof params.slug !== 'string') {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be a string.' } }\n\t\t\t}\n\t\t\tupdateParams.slug = (params.slug as string).toLowerCase().trim()\n\t\t\tif (updateParams.slug.length < 2) {\n\t\t\t\treturn { status: 400, body: { error: 'Slug must be at least 2 characters.' } }\n\t\t\t}\n\t\t\tif (updateParams.slug.length > MAX_SLUG_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: { error: `Slug must be at most ${MAX_SLUG_LENGTH} characters.` },\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!SLUG_PATTERN.test(updateParams.slug)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tbody: {\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\t'Slug must contain only lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen.',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate metadata\n\t\tif (params.metadata !== undefined) {\n\t\t\tif (typeof params.metadata !== 'object' || params.metadata === null || Array.isArray(params.metadata)) {\n\t\t\t\treturn { status: 400, body: { error: 'Metadata must be a plain object.' } }\n\t\t\t}\n\t\t\tupdateParams.metadata = params.metadata as Record<string, unknown>\n\t\t}\n\n\t\ttry {\n\t\t\tconst org = await this.store.updateOrg(orgId, updateParams)\n\t\t\treturn { status: 200, body: { data: org } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof OrgSlugTakenError) {\n\t\t\t\treturn { status: 409, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Delete an organization. Requires owner.\n\t */\n\tasync deleteOrg(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<{ deleted: true }>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'owner')\n\t\tif (authResult) return authResult\n\n\t\ttry {\n\t\t\tawait this.store.deleteOrg(orgId)\n\t\t\treturn { status: 200, body: { data: { deleted: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List all organizations the authenticated user belongs to.\n\t */\n\tasync listUserOrgs(userId: string): Promise<OrgRouteResponse<Organization[]>> {\n\t\tconst orgs = await this.store.listUserOrgs(userId)\n\t\treturn { status: 200, body: { data: orgs } }\n\t}\n\n\t// --- Members ---\n\n\t/**\n\t * Add a member to an organization. Requires admin or higher.\n\t */\n\tasync addMember(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { targetUserId?: unknown; role?: unknown },\n\t): Promise<OrgRouteResponse<Membership>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.targetUserId !== 'string' || params.targetUserId.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Target user ID is required.' } }\n\t\t}\n\t\tif (typeof params.role !== 'string' || !isValidRole(params.role)) {\n\t\t\treturn { status: 400, body: { error: 'A valid role is required (admin, member, viewer, billing).' } }\n\t\t}\n\n\t\t// Cannot assign owner role via addMember — use transferOwnership\n\t\tif (params.role === 'owner') {\n\t\t\treturn { status: 400, body: { error: 'Cannot assign owner role directly. Use ownership transfer.' } }\n\t\t}\n\n\t\t// Admins cannot add other admins (only owner can)\n\t\tconst callerMembership = await this.store.getMembership(orgId, userId)\n\t\tif (callerMembership && callerMembership.role !== 'owner' && params.role === 'admin') {\n\t\t\treturn { status: 403, body: { error: 'Only the owner can add admin members.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst membership = await this.store.addMember(orgId, params.targetUserId, params.role as OrgRole, userId)\n\t\t\treturn { status: 201, body: { data: membership } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MemberAlreadyExistsError) {\n\t\t\t\treturn { status: 409, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Remove a member from an organization. Requires admin or higher.\n\t * Members can also remove themselves (leave).\n\t */\n\tasync removeMember(\n\t\tuserId: string,\n\t\torgId: string,\n\t\ttargetUserId: string,\n\t): Promise<OrgRouteResponse<{ removed: true }>> {\n\t\t// Allow self-removal (leaving) for any member\n\t\tconst isSelfRemoval = userId === targetUserId\n\n\t\tif (!isSelfRemoval) {\n\t\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\t\tif (authResult) return authResult\n\t\t} else {\n\t\t\t// Verify caller is a member\n\t\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\t\tif (!membership) {\n\t\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.store.removeMember(orgId, targetUserId)\n\t\t\treturn { status: 200, body: { data: { removed: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof CannotRemoveOwnerError) {\n\t\t\t\treturn { status: 400, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MembershipNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Update a member's role. Requires admin or higher.\n\t */\n\tasync updateMemberRole(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { targetUserId?: unknown; role?: unknown },\n\t): Promise<OrgRouteResponse<Membership>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.targetUserId !== 'string' || params.targetUserId.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Target user ID is required.' } }\n\t\t}\n\t\tif (typeof params.role !== 'string' || !isValidRole(params.role)) {\n\t\t\treturn { status: 400, body: { error: 'A valid role is required (admin, member, viewer, billing).' } }\n\t\t}\n\t\tif (params.role === 'owner') {\n\t\t\treturn { status: 400, body: { error: 'Cannot assign owner role directly. Use ownership transfer.' } }\n\t\t}\n\n\t\t// Admins cannot promote to admin (only owner can)\n\t\tconst callerMembership = await this.store.getMembership(orgId, userId)\n\t\tif (callerMembership && callerMembership.role !== 'owner' && params.role === 'admin') {\n\t\t\treturn { status: 403, body: { error: 'Only the owner can assign admin role.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst membership = await this.store.updateMemberRole(orgId, params.targetUserId, params.role as OrgRole)\n\t\t\treturn { status: 200, body: { data: membership } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof MembershipNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List all members of an organization. Requires membership.\n\t */\n\tasync listMembers(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<Membership[]>> {\n\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\tif (!membership) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst members = await this.store.listMembers(orgId)\n\t\t\treturn { status: 200, body: { data: members } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Transfer ownership to another member. Requires owner.\n\t */\n\tasync transferOwnership(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { newOwnerId?: unknown },\n\t): Promise<OrgRouteResponse<{ transferred: true }>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'owner')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.newOwnerId !== 'string' || params.newOwnerId.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'New owner ID is required.' } }\n\t\t}\n\n\t\tif (params.newOwnerId === userId) {\n\t\t\treturn { status: 400, body: { error: 'You are already the owner.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.store.transferOwnership(orgId, params.newOwnerId)\n\t\t\treturn { status: 200, body: { data: { transferred: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MembershipNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: 'Target user is not a member of this organization.' } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t// --- Invitations ---\n\n\t/**\n\t * Create an invitation to join the organization. Requires admin or higher.\n\t */\n\tasync createInvitation(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tparams: { email?: unknown; role?: unknown },\n\t): Promise<OrgRouteResponse<OrgInvitation>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\tif (typeof params.email !== 'string' || !isValidEmail(params.email.trim())) {\n\t\t\treturn { status: 400, body: { error: 'A valid email address is required.' } }\n\t\t}\n\t\tif (typeof params.role !== 'string' || !isValidRole(params.role)) {\n\t\t\treturn { status: 400, body: { error: 'A valid role is required (admin, member, viewer, billing).' } }\n\t\t}\n\t\tif (params.role === 'owner') {\n\t\t\treturn { status: 400, body: { error: 'Cannot invite with owner role. Use ownership transfer.' } }\n\t\t}\n\n\t\t// Admins cannot invite admins\n\t\tconst callerMembership = await this.store.getMembership(orgId, userId)\n\t\tif (callerMembership && callerMembership.role !== 'owner' && params.role === 'admin') {\n\t\t\treturn { status: 403, body: { error: 'Only the owner can invite admin members.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst invitation = await this.store.createInvitation(orgId, userId, {\n\t\t\t\temail: params.email.trim(),\n\t\t\t\trole: params.role as OrgRole,\n\t\t\t})\n\t\t\treturn { status: 201, body: { data: invitation } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Accept an invitation by its token. The authenticated user joins the org.\n\t */\n\tasync acceptInvitation(\n\t\tuserId: string,\n\t\tparams: { token?: unknown },\n\t): Promise<OrgRouteResponse<Membership>> {\n\t\tif (typeof params.token !== 'string' || params.token.length === 0) {\n\t\t\treturn { status: 400, body: { error: 'Invitation token is required.' } }\n\t\t}\n\n\t\ttry {\n\t\t\tconst invitation = await this.store.consumeInvitation(params.token)\n\n\t\t\t// Add the user as a member with the invited role\n\t\t\tconst membership = await this.store.addMember(\n\t\t\t\tinvitation.orgId,\n\t\t\t\tuserId,\n\t\t\t\tinvitation.role,\n\t\t\t\tinvitation.invitedBy,\n\t\t\t)\n\t\t\treturn { status: 200, body: { data: membership } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof InvitationNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof InvitationExpiredError) {\n\t\t\t\treturn { status: 410, body: { error: err.message } }\n\t\t\t}\n\t\t\tif (err instanceof MemberAlreadyExistsError) {\n\t\t\t\treturn { status: 409, body: { error: 'You are already a member of this organization.' } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * Revoke a pending invitation. Requires admin or higher.\n\t */\n\tasync revokeInvitation(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tinvitationId: string,\n\t): Promise<OrgRouteResponse<{ revoked: true }>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\ttry {\n\t\t\tawait this.store.revokeInvitation(invitationId)\n\t\t\treturn { status: 200, body: { data: { revoked: true } } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof InvitationNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List pending invitations for an organization. Requires admin or higher.\n\t */\n\tasync listPendingInvitations(\n\t\tuserId: string,\n\t\torgId: string,\n\t): Promise<OrgRouteResponse<OrgInvitation[]>> {\n\t\tconst authResult = await this.requireRole(orgId, userId, 'admin')\n\t\tif (authResult) return authResult\n\n\t\ttry {\n\t\t\tconst invitations = await this.store.listPendingInvitations(orgId)\n\t\t\treturn { status: 200, body: { data: invitations } }\n\t\t} catch (err) {\n\t\t\tif (err instanceof OrgNotFoundError) {\n\t\t\t\treturn { status: 404, body: { error: err.message } }\n\t\t\t}\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * List pending invitations for the authenticated user's email.\n\t */\n\tasync listMyInvitations(\n\t\temail: string,\n\t): Promise<OrgRouteResponse<OrgInvitation[]>> {\n\t\tif (!isValidEmail(email)) {\n\t\t\treturn { status: 400, body: { error: 'A valid email address is required.' } }\n\t\t}\n\n\t\tconst invitations = await this.store.listInvitationsForEmail(email)\n\t\treturn { status: 200, body: { data: invitations } }\n\t}\n\n\t// --- Private helpers ---\n\n\t/**\n\t * Check if the caller has the required role in the org.\n\t * Returns an error response if not authorized, or null if authorized.\n\t */\n\tprivate async requireRole(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trequiredRole: OrgRole,\n\t): Promise<OrgRouteResponse<never> | null> {\n\t\tconst membership = await this.store.getMembership(orgId, userId)\n\t\tif (!membership) {\n\t\t\treturn { status: 404, body: { error: 'Organization not found.' } }\n\t\t}\n\t\tif (!hasRoleLevel(membership.role, requiredRole)) {\n\t\t\treturn {\n\t\t\t\tstatus: 403,\n\t\t\t\tbody: { error: `This action requires at least the \"${requiredRole}\" role.` },\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst ASSIGNABLE_ROLES = new Set<string>(['admin', 'member', 'viewer', 'billing'])\n\nfunction isValidRole(role: string): boolean {\n\treturn ASSIGNABLE_ROLES.has(role) || role === 'owner'\n}\n","import type {\n\tOrganization,\n\tCreateOrgParams,\n\tUpdateOrgParams,\n\tMembership,\n\tOrgRole,\n\tOrgInvitation,\n\tCreateInvitationParams,\n\tInvitationStatus,\n} from './org-types'\nimport {\n\tOrgNotFoundError,\n\tOrgSlugTakenError,\n\tMembershipNotFoundError,\n\tMemberAlreadyExistsError,\n\tCannotRemoveOwnerError,\n\tInvitationNotFoundError,\n\tInvitationExpiredError,\n} from './org-types'\n\n// ============================================================================\n// OrgStore interface\n// ============================================================================\n\n/**\n * Persistence interface for organizations, memberships, and invitations.\n *\n * Implement this interface to back organizations with any storage:\n * - `InMemoryOrgStore` for development and testing\n * - PostgreSQL/MySQL via Drizzle for production\n * - SQLite for self-hosted or embedded scenarios\n *\n * All methods are async to support any storage backend.\n */\nexport interface OrgStore {\n\t// --- Organizations ---\n\n\t/** Create a new organization. The caller becomes the owner. */\n\tcreateOrg(ownerId: string, params: CreateOrgParams): Promise<Organization>\n\n\t/** Get an organization by ID. Returns null if not found. */\n\tgetOrg(orgId: string): Promise<Organization | null>\n\n\t/** Get an organization by slug. Returns null if not found. */\n\tgetOrgBySlug(slug: string): Promise<Organization | null>\n\n\t/** Update an organization's mutable fields. */\n\tupdateOrg(orgId: string, params: UpdateOrgParams): Promise<Organization>\n\n\t/** Delete an organization and all its memberships and invitations. */\n\tdeleteOrg(orgId: string): Promise<void>\n\n\t/** List all organizations a user is a member of. */\n\tlistUserOrgs(userId: string): Promise<Organization[]>\n\n\t// --- Memberships ---\n\n\t/** Add a user as a member of an organization. */\n\taddMember(orgId: string, userId: string, role: OrgRole, invitedBy: string | null): Promise<Membership>\n\n\t/** Remove a user from an organization. Cannot remove the owner. */\n\tremoveMember(orgId: string, userId: string): Promise<void>\n\n\t/** Update a member's role within an organization. */\n\tupdateMemberRole(orgId: string, userId: string, role: OrgRole): Promise<Membership>\n\n\t/** List all members of an organization. */\n\tlistMembers(orgId: string): Promise<Membership[]>\n\n\t/** Get a specific user's membership in an organization. Returns null if not a member. */\n\tgetMembership(orgId: string, userId: string): Promise<Membership | null>\n\n\t/** Transfer ownership of an organization to another member. */\n\ttransferOwnership(orgId: string, newOwnerId: string): Promise<void>\n\n\t// --- Invitations ---\n\n\t/** Create an invitation to join an organization. */\n\tcreateInvitation(orgId: string, invitedBy: string, params: CreateInvitationParams): Promise<OrgInvitation>\n\n\t/** Look up an invitation by its single-use token. Returns null if not found or already consumed. */\n\tgetInvitationByToken(token: string): Promise<OrgInvitation | null>\n\n\t/** Consume an invitation (mark as accepted). Returns the invitation details. */\n\tconsumeInvitation(token: string): Promise<OrgInvitation>\n\n\t/** Revoke a pending invitation. */\n\trevokeInvitation(invitationId: string): Promise<void>\n\n\t/** List pending invitations for an organization. */\n\tlistPendingInvitations(orgId: string): Promise<OrgInvitation[]>\n\n\t/** List pending invitations for a specific email address. */\n\tlistInvitationsForEmail(email: string): Promise<OrgInvitation[]>\n\n\t/** Remove expired invitations. Returns count of removed invitations. */\n\tcleanExpiredInvitations(): Promise<number>\n}\n\n// ============================================================================\n// InMemoryOrgStore\n// ============================================================================\n\n/**\n * In-memory implementation of OrgStore for development and testing.\n *\n * Data is lost when the process exits. For production, implement OrgStore\n * with a persistent backend (PostgreSQL, MySQL, SQLite via Drizzle).\n *\n * @example\n * ```typescript\n * const orgStore = new InMemoryOrgStore()\n * const org = await orgStore.createOrg('user-1', { name: 'Acme Inc', slug: 'acme' })\n * ```\n */\nexport class InMemoryOrgStore implements OrgStore {\n\tprivate orgs = new Map<string, Organization>()\n\tprivate memberships = new Map<string, Membership>()\n\tprivate invitations = new Map<string, OrgInvitation>()\n\n\t// --- Organizations ---\n\n\tasync createOrg(ownerId: string, params: CreateOrgParams): Promise<Organization> {\n\t\tconst slug = params.slug ?? slugify(params.name)\n\n\t\t// Check slug uniqueness\n\t\tfor (const org of this.orgs.values()) {\n\t\t\tif (org.slug === slug) {\n\t\t\t\tthrow new OrgSlugTakenError()\n\t\t\t}\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst org: Organization = {\n\t\t\tid: generateId(),\n\t\t\tname: params.name,\n\t\t\tslug,\n\t\t\townerId,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t\tmetadata: params.metadata ?? {},\n\t\t}\n\n\t\tthis.orgs.set(org.id, org)\n\n\t\t// Add the creator as owner\n\t\tawait this.addMember(org.id, ownerId, 'owner', null)\n\n\t\treturn org\n\t}\n\n\tasync getOrg(orgId: string): Promise<Organization | null> {\n\t\treturn this.orgs.get(orgId) ?? null\n\t}\n\n\tasync getOrgBySlug(slug: string): Promise<Organization | null> {\n\t\tfor (const org of this.orgs.values()) {\n\t\t\tif (org.slug === slug) return org\n\t\t}\n\t\treturn null\n\t}\n\n\tasync updateOrg(orgId: string, params: UpdateOrgParams): Promise<Organization> {\n\t\tconst org = this.orgs.get(orgId)\n\t\tif (!org) throw new OrgNotFoundError()\n\n\t\tif (params.slug !== undefined && params.slug !== org.slug) {\n\t\t\t// Check slug uniqueness\n\t\t\tfor (const existing of this.orgs.values()) {\n\t\t\t\tif (existing.slug === params.slug && existing.id !== orgId) {\n\t\t\t\t\tthrow new OrgSlugTakenError()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst updated: Organization = {\n\t\t\t...org,\n\t\t\tname: params.name ?? org.name,\n\t\t\tslug: params.slug ?? org.slug,\n\t\t\tupdatedAt: Date.now(),\n\t\t\tmetadata: params.metadata\n\t\t\t\t? { ...org.metadata, ...params.metadata }\n\t\t\t\t: org.metadata,\n\t\t}\n\n\t\tthis.orgs.set(orgId, updated)\n\t\treturn updated\n\t}\n\n\tasync deleteOrg(orgId: string): Promise<void> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\t// Remove all memberships\n\t\tfor (const [id, membership] of this.memberships) {\n\t\t\tif (membership.orgId === orgId) {\n\t\t\t\tthis.memberships.delete(id)\n\t\t\t}\n\t\t}\n\n\t\t// Remove all invitations\n\t\tfor (const [id, invitation] of this.invitations) {\n\t\t\tif (invitation.orgId === orgId) {\n\t\t\t\tthis.invitations.delete(id)\n\t\t\t}\n\t\t}\n\n\t\tthis.orgs.delete(orgId)\n\t}\n\n\tasync listUserOrgs(userId: string): Promise<Organization[]> {\n\t\tconst orgIds = new Set<string>()\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.userId === userId) {\n\t\t\t\torgIds.add(membership.orgId)\n\t\t\t}\n\t\t}\n\n\t\tconst result: Organization[] = []\n\t\tfor (const orgId of orgIds) {\n\t\t\tconst org = this.orgs.get(orgId)\n\t\t\tif (org) result.push(org)\n\t\t}\n\t\treturn result\n\t}\n\n\t// --- Memberships ---\n\n\tasync addMember(\n\t\torgId: string,\n\t\tuserId: string,\n\t\trole: OrgRole,\n\t\tinvitedBy: string | null,\n\t): Promise<Membership> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\t// Check if already a member\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\tthrow new MemberAlreadyExistsError()\n\t\t\t}\n\t\t}\n\n\t\tconst membership: Membership = {\n\t\t\tid: generateId(),\n\t\t\torgId,\n\t\t\tuserId,\n\t\t\trole,\n\t\t\tinvitedBy,\n\t\t\tjoinedAt: Date.now(),\n\t\t\tmetadata: {},\n\t\t}\n\n\t\tthis.memberships.set(membership.id, membership)\n\t\treturn membership\n\t}\n\n\tasync removeMember(orgId: string, userId: string): Promise<void> {\n\t\tconst org = this.orgs.get(orgId)\n\t\tif (!org) throw new OrgNotFoundError()\n\n\t\tif (org.ownerId === userId) {\n\t\t\tthrow new CannotRemoveOwnerError()\n\t\t}\n\n\t\tlet found = false\n\t\tfor (const [id, membership] of this.memberships) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\tthis.memberships.delete(id)\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (!found) throw new MembershipNotFoundError()\n\t}\n\n\tasync updateMemberRole(orgId: string, userId: string, role: OrgRole): Promise<Membership> {\n\t\tfor (const [id, membership] of this.memberships) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\tconst updated = { ...membership, role }\n\t\t\t\tthis.memberships.set(id, updated)\n\n\t\t\t\t// If promoting to owner, update the org's ownerId and demote previous owner\n\t\t\t\tif (role === 'owner') {\n\t\t\t\t\tconst org = this.orgs.get(orgId)\n\t\t\t\t\tif (org) {\n\t\t\t\t\t\t// Demote previous owner to admin\n\t\t\t\t\t\tfor (const [mId, m] of this.memberships) {\n\t\t\t\t\t\t\tif (m.orgId === orgId && m.userId === org.ownerId && m.userId !== userId) {\n\t\t\t\t\t\t\t\tthis.memberships.set(mId, { ...m, role: 'admin' })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.orgs.set(orgId, { ...org, ownerId: userId, updatedAt: Date.now() })\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn updated\n\t\t\t}\n\t\t}\n\n\t\tthrow new MembershipNotFoundError()\n\t}\n\n\tasync listMembers(orgId: string): Promise<Membership[]> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\tconst result: Membership[] = []\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.orgId === orgId) {\n\t\t\t\tresult.push(membership)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tasync getMembership(orgId: string, userId: string): Promise<Membership | null> {\n\t\tfor (const membership of this.memberships.values()) {\n\t\t\tif (membership.orgId === orgId && membership.userId === userId) {\n\t\t\t\treturn membership\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tasync transferOwnership(orgId: string, newOwnerId: string): Promise<void> {\n\t\tconst org = this.orgs.get(orgId)\n\t\tif (!org) throw new OrgNotFoundError()\n\n\t\t// Verify new owner is a member\n\t\tconst membership = await this.getMembership(orgId, newOwnerId)\n\t\tif (!membership) throw new MembershipNotFoundError()\n\n\t\t// Demote current owner to admin\n\t\tawait this.updateMemberRole(orgId, org.ownerId, 'admin')\n\n\t\t// Promote new owner\n\t\tawait this.updateMemberRole(orgId, newOwnerId, 'owner')\n\t}\n\n\t// --- Invitations ---\n\n\tasync createInvitation(\n\t\torgId: string,\n\t\tinvitedBy: string,\n\t\tparams: CreateInvitationParams,\n\t): Promise<OrgInvitation> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\tconst now = Date.now()\n\t\tconst invitation: OrgInvitation = {\n\t\t\tid: generateId(),\n\t\t\torgId,\n\t\t\temail: params.email.toLowerCase().trim(),\n\t\t\trole: params.role,\n\t\t\tinvitedBy,\n\t\t\ttoken: generateToken(),\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + 7 * 24 * 60 * 60 * 1000, // 7 days\n\t\t\tstatus: 'pending',\n\t\t}\n\n\t\tthis.invitations.set(invitation.id, invitation)\n\t\treturn invitation\n\t}\n\n\tasync getInvitationByToken(token: string): Promise<OrgInvitation | null> {\n\t\tfor (const invitation of this.invitations.values()) {\n\t\t\tif (invitation.token === token && invitation.status === 'pending') {\n\t\t\t\treturn invitation\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tasync consumeInvitation(token: string): Promise<OrgInvitation> {\n\t\tfor (const [id, invitation] of this.invitations) {\n\t\t\tif (invitation.token === token) {\n\t\t\t\tif (invitation.status !== 'pending') {\n\t\t\t\t\tthrow new InvitationNotFoundError()\n\t\t\t\t}\n\t\t\t\tif (Date.now() > invitation.expiresAt) {\n\t\t\t\t\tthis.invitations.set(id, { ...invitation, status: 'expired' })\n\t\t\t\t\tthrow new InvitationExpiredError()\n\t\t\t\t}\n\n\t\t\t\tconst consumed = { ...invitation, status: 'accepted' as InvitationStatus }\n\t\t\t\tthis.invitations.set(id, consumed)\n\t\t\t\treturn consumed\n\t\t\t}\n\t\t}\n\n\t\tthrow new InvitationNotFoundError()\n\t}\n\n\tasync revokeInvitation(invitationId: string): Promise<void> {\n\t\tconst invitation = this.invitations.get(invitationId)\n\t\tif (!invitation || invitation.status !== 'pending') {\n\t\t\tthrow new InvitationNotFoundError()\n\t\t}\n\t\tthis.invitations.set(invitationId, { ...invitation, status: 'revoked' })\n\t}\n\n\tasync listPendingInvitations(orgId: string): Promise<OrgInvitation[]> {\n\t\tif (!this.orgs.has(orgId)) throw new OrgNotFoundError()\n\n\t\tconst result: OrgInvitation[] = []\n\t\tconst now = Date.now()\n\t\tfor (const invitation of this.invitations.values()) {\n\t\t\tif (invitation.orgId === orgId && invitation.status === 'pending') {\n\t\t\t\tif (now > invitation.expiresAt) {\n\t\t\t\t\t// Auto-expire\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tresult.push(invitation)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tasync listInvitationsForEmail(email: string): Promise<OrgInvitation[]> {\n\t\tconst normalizedEmail = email.toLowerCase().trim()\n\t\tconst result: OrgInvitation[] = []\n\t\tconst now = Date.now()\n\t\tfor (const invitation of this.invitations.values()) {\n\t\t\tif (\n\t\t\t\tinvitation.email === normalizedEmail &&\n\t\t\t\tinvitation.status === 'pending' &&\n\t\t\t\tnow <= invitation.expiresAt\n\t\t\t) {\n\t\t\t\tresult.push(invitation)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tasync cleanExpiredInvitations(): Promise<number> {\n\t\tlet count = 0\n\t\tconst now = Date.now()\n\t\tfor (const [id, invitation] of this.invitations) {\n\t\t\tif (invitation.status === 'pending' && now > invitation.expiresAt) {\n\t\t\t\tthis.invitations.set(id, { ...invitation, status: 'expired' })\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\nfunction generateId(): string {\n\treturn globalThis.crypto.randomUUID()\n}\n\nfunction generateToken(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Convert a name to a URL-friendly slug.\n * Lowercase, replace spaces/special chars with hyphens, collapse multiple hyphens.\n */\nfunction slugify(name: string): string {\n\treturn name\n\t\t.toLowerCase()\n\t\t.trim()\n\t\t.replace(/[^a-z0-9\\s-]/g, '')\n\t\t.replace(/[\\s]+/g, '-')\n\t\t.replace(/-+/g, '-')\n\t\t.replace(/^-|-$/g, '')\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Permissions\n// ============================================================================\n\n/**\n * A permission is a `resource:action` string.\n *\n * Resources are typically collection names or system resources.\n * Actions are the operations allowed on that resource.\n *\n * @example\n * ```typescript\n * const permission: Permission = 'todos:write'\n * const adminPerm: Permission = 'org:manage-members'\n * const wildcard: Permission = 'todos:*' // all actions on todos\n * const superAdmin: Permission = '*:*' // all actions on all resources\n * ```\n */\nexport type Permission = `${string}:${string}`\n\n/**\n * Parse a permission string into its resource and action parts.\n */\nexport function parsePermission(permission: Permission): { resource: string; action: string } {\n\tconst colonIndex = permission.indexOf(':')\n\tif (colonIndex === -1) {\n\t\tthrow new InvalidPermissionError(permission)\n\t}\n\tconst resource = permission.slice(0, colonIndex)\n\tconst action = permission.slice(colonIndex + 1)\n\tif (resource.length === 0 || action.length === 0) {\n\t\tthrow new InvalidPermissionError(permission)\n\t}\n\treturn { resource, action }\n}\n\n/**\n * Check if a granted permission covers a required permission.\n * Supports wildcards: `todos:*` covers `todos:read`, `*:*` covers everything.\n */\nexport function permissionCovers(granted: Permission, required: Permission): boolean {\n\tconst g = parsePermission(granted)\n\tconst r = parsePermission(required)\n\n\tconst resourceMatch = g.resource === '*' || g.resource === r.resource\n\tconst actionMatch = g.action === '*' || g.action === r.action\n\treturn resourceMatch && actionMatch\n}\n\n// ============================================================================\n// Role Definitions\n// ============================================================================\n\n/**\n * A role definition describes a named set of permissions with optional inheritance.\n *\n * Roles form a hierarchy via `inherits`. When evaluating permissions,\n * all inherited roles' permissions are included transitively.\n *\n * @example\n * ```typescript\n * const roles: RoleDefinition[] = [\n * { name: 'viewer', permissions: ['todos:read', 'projects:read'] },\n * { name: 'member', permissions: ['todos:write', 'projects:write'], inherits: ['viewer'] },\n * { name: 'admin', permissions: ['org:manage-members'], inherits: ['member'] },\n * ]\n * ```\n */\nexport interface RoleDefinition {\n\t/** Unique name for this role */\n\tname: string\n\t/** Permissions directly granted to this role */\n\tpermissions: Permission[]\n\t/** Roles this role inherits from (all their permissions are included) */\n\tinherits?: string[]\n}\n\n// ============================================================================\n// Built-in Roles\n// ============================================================================\n\n/**\n * Default built-in roles for organizations.\n *\n * These can be overridden or extended using `defineRoles()`.\n */\nexport const BUILT_IN_ROLES: readonly RoleDefinition[] = [\n\t{\n\t\tname: 'viewer',\n\t\tpermissions: ['*:read'],\n\t},\n\t{\n\t\tname: 'billing',\n\t\tpermissions: ['org:billing'],\n\t},\n\t{\n\t\tname: 'member',\n\t\tpermissions: ['*:write', '*:delete'],\n\t\tinherits: ['viewer'],\n\t},\n\t{\n\t\tname: 'admin',\n\t\tpermissions: ['org:manage-members', 'org:manage-settings', 'org:manage-invitations'],\n\t\tinherits: ['member'],\n\t},\n\t{\n\t\tname: 'owner',\n\t\tpermissions: ['*:*'],\n\t},\n] as const\n\n// ============================================================================\n// RBAC Configuration\n// ============================================================================\n\n/**\n * Configuration for the RBAC engine.\n */\nexport interface RbacConfig {\n\t/** Role definitions. Defaults to BUILT_IN_ROLES if not provided. */\n\troles?: RoleDefinition[]\n}\n\n// ============================================================================\n// Scope Types\n// ============================================================================\n\n/**\n * A sync scope filter for a single collection.\n * The keys are field names and values are the required values.\n * A special `__readonly` key can be set to restrict to read-only access.\n *\n * @example\n * ```typescript\n * // Only sync todos belonging to the user within the org\n * const scope: ScopeFilter = { orgId: 'org-123', userId: 'user-456' }\n *\n * // Read-only scope for viewers\n * const readOnlyScope: ScopeFilter = { orgId: 'org-123', __readonly: true }\n * ```\n */\nexport interface ScopeFilter {\n\t[field: string]: unknown\n}\n\n/**\n * A complete set of sync scopes, keyed by collection name.\n *\n * @example\n * ```typescript\n * const scopes: SyncScopes = {\n * todos: { orgId: 'org-123' },\n * projects: { orgId: 'org-123' },\n * }\n * ```\n */\nexport type SyncScopes = Record<string, ScopeFilter>\n\n/**\n * Custom scope resolver for a specific collection.\n * Given the user context, returns the scope filter for that collection.\n */\nexport type CollectionScopeResolver = (ctx: ScopeContext) => ScopeFilter | null\n\n/**\n * Context passed to scope resolvers.\n */\nexport interface ScopeContext {\n\tuserId: string\n\torgId: string\n\trole: string\n\tpermissions: Permission[]\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class RbacError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'RbacError'\n\t}\n}\n\nexport class InvalidPermissionError extends RbacError {\n\tconstructor(permission: string) {\n\t\tsuper(\n\t\t\t`Invalid permission format: \"${permission}\". Expected \"resource:action\".`,\n\t\t\t'INVALID_PERMISSION',\n\t\t\t{ permission },\n\t\t)\n\t}\n}\n\nexport class RoleNotFoundError extends RbacError {\n\tconstructor(role: string) {\n\t\tsuper(\n\t\t\t`Role \"${role}\" is not defined.`,\n\t\t\t'ROLE_NOT_FOUND',\n\t\t\t{ role },\n\t\t)\n\t}\n}\n\nexport class CircularInheritanceError extends RbacError {\n\tconstructor(chain: string[]) {\n\t\tsuper(\n\t\t\t`Circular role inheritance detected: ${chain.join(' → ')}.`,\n\t\t\t'CIRCULAR_INHERITANCE',\n\t\t\t{ chain },\n\t\t)\n\t}\n}\n","import type { OrgStore } from '../org/org-store'\nimport type {\n\tPermission,\n\tRoleDefinition,\n\tRbacConfig,\n\tSyncScopes,\n\tScopeFilter,\n\tScopeContext,\n\tCollectionScopeResolver,\n} from './rbac-types'\nimport {\n\tBUILT_IN_ROLES,\n\tpermissionCovers,\n\tRoleNotFoundError,\n\tCircularInheritanceError,\n} from './rbac-types'\n\n// ============================================================================\n// RbacEngine\n// ============================================================================\n\n/**\n * Permission evaluation engine for role-based access control.\n *\n * The engine resolves permissions through role inheritance, supports\n * wildcard matching, and integrates with the OrgStore for membership lookups.\n *\n * @example\n * ```typescript\n * const rbac = new RbacEngine({ orgStore })\n *\n * // Check a permission\n * const canWrite = await rbac.hasPermission('user-1', 'org-1', 'todos:write')\n *\n * // Get all permissions for a user in an org\n * const perms = await rbac.getUserPermissions('user-1', 'org-1')\n *\n * // Resolve sync scopes\n * const scopes = await rbac.resolveScopes('user-1', 'org-1')\n * ```\n */\nexport class RbacEngine {\n\tprivate readonly orgStore: OrgStore\n\tprivate readonly roleMap: Map<string, RoleDefinition>\n\tprivate readonly resolvedPermissions = new Map<string, Permission[]>()\n\tprivate readonly collectionResolvers = new Map<string, CollectionScopeResolver>()\n\n\tconstructor(orgStore: OrgStore, config?: RbacConfig) {\n\t\tthis.orgStore = orgStore\n\n\t\tconst roles = config?.roles ?? [...BUILT_IN_ROLES]\n\t\tthis.roleMap = new Map()\n\t\tfor (const role of roles) {\n\t\t\tthis.roleMap.set(role.name, role)\n\t\t}\n\n\t\t// Validate and pre-resolve all role permissions\n\t\tthis.validateRoles()\n\t}\n\n\t// --- Permission Checks ---\n\n\t/**\n\t * Check if a user has a specific permission in an organization.\n\t *\n\t * Resolves the user's role from the org membership, then evaluates\n\t * the role's permissions (including inherited ones) against the required permission.\n\t *\n\t * Returns false (not an error) if the user is not a member.\n\t */\n\tasync hasPermission(userId: string, orgId: string, permission: Permission): Promise<boolean> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return false\n\n\t\tconst rolePerms = this.getRolePermissions(membership.role)\n\t\treturn rolePerms.some((granted) => permissionCovers(granted, permission))\n\t}\n\n\t/**\n\t * Get all effective permissions for a user in an organization.\n\t *\n\t * Returns an empty array if the user is not a member.\n\t */\n\tasync getUserPermissions(userId: string, orgId: string): Promise<Permission[]> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return []\n\n\t\treturn this.getRolePermissions(membership.role)\n\t}\n\n\t/**\n\t * Get all effective permissions for a role name.\n\t * Includes permissions from inherited roles.\n\t *\n\t * @throws {RoleNotFoundError} if the role is not defined\n\t */\n\tgetRolePermissions(roleName: string): Permission[] {\n\t\tconst cached = this.resolvedPermissions.get(roleName)\n\t\tif (cached) return cached\n\n\t\tif (!this.roleMap.has(roleName)) {\n\t\t\tthrow new RoleNotFoundError(roleName)\n\t\t}\n\n\t\tconst perms = this.resolvePermissionsForRole(roleName, new Set())\n\t\tthis.resolvedPermissions.set(roleName, perms)\n\t\treturn perms\n\t}\n\n\t/**\n\t * Check if a role has a specific permission.\n\t */\n\troleHasPermission(roleName: string, permission: Permission): boolean {\n\t\tconst perms = this.getRolePermissions(roleName)\n\t\treturn perms.some((granted) => permissionCovers(granted, permission))\n\t}\n\n\t// --- Scope Resolution ---\n\n\t/**\n\t * Register a custom scope resolver for a collection.\n\t *\n\t * Custom resolvers override the default scope logic for that collection.\n\t */\n\tregisterScopeResolver(collection: string, resolver: CollectionScopeResolver): void {\n\t\tthis.collectionResolvers.set(collection, resolver)\n\t}\n\n\t/**\n\t * Resolve sync scopes for a user in an organization.\n\t *\n\t * The scopes determine what data the user can see and modify during sync.\n\t * - Owner/Admin: all org data\n\t * - Member: all org data (read + write)\n\t * - Viewer: all org data (read-only)\n\t * - Billing: no data scopes (billing-only access)\n\t *\n\t * Custom collection resolvers can override the defaults.\n\t *\n\t * @param collections - List of collection names to resolve scopes for.\n\t * If not provided, only custom-registered collections are included.\n\t */\n\tasync resolveScopes(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollections?: string[],\n\t): Promise<SyncScopes | null> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return null\n\n\t\tconst permissions = this.getRolePermissions(membership.role)\n\t\tconst ctx: ScopeContext = {\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\trole: membership.role,\n\t\t\tpermissions,\n\t\t}\n\n\t\tconst scopes: SyncScopes = {}\n\n\t\t// Check if user has any data permissions at all\n\t\tconst hasAnyRead = permissions.some(\n\t\t\t(p) => permissionCovers(p, '*:read' as Permission),\n\t\t)\n\t\tif (!hasAnyRead) {\n\t\t\t// No data access (e.g., billing-only role)\n\t\t\treturn scopes\n\t\t}\n\n\t\tconst isReadOnly = !permissions.some(\n\t\t\t(p) => permissionCovers(p, '*:write' as Permission),\n\t\t)\n\n\t\tconst collectionsToResolve = collections ?? [...this.collectionResolvers.keys()]\n\n\t\tfor (const collection of collectionsToResolve) {\n\t\t\t// Check custom resolver first\n\t\t\tconst customResolver = this.collectionResolvers.get(collection)\n\t\t\tif (customResolver) {\n\t\t\t\tconst customScope = customResolver(ctx)\n\t\t\t\tif (customScope) {\n\t\t\t\t\tscopes[collection] = customScope\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Default scope: filter by orgId\n\t\t\tconst scope: ScopeFilter = { orgId }\n\t\t\tif (isReadOnly) {\n\t\t\t\tscope.__readonly = true\n\t\t\t}\n\t\t\tscopes[collection] = scope\n\t\t}\n\n\t\treturn scopes\n\t}\n\n\t// --- Role Management ---\n\n\t/**\n\t * Get all defined role names.\n\t */\n\tgetRoleNames(): string[] {\n\t\treturn [...this.roleMap.keys()]\n\t}\n\n\t/**\n\t * Get a role definition by name.\n\t */\n\tgetRoleDefinition(roleName: string): RoleDefinition | null {\n\t\treturn this.roleMap.get(roleName) ?? null\n\t}\n\n\t// --- Private ---\n\n\t/**\n\t * Validate all role definitions for circular inheritance and unknown references.\n\t */\n\tprivate validateRoles(): void {\n\t\tfor (const role of this.roleMap.values()) {\n\t\t\tif (role.inherits) {\n\t\t\t\tfor (const parent of role.inherits) {\n\t\t\t\t\tif (!this.roleMap.has(parent)) {\n\t\t\t\t\t\tthrow new RoleNotFoundError(parent)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check for circular inheritance\n\t\tfor (const role of this.roleMap.values()) {\n\t\t\tthis.detectCircularInheritance(role.name, new Set())\n\t\t}\n\t}\n\n\t/**\n\t * Detect circular inheritance in role hierarchy.\n\t */\n\tprivate detectCircularInheritance(roleName: string, visited: Set<string>): void {\n\t\tif (visited.has(roleName)) {\n\t\t\tthrow new CircularInheritanceError([...visited, roleName])\n\t\t}\n\t\tvisited.add(roleName)\n\n\t\tconst role = this.roleMap.get(roleName)\n\t\tif (role?.inherits) {\n\t\t\tfor (const parent of role.inherits) {\n\t\t\t\tthis.detectCircularInheritance(parent, new Set(visited))\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Recursively resolve all permissions for a role, including inherited permissions.\n\t */\n\tprivate resolvePermissionsForRole(roleName: string, visited: Set<string>): Permission[] {\n\t\tif (visited.has(roleName)) return []\n\t\tvisited.add(roleName)\n\n\t\tconst role = this.roleMap.get(roleName)\n\t\tif (!role) return []\n\n\t\tconst perms = new Set<Permission>(role.permissions)\n\n\t\tif (role.inherits) {\n\t\t\tfor (const parent of role.inherits) {\n\t\t\t\tconst parentPerms = this.resolvePermissionsForRole(parent, visited)\n\t\t\t\tfor (const p of parentPerms) {\n\t\t\t\t\tperms.add(p)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...perms]\n\t}\n}\n\n// ============================================================================\n// defineRoles builder\n// ============================================================================\n\n/**\n * Builder for defining custom roles.\n *\n * @example\n * ```typescript\n * const roles = defineRoles()\n * .role('viewer', ['*:read'])\n * .role('editor', ['*:write'], { inherits: ['viewer'] })\n * .role('admin', ['org:manage-members'], { inherits: ['editor'] })\n * .build()\n * ```\n */\nexport function defineRoles(): RoleBuilder {\n\treturn new RoleBuilder()\n}\n\nclass RoleBuilder {\n\tprivate roles: RoleDefinition[] = []\n\n\t/**\n\t * Add a role definition.\n\t */\n\trole(\n\t\tname: string,\n\t\tpermissions: Permission[],\n\t\toptions?: { inherits?: string[] },\n\t): RoleBuilder {\n\t\tthis.roles.push({\n\t\t\tname,\n\t\t\tpermissions,\n\t\t\tinherits: options?.inherits,\n\t\t})\n\t\treturn this\n\t}\n\n\t/**\n\t * Include the built-in roles as a base.\n\t */\n\twithBuiltInRoles(): RoleBuilder {\n\t\tthis.roles = [...BUILT_IN_ROLES, ...this.roles]\n\t\treturn this\n\t}\n\n\t/**\n\t * Build and return the role definitions array.\n\t */\n\tbuild(): RoleDefinition[] {\n\t\treturn [...this.roles]\n\t}\n}\n","import type { OrgStore } from '../org/org-store'\nimport type { OrgRole } from '../org/org-types'\nimport type {\n\tPermission,\n\tSyncScopes,\n\tScopeFilter,\n\tScopeContext,\n\tCollectionScopeResolver,\n} from './rbac-types'\nimport { permissionCovers } from './rbac-types'\nimport { RbacEngine } from './rbac-engine'\n\n// ============================================================================\n// OrgScopeResolver\n// ============================================================================\n\n/**\n * Resolves sync scopes for org-aware data filtering.\n *\n * Given a user, an organization, and a set of collections, this resolver\n * determines what data the user should receive during sync:\n *\n * - **Owner/Admin**: All data in the org (no field-level filtering)\n * - **Member**: All org data with read/write access\n * - **Viewer**: All org data with read-only flag\n * - **Billing**: No data access (empty scopes)\n *\n * Developers can register per-collection scope resolvers for fine-grained control.\n *\n * @example\n * ```typescript\n * const resolver = new OrgScopeResolver(orgStore, rbacEngine)\n *\n * // Custom scope: members only see their own todos\n * resolver.registerCollectionScope('todos', (ctx) => {\n * if (ctx.role === 'member') {\n * return { orgId: ctx.orgId, userId: ctx.userId }\n * }\n * return { orgId: ctx.orgId } // admins/owners see all\n * })\n *\n * const scopes = await resolver.resolve('user-1', 'org-1', ['todos', 'projects'])\n * // { todos: { orgId: 'org-1', userId: 'user-1' }, projects: { orgId: 'org-1' } }\n * ```\n */\nexport class OrgScopeResolver {\n\tprivate readonly orgStore: OrgStore\n\tprivate readonly rbac: RbacEngine\n\tprivate readonly collectionScopes = new Map<string, CollectionScopeResolver>()\n\n\tconstructor(orgStore: OrgStore, rbac: RbacEngine) {\n\t\tthis.orgStore = orgStore\n\t\tthis.rbac = rbac\n\t}\n\n\t/**\n\t * Register a custom scope resolver for a collection.\n\t * This overrides the default orgId-based filtering for that collection.\n\t */\n\tregisterCollectionScope(collection: string, resolver: CollectionScopeResolver): void {\n\t\tthis.collectionScopes.set(collection, resolver)\n\t}\n\n\t/**\n\t * Resolve sync scopes for all specified collections.\n\t *\n\t * Returns null if the user is not a member of the organization.\n\t * Returns an empty object if the user has no data access (e.g., billing role).\n\t */\n\tasync resolve(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollections: string[],\n\t): Promise<SyncScopes | null> {\n\t\tconst membership = await this.orgStore.getMembership(orgId, userId)\n\t\tif (!membership) return null\n\n\t\tconst permissions = this.rbac.getRolePermissions(membership.role)\n\t\tconst ctx: ScopeContext = {\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\trole: membership.role,\n\t\t\tpermissions,\n\t\t}\n\n\t\tconst scopes: SyncScopes = {}\n\n\t\tfor (const collection of collections) {\n\t\t\tconst scope = this.resolveCollectionScope(ctx, collection)\n\t\t\tif (scope) {\n\t\t\t\tscopes[collection] = scope\n\t\t\t}\n\t\t}\n\n\t\treturn scopes\n\t}\n\n\t/**\n\t * Check if a user can write to a specific collection in an org.\n\t */\n\tasync canWrite(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollection: string,\n\t): Promise<boolean> {\n\t\treturn this.rbac.hasPermission(\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\t`${collection}:write` as Permission,\n\t\t)\n\t}\n\n\t/**\n\t * Check if a user can read from a specific collection in an org.\n\t */\n\tasync canRead(\n\t\tuserId: string,\n\t\torgId: string,\n\t\tcollection: string,\n\t): Promise<boolean> {\n\t\treturn this.rbac.hasPermission(\n\t\t\tuserId,\n\t\t\torgId,\n\t\t\t`${collection}:read` as Permission,\n\t\t)\n\t}\n\n\t// --- Private ---\n\n\tprivate resolveCollectionScope(ctx: ScopeContext, collection: string): ScopeFilter | null {\n\t\t// Check if user has read permission for this collection\n\t\tconst canRead = ctx.permissions.some(\n\t\t\t(p) =>\n\t\t\t\tpermissionCovers(p, `${collection}:read` as Permission) ||\n\t\t\t\tpermissionCovers(p, '*:read' as Permission),\n\t\t)\n\t\tif (!canRead) return null\n\n\t\t// Check custom resolver\n\t\tconst customResolver = this.collectionScopes.get(collection)\n\t\tif (customResolver) {\n\t\t\treturn customResolver(ctx)\n\t\t}\n\n\t\t// Default scope: filter by orgId\n\t\tconst scope: ScopeFilter = { orgId: ctx.orgId }\n\n\t\t// Check if write access is available\n\t\tconst canWrite = ctx.permissions.some(\n\t\t\t(p) =>\n\t\t\t\tpermissionCovers(p, `${collection}:write` as Permission) ||\n\t\t\t\tpermissionCovers(p, '*:write' as Permission),\n\t\t)\n\t\tif (!canWrite) {\n\t\t\tscope.__readonly = true\n\t\t}\n\n\t\treturn scope\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// OAuth Provider Configuration\n// ============================================================================\n\n/**\n * Configuration for an OAuth 2.0 provider.\n */\nexport interface OAuthProviderConfig {\n\t/** Provider identifier (e.g., 'google', 'github', 'microsoft') */\n\tproviderId: string\n\t/** OAuth client ID */\n\tclientId: string\n\t/** OAuth client secret */\n\tclientSecret: string\n\t/** Authorization endpoint URL */\n\tauthorizationUrl: string\n\t/** Token exchange endpoint URL */\n\ttokenUrl: string\n\t/** User info endpoint URL */\n\tuserInfoUrl: string\n\t/** OAuth scopes to request */\n\tscopes: string[]\n\t/** Redirect URI for the callback */\n\tredirectUri: string\n}\n\n// ============================================================================\n// OAuth Tokens\n// ============================================================================\n\n/**\n * Tokens returned by the OAuth provider after code exchange.\n */\nexport interface OAuthTokens {\n\t/** OAuth access token */\n\taccessToken: string\n\t/** Token type (usually 'Bearer') */\n\ttokenType: string\n\t/** Access token expiry in seconds (if provided) */\n\texpiresIn?: number\n\t/** Refresh token (if provided) */\n\trefreshToken?: string\n\t/** ID token (if provided, e.g., OpenID Connect) */\n\tidToken?: string\n\t/** Granted scopes (may differ from requested scopes) */\n\tscope?: string\n}\n\n// ============================================================================\n// OAuth User Info\n// ============================================================================\n\n/**\n * User information from the OAuth provider.\n */\nexport interface OAuthUserInfo {\n\t/** Provider-specific user ID */\n\tproviderId: string\n\t/** Provider name (e.g., 'google', 'github') */\n\tprovider: string\n\t/** User's email address (may be null if not granted) */\n\temail: string | null\n\t/** Whether the email is verified by the provider */\n\temailVerified: boolean\n\t/** User's display name */\n\tname: string | null\n\t/** URL to the user's avatar/profile picture */\n\tavatarUrl: string | null\n\t/** Raw profile data from the provider */\n\trawProfile: Record<string, unknown>\n}\n\n// ============================================================================\n// OAuth State\n// ============================================================================\n\n/**\n * State stored during the OAuth flow for CSRF protection.\n */\nexport interface OAuthState {\n\t/** Random state parameter for CSRF protection */\n\tstate: string\n\t/** Provider ID */\n\tprovider: string\n\t/** Redirect URI used for this flow */\n\tredirectUri: string\n\t/** When this state was created (ms since epoch) */\n\tcreatedAt: number\n\t/** When this state expires (ms since epoch) */\n\texpiresAt: number\n\t/** Optional: user-defined data to pass through the flow */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Store for OAuth state parameters.\n */\nexport interface OAuthStateStore {\n\t/** Store a state parameter for later validation. */\n\tstore(state: OAuthState): Promise<void>\n\t/** Consume a state parameter (single-use). Returns null if not found or expired. */\n\tconsume(stateValue: string): Promise<OAuthState | null>\n\t/** Clean up expired states. */\n\tcleanExpired(): Promise<number>\n}\n\n// ============================================================================\n// Linked Identity\n// ============================================================================\n\n/**\n * A linked OAuth identity for a user.\n * Users can have multiple linked identities (e.g., Google + GitHub).\n */\nexport interface LinkedIdentity {\n\t/** Unique ID of this link */\n\tid: string\n\t/** Kora user ID */\n\tuserId: string\n\t/** OAuth provider name */\n\tprovider: string\n\t/** Provider-specific user ID */\n\tproviderUserId: string\n\t/** Provider email (at time of linking) */\n\temail: string | null\n\t/** When this identity was linked */\n\tlinkedAt: number\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class OAuthError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'OAuthError'\n\t}\n}\n\nexport class OAuthStateMismatchError extends OAuthError {\n\tconstructor() {\n\t\tsuper('OAuth state parameter does not match. Possible CSRF attack.', 'OAUTH_STATE_MISMATCH')\n\t}\n}\n\nexport class OAuthCodeExchangeError extends OAuthError {\n\tconstructor(details?: string) {\n\t\tsuper(\n\t\t\t`Failed to exchange authorization code for tokens.${details ? ` ${details}` : ''}`,\n\t\t\t'OAUTH_CODE_EXCHANGE_FAILED',\n\t\t\tdetails ? { details } : undefined,\n\t\t)\n\t}\n}\n\nexport class OAuthUserInfoError extends OAuthError {\n\tconstructor(details?: string) {\n\t\tsuper(\n\t\t\t`Failed to fetch user info from OAuth provider.${details ? ` ${details}` : ''}`,\n\t\t\t'OAUTH_USER_INFO_FAILED',\n\t\t\tdetails ? { details } : undefined,\n\t\t)\n\t}\n}\n\nexport class OAuthProviderNotFoundError extends OAuthError {\n\tconstructor(provider: string) {\n\t\tsuper(`OAuth provider \"${provider}\" is not configured.`, 'OAUTH_PROVIDER_NOT_FOUND', { provider })\n\t}\n}\n","import type {\n\tOAuthProviderConfig,\n\tOAuthTokens,\n\tOAuthUserInfo,\n\tOAuthState,\n\tOAuthStateStore,\n} from './oauth-types'\nimport {\n\tOAuthStateMismatchError,\n\tOAuthCodeExchangeError,\n\tOAuthUserInfoError,\n\tOAuthProviderNotFoundError,\n} from './oauth-types'\n\n// ============================================================================\n// InMemoryOAuthStateStore\n// ============================================================================\n\n/** Default state TTL: 10 minutes */\nconst DEFAULT_STATE_TTL_MS = 10 * 60 * 1000\n\n/**\n * In-memory OAuth state store for development.\n * Use Redis or a database in production for multi-server deployments.\n */\nexport class InMemoryOAuthStateStore implements OAuthStateStore {\n\tprivate readonly states = new Map<string, OAuthState>()\n\n\tasync store(state: OAuthState): Promise<void> {\n\t\tthis.states.set(state.state, state)\n\t}\n\n\tasync consume(stateValue: string): Promise<OAuthState | null> {\n\t\tconst state = this.states.get(stateValue)\n\t\tif (!state) return null\n\n\t\t// Single-use\n\t\tthis.states.delete(stateValue)\n\n\t\t// Check expiry\n\t\tif (Date.now() > state.expiresAt) return null\n\n\t\treturn state\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [key, state] of this.states) {\n\t\t\tif (now > state.expiresAt) {\n\t\t\t\tthis.states.delete(key)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// OAuthManager\n// ============================================================================\n\n/**\n * Configuration for the OAuth manager.\n */\nexport interface OAuthManagerConfig {\n\t/** Registered OAuth providers */\n\tproviders: OAuthProviderConfig[]\n\t/** State store. Defaults to InMemoryOAuthStateStore. */\n\tstateStore?: OAuthStateStore\n\t/** State TTL in milliseconds. Defaults to 10 minutes. */\n\tstateTtlMs?: number\n\t/**\n\t * Custom fetch function. Defaults to global fetch.\n\t * Useful for testing or custom HTTP clients.\n\t */\n\tfetch?: typeof globalThis.fetch\n}\n\n/**\n * Manages the OAuth 2.0 authorization code flow.\n *\n * Supports any standard OAuth 2.0 / OpenID Connect provider.\n * Pre-built configurations available for Google, GitHub, and Microsoft.\n *\n * @example\n * ```typescript\n * const oauth = new OAuthManager({\n * providers: [\n * googleProvider({ clientId: '...', clientSecret: '...', redirectUri: '...' }),\n * githubProvider({ clientId: '...', clientSecret: '...', redirectUri: '...' }),\n * ],\n * })\n *\n * // Step 1: Generate authorization URL\n * const { url, state } = await oauth.getAuthorizationUrl('google')\n * // Redirect user to url...\n *\n * // Step 2: Handle callback\n * const { tokens, userInfo } = await oauth.handleCallback('google', code, stateParam)\n * ```\n */\nexport class OAuthManager {\n\tprivate readonly providers = new Map<string, OAuthProviderConfig>()\n\tprivate readonly stateStore: OAuthStateStore\n\tprivate readonly stateTtlMs: number\n\tprivate readonly fetchFn: typeof globalThis.fetch\n\n\tconstructor(config: OAuthManagerConfig) {\n\t\tfor (const provider of config.providers) {\n\t\t\tthis.providers.set(provider.providerId, provider)\n\t\t}\n\t\tthis.stateStore = config.stateStore ?? new InMemoryOAuthStateStore()\n\t\tthis.stateTtlMs = config.stateTtlMs ?? DEFAULT_STATE_TTL_MS\n\t\tthis.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis)\n\t}\n\n\t/**\n\t * Generate an authorization URL for the user to visit.\n\t * Returns the URL and the state parameter for CSRF validation.\n\t */\n\tasync getAuthorizationUrl(\n\t\tproviderId: string,\n\t\tmetadata?: Record<string, unknown>,\n\t): Promise<{ url: string; state: string }> {\n\t\tconst provider = this.getProvider(providerId)\n\n\t\tconst state = generateState()\n\t\tconst now = Date.now()\n\n\t\tconst oauthState: OAuthState = {\n\t\t\tstate,\n\t\t\tprovider: providerId,\n\t\t\tredirectUri: provider.redirectUri,\n\t\t\tcreatedAt: now,\n\t\t\texpiresAt: now + this.stateTtlMs,\n\t\t\tmetadata,\n\t\t}\n\n\t\tawait this.stateStore.store(oauthState)\n\n\t\tconst params = new URLSearchParams({\n\t\t\tclient_id: provider.clientId,\n\t\t\tredirect_uri: provider.redirectUri,\n\t\t\tresponse_type: 'code',\n\t\t\tscope: provider.scopes.join(' '),\n\t\t\tstate,\n\t\t})\n\n\t\tconst url = `${provider.authorizationUrl}?${params.toString()}`\n\t\treturn { url, state }\n\t}\n\n\t/**\n\t * Handle the OAuth callback after the user authorizes.\n\t * Validates the state parameter, exchanges the code for tokens,\n\t * and fetches user info.\n\t *\n\t * @param providerId - The OAuth provider\n\t * @param code - The authorization code from the callback\n\t * @param state - The state parameter from the callback\n\t * @returns Tokens and user info from the provider\n\t */\n\tasync handleCallback(\n\t\tproviderId: string,\n\t\tcode: string,\n\t\tstate: string,\n\t): Promise<{ tokens: OAuthTokens; userInfo: OAuthUserInfo; stateMetadata?: Record<string, unknown> }> {\n\t\tconst provider = this.getProvider(providerId)\n\n\t\t// Validate state (CSRF protection)\n\t\tconst oauthState = await this.stateStore.consume(state)\n\t\tif (!oauthState || oauthState.provider !== providerId) {\n\t\t\tthrow new OAuthStateMismatchError()\n\t\t}\n\n\t\t// Exchange code for tokens\n\t\tconst tokens = await this.exchangeCodeForTokens(provider, code)\n\n\t\t// Fetch user info\n\t\tconst userInfo = await this.fetchUserInfo(provider, tokens.accessToken)\n\n\t\treturn { tokens, userInfo, stateMetadata: oauthState.metadata }\n\t}\n\n\t/**\n\t * Get a registered provider by ID.\n\t */\n\tgetProvider(providerId: string): OAuthProviderConfig {\n\t\tconst provider = this.providers.get(providerId)\n\t\tif (!provider) {\n\t\t\tthrow new OAuthProviderNotFoundError(providerId)\n\t\t}\n\t\treturn provider\n\t}\n\n\t/**\n\t * List all registered provider IDs.\n\t */\n\tgetProviderIds(): string[] {\n\t\treturn [...this.providers.keys()]\n\t}\n\n\t// --- Private ---\n\n\tprivate async exchangeCodeForTokens(\n\t\tprovider: OAuthProviderConfig,\n\t\tcode: string,\n\t): Promise<OAuthTokens> {\n\t\tconst body = new URLSearchParams({\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tredirect_uri: provider.redirectUri,\n\t\t\tclient_id: provider.clientId,\n\t\t\tclient_secret: provider.clientSecret,\n\t\t})\n\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(provider.tokenUrl, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t},\n\t\t\t\tbody: body.toString(),\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tthrow new OAuthCodeExchangeError(\n\t\t\t\terr instanceof Error ? err.message : 'Network error',\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tlet details = `HTTP ${response.status}`\n\t\t\ttry {\n\t\t\t\tconst errorBody = await response.text()\n\t\t\t\tdetails += `: ${errorBody}`\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t\tthrow new OAuthCodeExchangeError(details)\n\t\t}\n\n\t\tconst data = (await response.json()) as Record<string, unknown>\n\n\t\treturn {\n\t\t\taccessToken: data['access_token'] as string,\n\t\t\ttokenType: (data['token_type'] as string) ?? 'Bearer',\n\t\t\texpiresIn: data['expires_in'] as number | undefined,\n\t\t\trefreshToken: data['refresh_token'] as string | undefined,\n\t\t\tidToken: data['id_token'] as string | undefined,\n\t\t\tscope: data['scope'] as string | undefined,\n\t\t}\n\t}\n\n\tprivate async fetchUserInfo(\n\t\tprovider: OAuthProviderConfig,\n\t\taccessToken: string,\n\t): Promise<OAuthUserInfo> {\n\t\tlet response: Response\n\t\ttry {\n\t\t\tresponse = await this.fetchFn(provider.userInfoUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t},\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tthrow new OAuthUserInfoError(\n\t\t\t\terr instanceof Error ? err.message : 'Network error',\n\t\t\t)\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tthrow new OAuthUserInfoError(`HTTP ${response.status}`)\n\t\t}\n\n\t\tconst profile = (await response.json()) as Record<string, unknown>\n\n\t\t// Normalize user info based on provider\n\t\treturn normalizeUserInfo(provider.providerId, profile)\n\t}\n}\n\n// ============================================================================\n// User Info Normalization\n// ============================================================================\n\nfunction normalizeUserInfo(providerId: string, profile: Record<string, unknown>): OAuthUserInfo {\n\tswitch (providerId) {\n\t\tcase 'google':\n\t\t\treturn {\n\t\t\t\tproviderId: profile['sub'] as string,\n\t\t\t\tprovider: 'google',\n\t\t\t\temail: (profile['email'] as string) ?? null,\n\t\t\t\temailVerified: (profile['email_verified'] as boolean) ?? false,\n\t\t\t\tname: (profile['name'] as string) ?? null,\n\t\t\t\tavatarUrl: (profile['picture'] as string) ?? null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t\tcase 'github':\n\t\t\treturn {\n\t\t\t\tproviderId: String(profile['id']),\n\t\t\t\tprovider: 'github',\n\t\t\t\temail: (profile['email'] as string) ?? null,\n\t\t\t\temailVerified: false, // GitHub doesn't confirm in the profile response\n\t\t\t\tname: (profile['name'] as string) ?? (profile['login'] as string) ?? null,\n\t\t\t\tavatarUrl: (profile['avatar_url'] as string) ?? null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t\tcase 'microsoft':\n\t\t\treturn {\n\t\t\t\tproviderId: profile['id'] as string,\n\t\t\t\tprovider: 'microsoft',\n\t\t\t\temail: (profile['mail'] as string) ?? (profile['userPrincipalName'] as string) ?? null,\n\t\t\t\temailVerified: false,\n\t\t\t\tname: (profile['displayName'] as string) ?? null,\n\t\t\t\tavatarUrl: null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t\tdefault:\n\t\t\t// Generic normalization\n\t\t\treturn {\n\t\t\t\tproviderId: String(profile['id'] ?? profile['sub'] ?? ''),\n\t\t\t\tprovider: providerId,\n\t\t\t\temail: (profile['email'] as string) ?? null,\n\t\t\t\temailVerified: (profile['email_verified'] as boolean) ?? false,\n\t\t\t\tname: (profile['name'] as string) ?? null,\n\t\t\t\tavatarUrl: (profile['picture'] as string) ?? (profile['avatar_url'] as string) ?? null,\n\t\t\t\trawProfile: profile,\n\t\t\t}\n\t}\n}\n\n// ============================================================================\n// Provider Factories\n// ============================================================================\n\ninterface ProviderFactoryConfig {\n\tclientId: string\n\tclientSecret: string\n\tredirectUri: string\n\tscopes?: string[]\n}\n\n/**\n * Create a Google OAuth provider configuration.\n */\nexport function googleProvider(config: ProviderFactoryConfig): OAuthProviderConfig {\n\treturn {\n\t\tproviderId: 'google',\n\t\tclientId: config.clientId,\n\t\tclientSecret: config.clientSecret,\n\t\tauthorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',\n\t\ttokenUrl: 'https://oauth2.googleapis.com/token',\n\t\tuserInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',\n\t\tscopes: config.scopes ?? ['openid', 'email', 'profile'],\n\t\tredirectUri: config.redirectUri,\n\t}\n}\n\n/**\n * Create a GitHub OAuth provider configuration.\n */\nexport function githubProvider(config: ProviderFactoryConfig): OAuthProviderConfig {\n\treturn {\n\t\tproviderId: 'github',\n\t\tclientId: config.clientId,\n\t\tclientSecret: config.clientSecret,\n\t\tauthorizationUrl: 'https://github.com/login/oauth/authorize',\n\t\ttokenUrl: 'https://github.com/login/oauth/access_token',\n\t\tuserInfoUrl: 'https://api.github.com/user',\n\t\tscopes: config.scopes ?? ['read:user', 'user:email'],\n\t\tredirectUri: config.redirectUri,\n\t}\n}\n\n/**\n * Create a Microsoft OAuth provider configuration.\n */\nexport function microsoftProvider(\n\tconfig: ProviderFactoryConfig & { tenantId?: string },\n): OAuthProviderConfig {\n\tconst tenant = config.tenantId ?? 'common'\n\treturn {\n\t\tproviderId: 'microsoft',\n\t\tclientId: config.clientId,\n\t\tclientSecret: config.clientSecret,\n\t\tauthorizationUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,\n\t\ttokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,\n\t\tuserInfoUrl: 'https://graph.microsoft.com/v1.0/me',\n\t\tscopes: config.scopes ?? ['openid', 'email', 'profile', 'User.Read'],\n\t\tredirectUri: config.redirectUri,\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateState(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet binary = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] as number)\n\t}\n\treturn btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Session Types\n// ============================================================================\n\n/**\n * A user session with metadata.\n */\nexport interface Session {\n\t/** Unique session ID */\n\tid: string\n\t/** User ID */\n\tuserId: string\n\t/** Device ID (if available) */\n\tdeviceId: string | null\n\t/** IP address of the client (for display, not for security decisions) */\n\tipAddress: string | null\n\t/** User agent string */\n\tuserAgent: string | null\n\t/** When the session was created */\n\tcreatedAt: number\n\t/** When the session was last active */\n\tlastActiveAt: number\n\t/** When the session expires (absolute expiry) */\n\texpiresAt: number\n\t/** Whether MFA has been completed for this session */\n\tmfaVerified: boolean\n\t/** Custom metadata */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Configuration for the session manager.\n */\nexport interface SessionManagerConfig {\n\t/** Session store implementation */\n\tstore: SessionStore\n\t/** Session TTL in milliseconds. Default: 7 days */\n\tsessionTtlMs?: number\n\t/** Idle timeout in milliseconds. Default: 30 minutes */\n\tidleTimeoutMs?: number\n\t/** Maximum concurrent sessions per user. Default: 10 */\n\tmaxSessionsPerUser?: number\n\t/** Whether to extend session on activity. Default: true */\n\tslidingWindow?: boolean\n}\n\n/**\n * Parameters for creating a new session.\n */\nexport interface CreateSessionParams {\n\tuserId: string\n\tdeviceId?: string\n\tipAddress?: string\n\tuserAgent?: string\n\tmfaVerified?: boolean\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Store for session data.\n */\nexport interface SessionStore {\n\t/** Create a new session */\n\tcreate(session: Session): Promise<void>\n\t/** Get a session by ID */\n\tgetById(sessionId: string): Promise<Session | null>\n\t/** Update a session */\n\tupdate(session: Session): Promise<void>\n\t/** Delete a session */\n\tdelete(sessionId: string): Promise<void>\n\t/** Get all sessions for a user */\n\tlistByUserId(userId: string): Promise<Session[]>\n\t/** Delete all sessions for a user */\n\tdeleteAllForUser(userId: string): Promise<number>\n\t/** Delete all sessions for a user except one */\n\tdeleteAllExcept(userId: string, keepSessionId: string): Promise<number>\n\t/** Clean up expired sessions */\n\tcleanExpired(): Promise<number>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class SessionError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'SessionError'\n\t}\n}\n\nexport class SessionNotFoundError extends SessionError {\n\tconstructor(sessionId: string) {\n\t\tsuper('Session not found or expired.', 'SESSION_NOT_FOUND', { sessionId })\n\t}\n}\n\nexport class SessionExpiredError extends SessionError {\n\tconstructor(sessionId: string) {\n\t\tsuper('Session has expired.', 'SESSION_EXPIRED', { sessionId })\n\t}\n}\n\nexport class SessionLimitExceededError extends SessionError {\n\tconstructor(userId: string, limit: number) {\n\t\tsuper(\n\t\t\t`Maximum concurrent sessions (${limit}) reached. Revoke an existing session first.`,\n\t\t\t'SESSION_LIMIT_EXCEEDED',\n\t\t\t{ userId, limit },\n\t\t)\n\t}\n}\n\nexport class SessionMfaRequiredError extends SessionError {\n\tconstructor(sessionId: string) {\n\t\tsuper('MFA verification is required for this session.', 'SESSION_MFA_REQUIRED', { sessionId })\n\t}\n}\n\n// ============================================================================\n// InMemorySessionStore\n// ============================================================================\n\n/**\n * In-memory session store for development and testing.\n */\nexport class InMemorySessionStore implements SessionStore {\n\tprivate readonly sessions = new Map<string, Session>()\n\n\tasync create(session: Session): Promise<void> {\n\t\tthis.sessions.set(session.id, { ...session })\n\t}\n\n\tasync getById(sessionId: string): Promise<Session | null> {\n\t\tconst session = this.sessions.get(sessionId)\n\t\treturn session ? { ...session } : null\n\t}\n\n\tasync update(session: Session): Promise<void> {\n\t\tthis.sessions.set(session.id, { ...session })\n\t}\n\n\tasync delete(sessionId: string): Promise<void> {\n\t\tthis.sessions.delete(sessionId)\n\t}\n\n\tasync listByUserId(userId: string): Promise<Session[]> {\n\t\tconst results: Session[] = []\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tif (session.userId === userId) {\n\t\t\t\tresults.push({ ...session })\n\t\t\t}\n\t\t}\n\t\treturn results.sort((a, b) => b.lastActiveAt - a.lastActiveAt)\n\t}\n\n\tasync deleteAllForUser(userId: string): Promise<number> {\n\t\tlet count = 0\n\t\tfor (const [id, session] of this.sessions) {\n\t\t\tif (session.userId === userId) {\n\t\t\t\tthis.sessions.delete(id)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync deleteAllExcept(userId: string, keepSessionId: string): Promise<number> {\n\t\tlet count = 0\n\t\tfor (const [id, session] of this.sessions) {\n\t\t\tif (session.userId === userId && id !== keepSessionId) {\n\t\t\t\tthis.sessions.delete(id)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync cleanExpired(): Promise<number> {\n\t\tconst now = Date.now()\n\t\tlet count = 0\n\t\tfor (const [id, session] of this.sessions) {\n\t\t\tif (now > session.expiresAt) {\n\t\t\t\tthis.sessions.delete(id)\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n}\n\n// ============================================================================\n// SessionManager\n// ============================================================================\n\nconst DEFAULT_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\nconst DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes\nconst DEFAULT_MAX_SESSIONS = 10\n\n/**\n * Manages user sessions with support for:\n * - Session creation, validation, and revocation\n * - Sliding window expiry (extends on activity)\n * - Idle timeout detection\n * - Max concurrent sessions per user\n * - MFA verification tracking\n * - \"Sign out everywhere\" support\n *\n * @example\n * ```typescript\n * const sessions = new SessionManager({\n * store: new InMemorySessionStore(),\n * sessionTtlMs: 7 * 24 * 60 * 60 * 1000, // 7 days\n * idleTimeoutMs: 30 * 60 * 1000, // 30 minutes\n * maxSessionsPerUser: 5,\n * })\n *\n * // Create session on login\n * const session = await sessions.create({\n * userId: 'user-123',\n * ipAddress: '192.168.1.1',\n * userAgent: 'Mozilla/5.0...',\n * })\n *\n * // Validate session on each request\n * const valid = await sessions.validate(session.id)\n *\n * // Touch session to extend idle timeout\n * await sessions.touch(session.id)\n * ```\n */\nexport class SessionManager {\n\tprivate readonly store: SessionStore\n\tprivate readonly sessionTtlMs: number\n\tprivate readonly idleTimeoutMs: number\n\tprivate readonly maxSessionsPerUser: number\n\tprivate readonly slidingWindow: boolean\n\n\tconstructor(config: SessionManagerConfig) {\n\t\tthis.store = config.store\n\t\tthis.sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS\n\t\tthis.idleTimeoutMs = config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS\n\t\tthis.maxSessionsPerUser = config.maxSessionsPerUser ?? DEFAULT_MAX_SESSIONS\n\t\tthis.slidingWindow = config.slidingWindow ?? true\n\t}\n\n\t/**\n\t * Create a new session for a user.\n\t * Enforces the maximum concurrent sessions limit.\n\t */\n\tasync create(params: CreateSessionParams): Promise<Session> {\n\t\t// Enforce max sessions\n\t\tconst existing = await this.store.listByUserId(params.userId)\n\t\tconst activeSessions = existing.filter((s) => Date.now() <= s.expiresAt)\n\n\t\tif (activeSessions.length >= this.maxSessionsPerUser) {\n\t\t\tthrow new SessionLimitExceededError(params.userId, this.maxSessionsPerUser)\n\t\t}\n\n\t\tconst now = Date.now()\n\t\tconst session: Session = {\n\t\t\tid: generateSessionId(),\n\t\t\tuserId: params.userId,\n\t\t\tdeviceId: params.deviceId ?? null,\n\t\t\tipAddress: params.ipAddress ?? null,\n\t\t\tuserAgent: params.userAgent ?? null,\n\t\t\tcreatedAt: now,\n\t\t\tlastActiveAt: now,\n\t\t\texpiresAt: now + this.sessionTtlMs,\n\t\t\tmfaVerified: params.mfaVerified ?? false,\n\t\t\tmetadata: params.metadata,\n\t\t}\n\n\t\tawait this.store.create(session)\n\t\treturn session\n\t}\n\n\t/**\n\t * Validate a session.\n\t * Returns the session if valid, throws if not found or expired.\n\t */\n\tasync validate(sessionId: string): Promise<Session> {\n\t\tconst session = await this.store.getById(sessionId)\n\t\tif (!session) {\n\t\t\tthrow new SessionNotFoundError(sessionId)\n\t\t}\n\n\t\tconst now = Date.now()\n\n\t\t// Check absolute expiry\n\t\tif (now > session.expiresAt) {\n\t\t\tawait this.store.delete(sessionId)\n\t\t\tthrow new SessionExpiredError(sessionId)\n\t\t}\n\n\t\t// Check idle timeout\n\t\tif (now - session.lastActiveAt > this.idleTimeoutMs) {\n\t\t\tawait this.store.delete(sessionId)\n\t\t\tthrow new SessionExpiredError(sessionId)\n\t\t}\n\n\t\treturn session\n\t}\n\n\t/**\n\t * Touch a session to update its last activity time.\n\t * If sliding window is enabled, also extends the absolute expiry.\n\t */\n\tasync touch(sessionId: string): Promise<Session> {\n\t\tconst session = await this.validate(sessionId)\n\t\tconst now = Date.now()\n\n\t\tsession.lastActiveAt = now\n\n\t\tif (this.slidingWindow) {\n\t\t\tsession.expiresAt = now + this.sessionTtlMs\n\t\t}\n\n\t\tawait this.store.update(session)\n\t\treturn session\n\t}\n\n\t/**\n\t * Mark a session as MFA-verified.\n\t */\n\tasync markMfaVerified(sessionId: string): Promise<Session> {\n\t\tconst session = await this.validate(sessionId)\n\t\tsession.mfaVerified = true\n\t\tawait this.store.update(session)\n\t\treturn session\n\t}\n\n\t/**\n\t * Require MFA verification on a session.\n\t * Throws SessionMfaRequiredError if MFA is not verified.\n\t */\n\tasync requireMfa(sessionId: string): Promise<Session> {\n\t\tconst session = await this.validate(sessionId)\n\t\tif (!session.mfaVerified) {\n\t\t\tthrow new SessionMfaRequiredError(sessionId)\n\t\t}\n\t\treturn session\n\t}\n\n\t/**\n\t * Revoke (delete) a session.\n\t */\n\tasync revoke(sessionId: string): Promise<void> {\n\t\tawait this.store.delete(sessionId)\n\t}\n\n\t/**\n\t * Revoke all sessions for a user (sign out everywhere).\n\t * Returns the number of sessions revoked.\n\t */\n\tasync revokeAll(userId: string): Promise<number> {\n\t\treturn this.store.deleteAllForUser(userId)\n\t}\n\n\t/**\n\t * Revoke all sessions for a user except the current one.\n\t * Returns the number of sessions revoked.\n\t */\n\tasync revokeOthers(userId: string, currentSessionId: string): Promise<number> {\n\t\treturn this.store.deleteAllExcept(userId, currentSessionId)\n\t}\n\n\t/**\n\t * List all active sessions for a user.\n\t */\n\tasync listSessions(userId: string): Promise<Session[]> {\n\t\tconst sessions = await this.store.listByUserId(userId)\n\t\tconst now = Date.now()\n\t\t// Filter out expired sessions\n\t\treturn sessions.filter((s) => now <= s.expiresAt && now - s.lastActiveAt <= this.idleTimeoutMs)\n\t}\n\n\t/**\n\t * Clean up expired sessions.\n\t * Returns the number of sessions cleaned.\n\t */\n\tasync cleanExpired(): Promise<number> {\n\t\treturn this.store.cleanExpired()\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateSessionId(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// TOTP Types\n// ============================================================================\n\n/**\n * Configuration for TOTP MFA.\n */\nexport interface TotpConfig {\n\t/** Issuer name shown in authenticator apps (e.g., \"MyApp\") */\n\tissuer: string\n\t/** Number of digits in the TOTP code. Default: 6 */\n\tdigits?: number\n\t/** Time step in seconds. Default: 30 */\n\tperiod?: number\n\t/** Hash algorithm. Default: 'SHA-1' (most compatible with authenticator apps) */\n\talgorithm?: 'SHA-1' | 'SHA-256' | 'SHA-512'\n\t/** Number of time windows to check before/after current. Default: 1 */\n\twindow?: number\n\t/** Number of recovery codes to generate. Default: 8 */\n\trecoveryCodes?: number\n}\n\n/**\n * A TOTP secret with metadata for a user.\n */\nexport interface TotpSecret {\n\t/** User ID */\n\tuserId: string\n\t/** Raw secret bytes (base32-encoded for display) */\n\tsecret: string\n\t/** Whether MFA is verified (user has confirmed setup with a valid code) */\n\tverified: boolean\n\t/** Hashed recovery codes (unused ones only) */\n\trecoveryCodes: string[]\n\t/** When this secret was created */\n\tcreatedAt: number\n\t/** When MFA was verified (confirmed with first valid code) */\n\tverifiedAt: number | null\n}\n\n/**\n * Setup result returned when enabling TOTP MFA.\n */\nexport interface TotpSetupResult {\n\t/** The raw secret in base32 encoding (for manual entry) */\n\tsecret: string\n\t/** otpauth:// URI for QR code generation */\n\turi: string\n\t/** Plaintext recovery codes (shown once, then discarded) */\n\trecoveryCodes: string[]\n}\n\n/**\n * Store for TOTP secrets.\n */\nexport interface TotpStore {\n\t/** Save or update a TOTP secret for a user */\n\tsave(secret: TotpSecret): Promise<void>\n\t/** Get a TOTP secret by user ID */\n\tgetByUserId(userId: string): Promise<TotpSecret | null>\n\t/** Delete TOTP secret for a user (disable MFA) */\n\tdelete(userId: string): Promise<void>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class TotpError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'TotpError'\n\t}\n}\n\nexport class TotpInvalidCodeError extends TotpError {\n\tconstructor() {\n\t\tsuper('Invalid TOTP code.', 'TOTP_INVALID_CODE')\n\t}\n}\n\nexport class TotpNotEnabledError extends TotpError {\n\tconstructor(userId: string) {\n\t\tsuper('TOTP MFA is not enabled for this user.', 'TOTP_NOT_ENABLED', { userId })\n\t}\n}\n\nexport class TotpAlreadyEnabledError extends TotpError {\n\tconstructor(userId: string) {\n\t\tsuper('TOTP MFA is already enabled for this user.', 'TOTP_ALREADY_ENABLED', { userId })\n\t}\n}\n\nexport class TotpNotVerifiedError extends TotpError {\n\tconstructor(userId: string) {\n\t\tsuper(\n\t\t\t'TOTP MFA setup is pending verification. Verify with a valid code first.',\n\t\t\t'TOTP_NOT_VERIFIED',\n\t\t\t{ userId },\n\t\t)\n\t}\n}\n\nexport class TotpRecoveryExhaustedError extends TotpError {\n\tconstructor() {\n\t\tsuper('All recovery codes have been used. Please regenerate.', 'TOTP_RECOVERY_EXHAUSTED')\n\t}\n}\n\n// ============================================================================\n// InMemoryTotpStore\n// ============================================================================\n\n/**\n * In-memory TOTP store for development and testing.\n */\nexport class InMemoryTotpStore implements TotpStore {\n\tprivate readonly secrets = new Map<string, TotpSecret>()\n\n\tasync save(secret: TotpSecret): Promise<void> {\n\t\tthis.secrets.set(secret.userId, secret)\n\t}\n\n\tasync getByUserId(userId: string): Promise<TotpSecret | null> {\n\t\treturn this.secrets.get(userId) ?? null\n\t}\n\n\tasync delete(userId: string): Promise<void> {\n\t\tthis.secrets.delete(userId)\n\t}\n}\n\n// ============================================================================\n// TotpManager\n// ============================================================================\n\nconst DEFAULT_DIGITS = 6\nconst DEFAULT_PERIOD = 30\nconst DEFAULT_ALGORITHM = 'SHA-1'\nconst DEFAULT_WINDOW = 1\nconst DEFAULT_RECOVERY_CODES = 8\nconst RECOVERY_CODE_LENGTH = 10\n\n/**\n * Manages TOTP-based Multi-Factor Authentication.\n *\n * Implements RFC 6238 (TOTP) and RFC 4226 (HOTP) with Web Crypto API.\n * Compatible with Google Authenticator, Authy, 1Password, and other\n * TOTP-compatible authenticator apps.\n *\n * @example\n * ```typescript\n * const totp = new TotpManager({\n * issuer: 'MyApp',\n * store: new InMemoryTotpStore(),\n * })\n *\n * // Step 1: Enable MFA (returns QR code URI and recovery codes)\n * const setup = await totp.enable('user-123', 'alice@example.com')\n * // Show setup.uri as QR code, show setup.recoveryCodes once\n *\n * // Step 2: Verify setup with a code from authenticator app\n * await totp.verifySetup('user-123', '123456')\n *\n * // Step 3: On login, verify TOTP code\n * const valid = await totp.verify('user-123', '654321')\n * ```\n */\nexport class TotpManager {\n\tprivate readonly store: TotpStore\n\tprivate readonly issuer: string\n\tprivate readonly digits: number\n\tprivate readonly period: number\n\tprivate readonly algorithm: 'SHA-1' | 'SHA-256' | 'SHA-512'\n\tprivate readonly window: number\n\tprivate readonly recoveryCodeCount: number\n\n\tconstructor(config: TotpConfig & { store: TotpStore }) {\n\t\tthis.store = config.store\n\t\tthis.issuer = config.issuer\n\t\tthis.digits = config.digits ?? DEFAULT_DIGITS\n\t\tthis.period = config.period ?? DEFAULT_PERIOD\n\t\tthis.algorithm = config.algorithm ?? DEFAULT_ALGORITHM\n\t\tthis.window = config.window ?? DEFAULT_WINDOW\n\t\tthis.recoveryCodeCount = config.recoveryCodes ?? DEFAULT_RECOVERY_CODES\n\t}\n\n\t/**\n\t * Enable TOTP MFA for a user.\n\t * Returns the secret URI (for QR code) and recovery codes.\n\t * The user must verify setup with a valid code before MFA is active.\n\t */\n\tasync enable(userId: string, accountName: string): Promise<TotpSetupResult> {\n\t\tconst existing = await this.store.getByUserId(userId)\n\t\tif (existing?.verified) {\n\t\t\tthrow new TotpAlreadyEnabledError(userId)\n\t\t}\n\n\t\tconst secretBytes = generateSecret(20)\n\t\tconst secret = base32Encode(secretBytes)\n\t\tconst recoveryCodes = generateRecoveryCodes(this.recoveryCodeCount, RECOVERY_CODE_LENGTH)\n\t\tconst hashedCodes = await Promise.all(recoveryCodes.map((c) => hashRecoveryCode(c)))\n\n\t\tconst totpSecret: TotpSecret = {\n\t\t\tuserId,\n\t\t\tsecret,\n\t\t\tverified: false,\n\t\t\trecoveryCodes: hashedCodes,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tverifiedAt: null,\n\t\t}\n\n\t\tawait this.store.save(totpSecret)\n\n\t\tconst uri = buildOtpauthUri({\n\t\t\tissuer: this.issuer,\n\t\t\taccountName,\n\t\t\tsecret,\n\t\t\talgorithm: this.algorithm,\n\t\t\tdigits: this.digits,\n\t\t\tperiod: this.period,\n\t\t})\n\n\t\treturn { secret, uri, recoveryCodes }\n\t}\n\n\t/**\n\t * Verify the TOTP setup by confirming the user can generate a valid code.\n\t * Must be called after `enable()` and before MFA is enforced.\n\t */\n\tasync verifySetup(userId: string, code: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (stored.verified) {\n\t\t\t// Already verified, just validate the code\n\t\t\treturn this.validateCode(stored.secret, code)\n\t\t}\n\n\t\tconst valid = this.validateCode(stored.secret, code)\n\t\tif (!valid) {\n\t\t\tthrow new TotpInvalidCodeError()\n\t\t}\n\n\t\t// Mark as verified\n\t\tstored.verified = true\n\t\tstored.verifiedAt = Date.now()\n\t\tawait this.store.save(stored)\n\n\t\treturn true\n\t}\n\n\t/**\n\t * Verify a TOTP code during login.\n\t * Returns true if the code is valid, false otherwise.\n\t */\n\tasync verify(userId: string, code: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (!stored.verified) {\n\t\t\tthrow new TotpNotVerifiedError(userId)\n\t\t}\n\n\t\treturn this.validateCode(stored.secret, code)\n\t}\n\n\t/**\n\t * Verify a recovery code as an alternative to TOTP.\n\t * Recovery codes are single-use.\n\t */\n\tasync verifyRecoveryCode(userId: string, recoveryCode: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (!stored.verified) {\n\t\t\tthrow new TotpNotVerifiedError(userId)\n\t\t}\n\n\t\tconst hashed = await hashRecoveryCode(recoveryCode.trim())\n\n\t\tconst index = stored.recoveryCodes.indexOf(hashed)\n\t\tif (index === -1) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Consume the recovery code (single-use)\n\t\tstored.recoveryCodes.splice(index, 1)\n\t\tawait this.store.save(stored)\n\n\t\treturn true\n\t}\n\n\t/**\n\t * Regenerate recovery codes. Requires a valid TOTP code for authorization.\n\t * Replaces all existing recovery codes.\n\t */\n\tasync regenerateRecoveryCodes(userId: string, totpCode: string): Promise<string[]> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\tif (!stored.verified) {\n\t\t\tthrow new TotpNotVerifiedError(userId)\n\t\t}\n\n\t\tconst valid = this.validateCode(stored.secret, totpCode)\n\t\tif (!valid) {\n\t\t\tthrow new TotpInvalidCodeError()\n\t\t}\n\n\t\tconst recoveryCodes = generateRecoveryCodes(this.recoveryCodeCount, RECOVERY_CODE_LENGTH)\n\t\tconst hashedCodes = await Promise.all(recoveryCodes.map((c) => hashRecoveryCode(c)))\n\n\t\tstored.recoveryCodes = hashedCodes\n\t\tawait this.store.save(stored)\n\n\t\treturn recoveryCodes\n\t}\n\n\t/**\n\t * Disable TOTP MFA for a user.\n\t * Requires a valid TOTP code or recovery code for authorization.\n\t */\n\tasync disable(userId: string, code: string): Promise<void> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored) {\n\t\t\tthrow new TotpNotEnabledError(userId)\n\t\t}\n\n\t\t// Accept either a TOTP code or a recovery code\n\t\tlet authorized = false\n\n\t\tif (stored.verified) {\n\t\t\tauthorized = this.validateCode(stored.secret, code)\n\t\t}\n\n\t\tif (!authorized) {\n\t\t\tconst hashed = await hashRecoveryCode(code.trim())\n\t\t\tauthorized = stored.recoveryCodes.includes(hashed)\n\t\t}\n\n\t\tif (!authorized) {\n\t\t\tthrow new TotpInvalidCodeError()\n\t\t}\n\n\t\tawait this.store.delete(userId)\n\t}\n\n\t/**\n\t * Check if a user has TOTP MFA enabled and verified.\n\t */\n\tasync isEnabled(userId: string): Promise<boolean> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\treturn stored !== null && stored.verified\n\t}\n\n\t/**\n\t * Get the number of remaining recovery codes for a user.\n\t */\n\tasync remainingRecoveryCodes(userId: string): Promise<number> {\n\t\tconst stored = await this.store.getByUserId(userId)\n\t\tif (!stored || !stored.verified) return 0\n\t\treturn stored.recoveryCodes.length\n\t}\n\n\t// --- Private ---\n\n\tprivate validateCode(base32Secret: string, code: string): boolean {\n\t\tconst secretBytes = base32Decode(base32Secret)\n\t\tconst now = Math.floor(Date.now() / 1000)\n\n\t\t// Check current window and adjacent windows\n\t\tfor (let offset = -this.window; offset <= this.window; offset++) {\n\t\t\tconst timeCounter = Math.floor((now + offset * this.period) / this.period)\n\t\t\tconst expected = generateTotpCode(secretBytes, timeCounter, this.digits, this.algorithm)\n\t\t\tif (timingSafeEqual(code, expected)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t}\n}\n\n// ============================================================================\n// TOTP Core (RFC 6238 / RFC 4226)\n// ============================================================================\n\n/**\n * Generate a TOTP code for a given time counter.\n * Implements HOTP (RFC 4226) with a time-based counter (RFC 6238).\n */\nfunction generateTotpCode(\n\tsecret: Uint8Array,\n\tcounter: number,\n\tdigits: number,\n\talgorithm: string,\n): string {\n\t// Counter as 8-byte big-endian\n\tconst counterBytes = new Uint8Array(8)\n\tlet c = counter\n\tfor (let i = 7; i >= 0; i--) {\n\t\tcounterBytes[i] = c & 0xff\n\t\tc = Math.floor(c / 256)\n\t}\n\n\t// HMAC-SHA1 (or SHA-256/SHA-512)\n\tconst hash = hmacSha(algorithm, secret, counterBytes)\n\n\t// Dynamic truncation (RFC 4226 section 5.4)\n\tconst offset = hash[hash.length - 1]! & 0x0f\n\tconst binary =\n\t\t((hash[offset]! & 0x7f) << 24) |\n\t\t((hash[offset + 1]! & 0xff) << 16) |\n\t\t((hash[offset + 2]! & 0xff) << 8) |\n\t\t(hash[offset + 3]! & 0xff)\n\n\tconst otp = binary % Math.pow(10, digits)\n\treturn otp.toString().padStart(digits, '0')\n}\n\n/**\n * Synchronous HMAC using a simplified implementation.\n * TOTP only needs HMAC-SHA1 which we implement directly\n * to avoid async Web Crypto for the hot path (validation).\n */\nfunction hmacSha(algorithm: string, key: Uint8Array, message: Uint8Array): Uint8Array {\n\t// Use the appropriate block size and hash\n\tconst blockSize = algorithm === 'SHA-512' ? 128 : 64\n\n\t// Pad or hash the key\n\tlet keyPad = key\n\tif (keyPad.length > blockSize) {\n\t\tkeyPad = sha1(keyPad)\n\t}\n\n\t// Create padded key\n\tconst ipad = new Uint8Array(blockSize)\n\tconst opad = new Uint8Array(blockSize)\n\tfor (let i = 0; i < blockSize; i++) {\n\t\tconst k = i < keyPad.length ? keyPad[i]! : 0\n\t\tipad[i] = k ^ 0x36\n\t\topad[i] = k ^ 0x5c\n\t}\n\n\t// Inner hash: H(key XOR ipad || message)\n\tconst innerData = new Uint8Array(blockSize + message.length)\n\tinnerData.set(ipad)\n\tinnerData.set(message, blockSize)\n\tconst innerHash = sha1(innerData)\n\n\t// Outer hash: H(key XOR opad || inner_hash)\n\tconst outerData = new Uint8Array(blockSize + innerHash.length)\n\touterData.set(opad)\n\touterData.set(innerHash, blockSize)\n\treturn sha1(outerData)\n}\n\n/**\n * SHA-1 implementation for TOTP HMAC.\n * SHA-1 is the standard for TOTP (RFC 6238) and is NOT used for security\n * hashing here — it's used as a PRF inside HMAC which is still secure.\n */\nfunction sha1(data: Uint8Array): Uint8Array {\n\tlet h0 = 0x67452301\n\tlet h1 = 0xefcdab89\n\tlet h2 = 0x98badcfe\n\tlet h3 = 0x10325476\n\tlet h4 = 0xc3d2e1f0\n\n\tconst bitLength = data.length * 8\n\n\t// Pre-processing: add padding\n\t// message + 1 bit + zeros + 64-bit length\n\tconst paddedLength = Math.ceil((data.length + 9) / 64) * 64\n\tconst padded = new Uint8Array(paddedLength)\n\tpadded.set(data)\n\tpadded[data.length] = 0x80\n\n\t// Append length as 64-bit big-endian\n\tconst view = new DataView(padded.buffer, padded.byteOffset)\n\t// For messages < 2^32 bits, high 32 bits are 0\n\tview.setUint32(paddedLength - 4, bitLength, false)\n\n\t// Process each 512-bit (64-byte) block\n\tconst w = new Int32Array(80)\n\n\tfor (let offset = 0; offset < paddedLength; offset += 64) {\n\t\tfor (let i = 0; i < 16; i++) {\n\t\t\tw[i] = view.getInt32(offset + i * 4, false)\n\t\t}\n\n\t\tfor (let i = 16; i < 80; i++) {\n\t\t\tw[i] = rotl32((w[i - 3]! ^ w[i - 8]! ^ w[i - 14]! ^ w[i - 16]!) | 0, 1)\n\t\t}\n\n\t\tlet a = h0\n\t\tlet b = h1\n\t\tlet c = h2\n\t\tlet d = h3\n\t\tlet e = h4\n\n\t\tfor (let i = 0; i < 80; i++) {\n\t\t\tlet f: number\n\t\t\tlet k: number\n\n\t\t\tif (i < 20) {\n\t\t\t\tf = (b & c) | (~b & d)\n\t\t\t\tk = 0x5a827999\n\t\t\t} else if (i < 40) {\n\t\t\t\tf = b ^ c ^ d\n\t\t\t\tk = 0x6ed9eba1\n\t\t\t} else if (i < 60) {\n\t\t\t\tf = (b & c) | (b & d) | (c & d)\n\t\t\t\tk = 0x8f1bbcdc\n\t\t\t} else {\n\t\t\t\tf = b ^ c ^ d\n\t\t\t\tk = 0xca62c1d6\n\t\t\t}\n\n\t\t\tconst temp = (rotl32(a, 5) + f + e + k + w[i]!) | 0\n\t\t\te = d\n\t\t\td = c\n\t\t\tc = rotl32(b, 30)\n\t\t\tb = a\n\t\t\ta = temp\n\t\t}\n\n\t\th0 = (h0 + a) | 0\n\t\th1 = (h1 + b) | 0\n\t\th2 = (h2 + c) | 0\n\t\th3 = (h3 + d) | 0\n\t\th4 = (h4 + e) | 0\n\t}\n\n\tconst result = new Uint8Array(20)\n\tconst rv = new DataView(result.buffer)\n\trv.setInt32(0, h0, false)\n\trv.setInt32(4, h1, false)\n\trv.setInt32(8, h2, false)\n\trv.setInt32(12, h3, false)\n\trv.setInt32(16, h4, false)\n\n\treturn result\n}\n\nfunction rotl32(value: number, shift: number): number {\n\treturn ((value << shift) | (value >>> (32 - shift))) | 0\n}\n\n// ============================================================================\n// Base32 Encoding/Decoding (RFC 4648)\n// ============================================================================\n\nconst BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n\n/**\n * Encode bytes to base32 string (RFC 4648, no padding).\n */\nexport function base32Encode(data: Uint8Array): string {\n\tlet result = ''\n\tlet bits = 0\n\tlet buffer = 0\n\n\tfor (let i = 0; i < data.length; i++) {\n\t\tbuffer = (buffer << 8) | data[i]!\n\t\tbits += 8\n\t\twhile (bits >= 5) {\n\t\t\tbits -= 5\n\t\t\tresult += BASE32_ALPHABET[(buffer >>> bits) & 0x1f]\n\t\t}\n\t}\n\n\tif (bits > 0) {\n\t\tresult += BASE32_ALPHABET[(buffer << (5 - bits)) & 0x1f]\n\t}\n\n\treturn result\n}\n\n/**\n * Decode a base32 string to bytes (RFC 4648).\n */\nexport function base32Decode(encoded: string): Uint8Array {\n\tconst cleaned = encoded.replace(/=+$/, '').toUpperCase()\n\tconst output: number[] = []\n\tlet bits = 0\n\tlet buffer = 0\n\n\tfor (let i = 0; i < cleaned.length; i++) {\n\t\tconst char = cleaned[i]!\n\t\tconst value = BASE32_ALPHABET.indexOf(char)\n\t\tif (value === -1) continue // skip invalid chars\n\n\t\tbuffer = (buffer << 5) | value\n\t\tbits += 5\n\n\t\tif (bits >= 8) {\n\t\t\tbits -= 8\n\t\t\toutput.push((buffer >>> bits) & 0xff)\n\t\t}\n\t}\n\n\treturn new Uint8Array(output)\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Generate a random secret of the given byte length.\n */\nfunction generateSecret(byteLength: number): Uint8Array {\n\tconst bytes = new Uint8Array(byteLength)\n\tglobalThis.crypto.getRandomValues(bytes)\n\treturn bytes\n}\n\n/**\n * Generate random recovery codes.\n */\nfunction generateRecoveryCodes(count: number, length: number): string[] {\n\tconst codes: string[] = []\n\tconst chars = 'abcdefghijklmnopqrstuvwxyz0123456789'\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst bytes = new Uint8Array(length)\n\t\tglobalThis.crypto.getRandomValues(bytes)\n\t\tlet code = ''\n\t\tfor (let j = 0; j < length; j++) {\n\t\t\tcode += chars[bytes[j]! % chars.length]\n\t\t}\n\t\t// Format as xxxxx-xxxxx for readability\n\t\tcodes.push(`${code.slice(0, 5)}-${code.slice(5)}`)\n\t}\n\n\treturn codes\n}\n\n/**\n * Hash a recovery code for storage (SHA-256).\n */\nasync function hashRecoveryCode(code: string): Promise<string> {\n\tconst encoded = new TextEncoder().encode(code.toLowerCase().replace(/[-\\s]/g, ''))\n\tconst hash = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\tconst bytes = new Uint8Array(hash)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n\n/**\n * Build an otpauth:// URI for QR code generation.\n */\nfunction buildOtpauthUri(params: {\n\tissuer: string\n\taccountName: string\n\tsecret: string\n\talgorithm: string\n\tdigits: number\n\tperiod: number\n}): string {\n\tconst label = `${encodeURIComponent(params.issuer)}:${encodeURIComponent(params.accountName)}`\n\tconst query = new URLSearchParams({\n\t\tsecret: params.secret,\n\t\tissuer: params.issuer,\n\t\talgorithm: params.algorithm.replace('-', ''),\n\t\tdigits: params.digits.toString(),\n\t\tperiod: params.period.toString(),\n\t})\n\treturn `otpauth://totp/${label}?${query.toString()}`\n}\n\n/**\n * Timing-safe string comparison to prevent timing attacks.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n\tif (a.length !== b.length) return false\n\n\tlet result = 0\n\tfor (let i = 0; i < a.length; i++) {\n\t\tresult |= a.charCodeAt(i) ^ b.charCodeAt(i)\n\t}\n\n\treturn result === 0\n}\n","import { KoraError } from '@korajs/core'\nimport type { AuthUser, StoredUser, UserStore } from '../provider/built-in/user-store'\nimport type { Session, SessionStore } from '../session/session'\nimport type { AuditLogger, AuditAction } from './audit-log'\n\n// ============================================================================\n// Admin API Types\n// ============================================================================\n\n/**\n * Configuration for the Admin API.\n */\nexport interface AdminApiConfig {\n\t/** User store for managing users */\n\tuserStore: UserStore\n\t/** Session store for managing sessions (optional) */\n\tsessionStore?: SessionStore\n\t/** Audit logger (optional) */\n\tauditLogger?: AuditLogger\n}\n\n/**\n * Paginated result set.\n */\nexport interface PaginatedResult<T> {\n\tdata: T[]\n\ttotal: number\n\tlimit: number\n\toffset: number\n}\n\n/**\n * User list query parameters.\n */\nexport interface UserListQuery {\n\t/** Search by email (substring match) */\n\temail?: string\n\t/** Filter by email verified status */\n\temailVerified?: boolean\n\t/** Maximum number of results */\n\tlimit?: number\n\t/** Offset for pagination */\n\toffset?: number\n}\n\n/**\n * Admin-level user update (different from self-update).\n */\nexport interface AdminUserUpdate {\n\t/** Update display name */\n\tname?: string\n\t/** Set email verified status */\n\temailVerified?: boolean\n\t/** Update email (admin override) */\n\temail?: string\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class AdminApiError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AdminApiError'\n\t}\n}\n\nexport class AdminUserNotFoundError extends AdminApiError {\n\tconstructor(userId: string) {\n\t\tsuper(`User \"${userId}\" not found.`, 'ADMIN_USER_NOT_FOUND', { userId })\n\t}\n}\n\nexport class AdminUnauthorizedError extends AdminApiError {\n\tconstructor() {\n\t\tsuper('Admin privileges required.', 'ADMIN_UNAUTHORIZED')\n\t}\n}\n\n// ============================================================================\n// AdminApi\n// ============================================================================\n\n/**\n * Administrative API for managing users, sessions, and system configuration.\n *\n * Provides elevated operations that should only be accessible to administrators.\n * All operations are audit-logged when an AuditLogger is configured.\n *\n * @example\n * ```typescript\n * const admin = new AdminApi({\n * userStore: myUserStore,\n * sessionStore: mySessionStore,\n * auditLogger: myAuditLogger,\n * })\n *\n * // List users\n * const { data, total } = await admin.listUsers({ limit: 20 })\n *\n * // Suspend a user (revokes all sessions)\n * await admin.suspendUser('admin-user-id', 'target-user-id', 'Policy violation')\n * ```\n */\nexport class AdminApi {\n\tprivate readonly userStore: UserStore\n\tprivate readonly sessionStore: SessionStore | null\n\tprivate readonly auditLogger: AuditLogger | null\n\n\tconstructor(config: AdminApiConfig) {\n\t\tthis.userStore = config.userStore\n\t\tthis.sessionStore = config.sessionStore ?? null\n\t\tthis.auditLogger = config.auditLogger ?? null\n\t}\n\n\t/**\n\t * Get a user by ID with full details.\n\t */\n\tasync getUser(adminId: string, userId: string): Promise<AuthUser> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\tthrow new AdminUserNotFoundError(userId)\n\t\t}\n\n\t\tawait this.audit('admin.user_lookup', adminId, userId, 'user')\n\n\t\treturn toAuthUser(user)\n\t}\n\n\t/**\n\t * List users with optional filtering and pagination.\n\t */\n\tasync listUsers(query: UserListQuery = {}): Promise<PaginatedResult<AuthUser>> {\n\t\tconst limit = query.limit ?? 50\n\t\tconst offset = query.offset ?? 0\n\n\t\t// Get all users (InMemoryUserStore doesn't have a list method, so we use findByEmail with patterns)\n\t\t// For now, we expose a listing helper\n\t\tconst allUsers = await this.userStore.listAll()\n\n\t\tlet filtered = allUsers\n\n\t\tif (query.email) {\n\t\t\tconst searchEmail = query.email.toLowerCase()\n\t\t\tfiltered = filtered.filter((u) => u.email.toLowerCase().includes(searchEmail))\n\t\t}\n\n\t\tif (query.emailVerified !== undefined) {\n\t\t\tfiltered = filtered.filter((u) => u.emailVerified === query.emailVerified)\n\t\t}\n\n\t\tconst total = filtered.length\n\t\tconst data = filtered.slice(offset, offset + limit).map(toAuthUser)\n\n\t\treturn { data, total, limit, offset }\n\t}\n\n\t/**\n\t * Update a user's profile (admin-level).\n\t */\n\tasync updateUser(adminId: string, userId: string, updates: AdminUserUpdate): Promise<AuthUser> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\tthrow new AdminUserNotFoundError(userId)\n\t\t}\n\n\t\tif (updates.name !== undefined) {\n\t\t\tuser.name = updates.name\n\t\t}\n\t\tif (updates.emailVerified !== undefined) {\n\t\t\tuser.emailVerified = updates.emailVerified\n\t\t}\n\t\tif (updates.email !== undefined) {\n\t\t\tuser.email = updates.email.toLowerCase().trim()\n\t\t}\n\n\t\tawait this.userStore.update(user)\n\n\t\tawait this.audit('user.update', adminId, userId, 'user', { updates })\n\n\t\treturn toAuthUser(user)\n\t}\n\n\t/**\n\t * Delete a user and all associated sessions.\n\t */\n\tasync deleteUser(adminId: string, userId: string): Promise<void> {\n\t\tconst user = await this.userStore.findById(userId)\n\t\tif (!user) {\n\t\t\tthrow new AdminUserNotFoundError(userId)\n\t\t}\n\n\t\t// Revoke all sessions first\n\t\tif (this.sessionStore) {\n\t\t\tawait this.sessionStore.deleteAllForUser(userId)\n\t\t}\n\n\t\tawait this.userStore.delete(userId)\n\n\t\tawait this.audit('user.delete', adminId, userId, 'user')\n\t}\n\n\t/**\n\t * Get all active sessions for a user.\n\t */\n\tasync getUserSessions(userId: string): Promise<Session[]> {\n\t\tif (!this.sessionStore) return []\n\t\treturn this.sessionStore.listByUserId(userId)\n\t}\n\n\t/**\n\t * Revoke all sessions for a user.\n\t */\n\tasync revokeUserSessions(adminId: string, userId: string): Promise<number> {\n\t\tif (!this.sessionStore) return 0\n\n\t\tconst count = await this.sessionStore.deleteAllForUser(userId)\n\n\t\tawait this.audit('session.revoke_all', adminId, userId, 'user', { sessionsRevoked: count })\n\n\t\treturn count\n\t}\n\n\t/**\n\t * Revoke a specific session.\n\t */\n\tasync revokeSession(adminId: string, sessionId: string): Promise<void> {\n\t\tif (!this.sessionStore) return\n\n\t\tawait this.sessionStore.delete(sessionId)\n\n\t\tawait this.audit('session.revoke', adminId, sessionId, 'session')\n\t}\n\n\t/**\n\t * Get system statistics.\n\t */\n\tasync getStats(): Promise<{\n\t\ttotalUsers: number\n\t\tverifiedUsers: number\n\t\tunverifiedUsers: number\n\t}> {\n\t\tconst allUsers = await this.userStore.listAll()\n\t\tconst verified = allUsers.filter((u) => u.emailVerified).length\n\n\t\treturn {\n\t\t\ttotalUsers: allUsers.length,\n\t\t\tverifiedUsers: verified,\n\t\t\tunverifiedUsers: allUsers.length - verified,\n\t\t}\n\t}\n\n\t// --- Private ---\n\n\tprivate async audit(\n\t\taction: AuditAction,\n\t\tactorId: string,\n\t\ttargetId: string,\n\t\ttargetType: string,\n\t\tmetadata?: Record<string, unknown>,\n\t): Promise<void> {\n\t\tif (!this.auditLogger) return\n\t\tawait this.auditLogger.log({\n\t\t\taction,\n\t\t\tactorId,\n\t\t\tactorType: 'admin',\n\t\t\ttargetId,\n\t\t\ttargetType,\n\t\t\tmetadata,\n\t\t})\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction toAuthUser(stored: StoredUser): AuthUser {\n\treturn {\n\t\tid: stored.id,\n\t\temail: stored.email,\n\t\tname: stored.name,\n\t\temailVerified: stored.emailVerified,\n\t\tcreatedAt: stored.createdAt,\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Audit Log Types\n// ============================================================================\n\n/**\n * Actions that can be audited.\n */\nconst AUDIT_ACTIONS = [\n\t// Auth\n\t'user.signup',\n\t'user.signin',\n\t'user.signout',\n\t'user.token_refresh',\n\t'user.password_change',\n\t'user.password_reset_request',\n\t'user.password_reset',\n\t'user.email_verify',\n\t// MFA\n\t'mfa.enable',\n\t'mfa.verify_setup',\n\t'mfa.verify',\n\t'mfa.recovery_used',\n\t'mfa.recovery_regenerate',\n\t'mfa.disable',\n\t// Session\n\t'session.create',\n\t'session.revoke',\n\t'session.revoke_all',\n\t'session.expired',\n\t// OAuth\n\t'oauth.authorize',\n\t'oauth.callback',\n\t'oauth.link',\n\t'oauth.unlink',\n\t// User management\n\t'user.create',\n\t'user.update',\n\t'user.delete',\n\t'user.suspend',\n\t'user.unsuspend',\n\t// Org management\n\t'org.create',\n\t'org.update',\n\t'org.delete',\n\t'org.member_add',\n\t'org.member_remove',\n\t'org.member_role_change',\n\t'org.ownership_transfer',\n\t'org.invitation_create',\n\t'org.invitation_accept',\n\t'org.invitation_revoke',\n\t// Admin\n\t'admin.user_lookup',\n\t'admin.impersonate',\n\t'admin.config_change',\n] as const\n\nexport type AuditAction = (typeof AUDIT_ACTIONS)[number]\n\n/**\n * An audit log entry.\n */\nexport interface AuditEntry {\n\t/** Unique entry ID */\n\tid: string\n\t/** When this event occurred */\n\ttimestamp: number\n\t/** The action that was performed */\n\taction: AuditAction\n\t/** Who performed the action (user ID, \"system\", or \"anonymous\") */\n\tactorId: string\n\t/** The type of actor */\n\tactorType: 'user' | 'admin' | 'system'\n\t/** The target of the action (e.g., user ID, org ID, session ID) */\n\ttargetId: string | null\n\t/** The type of target */\n\ttargetType: string | null\n\t/** IP address of the actor */\n\tipAddress: string | null\n\t/** User agent */\n\tuserAgent: string | null\n\t/** Whether the action succeeded */\n\tsuccess: boolean\n\t/** Error message if the action failed */\n\terrorMessage: string | null\n\t/** Additional structured context */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * Query parameters for searching audit logs.\n */\nexport interface AuditLogQuery {\n\t/** Filter by actor ID */\n\tactorId?: string\n\t/** Filter by target ID */\n\ttargetId?: string\n\t/** Filter by action(s) */\n\tactions?: AuditAction[]\n\t/** Filter by success/failure */\n\tsuccess?: boolean\n\t/** Start time (inclusive) */\n\tstartTime?: number\n\t/** End time (inclusive) */\n\tendTime?: number\n\t/** Maximum number of entries to return */\n\tlimit?: number\n\t/** Offset for pagination */\n\toffset?: number\n}\n\n/**\n * Store for audit log entries.\n */\nexport interface AuditLogStore {\n\t/** Append an audit entry */\n\tappend(entry: AuditEntry): Promise<void>\n\t/** Query audit entries */\n\tquery(params: AuditLogQuery): Promise<AuditEntry[]>\n\t/** Count matching entries */\n\tcount(params: AuditLogQuery): Promise<number>\n\t/** Delete entries older than a given timestamp */\n\tpurgeOlderThan(timestamp: number): Promise<number>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class AuditLogError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'AuditLogError'\n\t}\n}\n\n// ============================================================================\n// InMemoryAuditLogStore\n// ============================================================================\n\n/**\n * In-memory audit log store for development and testing.\n */\nexport class InMemoryAuditLogStore implements AuditLogStore {\n\tprivate readonly entries: AuditEntry[] = []\n\n\tasync append(entry: AuditEntry): Promise<void> {\n\t\tthis.entries.push({ ...entry })\n\t}\n\n\tasync query(params: AuditLogQuery): Promise<AuditEntry[]> {\n\t\tlet results = this.filterEntries(params)\n\n\t\t// Sort by timestamp descending (newest first)\n\t\tresults.sort((a, b) => b.timestamp - a.timestamp)\n\n\t\t// Pagination\n\t\tconst offset = params.offset ?? 0\n\t\tconst limit = params.limit ?? 100\n\t\tresults = results.slice(offset, offset + limit)\n\n\t\treturn results.map((e) => ({ ...e }))\n\t}\n\n\tasync count(params: AuditLogQuery): Promise<number> {\n\t\treturn this.filterEntries(params).length\n\t}\n\n\tasync purgeOlderThan(timestamp: number): Promise<number> {\n\t\tconst initialLength = this.entries.length\n\t\tlet writeIndex = 0\n\t\tfor (let i = 0; i < this.entries.length; i++) {\n\t\t\tif (this.entries[i]!.timestamp >= timestamp) {\n\t\t\t\tthis.entries[writeIndex] = this.entries[i]!\n\t\t\t\twriteIndex++\n\t\t\t}\n\t\t}\n\t\tthis.entries.length = writeIndex\n\t\treturn initialLength - writeIndex\n\t}\n\n\tprivate filterEntries(params: AuditLogQuery): AuditEntry[] {\n\t\treturn this.entries.filter((e) => {\n\t\t\tif (params.actorId !== undefined && e.actorId !== params.actorId) return false\n\t\t\tif (params.targetId !== undefined && e.targetId !== params.targetId) return false\n\t\t\tif (params.actions !== undefined && !params.actions.includes(e.action)) return false\n\t\t\tif (params.success !== undefined && e.success !== params.success) return false\n\t\t\tif (params.startTime !== undefined && e.timestamp < params.startTime) return false\n\t\t\tif (params.endTime !== undefined && e.timestamp > params.endTime) return false\n\t\t\treturn true\n\t\t})\n\t}\n}\n\n// ============================================================================\n// AuditLogger\n// ============================================================================\n\n/**\n * Structured audit logger for authentication events.\n *\n * Records all security-relevant actions for compliance and debugging.\n * Designed to be plugged into the auth system at key decision points.\n *\n * @example\n * ```typescript\n * const auditLog = new AuditLogger({\n * store: new InMemoryAuditLogStore(),\n * })\n *\n * await auditLog.log({\n * action: 'user.signin',\n * actorId: 'user-123',\n * actorType: 'user',\n * success: true,\n * ipAddress: '192.168.1.1',\n * })\n *\n * const entries = await auditLog.query({ actorId: 'user-123', limit: 50 })\n * ```\n */\nexport class AuditLogger {\n\tprivate readonly store: AuditLogStore\n\tprivate readonly retentionMs: number | null\n\n\tconstructor(config: { store: AuditLogStore; retentionDays?: number }) {\n\t\tthis.store = config.store\n\t\tthis.retentionMs = config.retentionDays\n\t\t\t? config.retentionDays * 24 * 60 * 60 * 1000\n\t\t\t: null\n\t}\n\n\t/**\n\t * Log an audit event.\n\t */\n\tasync log(params: {\n\t\taction: AuditAction\n\t\tactorId: string\n\t\tactorType?: 'user' | 'admin' | 'system'\n\t\ttargetId?: string\n\t\ttargetType?: string\n\t\tipAddress?: string\n\t\tuserAgent?: string\n\t\tsuccess?: boolean\n\t\terrorMessage?: string\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<AuditEntry> {\n\t\tconst entry: AuditEntry = {\n\t\t\tid: generateAuditId(),\n\t\t\ttimestamp: Date.now(),\n\t\t\taction: params.action,\n\t\t\tactorId: params.actorId,\n\t\t\tactorType: params.actorType ?? 'user',\n\t\t\ttargetId: params.targetId ?? null,\n\t\t\ttargetType: params.targetType ?? null,\n\t\t\tipAddress: params.ipAddress ?? null,\n\t\t\tuserAgent: params.userAgent ?? null,\n\t\t\tsuccess: params.success ?? true,\n\t\t\terrorMessage: params.errorMessage ?? null,\n\t\t\tmetadata: params.metadata,\n\t\t}\n\n\t\tawait this.store.append(entry)\n\t\treturn entry\n\t}\n\n\t/**\n\t * Query audit log entries.\n\t */\n\tasync query(params: AuditLogQuery): Promise<AuditEntry[]> {\n\t\treturn this.store.query(params)\n\t}\n\n\t/**\n\t * Count matching audit entries.\n\t */\n\tasync count(params: AuditLogQuery): Promise<number> {\n\t\treturn this.store.count(params)\n\t}\n\n\t/**\n\t * Purge old entries based on retention policy.\n\t * Returns the number of entries purged.\n\t */\n\tasync purge(): Promise<number> {\n\t\tif (this.retentionMs === null) return 0\n\t\tconst cutoff = Date.now() - this.retentionMs\n\t\treturn this.store.purgeOlderThan(cutoff)\n\t}\n\n\t/**\n\t * Get recent activity for a user.\n\t */\n\tasync getUserActivity(userId: string, limit: number = 50): Promise<AuditEntry[]> {\n\t\treturn this.store.query({ actorId: userId, limit })\n\t}\n\n\t/**\n\t * Get failed login attempts for a user within a time window.\n\t */\n\tasync getFailedLogins(userId: string, windowMs: number): Promise<AuditEntry[]> {\n\t\treturn this.store.query({\n\t\t\ttargetId: userId,\n\t\t\tactions: ['user.signin'],\n\t\t\tsuccess: false,\n\t\t\tstartTime: Date.now() - windowMs,\n\t\t})\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateAuditId(): string {\n\tconst bytes = new Uint8Array(16)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n","import { KoraError } from '@korajs/core'\n\n// ============================================================================\n// Webhook Types\n// ============================================================================\n\n/**\n * Events that can trigger webhooks.\n */\nconst WEBHOOK_EVENTS = [\n\t'user.created',\n\t'user.updated',\n\t'user.deleted',\n\t'user.signin',\n\t'user.signout',\n\t'user.password_changed',\n\t'user.email_verified',\n\t'mfa.enabled',\n\t'mfa.disabled',\n\t'session.created',\n\t'session.revoked',\n\t'org.created',\n\t'org.updated',\n\t'org.deleted',\n\t'org.member_added',\n\t'org.member_removed',\n\t'org.invitation_created',\n\t'org.invitation_accepted',\n] as const\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number]\n\n/**\n * A webhook endpoint configuration.\n */\nexport interface WebhookEndpoint {\n\t/** Unique endpoint ID */\n\tid: string\n\t/** Destination URL */\n\turl: string\n\t/** Events this endpoint subscribes to */\n\tevents: WebhookEvent[]\n\t/** Signing secret for HMAC verification */\n\tsecret: string\n\t/** Whether this endpoint is active */\n\tactive: boolean\n\t/** When this endpoint was created */\n\tcreatedAt: number\n\t/** Custom metadata */\n\tmetadata?: Record<string, unknown>\n}\n\n/**\n * A webhook delivery attempt.\n */\nexport interface WebhookDelivery {\n\t/** Unique delivery ID */\n\tid: string\n\t/** Endpoint ID */\n\tendpointId: string\n\t/** Event that triggered this delivery */\n\tevent: WebhookEvent\n\t/** Request payload (JSON) */\n\tpayload: string\n\t/** HTTP response status (null if network error) */\n\tresponseStatus: number | null\n\t/** Whether the delivery was successful (2xx response) */\n\tsuccess: boolean\n\t/** Error message if delivery failed */\n\terror: string | null\n\t/** Number of attempts made */\n\tattempts: number\n\t/** When the delivery was first attempted */\n\tcreatedAt: number\n\t/** When the delivery was last attempted */\n\tlastAttemptAt: number\n}\n\n/**\n * Payload sent to webhook endpoints.\n */\nexport interface WebhookPayload {\n\t/** Unique event ID */\n\tid: string\n\t/** Event type */\n\tevent: WebhookEvent\n\t/** When the event occurred */\n\ttimestamp: number\n\t/** Event data */\n\tdata: Record<string, unknown>\n}\n\n/**\n * Store for webhook endpoints and deliveries.\n */\nexport interface WebhookStore {\n\t/** Save a webhook endpoint */\n\tsaveEndpoint(endpoint: WebhookEndpoint): Promise<void>\n\t/** Get an endpoint by ID */\n\tgetEndpoint(id: string): Promise<WebhookEndpoint | null>\n\t/** List all endpoints */\n\tlistEndpoints(): Promise<WebhookEndpoint[]>\n\t/** Delete an endpoint */\n\tdeleteEndpoint(id: string): Promise<void>\n\t/** Save a delivery record */\n\tsaveDelivery(delivery: WebhookDelivery): Promise<void>\n\t/** List recent deliveries for an endpoint */\n\tlistDeliveries(endpointId: string, limit?: number): Promise<WebhookDelivery[]>\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class WebhookError extends KoraError {\n\tconstructor(message: string, code: string, context?: Record<string, unknown>) {\n\t\tsuper(message, code, context)\n\t\tthis.name = 'WebhookError'\n\t}\n}\n\nexport class WebhookEndpointNotFoundError extends WebhookError {\n\tconstructor(id: string) {\n\t\tsuper(`Webhook endpoint \"${id}\" not found.`, 'WEBHOOK_ENDPOINT_NOT_FOUND', { id })\n\t}\n}\n\n// ============================================================================\n// InMemoryWebhookStore\n// ============================================================================\n\nexport class InMemoryWebhookStore implements WebhookStore {\n\tprivate readonly endpoints = new Map<string, WebhookEndpoint>()\n\tprivate readonly deliveries: WebhookDelivery[] = []\n\n\tasync saveEndpoint(endpoint: WebhookEndpoint): Promise<void> {\n\t\tthis.endpoints.set(endpoint.id, { ...endpoint })\n\t}\n\n\tasync getEndpoint(id: string): Promise<WebhookEndpoint | null> {\n\t\tconst ep = this.endpoints.get(id)\n\t\treturn ep ? { ...ep } : null\n\t}\n\n\tasync listEndpoints(): Promise<WebhookEndpoint[]> {\n\t\treturn [...this.endpoints.values()].map((e) => ({ ...e }))\n\t}\n\n\tasync deleteEndpoint(id: string): Promise<void> {\n\t\tthis.endpoints.delete(id)\n\t}\n\n\tasync saveDelivery(delivery: WebhookDelivery): Promise<void> {\n\t\tconst existing = this.deliveries.findIndex((d) => d.id === delivery.id)\n\t\tif (existing >= 0) {\n\t\t\tthis.deliveries[existing] = { ...delivery }\n\t\t} else {\n\t\t\tthis.deliveries.push({ ...delivery })\n\t\t}\n\t}\n\n\tasync listDeliveries(endpointId: string, limit: number = 50): Promise<WebhookDelivery[]> {\n\t\treturn this.deliveries\n\t\t\t.filter((d) => d.endpointId === endpointId)\n\t\t\t.sort((a, b) => b.createdAt - a.createdAt)\n\t\t\t.slice(0, limit)\n\t\t\t.map((d) => ({ ...d }))\n\t}\n}\n\n// ============================================================================\n// WebhookManager\n// ============================================================================\n\nconst MAX_RETRIES = 3\nconst RETRY_DELAYS_MS = [1000, 5000, 30000] // 1s, 5s, 30s\n\n/**\n * Manages webhook registrations and event delivery.\n *\n * Sends HTTP POST requests to registered endpoints when auth events occur.\n * Includes HMAC-SHA256 signature verification and retry logic.\n *\n * @example\n * ```typescript\n * const webhooks = new WebhookManager({\n * store: new InMemoryWebhookStore(),\n * })\n *\n * // Register an endpoint\n * const endpoint = await webhooks.register({\n * url: 'https://myapp.com/webhooks/auth',\n * events: ['user.created', 'user.signin'],\n * })\n *\n * // Dispatch an event (usually called internally by auth system)\n * await webhooks.dispatch('user.created', { userId: 'user-123', email: 'alice@example.com' })\n * ```\n */\nexport class WebhookManager {\n\tprivate readonly store: WebhookStore\n\tprivate readonly fetchFn: typeof globalThis.fetch\n\n\tconstructor(config: { store: WebhookStore; fetch?: typeof globalThis.fetch }) {\n\t\tthis.store = config.store\n\t\tthis.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis)\n\t}\n\n\t/**\n\t * Register a new webhook endpoint.\n\t */\n\tasync register(params: {\n\t\turl: string\n\t\tevents: WebhookEvent[]\n\t\tmetadata?: Record<string, unknown>\n\t}): Promise<WebhookEndpoint> {\n\t\tconst endpoint: WebhookEndpoint = {\n\t\t\tid: generateId(),\n\t\t\turl: params.url,\n\t\t\tevents: [...params.events],\n\t\t\tsecret: generateSecret(),\n\t\t\tactive: true,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tmetadata: params.metadata,\n\t\t}\n\n\t\tawait this.store.saveEndpoint(endpoint)\n\t\treturn endpoint\n\t}\n\n\t/**\n\t * Update a webhook endpoint.\n\t */\n\tasync update(\n\t\tid: string,\n\t\tupdates: { url?: string; events?: WebhookEvent[]; active?: boolean },\n\t): Promise<WebhookEndpoint> {\n\t\tconst endpoint = await this.store.getEndpoint(id)\n\t\tif (!endpoint) {\n\t\t\tthrow new WebhookEndpointNotFoundError(id)\n\t\t}\n\n\t\tif (updates.url !== undefined) endpoint.url = updates.url\n\t\tif (updates.events !== undefined) endpoint.events = [...updates.events]\n\t\tif (updates.active !== undefined) endpoint.active = updates.active\n\n\t\tawait this.store.saveEndpoint(endpoint)\n\t\treturn endpoint\n\t}\n\n\t/**\n\t * Delete a webhook endpoint.\n\t */\n\tasync remove(id: string): Promise<void> {\n\t\tawait this.store.deleteEndpoint(id)\n\t}\n\n\t/**\n\t * List all webhook endpoints.\n\t */\n\tasync list(): Promise<WebhookEndpoint[]> {\n\t\treturn this.store.listEndpoints()\n\t}\n\n\t/**\n\t * Get a specific endpoint.\n\t */\n\tasync get(id: string): Promise<WebhookEndpoint> {\n\t\tconst endpoint = await this.store.getEndpoint(id)\n\t\tif (!endpoint) {\n\t\t\tthrow new WebhookEndpointNotFoundError(id)\n\t\t}\n\t\treturn endpoint\n\t}\n\n\t/**\n\t * Get recent deliveries for an endpoint.\n\t */\n\tasync getDeliveries(endpointId: string, limit?: number): Promise<WebhookDelivery[]> {\n\t\treturn this.store.listDeliveries(endpointId, limit)\n\t}\n\n\t/**\n\t * Dispatch an event to all matching webhook endpoints.\n\t * Delivery is best-effort with retries.\n\t */\n\tasync dispatch(\n\t\tevent: WebhookEvent,\n\t\tdata: Record<string, unknown>,\n\t): Promise<void> {\n\t\tconst endpoints = await this.store.listEndpoints()\n\t\tconst matching = endpoints.filter(\n\t\t\t(ep) => ep.active && ep.events.includes(event),\n\t\t)\n\n\t\tconst payload: WebhookPayload = {\n\t\t\tid: generateId(),\n\t\t\tevent,\n\t\t\ttimestamp: Date.now(),\n\t\t\tdata,\n\t\t}\n\n\t\tconst payloadJson = JSON.stringify(payload)\n\n\t\tawait Promise.allSettled(\n\t\t\tmatching.map((ep) => this.deliverToEndpoint(ep, payloadJson, event)),\n\t\t)\n\t}\n\n\t// --- Private ---\n\n\tprivate async deliverToEndpoint(\n\t\tendpoint: WebhookEndpoint,\n\t\tpayloadJson: string,\n\t\tevent: WebhookEvent,\n\t): Promise<void> {\n\t\tconst signature = await signPayload(payloadJson, endpoint.secret)\n\n\t\tconst delivery: WebhookDelivery = {\n\t\t\tid: generateId(),\n\t\t\tendpointId: endpoint.id,\n\t\t\tevent,\n\t\t\tpayload: payloadJson,\n\t\t\tresponseStatus: null,\n\t\t\tsuccess: false,\n\t\t\terror: null,\n\t\t\tattempts: 0,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tlastAttemptAt: Date.now(),\n\t\t}\n\n\t\tfor (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n\t\t\tdelivery.attempts = attempt + 1\n\t\t\tdelivery.lastAttemptAt = Date.now()\n\n\t\t\ttry {\n\t\t\t\tconst response = await this.fetchFn(endpoint.url, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'X-Webhook-Signature': signature,\n\t\t\t\t\t\t'X-Webhook-Event': event,\n\t\t\t\t\t\t'X-Webhook-Delivery': delivery.id,\n\t\t\t\t\t},\n\t\t\t\t\tbody: payloadJson,\n\t\t\t\t})\n\n\t\t\t\tdelivery.responseStatus = response.status\n\t\t\t\tdelivery.success = response.ok\n\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.store.saveDelivery(delivery)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tdelivery.error = `HTTP ${response.status}`\n\t\t\t} catch (err) {\n\t\t\t\tdelivery.error = err instanceof Error ? err.message : 'Network error'\n\t\t\t}\n\n\t\t\t// Wait before retrying (skip wait on last attempt)\n\t\t\tif (attempt < MAX_RETRIES - 1) {\n\t\t\t\tawait delay(RETRY_DELAYS_MS[attempt] ?? 1000)\n\t\t\t}\n\t\t}\n\n\t\t// All retries exhausted\n\t\tawait this.store.saveDelivery(delivery)\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction generateId(): string {\n\tconst bytes = new Uint8Array(16)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn hex\n}\n\nfunction generateSecret(): string {\n\tconst bytes = new Uint8Array(32)\n\tglobalThis.crypto.getRandomValues(bytes)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn `whsec_${hex}`\n}\n\n/**\n * Sign a payload with HMAC-SHA256 for webhook verification.\n */\nasync function signPayload(payload: string, secret: string): Promise<string> {\n\tconst encoder = new TextEncoder()\n\tconst key = await globalThis.crypto.subtle.importKey(\n\t\t'raw',\n\t\tencoder.encode(secret),\n\t\t{ name: 'HMAC', hash: 'SHA-256' },\n\t\tfalse,\n\t\t['sign'],\n\t)\n\tconst signature = await globalThis.crypto.subtle.sign(\n\t\t'HMAC',\n\t\tkey,\n\t\tencoder.encode(payload),\n\t)\n\tconst bytes = new Uint8Array(signature)\n\tlet hex = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\thex += bytes[i]!.toString(16).padStart(2, '0')\n\t}\n\treturn `sha256=${hex}`\n}\n\n/**\n * Verify a webhook payload signature.\n * Useful for consumers of webhooks to verify authenticity.\n */\nexport async function verifyWebhookSignature(\n\tpayload: string,\n\tsignature: string,\n\tsecret: string,\n): Promise<boolean> {\n\tconst expected = await signPayload(payload, secret)\n\t// Timing-safe comparison\n\tif (expected.length !== signature.length) return false\n\tlet result = 0\n\tfor (let i = 0; i < expected.length; i++) {\n\t\tresult |= expected.charCodeAt(i) ^ signature.charCodeAt(i)\n\t}\n\treturn result === 0\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],"mappings":"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","names":["randomBytes","KoraError","randomUUID","randomBytes","KoraError","KoraError","MIN_PASSWORD_LENGTH","MAX_PASSWORD_LENGTH","verifyPassword","KoraError","DEFAULT_TOKEN_TTL_MS","DEFAULT_MAX_REQUESTS","generateSecureToken","randomUUID","randomUUID","require","randomUUID","randomUUID","rowToStoredUser","rowToDevice","KoraError","KoraError","randomBytes","KoraError","KoraError","randomBytes","KoraError","isValidEmail","org","membership","KoraError","KoraError","KoraError","KoraError","KoraError","toAuthUser","KoraError","KoraError","KoraError","KoraError","generateId","generateSecret"]}
|