@luckystack/login 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sessionAdapter.ts","../src/logout.ts","../src/session.ts","../src/index.ts","../src/oauthProviders.ts","../src/redirectResolver.ts","../src/login.ts","../src/userAdapter.ts","../src/passwordPolicy.ts","../src/data/commonPasswords.ts","../src/passwordReset.ts","../src/forgotPassword.ts","../src/emailChange.ts","../src/emailChangeNotification.ts"],"sourcesContent":["//? Session storage DI surface. The default adapter persists session\r\n//? records in Redis (same behaviour as before extraction). Consumers can\r\n//? plug in DynamoDB / Memcached / Postgres-table / signed-JWT-stateless\r\n//? adapters by implementing `SessionAdapter` and calling\r\n//? `registerSessionAdapter(...)` at boot.\r\n//?\r\n//? The adapter owns ONLY storage primitives. Framework-level concerns\r\n//? (CSRF minting, hook dispatch, socket emission, single-session\r\n//? enforcement) stay in session.ts and call the adapter to read/write.\r\n\r\nimport { redis, tryCatch, formatKey } from '@luckystack/core';\r\n\r\nexport interface SessionAdapter {\r\n /** Human-readable identifier (used in logs/diagnostics). */\r\n name: string;\r\n\r\n /** Read the raw stored session string for `token`. Null when missing. */\r\n getRaw(token: string): Promise<string | null>;\r\n\r\n /**\r\n * Write the raw session string for `token` with a TTL. Implementations\r\n * MUST honour the TTL — sessions without one would never expire.\r\n */\r\n setRaw(token: string, value: string, ttlSeconds: number): Promise<void>;\r\n\r\n /** Delete the session record for `token`. Idempotent. */\r\n delete(token: string): Promise<void>;\r\n\r\n /**\r\n * Refresh the TTL on an existing session record (sliding-window\r\n * expiration). Returns true when the underlying record existed and the\r\n * TTL was applied, false otherwise. Implementations must not create a\r\n * record on miss.\r\n */\r\n expire(token: string, ttlSeconds: number): Promise<boolean>;\r\n\r\n /** Remaining TTL in seconds (or null when the record is missing). */\r\n ttl(token: string): Promise<number | null>;\r\n\r\n // ---- Active-tokens-per-user (for single-session enforcement and\r\n // revokeUserSessions). Implementations need a set-like structure keyed\r\n // by userId. Memcached/edge-KV adapters can serialise as a comma-list\r\n // if no native set type is available. ----\r\n\r\n trackActive(userId: string, token: string, ttlSeconds: number): Promise<void>;\r\n untrackActive(userId: string, token: string): Promise<void>;\r\n listActive(userId: string): Promise<string[]>;\r\n\r\n /**\r\n * Admin walk: yield every active session record. Optional because\r\n * non-scannable backends (signed-JWT-stateless, log-only) cannot\r\n * enumerate. Callers fall back to a per-user listActive when omitted.\r\n */\r\n listAll?(): Promise<{ token: string; raw: string }[]>;\r\n}\r\n\r\n/**\r\n * Default adapter: Redis-backed. Key layout matches what the framework\r\n * has used since the first release, so registering this explicitly is\r\n * equivalent to not registering anything.\r\n *\r\n * Imports `redis` and `tryCatch` from `@luckystack/core`. Project name\r\n * + prefix are resolved at call time so projectConfig changes take\r\n * effect without reloading this module.\r\n */\r\n\r\nconst sessionKey = (token: string): string => formatKey('-session', token);\r\nconst activeUsersKey = (userId: string): string => formatKey('-activeUsers', userId);\r\n\r\nexport const redisSessionAdapter: SessionAdapter = {\r\n name: 'redis',\r\n\r\n async getRaw(token) {\r\n const [error, value] = await tryCatch(async () => await redis.get(sessionKey(token)));\r\n if (error) return null;\r\n return value ?? null;\r\n },\r\n\r\n async setRaw(token, value, ttlSeconds) {\r\n const key = sessionKey(token);\r\n await redis.set(key, value);\r\n await redis.expire(key, ttlSeconds);\r\n },\r\n\r\n async delete(token) {\r\n await redis.del(sessionKey(token));\r\n },\r\n\r\n async expire(token, ttlSeconds) {\r\n const result = await redis.expire(sessionKey(token), ttlSeconds);\r\n return result === 1;\r\n },\r\n\r\n async ttl(token) {\r\n const [error, value] = await tryCatch(async () => await redis.ttl(sessionKey(token)));\r\n if (error) return null;\r\n return typeof value === 'number' ? value : null;\r\n },\r\n\r\n async trackActive(userId, token, ttlSeconds) {\r\n const key = activeUsersKey(userId);\r\n await redis.sadd(key, token);\r\n await redis.expire(key, ttlSeconds);\r\n },\r\n\r\n async untrackActive(userId, token) {\r\n await redis.srem(activeUsersKey(userId), token);\r\n },\r\n\r\n async listActive(userId) {\r\n const [error, value] = await tryCatch(async () => await redis.smembers(activeUsersKey(userId)));\r\n if (error || !Array.isArray(value)) return [];\r\n return value;\r\n },\r\n\r\n async listAll() {\r\n const pattern = `${formatKey('-session', '')}:*`;\r\n const collected: { token: string; raw: string }[] = [];\r\n let cursor = '0';\r\n\r\n do {\r\n const [scanError, scanResponse] = await tryCatch(\r\n async () => await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100),\r\n );\r\n if (scanError || !scanResponse) break;\r\n\r\n cursor = scanResponse[0];\r\n const keys = scanResponse[1];\r\n\r\n if (Array.isArray(keys) && keys.length > 0) {\r\n const values = await Promise.all(keys.map((k) => redis.get(k)));\r\n for (const [idx, k] of keys.entries()) {\r\n const raw = values[idx];\r\n if (raw) {\r\n collected.push({\r\n token: k.replace(`${formatKey('-session', '')}:`, ''),\r\n raw,\r\n });\r\n }\r\n }\r\n }\r\n } while (cursor !== '0');\r\n\r\n return collected;\r\n },\r\n};\r\n\r\nlet activeAdapter: SessionAdapter = redisSessionAdapter;\r\n\r\n/**\r\n * Replace the active session storage backend. Call once at boot from\r\n * `luckystack/server/index.ts`, BEFORE the first login request. Common\r\n * use cases:\r\n *\r\n * - DynamoDB / Cosmos DB for serverless / edge deployments\r\n * - Postgres `sessions` table when you already operate Postgres\r\n * - signed-JWT-stateless (the adapter writes nothing, reads from the\r\n * JWT, and stores activeUsers per token in a small Redis hash)\r\n * - in-memory mock for integration tests\r\n *\r\n * Last-write-wins: subsequent calls overwrite the active adapter.\r\n */\r\nexport const registerSessionAdapter = (adapter: SessionAdapter): void => {\r\n activeAdapter = adapter;\r\n};\r\n\r\n/** Read the currently-active session adapter (defaults to Redis). */\r\nexport const getSessionAdapter = (): SessionAdapter => activeAdapter;\r\n","/* eslint-disable unicorn/no-abusive-eslint-disable */\r\n/* eslint-disable */\r\nimport { Socket } from \"socket.io\";\r\nimport { redis as redisClient, tryCatch, socketEventNames, dispatchHook, getLogger } from '@luckystack/core';\r\nimport { deleteSession, activeUsersKeyFor } from \"./session\";\r\n\r\nexport const logout = async ({ token, socket, userId, skipSessionDelete }: {\r\n token: string | null,\r\n socket: Socket | undefined,\r\n userId: string | null,\r\n skipSessionDelete?: boolean,\r\n}) => {\r\n const [error, result] = await tryCatch(async () => {\r\n if (!socket) {\r\n getLogger().warn('logout: invalid socket');\r\n return;\r\n }\r\n if (!token) {\r\n getLogger().warn('logout: no token provided');\r\n return;\r\n }\r\n\r\n const preLogoutResult = await dispatchHook('preLogout', { userId, token });\r\n if (preLogoutResult.stopped) {\r\n getLogger().warn('logout: aborted by preLogout hook', { errorCode: preLogoutResult.signal.errorCode });\r\n socket.emit(socketEventNames.logout, \"error\");\r\n return;\r\n }\r\n\r\n getLogger().debug(`logout: user ${userId ?? '?'}`, { token });\r\n\r\n if (!skipSessionDelete) {\r\n await deleteSession(token);\r\n }\r\n if (userId) {\r\n await redisClient.srem(activeUsersKeyFor(userId), token);\r\n }\r\n socket.leave(token);\r\n\r\n // Presence state (disconnectTimers, tempDisconnectedSockets) is cleaned up\r\n // by `@luckystack/presence`'s `postLogout` hook handler, registered at\r\n // server startup via `registerPresenceHooks()`. Keeps login → presence\r\n // a one-way dep: presence knows about login (uses getSession/deleteSession),\r\n // login does not know about presence.\r\n await dispatchHook('postLogout', { userId, token });\r\n return true;\r\n });\r\n if (error) {\r\n if (socket) {\r\n socket.emit(socketEventNames.logout, \"error\");\r\n }\r\n } else if (result && socket) {\r\n //? Opt-in logout tracing. This fires on EVERY successful logout, so an\r\n //? unconditional warn+stacktrace spams the server log. Set\r\n //? LUCKYSTACK_TRACE_SESSION_DELETES=1 when hunting a \"why was I logged out?\"\r\n //? bug to print the originating caller's stack; silent otherwise. The\r\n //? client still receives the success-emit and clears its session regardless.\r\n if (process.env.LUCKYSTACK_TRACE_SESSION_DELETES === '1') {\r\n getLogger().warn('[session] logout success — emitting logout to socket', {\r\n tokenPrefix: token ? token.slice(0, 8) : null,\r\n userId,\r\n socketId: socket.id,\r\n stack: new Error('logout invoked').stack,\r\n });\r\n }\r\n socket.emit(socketEventNames.logout, \"success\");\r\n }\r\n}\r\n","import type { BaseSessionLayout as SessionLayout } from \"./sessionLayout\";\r\nimport { randomBytes } from 'node:crypto';\r\nimport {\r\n socketEventNames,\r\n dispatchHook,\r\n getCsrfConfig,\r\n getLogger,\r\n getProjectConfig,\r\n formatKey,\r\n tryCatch,\r\n} from '@luckystack/core';\r\n\r\nimport { getSessionAdapter } from './sessionAdapter';\r\n\r\n//? Resolved at call time so project registration order doesn't matter.\r\n//? Session TTL comes from projectConfig and is honoured by every adapter\r\n//? (Redis default, or any custom adapter the consumer registers).\r\nconst getSessionTtl = (): number => 60 * 60 * 24 * getProjectConfig().session.expiryDays;\r\n\r\nconst isSessionLayout = (value: unknown): value is SessionLayout => {\r\n return value !== null && typeof value === 'object' && 'id' in value;\r\n};\r\n\r\nconst parseSessionLayout = (value: string): SessionLayout | null => {\r\n const parsed = JSON.parse(value) as unknown;\r\n return isSessionLayout(parsed) ? parsed : null;\r\n};\r\n\r\n/**\r\n * Save or update a user session through the active session adapter.\r\n *\r\n * @param token - The session token (unique identifier)\r\n * @param data - The session data to store\r\n * @param newUser - If true, this is a new login (triggers single-session enforcement)\r\n */\r\nconst saveSession = async (token: string, data: SessionLayout, newUser?: boolean): Promise<void> => {\r\n const [error] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n\r\n if (newUser) {\r\n const preSessionCreateResult = await dispatchHook('preSessionCreate', {\r\n token,\r\n user: data,\r\n persistent: !getProjectConfig().session.basedToken,\r\n });\r\n if (preSessionCreateResult.stopped) {\r\n getLogger().warn(`session create aborted by preSessionCreate hook`, { errorCode: preSessionCreateResult.signal.errorCode });\r\n return;\r\n }\r\n }\r\n\r\n //? Mint a CSRF token on first session write. Subsequent writes preserve\r\n //? the existing token so the client doesn't have to re-fetch on every\r\n //? session update. The token is rotated on logout via `deleteSession`.\r\n //? Token length is consumer-configurable via `registerCsrfConfig({ tokenLength })`.\r\n data.csrfToken ??= randomBytes(getCsrfConfig().tokenLength).toString('hex');\r\n\r\n const ttl = getSessionTtl();\r\n await adapter.setRaw(token, JSON.stringify(data), ttl);\r\n\r\n const userId = data.id;\r\n\r\n //? Always track active tokens BEFORE the io check — `trackActive` is a\r\n //? Redis-only write, independent of any live socket. Gating it behind a\r\n //? live io meant processes without a Socket.io instance (the test harness,\r\n //? background workers, CLI tasks) persisted the session but never populated\r\n //? the active-tokens set, so `listSessions` / `revokeUserSessions` saw\r\n //? nothing for that user.\r\n if (userId) {\r\n await adapter.trackActive(userId, token, ttl);\r\n }\r\n\r\n const { getIoInstance } = await import('@luckystack/core');\r\n const io = getIoInstance();\r\n //? Socket fanout + single-session enforcement below need a live io; skip\r\n //? them when there isn't one. The session is already persisted + tracked.\r\n if (!io) return;\r\n\r\n // Handle session-limit enforcement on new login. Logic:\r\n // 1. perUser = 'single' (default) OR legacy `allowMultiple: false` →\r\n // kick every prior session for this user.\r\n // 2. perUser = 'multiple' with `maxConcurrentPerUser` cap reached:\r\n // `onConflict === 'revokeOld'` kicks oldest until under cap,\r\n // `onConflict === 'rejectNew'` would refuse the new login (handled\r\n // at the login API layer, not here).\r\n // 3. perUser = 'multiple' with no cap → no kick.\r\n if (newUser && userId) {\r\n const sessionCfg = getProjectConfig().session;\r\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- BC shim: legacy `allowMultiple` still honored when set\r\n const effectivePerUser = sessionCfg.allowMultiple ? 'multiple' : sessionCfg.perUser;\r\n const cap = sessionCfg.maxConcurrentPerUser;\r\n\r\n const allTokens = await adapter.listActive(userId);\r\n // Exclude the current token — it was just added and has no socket room yet\r\n const previousTokens = allTokens.filter(t => t !== token);\r\n\r\n let tokensToKick: string[] = [];\r\n if (effectivePerUser === 'single') {\r\n tokensToKick = previousTokens;\r\n } else if (cap !== null && previousTokens.length + 1 > cap && sessionCfg.onConflict === 'revokeOld') {\r\n // Kick oldest until under cap. Without ordering metadata in the adapter,\r\n // we drop the head of the list — most adapters return insertion-order\r\n // for set-like structures, but consumers needing strict LRU should\r\n // implement a custom adapter that orders deterministically.\r\n const excess = previousTokens.length + 1 - cap;\r\n tokensToKick = previousTokens.slice(0, excess);\r\n }\r\n\r\n if (tokensToKick.length > 0) {\r\n const { logout } = await import('./logout');\r\n\r\n await Promise.all(tokensToKick.map(async (previousToken) => {\r\n const sockets = io.sockets.adapter.rooms.get(previousToken);\r\n if (sockets) {\r\n getLogger().debug(`Kicking previous session for user ${userId}`);\r\n // Optional UI notification before the disconnect lands.\r\n if (sessionCfg.notifyOldDeviceOnRevoke) {\r\n io.to(previousToken).emit(socketEventNames.sessionReplaced, JSON.stringify({\r\n reason: 'session-replaced',\r\n userId,\r\n }));\r\n }\r\n for (const socketId of sockets) {\r\n const socket = io.sockets.sockets.get(socketId);\r\n if (socket) {\r\n await logout({ token: previousToken, socket, userId });\r\n }\r\n }\r\n } else {\r\n // No active sockets, just clean up the adapter state\r\n await adapter.delete(previousToken);\r\n await adapter.untrackActive(userId, previousToken);\r\n }\r\n }));\r\n }\r\n }\r\n\r\n // Broadcast session updates to connected clients\r\n if (io.sockets.adapter.rooms.has(token)) {\r\n io.to(token).emit(socketEventNames.updateSession, JSON.stringify(data));\r\n }\r\n\r\n if (newUser) {\r\n await dispatchHook('postSessionCreate', { token, user: data, persistent: !getProjectConfig().session.basedToken });\r\n }\r\n }, undefined, { fn: 'saveSession', token });\r\n\r\n if (error) {\r\n getLogger().error('saveSession failed', error, { token });\r\n }\r\n};\r\n\r\n/**\r\n * Retrieve a user session through the active session adapter.\r\n *\r\n * @param token - The session token\r\n * @returns The session data or null if not found\r\n */\r\nconst getSession = async (token: string | null): Promise<SessionLayout | null> => {\r\n if (!token) return null;\r\n\r\n const [error, value] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n\r\n const raw = await adapter.getRaw(token);\r\n if (!raw) return null;\r\n\r\n const parsed = parseSessionLayout(raw);\r\n const userId = parsed?.id ?? null;\r\n\r\n // Sliding expiration: each successful authenticated access extends session lifetime.\r\n const newTtl = getSessionTtl();\r\n const oldTtl = await adapter.ttl(token);\r\n const preRefreshResult = await dispatchHook('preSessionRefresh', { token, userId, oldTtl, newTtl });\r\n\r\n //? A `preSessionRefresh` handler can stop the TTL extension (e.g. admin\r\n //? freezing a session pending review). When stopped, we skip the\r\n //? `adapter.expire` call and surface `applied: false` on the post payload\r\n //? so audit handlers see the same signal.\r\n let applied = false;\r\n if (!preRefreshResult.stopped) {\r\n applied = await adapter.expire(token, newTtl);\r\n }\r\n await dispatchHook('postSessionRefresh', {\r\n token,\r\n userId,\r\n oldTtl,\r\n newTtl,\r\n applied,\r\n });\r\n\r\n if (!parsed) return null;\r\n const merged: SessionLayout = { ...parsed, token };\r\n return merged;\r\n }, undefined, { fn: 'getSession', token });\r\n\r\n if (error) {\r\n getLogger().error('getSession failed', error, { token });\r\n return null;\r\n }\r\n return value;\r\n};\r\n\r\n/**\r\n * Delete a user session through the active adapter and notify connected\r\n * clients.\r\n *\r\n * @param token - The session token to delete\r\n * @returns true if successful\r\n */\r\nconst deleteSession = async (token: string): Promise<boolean> => {\r\n //? Opt-in spurious-delete tracing. `deleteSession` runs on EVERY legitimate\r\n //? logout / revokeSession / signOutEverywhere / deleteAccount, so an\r\n //? unconditional warn+stacktrace drowns the signal it's meant to surface and\r\n //? spams dev/test output with stacktrace-shaped lines that read like errors.\r\n //? Set LUCKYSTACK_TRACE_SESSION_DELETES=1 when hunting a \"why was I logged\r\n //? out?\" bug to print the originating caller's stack; silent otherwise.\r\n if (process.env.LUCKYSTACK_TRACE_SESSION_DELETES === '1') {\r\n getLogger().warn('[session] deleteSession invoked', {\r\n tokenPrefix: token.slice(0, 8),\r\n stack: new Error('deleteSession invoked').stack,\r\n });\r\n }\r\n\r\n const [error, ok] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n\r\n const raw = await adapter.getRaw(token);\r\n let resolvedUserId: string | null = null;\r\n\r\n if (raw) {\r\n resolvedUserId = parseSessionLayout(raw)?.id ?? null;\r\n }\r\n\r\n const preSessionDeleteResult = await dispatchHook('preSessionDelete', { token, userId: resolvedUserId });\r\n if (preSessionDeleteResult.stopped) {\r\n getLogger().warn(`session delete aborted by preSessionDelete hook`, { errorCode: preSessionDeleteResult.signal.errorCode });\r\n return false;\r\n }\r\n\r\n if (raw && resolvedUserId) {\r\n const { getIoInstance } = await import('@luckystack/core');\r\n const ioInstance = getIoInstance();\r\n\r\n // Reuse the same logout flow as single-session enforcement.\r\n if (ioInstance) {\r\n const { logout } = await import('./logout');\r\n const sockets = ioInstance.sockets.adapter.rooms.get(token);\r\n\r\n if (sockets) {\r\n await Promise.all([...sockets].map(async (socketId) => {\r\n const socket = ioInstance.sockets.sockets.get(socketId);\r\n if (!socket) return;\r\n\r\n await logout({\r\n token,\r\n socket,\r\n userId: resolvedUserId,\r\n skipSessionDelete: true,\r\n });\r\n }));\r\n }\r\n }\r\n\r\n await adapter.untrackActive(resolvedUserId, token);\r\n }\r\n\r\n await adapter.delete(token);\r\n await dispatchHook('postSessionDelete', { token, userId: resolvedUserId });\r\n return true;\r\n }, undefined, { fn: 'deleteSession', token });\r\n\r\n if (error) {\r\n getLogger().error('deleteSession failed', error, { token });\r\n return false;\r\n }\r\n return ok ?? false;\r\n};\r\n\r\n/**\r\n * Get all active sessions (admin utility). Returns empty when the active\r\n * adapter doesn't implement enumeration (signed-JWT-stateless, log-only).\r\n *\r\n * @returns Array of all session data\r\n */\r\nconst getAllSessions = async (): Promise<SessionLayout[]> => {\r\n const [error, sessions] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n if (!adapter.listAll) {\r\n getLogger().warn(`[session] getAllSessions: adapter '${adapter.name}' does not implement listAll — returning empty`);\r\n return [];\r\n }\r\n\r\n const rows = await adapter.listAll();\r\n const parsedSessions: SessionLayout[] = [];\r\n for (const { raw } of rows) {\r\n const parsed = parseSessionLayout(raw);\r\n if (parsed) parsedSessions.push(parsed);\r\n }\r\n return parsedSessions;\r\n }, undefined, { fn: 'getAllSessions' });\r\n\r\n if (error) {\r\n getLogger().error('getAllSessions failed', error);\r\n return [];\r\n }\r\n return sessions ?? [];\r\n};\r\n\r\n/**\r\n * Revoke every active session for a user, optionally keeping one alive.\r\n *\r\n * @param userId - The user whose sessions to revoke\r\n * @param exceptToken - If provided, this session is left untouched. Pass the\r\n * caller's own token when revoking after a password change so the user\r\n * isn't logged out of the device they just changed it from. Pass null/omit\r\n * to revoke everything (account deletion, \"sign out everywhere\", etc.).\r\n *\r\n * Each revocation goes through `deleteSession`, so connected sockets are\r\n * told to log out via the same flow as a normal logout. Returns the number\r\n * of sessions actually revoked.\r\n */\r\nconst revokeUserSessions = async (userId: string, exceptToken?: string | null): Promise<number> => {\r\n if (!userId) return 0;\r\n\r\n const adapter = getSessionAdapter();\r\n const tokens = await adapter.listActive(userId);\r\n const targets = exceptToken ? tokens.filter((t) => t !== exceptToken) : tokens;\r\n\r\n await Promise.all(targets.map((token) => deleteSession(token)));\r\n return targets.length;\r\n};\r\n\r\n//? Legacy key-builders preserved as exports for downstream code that\r\n//? assumed a stable Redis layout (admin tooling, dev REPL scripts). New\r\n//? callers should use the active SessionAdapter instead.\r\nconst sessionKeyFor = (token: string): string => formatKey('-session', token);\r\nconst activeUsersKeyFor = (userId: string): string => formatKey('-activeUsers', userId);\r\n\r\nexport { saveSession, getSession, deleteSession, getAllSessions, revokeUserSessions, sessionKeyFor, activeUsersKeyFor };\r\n","// Registers module augmentations on @luckystack/core for auth/session hooks.\r\nimport './hookPayloads';\r\n\r\nexport type { BaseSessionLayout, SessionLocation, AuthProps } from './sessionLayout';\r\nexport type {\r\n PreLoginPayload,\r\n PostLoginPayload,\r\n PreRegisterPayload,\r\n PostRegisterPayload,\r\n PreLogoutPayload,\r\n PostLogoutPayload,\r\n PreSessionCreatePayload,\r\n PostSessionCreatePayload,\r\n PreSessionDeletePayload,\r\n PostSessionDeletePayload,\r\n PreEmailChangePayload,\r\n PostEmailChangeRequestedPayload,\r\n PostEmailChangedPayload,\r\n} from './hookPayloads';\r\nexport { saveSession, getSession, deleteSession, getAllSessions, revokeUserSessions, sessionKeyFor, activeUsersKeyFor } from './session';\r\nexport { registerSessionAdapter, getSessionAdapter, redisSessionAdapter } from './sessionAdapter';\r\nexport type { SessionAdapter } from './sessionAdapter';\r\nexport { loginWithCredentials, loginCallback, createOAuthState, registerWithCredentials, loginWithCredentialsCore } from './login';\r\nexport { logout } from './logout';\r\n\r\n// Password-reset primitives. Used by the framework's `framework`-mode\r\n// forgot-password flow AND exported for consumers who picked `'custom'`.\r\nexport {\r\n createPasswordResetToken,\r\n consumePasswordResetToken,\r\n updatePasswordHash,\r\n verifyPassword,\r\n PasswordPolicyError,\r\n} from './passwordReset';\r\nexport { validatePassword } from './passwordPolicy';\r\nexport { sendPasswordResetEmail } from './forgotPassword';\r\n\r\n// Email-change primitives. Used by the framework's settings flow to confirm\r\n// a new email address before applying it (token mailed to NEW address proves\r\n// ownership of the new mailbox).\r\nexport {\r\n createEmailChangeToken,\r\n consumeEmailChangeToken,\r\n} from './emailChange';\r\nexport type { EmailChangePayload } from './emailChange';\r\nexport { sendEmailChangeConfirmation } from './emailChangeNotification';\r\n\r\n// OAuth provider registry + composable helpers.\r\nexport {\r\n registerOAuthProviders,\r\n getOAuthProviders,\r\n isFullOAuthProvider,\r\n credentialsProvider,\r\n googleProvider,\r\n githubProvider,\r\n discordProvider,\r\n facebookProvider,\r\n microsoftProvider,\r\n} from './oauthProviders';\r\nexport type {\r\n OAuthProvider,\r\n CredentialsProvider,\r\n FullOAuthProvider,\r\n} from './oauthProviders';\r\n\r\n// User adapter registry: lets consumers swap out the Prisma User model behind auth flows.\r\nexport {\r\n registerUserAdapter,\r\n getUserAdapter,\r\n isUserAdapterRegistered,\r\n defaultPrismaUserAdapter,\r\n} from './userAdapter';\r\nexport type {\r\n UserAdapter,\r\n UserAdapterCreateInput,\r\n UserRecord,\r\n} from './userAdapter';\r\n\r\n// Post-login redirect resolver — lets consumers compute the OAuth callback\r\n// destination dynamically (per-user, per-tenant, per-provider).\r\nexport {\r\n registerPostLoginRedirect,\r\n getPostLoginRedirect,\r\n} from './redirectResolver';\r\nexport type {\r\n PostLoginRedirectResolver,\r\n PostLoginRedirectInput,\r\n} from './redirectResolver';\r\n","//? OAuth provider registry. Replaces the previously hardcoded provider list\r\n//? in `loginConfig.ts`. Consumers register providers from their\r\n//? `luckystack/login/oauthProviders.ts` overlay file at boot.\r\n//?\r\n//? Two-layer surface:\r\n//? 1. `registerOAuthProviders([...])` — accept an array of provider definitions.\r\n//? 2. Built-in helpers (`googleProvider`, `githubProvider`, ...) so the common\r\n//? case is one function call per provider with only the secrets + callback.\r\n//?\r\n//? Custom providers (Okta, Apple, X, etc.) plug in as raw `OAuthProvider`\r\n//? objects alongside the helpers.\r\n\r\nimport { tryCatch } from '@luckystack/core';\r\n\r\ntype OAuthUserData = Record<string, unknown>;\r\n\r\nexport const asOAuthUserData = (value: unknown): OAuthUserData => {\r\n if (value && typeof value === 'object') {\r\n return value as OAuthUserData;\r\n }\r\n return {};\r\n};\r\n\r\nexport interface CredentialsProvider {\r\n name: 'credentials';\r\n}\r\n\r\nexport interface FullOAuthProvider {\r\n name: string;\r\n clientID: string;\r\n clientSecret: string;\r\n callbackURL: string;\r\n authorizationURL: string;\r\n tokenExchangeURL: string;\r\n tokenExchangeMethod: 'json' | 'form';\r\n userInfoURL: string;\r\n /**\r\n * OAuth scopes requested at the authorization step. Consumers can extend\r\n * a built-in provider's defaults to request additional permissions:\r\n *\r\n * ```ts\r\n * const customGoogle = googleProvider({...});\r\n * customGoogle.scope.push('https://www.googleapis.com/auth/calendar.readonly');\r\n * ```\r\n *\r\n * Or pass `extraScopes` to the built-in factory (see `OAuthHelperInput`).\r\n */\r\n scope: string[];\r\n nameKey: string;\r\n emailKey: string;\r\n avatarKey?: string;\r\n avatarCodeKey: string;\r\n getEmail?: (accessToken: string) => Promise<string | false | undefined>;\r\n getAvatar?: (params: { userData: OAuthUserData; avatarId?: string; accessToken: string }) => string | undefined | Promise<string | undefined>;\r\n /**\r\n * Optional per-provider hook to attach extra runtime-only data to the\r\n * session (NOT to the User row). Return key/value pairs that get merged\r\n * into `BaseSessionLayout` before `saveSession`. To get strict typing\r\n * downstream, extend `BaseSessionLayout` via module augmentation:\r\n *\r\n * ```ts\r\n * declare module '@luckystack/core' {\r\n * interface BaseSessionLayout {\r\n * googleCalendarToken?: string;\r\n * }\r\n * }\r\n * ```\r\n *\r\n * Data lives on the session in Redis and disappears on logout — no Prisma\r\n * schema change required. Don't store secrets you wouldn't want surfaced\r\n * to a user inspecting their own session.\r\n */\r\n extraSessionFields?: (params: {\r\n userData: OAuthUserData;\r\n accessToken: string;\r\n }) => Promise<Record<string, unknown>> | Record<string, unknown>;\r\n}\r\n\r\nexport type OAuthProvider = CredentialsProvider | FullOAuthProvider;\r\n\r\nexport const isFullOAuthProvider = (provider: OAuthProvider): provider is FullOAuthProvider =>\r\n provider.name !== 'credentials';\r\n\r\nexport interface OAuthEndpointOverrides {\r\n /** Override the provider's authorization URL (e.g. GitHub Enterprise host, Microsoft custom tenant). */\r\n authorizationURL?: string;\r\n /** Override the token exchange URL. */\r\n tokenExchangeURL?: string;\r\n /** Override the userInfo URL. */\r\n userInfoURL?: string;\r\n}\r\n\r\ninterface OAuthHelperInput {\r\n clientId: string | undefined;\r\n clientSecret: string | undefined;\r\n callbackUrl: string;\r\n /** Optional URL overrides for self-hosted / custom-tenant deployments. */\r\n endpoints?: OAuthEndpointOverrides;\r\n /**\r\n * Additional OAuth scopes to request alongside the built-in defaults. Use\r\n * for \"give me access to the user's calendar / drive / repos\" flows.\r\n * Merged with the helper's default scopes; duplicates are deduplicated.\r\n */\r\n extraScopes?: string[];\r\n /**\r\n * Per-provider hook for attaching runtime-only fields to the session.\r\n * See `FullOAuthProvider.extraSessionFields` for usage + module-augmentation\r\n * pattern.\r\n */\r\n extraSessionFields?: FullOAuthProvider['extraSessionFields'];\r\n}\r\n\r\nconst mergeScopes = (defaults: string[], extra: string[] | undefined): string[] => {\r\n if (!extra || extra.length === 0) return defaults;\r\n return [...new Set([...defaults, ...extra])];\r\n};\r\n\r\nconst requireString = (value: string | undefined, label: string): string => {\r\n if (!value || value.length === 0) {\r\n throw new Error(`OAuth provider configuration error: ${label} is empty.`);\r\n }\r\n return value;\r\n};\r\n\r\nexport const credentialsProvider = (): CredentialsProvider => ({ name: 'credentials' });\r\n\r\nexport const googleProvider = (input: OAuthHelperInput): FullOAuthProvider => ({\r\n name: 'google',\r\n clientID: requireString(input.clientId, 'google clientId'),\r\n clientSecret: requireString(input.clientSecret, 'google clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? 'https://accounts.google.com/o/oauth2/v2/auth',\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? 'https://oauth2.googleapis.com/token',\r\n tokenExchangeMethod: 'json',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://www.googleapis.com/oauth2/v1/userinfo',\r\n scope: mergeScopes([\r\n 'https://www.googleapis.com/auth/userinfo.profile',\r\n 'https://www.googleapis.com/auth/userinfo.email',\r\n ], input.extraScopes),\r\n nameKey: 'name',\r\n emailKey: 'email',\r\n avatarKey: 'picture',\r\n avatarCodeKey: '',\r\n extraSessionFields: input.extraSessionFields,\r\n});\r\n\r\nexport const githubProvider = (input: OAuthHelperInput): FullOAuthProvider => ({\r\n name: 'github',\r\n clientID: requireString(input.clientId, 'github clientId'),\r\n clientSecret: requireString(input.clientSecret, 'github clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? 'https://github.com/login/oauth/authorize',\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? 'https://github.com/login/oauth/access_token',\r\n tokenExchangeMethod: 'json',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://api.github.com/user',\r\n scope: mergeScopes(['read:user', 'user:email'], input.extraScopes),\r\n nameKey: 'login',\r\n emailKey: 'email',\r\n avatarKey: 'avatar_url',\r\n avatarCodeKey: '',\r\n extraSessionFields: input.extraSessionFields,\r\n getEmail: async (accessToken) => {\r\n const fetchEmails = async () => {\r\n const response = await fetch('https://api.github.com/user/emails', {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json',\r\n Authorization: `Bearer ${accessToken}`,\r\n },\r\n });\r\n if (!response.ok) return false;\r\n const emails: unknown = await response.json();\r\n if (!Array.isArray(emails)) return false;\r\n return emails as { primary?: boolean; email?: string }[];\r\n };\r\n\r\n const [error, emails] = await tryCatch(fetchEmails);\r\n if (error || !emails) return false;\r\n\r\n let mainEmail: string | undefined;\r\n for (const entry of emails) {\r\n if (entry.primary && typeof entry.email === 'string') mainEmail = entry.email;\r\n }\r\n mainEmail ??= emails[0]?.email;\r\n return mainEmail;\r\n },\r\n});\r\n\r\nexport const discordProvider = (input: OAuthHelperInput): FullOAuthProvider => ({\r\n name: 'discord',\r\n clientID: requireString(input.clientId, 'discord clientId'),\r\n clientSecret: requireString(input.clientSecret, 'discord clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? 'https://discord.com/oauth2/authorize',\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? 'https://discord.com/api/oauth2/token',\r\n tokenExchangeMethod: 'form',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://discord.com/api/users/@me',\r\n scope: mergeScopes(['identify', 'email'], input.extraScopes),\r\n nameKey: 'username',\r\n emailKey: 'email',\r\n avatarCodeKey: 'avatar',\r\n extraSessionFields: input.extraSessionFields,\r\n getAvatar: ({ userData, avatarId }) => {\r\n if (!avatarId) return;\r\n const userId = typeof userData.id === 'string' ? userData.id : '';\r\n if (!userId) return;\r\n const format = avatarId.startsWith('a_') ? 'gif' : 'png';\r\n return `https://cdn.discordapp.com/avatars/${userId}/${avatarId}.${format}`;\r\n },\r\n});\r\n\r\nexport interface FacebookProviderInput extends OAuthHelperInput {\r\n /** Graph API version segment used in the URLs. Default `'v18.0'` (current as of 2026 — bump as Meta deprecates older versions). */\r\n apiVersion?: string;\r\n}\r\n\r\nexport const facebookProvider = (input: FacebookProviderInput): FullOAuthProvider => {\r\n const v = input.apiVersion ?? 'v18.0';\r\n return {\r\n name: 'facebook',\r\n clientID: requireString(input.clientId, 'facebook clientId'),\r\n clientSecret: requireString(input.clientSecret, 'facebook clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? `https://www.facebook.com/${v}/dialog/oauth`,\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? `https://graph.facebook.com/${v}/oauth/access_token`,\r\n tokenExchangeMethod: 'form',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://graph.facebook.com/me?fields=id,name,email,picture.type(large)',\r\n scope: mergeScopes(['public_profile', 'email'], input.extraScopes),\r\n nameKey: 'name',\r\n emailKey: 'email',\r\n avatarCodeKey: '',\r\n extraSessionFields: input.extraSessionFields,\r\n getAvatar: ({ userData }) => {\r\n const picture = asOAuthUserData(userData.picture);\r\n const pictureData = asOAuthUserData(picture.data);\r\n const url = pictureData.url;\r\n return typeof url === 'string' ? url : undefined;\r\n },\r\n };\r\n};\r\n\r\nexport interface MicrosoftProviderInput extends OAuthHelperInput {\r\n /** Tenant id segment in the authorize/token URLs. Default `'common'`. */\r\n tenant?: string;\r\n /** OAuth API version segment. Default `'v2.0'`. */\r\n apiVersion?: string;\r\n /** Graph API version segment for /me. Default `'v1.0'`. */\r\n graphApiVersion?: string;\r\n}\r\n\r\n//? NOTE: Microsoft flow is implemented but has not been end-to-end verified\r\n//? against an Azure AD tenant as of 2026-05-14. The token-exchange URL, the\r\n//? Graph /me shape, and the photo data-URL pipeline are based on Microsoft\r\n//? docs and pattern-matching against Google/GitHub. First consumer to wire a\r\n//? real Azure tenant should report back so this note can be dropped.\r\nexport const microsoftProvider = (input: MicrosoftProviderInput): FullOAuthProvider => {\r\n const tenant = input.tenant ?? 'common';\r\n const oauthVersion = input.apiVersion ?? 'v2.0';\r\n const graphVersion = input.graphApiVersion ?? 'v1.0';\r\n return {\r\n name: 'microsoft',\r\n clientID: requireString(input.clientId, 'microsoft clientId'),\r\n clientSecret: requireString(input.clientSecret, 'microsoft clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? `https://login.microsoftonline.com/${tenant}/oauth2/${oauthVersion}/authorize`,\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? `https://login.microsoftonline.com/${tenant}/oauth2/${oauthVersion}/token`,\r\n tokenExchangeMethod: 'form',\r\n userInfoURL: input.endpoints?.userInfoURL ?? `https://graph.microsoft.com/${graphVersion}/me`,\r\n scope: mergeScopes(['openid', 'profile', 'email', 'User.Read'], input.extraScopes),\r\n nameKey: 'displayName',\r\n emailKey: 'mail',\r\n avatarCodeKey: 'id',\r\n extraSessionFields: input.extraSessionFields,\r\n //? Graph's /photo/$value requires bearer auth, so we can't store the URL —\r\n //? a browser <img> would 401. Fetch the bytes and inline as a data URL.\r\n getAvatar: async ({ avatarId, accessToken }) => {\r\n if (!avatarId) return;\r\n const fetchPhoto = async () => {\r\n const response = await fetch(\r\n `https://graph.microsoft.com/${graphVersion}/users/${avatarId}/photo/$value`,\r\n { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` } },\r\n );\r\n if (!response.ok) return;\r\n const contentType = response.headers.get('content-type') ?? 'image/jpeg';\r\n const buffer = Buffer.from(await response.arrayBuffer());\r\n return `data:${contentType};base64,${buffer.toString('base64')}`;\r\n };\r\n const [error, dataUrl] = await tryCatch(fetchPhoto);\r\n if (error) return;\r\n return dataUrl ?? undefined;\r\n },\r\n getEmail: async (accessToken) => {\r\n const fetchProfile = async () => {\r\n const response = await fetch(`https://graph.microsoft.com/${graphVersion}/me`, {\r\n method: 'GET',\r\n headers: {\r\n Authorization: `Bearer ${accessToken}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n });\r\n if (!response.ok) return false;\r\n const data: unknown = await response.json();\r\n if (!data || typeof data !== 'object') return false;\r\n return data as Record<string, unknown>;\r\n };\r\n\r\n const [error, profile] = await tryCatch(fetchProfile);\r\n if (error || !profile) return false;\r\n\r\n const record = asOAuthUserData(profile);\r\n const mail = typeof record.mail === 'string' ? record.mail : '';\r\n const userPrincipalName = typeof record.userPrincipalName === 'string' ? record.userPrincipalName : '';\r\n return (mail || userPrincipalName) || false;\r\n },\r\n };\r\n};\r\n\r\nlet registeredProviders: OAuthProvider[] = [{ name: 'credentials' }];\r\n\r\n//? Always returns the active list. The default value contains only the\r\n//? `credentials` entry so calls into `loginWithCredentials` keep working in\r\n//? environments that never register OAuth providers (tests, CLI, etc.).\r\nexport const getOAuthProviders = (): OAuthProvider[] => registeredProviders;\r\n\r\nexport const registerOAuthProviders = (providers: OAuthProvider[]): OAuthProvider[] => {\r\n registeredProviders = providers;\r\n return registeredProviders;\r\n};\r\n","//? Post-login redirect resolver. Lets consumers compute the OAuth callback\n//? redirect URL dynamically (per-user, per-tenant, per-provider).\n//?\n//? Why a dedicated registry instead of a hook:\n//? The framework's hooks are \"stop-or-continue\" — handlers return undefined\n//? or a stop signal. They don't carry a value. A redirect-resolution\n//? handler must RETURN a string, so it lives in its own registry.\n//?\n//? Validation: the resolved URL is checked against\n//? `ProjectConfig.http.cors.allowedOrigins` (plus localhost + the bound\n//? SERVER_IP) before being used. An invalid URL falls back to\n//? `ProjectConfig.loginRedirectUrl`.\n\nexport interface PostLoginRedirectInput {\n userId: string;\n provider: string;\n isNewUser: boolean;\n defaultUrl: string;\n}\n\nexport type PostLoginRedirectResolver = (\n input: PostLoginRedirectInput,\n) => string | Promise<string>;\n\nlet activeResolver: PostLoginRedirectResolver | null = null;\n\nexport const registerPostLoginRedirect = (\n resolver: PostLoginRedirectResolver,\n): PostLoginRedirectResolver => {\n activeResolver = resolver;\n return resolver;\n};\n\nexport const getPostLoginRedirect = (): PostLoginRedirectResolver | null => activeResolver;\n","/* eslint-disable unicorn/no-abusive-eslint-disable */\r\n/* eslint-disable */\r\nimport { asOAuthUserData, getOAuthProviders, isFullOAuthProvider, type FullOAuthProvider } from './oauthProviders';\r\nimport { getPostLoginRedirect } from './redirectResolver';\r\nimport { IncomingMessage, ServerResponse } from 'node:http';\r\nimport { URLSearchParams } from 'node:url';\r\nimport { tryCatch, redis as redisClient, getUploadsDir, dispatchHook, getLogger, formatKey } from '@luckystack/core';\r\nimport bcrypt from 'bcryptjs';\r\nimport { randomBytes } from 'node:crypto';\r\nimport { saveSession } from \"./session\"\r\nimport validator from 'validator';\r\nimport type { BaseSessionLayout as SessionLayout } from './sessionLayout';\r\nimport { getProjectConfig } from '@luckystack/core';\r\nimport { getUserAdapter } from './userAdapter';\r\nimport { validatePassword } from './passwordPolicy';\r\nimport path from 'node:path';\r\nimport { existsSync } from 'node:fs';\r\n\r\ninterface paramsType {\r\n email?: string,\r\n password?: string,\r\n name?: string,\r\n confirmPassword?: string,\r\n}\r\n\r\n//? Resolved at call time via getUploadsDir() so consumer path overrides win.\r\nconst uploadsFolder = (): string => getUploadsDir();\r\n//? Resolve at call time so dotenv/test-setup timing can't capture a stale\r\n//? value at module load. Routes through `projectConfig.logging.devLogs`\r\n//? which is the standard predicate everywhere else in the codebase.\r\nconst isDevMode = (): boolean => getProjectConfig().logging.devLogs;\r\nconst { compare, genSalt, hash } = bcrypt;\r\nconst { escape, isEmail } = validator;\r\n\r\n//? Use the shared `getProjectName()` helper so the OAuth state Redis key\r\n//? prefix matches the namespace used everywhere else (sessions, activeUsers,\r\n//? rate-limit, password-reset). Previously this used a separate\r\n//? `auth.oauthStateProjectNameFallback` config field which could silently\r\n//? drift from `session.projectName`.\r\nconst getOAuthStateKey = (providerName: string, state: string): string => {\r\n return formatKey('-oauth-state', `${providerName}:${state}`);\r\n};\r\n\r\nexport const createOAuthState = async (providerName: string): Promise<string | null> => {\r\n const state = randomBytes(32).toString('hex');\r\n const key = getOAuthStateKey(providerName, state);\r\n const result = await redisClient.set(key, '1', 'EX', getProjectConfig().auth.oauthStateTtlSeconds, 'NX');\r\n\r\n if (result !== 'OK') {\r\n return null;\r\n }\r\n\r\n return state;\r\n};\r\n\r\nconst consumeOAuthState = async (providerName: string, state: string): Promise<boolean> => {\r\n if (!state) {\r\n return false;\r\n }\r\n\r\n const key = getOAuthStateKey(providerName, state);\r\n const txResult = await redisClient.multi().get(key).del(key).exec();\r\n if (!txResult || txResult.length < 2) {\r\n return false;\r\n }\r\n\r\n const getResult = txResult[0];\r\n if (!getResult || getResult[0]) {\r\n return false;\r\n }\r\n\r\n return getResult[1] === '1';\r\n};\r\n\r\nconst asRecord = (value: unknown): Record<string, unknown> => {\r\n if (value && typeof value === 'object') {\r\n return value as Record<string, unknown>;\r\n }\r\n return {};\r\n};\r\n\r\nconst sanitizeUserForSession = <T extends { password?: unknown }>(user: T): Omit<T, 'password'> => {\r\n const { password: _password, ...safeUser } = user;\r\n return safeUser;\r\n};\r\n\r\nconst toReasonKey = (error: unknown, fallback = 'api.internalServerError'): string => {\r\n if (typeof error === 'string' && error.length > 0) {\r\n return error;\r\n }\r\n\r\n if (error instanceof Error && error.message) {\r\n const message = error.message.toLowerCase();\r\n if (message.includes('authentication failed') || message.includes('scram failure')) {\r\n return 'api.internalServerError';\r\n }\r\n }\r\n\r\n return fallback;\r\n};\r\n\r\ninterface NormalizedCredentials {\r\n email: string;\r\n password: string;\r\n name: string | undefined;\r\n confirmPassword: string | undefined;\r\n}\r\n\r\nconst normalizeCredentials = (params: paramsType): NormalizedCredentials => ({\r\n //? Don't HTML-escape password OR confirmPassword before bcrypt — neither\r\n //? reaches HTML, and they're memory-compared for equality during register.\r\n //? Escaping confirmPassword while leaving password raw made any password\r\n //? containing `& < > \" '` fail the `password !== confirmPassword` check\r\n //? (regression introduced in pass-1 fix #9 which only un-escaped password).\r\n //? Email is normalized (lowercase + trim) and validated via `isEmail`;\r\n //? HTML-escaping it would make legacy raw rows unfindable.\r\n email: (params.email || '').trim().toLowerCase(),\r\n password: params.password || '',\r\n name: params.name ? escape(params.name) : undefined,\r\n confirmPassword: params.confirmPassword || undefined,\r\n});\r\n\r\nconst validateCredentialsShape = (creds: NormalizedCredentials): { status: false; reason: string } | null => {\r\n const authLimits = getProjectConfig().auth;\r\n if (!creds.email || !creds.password) return { status: false, reason: 'login.empty' };\r\n if (creds.email.length > authLimits.emailMaxLength) return { status: false, reason: 'login.emailCharacterLimit' };\r\n if (creds.name && creds.name.length > authLimits.nameMaxLength) return { status: false, reason: 'login.nameCharacterLimit' };\r\n if (!isEmail(creds.email)) return { status: false, reason: 'login.invalidEmailFormat' };\r\n //? Full password-policy check (length, complexity, common-list, customValidator).\r\n //? Policy lives in `projectConfig.auth.passwordPolicy`; the deprecated\r\n //? `passwordMinLength`/`passwordMaxLength` top-level fields are still read\r\n //? from the policy's `minLength`/`maxLength` defaults.\r\n const passwordReason = validatePassword(creds.password);\r\n if (passwordReason) return { status: false, reason: passwordReason };\r\n return null;\r\n};\r\n\r\nconst registerWithCredentials = async ({\r\n email,\r\n password,\r\n name,\r\n confirmPassword,\r\n}: {\r\n email: string;\r\n password: string;\r\n name: string;\r\n confirmPassword: string;\r\n}) => {\r\n if (password !== confirmPassword) {\r\n return { status: false, reason: 'login.passwordNotMatch' };\r\n }\r\n\r\n const preRegisterResult = await dispatchHook('preRegister', { email, provider: 'credentials', name });\r\n if (preRegisterResult.stopped) {\r\n return { status: false, reason: preRegisterResult.signal.errorCode };\r\n }\r\n\r\n const userAdapter = getUserAdapter();\r\n const [checkEmailError, checkEmailResponse] = await tryCatch(() =>\r\n userAdapter.findByEmail({ email, provider: 'credentials' })\r\n );\r\n if (checkEmailError) {\r\n getLogger().error('login: findByEmail failed during register', checkEmailError);\r\n return { status: false, reason: toReasonKey(checkEmailError) };\r\n }\r\n if (checkEmailResponse) return { status: false, reason: 'login.emailExists' };\r\n\r\n const createNewUser = async () => {\r\n const salt = await genSalt(getProjectConfig().auth.bcryptRounds);\r\n const hashedPassword = await hash(password, salt);\r\n return await userAdapter.create({\r\n email,\r\n provider: 'credentials',\r\n name,\r\n password: hashedPassword,\r\n avatar: '',\r\n avatarFallback: `#${Math.floor(Math.random() * 0xFF_FF_FF).toString(16).padStart(6, \"0\")}`,\r\n language: getProjectConfig().defaultLanguage,\r\n });\r\n };\r\n\r\n const [createNewUserError, createNewUserResponse] = await tryCatch(createNewUser);\r\n if (createNewUserError) return { status: false, reason: toReasonKey(createNewUserError) };\r\n if (!createNewUserResponse) return { status: false, reason: 'login.createUserFailed' };\r\n\r\n await dispatchHook('postRegister', { userId: createNewUserResponse.id, provider: 'credentials' });\r\n return {\r\n status: true,\r\n reason: 'login.userCreated',\r\n session: sanitizeUserForSession(createNewUserResponse),\r\n };\r\n};\r\n\r\nconst loginWithCredentialsCore = async ({ email, password }: { email: string; password: string }) => {\r\n const preLoginResult = await dispatchHook('preLogin', { email, provider: 'credentials' });\r\n if (preLoginResult.stopped) {\r\n return { status: false, reason: preLoginResult.signal.errorCode };\r\n }\r\n\r\n const userAdapter = getUserAdapter();\r\n const [findUserError, findUserResponse] = await tryCatch(() =>\r\n userAdapter.findByEmail({ email, provider: 'credentials' })\r\n );\r\n if (findUserError) {\r\n getLogger().error('login: findByEmail failed', findUserError);\r\n return { status: false, reason: toReasonKey(findUserError) };\r\n }\r\n if (!findUserResponse) return { status: false, reason: 'login.userNotFound' };\r\n\r\n const [checkPasswordError, checkPasswordResponse] = await tryCatch(() =>\r\n compare(password, findUserResponse.password!)\r\n );\r\n if (checkPasswordError) {\r\n getLogger().error('login: bcrypt compare failed', checkPasswordError);\r\n return { status: false, reason: checkPasswordError };\r\n }\r\n if (!checkPasswordResponse) return { status: false, reason: 'login.wrongPassword' };\r\n\r\n const newToken = randomBytes(32).toString('hex');\r\n const previousLogin = (findUserResponse as { lastLogin?: Date | null }).lastLogin ?? null;\r\n const nowLogin = new Date();\r\n\r\n // Best-effort lastLogin update — silently no-ops if the user adapter\r\n // (or User schema) doesn't accept the field.\r\n await tryCatch(() => userAdapter.update(findUserResponse.id, { lastLogin: nowLogin } as never));\r\n\r\n const newUser: SessionLayout = {\r\n ...sanitizeUserForSession(findUserResponse),\r\n token: newToken,\r\n lastLogin: nowLogin,\r\n previousLogin,\r\n };\r\n\r\n const filePath = path.join(uploadsFolder(), `${newUser.id}.webp`);\r\n if (existsSync(filePath)) {\r\n newUser.avatar = `${newUser.id}.webp`;\r\n }\r\n\r\n await saveSession(newToken, newUser, true);\r\n await dispatchHook('postLogin', { userId: newUser.id, provider: 'credentials', isNewUser: false, token: newToken });\r\n if (isDevMode()) {\r\n getLogger().debug(`credentials login success for user ${newUser.id}`);\r\n }\r\n return { status: true, reason: 'login.loggedIn', newToken, session: newUser };\r\n};\r\n\r\n//? Thin dispatcher: keeps the existing single-entry HTTP surface but forwards\r\n//? to the dedicated register / login functions. `name && confirmPassword`\r\n//? present in the body means the client is registering; otherwise it's a\r\n//? login.\r\nconst loginWithCredentials = async (params: paramsType) => {\r\n const creds = normalizeCredentials(params);\r\n\r\n if (isDevMode()) {\r\n getLogger().debug(`credentials auth attempt for ${creds.email || 'unknown-email'}`);\r\n }\r\n\r\n const validationError = validateCredentialsShape(creds);\r\n if (validationError) return validationError;\r\n\r\n if (creds.name && creds.confirmPassword) {\r\n return registerWithCredentials({\r\n email: creds.email,\r\n password: creds.password,\r\n name: creds.name,\r\n confirmPassword: creds.confirmPassword,\r\n });\r\n }\r\n return loginWithCredentialsCore({ email: creds.email, password: creds.password });\r\n};\r\n\r\nexport interface OAuthCallbackResult {\r\n token: string;\r\n redirectUrl: string;\r\n userId: string;\r\n provider: string;\r\n isNewUser: boolean;\r\n}\r\n\r\nconst isAllowedRedirectUrl = (url: string): boolean => {\r\n if (!URL.canParse(url, 'http://placeholder')) return false;\r\n const parsed = new URL(url, 'http://placeholder');\r\n if (parsed.origin === 'http://placeholder') {\r\n // relative URL — same-origin, always safe\r\n return true;\r\n }\r\n //? `allowedOrigins` is `string[] | (origin: string) => boolean` since we\r\n //? added the function-resolver variant. Branch on the shape.\r\n const allowed = getProjectConfig().http.cors.allowedOrigins ?? [];\r\n if (typeof allowed === 'function') {\r\n return allowed(parsed.origin);\r\n }\r\n return allowed.some((origin) => {\r\n if (!URL.canParse(origin)) return false;\r\n return new URL(origin).origin === parsed.origin;\r\n });\r\n};\r\n\r\nconst exchangeOAuthToken = async (provider: FullOAuthProvider, code: string): Promise<string | null> => {\r\n const values = {\r\n code,\r\n client_id: provider.clientID,\r\n client_secret: provider.clientSecret,\r\n redirect_uri: provider.callbackURL,\r\n grant_type: 'authorization_code',\r\n };\r\n\r\n const getToken = async () => {\r\n if (provider.tokenExchangeMethod === 'json') {\r\n const response = await fetch(provider.tokenExchangeURL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },\r\n body: JSON.stringify(values),\r\n });\r\n return await response.json();\r\n }\r\n if (provider.tokenExchangeMethod === 'form') {\r\n const formParams = new URLSearchParams();\r\n formParams.append('client_id', provider.clientID);\r\n formParams.append('client_secret', provider.clientSecret);\r\n formParams.append('code', values.code);\r\n formParams.append('grant_type', 'authorization_code');\r\n formParams.append('redirect_uri', provider.callbackURL);\r\n\r\n if (isDevMode()) {\r\n getLogger().debug('oauth: token-exchange form params', { params: formParams.toString() });\r\n }\r\n const response = await fetch(provider.tokenExchangeURL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\r\n body: formParams.toString(),\r\n });\r\n return await response.json();\r\n }\r\n return null;\r\n };\r\n\r\n const [error, response] = await tryCatch(getToken);\r\n if (error) {\r\n getLogger().error('oauth: token exchange failed', error);\r\n return null;\r\n }\r\n const tokenData = asRecord(response);\r\n const accessToken = typeof tokenData.access_token === 'string' ? tokenData.access_token : '';\r\n if (!accessToken) {\r\n getLogger().warn('oauth: no access_token in token response', { provider: provider.name });\r\n return null;\r\n }\r\n return accessToken;\r\n};\r\n\r\ninterface OAuthProfile {\r\n email: string | undefined;\r\n name: string;\r\n avatar: string;\r\n}\r\n\r\nconst fetchOAuthProfile = async (\r\n provider: FullOAuthProvider,\r\n accessToken: string,\r\n): Promise<OAuthProfile | null> => {\r\n const getUserData = async () => {\r\n const response = await fetch(provider.userInfoURL, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Accept': 'application/json',\r\n 'Authorization': `Bearer ${accessToken}`,\r\n },\r\n });\r\n return await response.json();\r\n };\r\n\r\n const [error, response] = await tryCatch(getUserData);\r\n if (error) {\r\n getLogger().error('oauth: userInfo fetch failed', error);\r\n return null;\r\n }\r\n\r\n const userData = asRecord(response);\r\n const name = String(userData[provider.nameKey] || 'didnt find a name');\r\n const emailValue = userData[provider.emailKey];\r\n let email: string | undefined = typeof emailValue === 'string' ? emailValue : undefined;\r\n\r\n const avatarId = provider.avatarCodeKey ? userData[provider.avatarCodeKey] : undefined;\r\n const avatarValue = provider?.avatarKey\r\n ? String(userData[provider.avatarKey] || '')\r\n : (provider.getAvatar\r\n ? await provider.getAvatar({ userData, avatarId: typeof avatarId === 'string' ? avatarId : '', accessToken })\r\n : '');\r\n const avatar = typeof avatarValue === 'string' ? avatarValue : '';\r\n\r\n //? Some providers (GitHub) don't return email in /userinfo — fall back to the\r\n //? provider's `getEmail` accessor.\r\n if (!email && provider.getEmail) {\r\n const selectedEmail = await provider.getEmail(accessToken);\r\n if (!selectedEmail) {\r\n getLogger().warn('oauth: no email found via provider.getEmail', { provider: provider.name });\r\n return null;\r\n }\r\n email = selectedEmail;\r\n }\r\n\r\n return { email, name, avatar };\r\n};\r\n\r\ninterface ResolvedOAuthUser {\r\n user: SessionLayout;\r\n isNewUser: boolean;\r\n}\r\n\r\nconst findOrCreateOAuthUser = async (\r\n provider: FullOAuthProvider,\r\n profile: OAuthProfile,\r\n): Promise<ResolvedOAuthUser | null> => {\r\n const { email, name, avatar } = profile;\r\n if (!email) return null;\r\n\r\n const preLoginResult = await dispatchHook('preLogin', { email, provider: provider.name });\r\n if (preLoginResult.stopped) {\r\n getLogger().warn(`oauth login aborted by preLogin hook`, { errorCode: preLoginResult.signal.errorCode });\r\n return null;\r\n }\r\n\r\n const userAdapter = getUserAdapter();\r\n const [findError, findResponse] = await tryCatch(() =>\r\n userAdapter.findByEmail({ email, provider: provider.name })\r\n );\r\n if (findError) {\r\n getLogger().error('oauth: findByEmail failed', findError);\r\n return null;\r\n }\r\n\r\n if (findResponse?.id) {\r\n const filePath = path.join(uploadsFolder(), `${findResponse.id}.webp`);\r\n if (existsSync(filePath)) {\r\n findResponse.avatar = `${findResponse.id}.webp`;\r\n }\r\n\r\n const previousLogin = (findResponse as { lastLogin?: Date | null }).lastLogin ?? null;\r\n const nowLogin = new Date();\r\n await tryCatch(() => userAdapter.update(findResponse.id, { lastLogin: nowLogin } as never));\r\n\r\n return {\r\n user: {\r\n ...sanitizeUserForSession(findResponse),\r\n token: '',\r\n lastLogin: nowLogin,\r\n previousLogin,\r\n },\r\n isNewUser: false,\r\n };\r\n }\r\n\r\n const preRegisterResult = await dispatchHook('preRegister', { email, provider: provider.name, name });\r\n if (preRegisterResult.stopped) {\r\n getLogger().warn(`oauth register aborted by preRegister hook`, { errorCode: preRegisterResult.signal.errorCode });\r\n return null;\r\n }\r\n\r\n const createNewUser = async () => userAdapter.create({\r\n email,\r\n provider: provider.name,\r\n name,\r\n avatar,\r\n avatarFallback: `#${Math.floor(Math.random() * 0xFF_FF_FF).toString(16).padStart(6, \"0\")}`,\r\n language: getProjectConfig().defaultLanguage,\r\n });\r\n const [createError, createResponse] = await tryCatch(createNewUser);\r\n if (createError) {\r\n getLogger().error('oauth: user create failed', createError);\r\n return null;\r\n }\r\n if (!createResponse) return null;\r\n\r\n return {\r\n user: { ...sanitizeUserForSession(createResponse), token: '' },\r\n isNewUser: true,\r\n };\r\n};\r\n\r\nconst resolvePostLoginRedirect = async ({\r\n fallbackUrl,\r\n userId,\r\n providerName,\r\n isNewUser,\r\n}: {\r\n fallbackUrl: string;\r\n userId: string;\r\n providerName: string;\r\n isNewUser: boolean;\r\n}): Promise<string> => {\r\n const resolver = getPostLoginRedirect();\r\n if (!resolver) return fallbackUrl;\r\n\r\n const [resolverError, resolved] = await tryCatch(() => resolver({\r\n userId,\r\n provider: providerName,\r\n isNewUser,\r\n defaultUrl: fallbackUrl,\r\n }));\r\n if (resolverError) {\r\n getLogger().warn(`[oauth] postLoginRedirect resolver threw`, { message: (resolverError as Error).message });\r\n return fallbackUrl;\r\n }\r\n if (resolved && isAllowedRedirectUrl(resolved)) return resolved;\r\n if (resolved) {\r\n getLogger().warn(\r\n `[oauth] postLoginRedirect returned a URL not in allowed origins — falling back`,\r\n { resolved, fallbackUrl },\r\n );\r\n }\r\n return fallbackUrl;\r\n};\r\n\r\n// Route that handles the callback from the OAuth provider\r\nconst loginCallback = async (\r\n pathname: string,\r\n req: IncomingMessage,\r\n _res: ServerResponse,\r\n options: { defaultRedirectUrl?: string } = {},\r\n): Promise<OAuthCallbackResult | false> => {\r\n const providerName = pathname.split('/')[3]; // google/github/etc.\r\n if (!providerName) return false;\r\n const provider = getOAuthProviders().find(p => p.name === providerName);\r\n if (!provider || !req.url) return false;\r\n if (!isFullOAuthProvider(provider)) return false;\r\n\r\n const queryString = req.url.split('?')[1] ?? '';\r\n const params = new URLSearchParams(queryString);\r\n const code = params.get('code');\r\n const state = params.get('state');\r\n\r\n const stateIsValid = await consumeOAuthState(provider.name, state || '');\r\n if (!stateIsValid) {\r\n getLogger().warn('oauth: invalid or missing state', { provider: provider.name });\r\n return false;\r\n }\r\n\r\n if (!code) {\r\n getLogger().warn('oauth: no code provided in callback url', { provider: provider.name });\r\n return false;\r\n }\r\n\r\n const accessToken = await exchangeOAuthToken(provider, code);\r\n if (!accessToken) return false;\r\n\r\n const profile = await fetchOAuthProfile(provider, accessToken);\r\n if (!profile) return false;\r\n\r\n const resolved = await findOrCreateOAuthUser(provider, profile);\r\n if (!resolved) return false;\r\n\r\n const newToken = randomBytes(32).toString('hex');\r\n resolved.user.token = newToken;\r\n\r\n //? Per-provider runtime extras (calendar tokens, tenant ids, etc.) are\r\n //? merged into the session BEFORE save so saveSession + the resulting\r\n //? sessionStorage broadcast see the final shape. Errors are logged but\r\n //? do not block login — a missing extra is not worth keeping the user\r\n //? from signing in.\r\n const extraSessionFields = provider.extraSessionFields;\r\n if (extraSessionFields) {\r\n const [extraErr, extra] = await tryCatch(async () =>\r\n extraSessionFields({ userData: asOAuthUserData(profile), accessToken }),\r\n );\r\n if (extraErr) {\r\n getLogger().warn(`[oauth:${providerName}] extraSessionFields hook threw — continuing without extras`, { err: extraErr });\r\n } else if (extra) {\r\n Object.assign(resolved.user, extra);\r\n }\r\n }\r\n\r\n await saveSession(newToken, resolved.user, true);\r\n\r\n if (resolved.isNewUser) {\r\n await dispatchHook('postRegister', { userId: resolved.user.id, provider: providerName });\r\n }\r\n await dispatchHook('postLogin', {\r\n userId: resolved.user.id,\r\n provider: providerName,\r\n isNewUser: resolved.isNewUser,\r\n token: newToken,\r\n });\r\n\r\n if (isDevMode()) {\r\n getLogger().debug(`oauth login success for user ${resolved.user.id}`);\r\n }\r\n\r\n const fallbackUrl =\r\n options.defaultRedirectUrl\r\n ?? getProjectConfig().loginRedirectUrl\r\n ?? '/';\r\n\r\n const redirectUrl = await resolvePostLoginRedirect({\r\n fallbackUrl,\r\n userId: resolved.user.id,\r\n providerName,\r\n isNewUser: resolved.isNewUser,\r\n });\r\n\r\n return {\r\n token: newToken,\r\n redirectUrl,\r\n userId: resolved.user.id,\r\n provider: providerName,\r\n isNewUser: resolved.isNewUser,\r\n };\r\n};\r\n\r\nexport { loginWithCredentials, loginCallback, registerWithCredentials, loginWithCredentialsCore }\r\n","//? User adapter registry. Decouples auth flows from a specific Prisma User\r\n//? schema so consumers with different column names, additional fields, or a\r\n//? non-Prisma data layer can plug their own implementation.\r\n//?\r\n//? `defaultPrismaUserAdapter()` implements the framework's recommended schema\r\n//? (id, email, password?, provider, name, avatar, avatarFallback, admin,\r\n//? language) and is used automatically when no adapter has been registered.\r\n\r\nimport { prisma, type BaseSessionLayout } from '@luckystack/core';\r\n\r\nexport interface UserAdapterCreateInput {\r\n email: string;\r\n provider: string;\r\n name?: string | null;\r\n password?: string | null;\r\n avatar?: string | null;\r\n avatarFallback?: string | null;\r\n language?: string | null;\r\n}\r\n\r\nexport interface UserRecord extends BaseSessionLayout {\r\n password?: string | null;\r\n provider?: string | null;\r\n /** Most recent successful login. Mirrors the optional `lastLogin` column on User. */\r\n lastLogin?: Date | null;\r\n}\r\n\r\nexport interface UserAdapter {\r\n findByEmail: (params: { email: string; provider: string }) => Promise<UserRecord | null>;\r\n findById: (id: string) => Promise<UserRecord | null>;\r\n create: (input: UserAdapterCreateInput) => Promise<UserRecord>;\r\n update: (id: string, patch: Partial<UserRecord>) => Promise<UserRecord>;\r\n}\r\n\r\nlet registeredAdapter: UserAdapter | null = null;\r\n\r\nexport const registerUserAdapter = (adapter: UserAdapter): UserAdapter => {\r\n registeredAdapter = adapter;\r\n return registeredAdapter;\r\n};\r\n\r\nexport const isUserAdapterRegistered = (): boolean => registeredAdapter !== null;\r\n\r\n//? Default adapter: backed by the Prisma client exposed from `@luckystack/core`.\r\n//?\r\n//? The single `as unknown as PrismaUserDelegate` below is intentional and\r\n//? load-bearing — it is the abstraction boundary between the framework's\r\n//? loose `UserAdapterCreateInput` (string-typed fields) and the consumer's\r\n//? strict Prisma model (which may declare enums for `provider`, `language`,\r\n//? etc.). The framework cannot know those enum types statically, so it\r\n//? type-erases once at this seam and assumes the runtime `prisma.user`\r\n//? satisfies the documented `PrismaUserDelegate` shape. Consumers whose User\r\n//? schema diverges (different column names, mandatory extra fields) should\r\n//? register their own `UserAdapter` via `registerUserAdapter(...)` rather\r\n//? than relying on this default.\r\ninterface PrismaUserDelegate {\r\n findFirst: (args: { where: { email: string; provider: string } }) => Promise<UserRecord | null>;\r\n findUnique: (args: { where: { id: string } }) => Promise<UserRecord | null>;\r\n create: (args: { data: Record<string, unknown> }) => Promise<UserRecord>;\r\n update: (args: { where: { id: string }; data: Record<string, unknown> }) => Promise<UserRecord>;\r\n}\r\n\r\n//? Bridge between Prisma's generated client surface and the adapter contract.\r\n//? Structural cast required because Prisma's auto-generated delegate types\r\n//? don't structurally match our trimmed `PrismaUserDelegate` shape; this is a\r\n//? known framework boundary (see /docs/ARCHITECTURE_AUTH.md).\r\nconst getPrismaUser = (): PrismaUserDelegate =>\r\n // eslint-disable-next-line no-restricted-syntax -- structural Prisma boundary\r\n prisma.user as unknown as PrismaUserDelegate;\r\n\r\nexport const defaultPrismaUserAdapter = (): UserAdapter => {\r\n return {\r\n findByEmail: async ({ email, provider }) =>\r\n getPrismaUser().findFirst({ where: { email, provider } }),\r\n findById: async (id) =>\r\n getPrismaUser().findUnique({ where: { id } }),\r\n create: async (input) =>\r\n getPrismaUser().create({\r\n data: {\r\n email: input.email,\r\n provider: input.provider,\r\n name: input.name,\r\n password: input.password ?? null,\r\n avatar: input.avatar ?? '',\r\n avatarFallback: input.avatarFallback,\r\n language: input.language,\r\n },\r\n }),\r\n update: async (id, patch) =>\r\n getPrismaUser().update({ where: { id }, data: patch }),\r\n };\r\n};\r\n\r\nlet cachedDefaultAdapter: UserAdapter | null = null;\r\n\r\nexport const getUserAdapter = (): UserAdapter => {\r\n if (registeredAdapter) return registeredAdapter;\r\n cachedDefaultAdapter ??= defaultPrismaUserAdapter();\r\n return cachedDefaultAdapter;\r\n};\r\n","//? Password-policy enforcement for credentials auth. Read by\n//? `registerWithCredentials` and `updatePasswordHash` to validate plaintext\n//? passwords against the consumer-tunable `projectConfig.auth.passwordPolicy`.\n//?\n//? Returns null when the password passes every check, or a reason-key\n//? (i18n-style errorCode) describing the first failure. Callers surface\n//? the reason to the client; the framework's i18n layer handles\n//? translation.\n\nimport { getProjectConfig } from '@luckystack/core';\n\nimport { COMMON_PASSWORDS } from './data/commonPasswords';\n\nconst UPPERCASE_REGEX = /[A-Z]/;\nconst LOWERCASE_REGEX = /[a-z]/;\nconst NUMBER_REGEX = /\\d/;\nconst SPECIAL_REGEX = /[^A-Za-z0-9]/;\n\n/**\n * Validate `password` against the active project's password policy.\n * Returns null on success, a string reason-key on the first failure.\n *\n * The policy lives in `projectConfig.auth.passwordPolicy` and is read at\n * call time, so consumers can override defaults via `registerProjectConfig`.\n */\nexport const validatePassword = (password: string): string | null => {\n const policy = getProjectConfig().auth.passwordPolicy;\n\n if (password.length < policy.minLength) return 'login.passwordCharacterMinimum';\n if (password.length > policy.maxLength) return 'login.passwordCharacterLimit';\n if (policy.requireUppercase && !UPPERCASE_REGEX.test(password)) return 'login.passwordRequiresUppercase';\n if (policy.requireLowercase && !LOWERCASE_REGEX.test(password)) return 'login.passwordRequiresLowercase';\n if (policy.requireNumber && !NUMBER_REGEX.test(password)) return 'login.passwordRequiresNumber';\n if (policy.requireSpecial && !SPECIAL_REGEX.test(password)) return 'login.passwordRequiresSpecial';\n if (policy.forbidCommon && COMMON_PASSWORDS.has(password.toLowerCase())) return 'login.passwordTooCommon';\n if (policy.customValidator) {\n const customReason = policy.customValidator(password);\n if (customReason) return customReason;\n }\n\n return null;\n};\n","//? Built-in common-password blocklist. Sourced from the public-domain\n//? SecLists Top-passwords list (Daniel Miessler), trimmed to the 200 most\n//? common entries to keep the package size small. Consumers needing a\n//? larger list (10k+ entries) can supply one through\n//? `projectConfig.auth.passwordPolicy.customValidator` and check their\n//? own data source there.\n//?\n//? Lookups are case-insensitive (the validator lower-cases before\n//? checking). Adding additional entries here is a non-breaking change.\n\nconst COMMON_PASSWORD_LIST: readonly string[] = [\n '123456', 'password', '12345678', 'qwerty', '123456789', '12345', '1234', '111111',\n '1234567', 'dragon', '123123', 'baseball', 'abc123', 'football', 'monkey', 'letmein',\n '696969', 'shadow', 'master', '666666', 'qwertyuiop', '123321', 'mustang', '1234567890',\n 'michael', '654321', 'pussy', 'superman', '1qaz2wsx', '7777777', 'fuckyou', '121212',\n '000000', 'qazwsx', '123qwe', 'killer', 'trustno1', 'jordan', 'jennifer', 'zxcvbnm',\n 'asdfgh', 'hunter', 'buster', 'soccer', 'harley', 'batman', 'andrew', 'tigger',\n 'sunshine', 'iloveyou', 'fuckme', '2000', 'charlie', 'robert', 'thomas', 'hockey',\n 'ranger', 'daniel', 'starwars', 'klaster', '112233', 'george', 'asshole', 'computer',\n 'michelle', 'jessica', 'pepper', '1111', 'zxcvbn', '555555', '11111111', '131313',\n 'freedom', '777777', 'pass', 'fuck', 'maggie', '159753', 'aaaaaa', 'ginger',\n 'princess', 'joshua', 'cheese', 'amanda', 'summer', 'love', 'ashley', '6969',\n 'nicole', 'chelsea', 'biteme', 'matthew', 'access', 'yankees', '987654321', 'dallas',\n 'austin', 'thunder', 'taylor', 'matrix', 'william', 'corvette', 'hello', 'martin',\n 'heather', 'secret', 'fucker', 'merlin', 'diamond', '1234qwer', 'gfhjkm', 'hammer',\n 'silver', 'richard', 'samantha', 'iceman', 'tigers', 'purple', 'andrea', 'horney',\n 'dakota', 'aaaaa', 'player', 'sucker', 'mercedes', 'whatever', 'orange', 'qweqwe',\n 'mickey', 'panties', 'hannah', 'fucking', 'paul', 'pakistan', 'patrick', 'martha',\n 'morgan', 'iwantu', 'lakers', 'rachel', 'slayer', 'scott', '2112', 'fish',\n 'porn', 'matt', 'qwert', 'cookie', 'eagle1', 'london', 'phoenix', 'chicago',\n 'badboy', 'braves', 'yankee', 'lover', 'barney', 'edward', 'raiders', 'green',\n 'maverick', 'rangers', 'joseph', 'maddog', 'mike', 'jasmine', 'arsenal', 'beach',\n 'tiger', 'snoopy', 'natasha', 'matthews', 'jackson', 'cumshot', 'chevy', 'gateway',\n 'gators', 'angel', 'junior', 'samson', '6666', 'amateur', 'gemini', 'apples',\n 'august', 'biscuit', '5150', 'apollo', 'parker', 'qwerty123', 'rabbit', 'angela',\n 'love123', 'rocket', 'scooby', 'beaver', 'oliver', 'walter', 'blowme', 'spider',\n '4321', 'cumshot1', 'lucky', 'helpme', 'jackie', 'monica',\n];\n\n/** Frozen Set for O(1) `.has()` lookups; entries are pre-lowercased. */\nexport const COMMON_PASSWORDS: ReadonlySet<string> = new Set(\n COMMON_PASSWORD_LIST.map((p) => p.toLowerCase()),\n);\n","//? Password-reset primitives. Used by the framework's own forgot-password\r\n//? flow when `ProjectConfig.auth.forgotPassword === 'framework'`, AND\r\n//? exported as building blocks for consumers who picked `'custom'` and want\r\n//? to wire their own UI / email layer without re-implementing crypto + Redis.\r\n//?\r\n//? Token format: 32 random bytes hex-encoded → 64-char URL-safe string.\r\n//? Storage: `<projectName>-pwreset:<token> → userId` with a configurable TTL\r\n//? (`auth.passwordResetTtlSeconds`, default 3600 = 1 hour).\r\n\r\n// eslint-disable-next-line import-x/no-named-as-default -- bcryptjs ships both a default and namespace export; using the default mirrors its README\r\nimport bcrypt from 'bcryptjs';\r\n/* eslint-disable import-x/no-named-as-default-member -- accessing genSalt/hash/compare via the default import is the documented pattern */\r\nimport { randomBytes } from 'node:crypto';\r\n\r\nimport { getProjectConfig, formatKey, redis } from '@luckystack/core';\r\n\r\nimport { validatePassword } from './passwordPolicy';\r\nimport { getUserAdapter } from './userAdapter';\r\n\r\n//? Resolve at call time via the shared `getProjectName()` helper (single\r\n//? source of truth — see also session.ts, rateLimiter.ts, login.ts). Avoids\r\n//? the module-load capture bug where dotenv timing could put tokens under\r\n//? the wrong namespace.\r\nconst tokenKey = (token: string): string => formatKey('-pwreset', token);\r\n\r\n/**\r\n * Create a one-time password-reset token bound to a user id. Stored in Redis\r\n * with the configured TTL. Returns the token string — caller emails it to\r\n * the user (typically embedded in a URL).\r\n */\r\nexport const createPasswordResetToken = async (userId: string): Promise<string> => {\r\n const token = randomBytes(32).toString('hex');\r\n const ttl = getProjectConfig().auth.passwordResetTtlSeconds;\r\n await redis.set(tokenKey(token), userId, 'EX', ttl);\r\n return token;\r\n};\r\n\r\n/**\r\n * Validate and consume a password-reset token. Returns the bound userId on\r\n * success and removes the token (one-time use). Returns null when the token\r\n * is missing, expired, or malformed.\r\n */\r\nexport const consumePasswordResetToken = async (token: string): Promise<string | null> => {\r\n if (!token || typeof token !== 'string') return null;\r\n const key = tokenKey(token);\r\n const txResult = await redis.multi().get(key).del(key).exec();\r\n if (!txResult || txResult.length === 0) return null;\r\n const first = txResult[0];\r\n if (!first) return null;\r\n const [getErr, value] = first;\r\n if (getErr) return null;\r\n return typeof value === 'string' && value.length > 0 ? value : null;\r\n};\r\n\r\n/**\r\n * Bcrypt-hash a plaintext password and write it to the user record via the\r\n * registered user adapter. Used by the reset flow and by the settings\r\n * password-change flow. Validates against the active password policy\r\n * (`projectConfig.auth.passwordPolicy`) before hashing — throws a `PasswordPolicyError`\r\n * when the plaintext fails policy. Catch and surface the error code to the\r\n * client; framework-mode reset/change flows already do this.\r\n */\r\nexport const updatePasswordHash = async (userId: string, plaintext: string): Promise<void> => {\r\n const reason = validatePassword(plaintext);\r\n if (reason) {\r\n throw new PasswordPolicyError(reason);\r\n }\r\n const salt = await bcrypt.genSalt(getProjectConfig().auth.bcryptRounds);\r\n const hashedPassword = await bcrypt.hash(plaintext, salt);\r\n const patch: Record<string, unknown> = { password: hashedPassword };\r\n await getUserAdapter().update(userId, patch);\r\n};\r\n\r\n/**\r\n * Thrown by `updatePasswordHash` when the supplied plaintext violates the\r\n * active password policy. `errorCode` matches the i18n reason key the rest\r\n * of the login flow uses (e.g. `login.passwordRequiresUppercase`).\r\n */\r\nexport class PasswordPolicyError extends Error {\r\n readonly errorCode: string;\r\n constructor(errorCode: string) {\r\n super(`Password policy violation: ${errorCode}`);\r\n this.name = 'PasswordPolicyError';\r\n this.errorCode = errorCode;\r\n }\r\n}\r\n\r\n/** Verify a plaintext password against a stored bcrypt hash. */\r\nexport const verifyPassword = async (plaintext: string, hash: string): Promise<boolean> => {\r\n return bcrypt.compare(plaintext, hash);\r\n};\r\n","//? Framework-mode forgot-password orchestration. Used only when\r\n//? `ProjectConfig.auth.forgotPassword === 'framework'`. Pulls in\r\n//? `@luckystack/email` lazily so the login package doesn't list it as a\r\n//? hard dep — `'custom'` and `'disabled'` modes never reach this code.\r\n\r\nimport { getProjectConfig, dispatchHook, getLogger } from '@luckystack/core';\r\n\r\nimport { createPasswordResetToken } from './passwordReset';\r\nimport { getUserAdapter } from './userAdapter';\r\n\r\ninterface SendResetEmailArgs {\r\n email: string;\r\n /**\r\n * App-facing display name used in the email greeting + subject. If omitted,\r\n * falls back to `projectConfig.auth.passwordResetBrand`, then to `'LuckyStack'`\r\n * as the absolute fallback. Real consumers should set this on either the\r\n * call site OR via `projectConfig.auth.passwordResetBrand`.\r\n */\r\n brand?: string;\r\n}\r\n\r\n/**\r\n * Look up the user by credentials-provider email, generate a reset token,\r\n * and email them a reset link. Always resolves successfully regardless of\r\n * whether the email matched a user (anti-enumeration). Returns the result\r\n * of the email send for diagnostics.\r\n */\r\nexport const sendPasswordResetEmail = async ({ email, brand }: SendResetEmailArgs): Promise<{ ok: boolean; reason?: string }> => {\r\n const config = getProjectConfig();\r\n getLogger().info('[forgotPassword] start', { email, forgotPasswordMode: config.auth.forgotPassword });\r\n if (config.auth.forgotPassword !== 'framework') {\r\n getLogger().warn('[forgotPassword] auth.forgotPassword is not \"framework\" — nothing sent', { mode: config.auth.forgotPassword });\r\n return { ok: false, reason: 'forgotPassword-not-framework' };\r\n }\r\n\r\n const resolvedBrand = brand ?? config.auth.passwordResetBrand ?? 'LuckyStack';\r\n\r\n // Lazy import — the email package is an optional peer dep so its types\r\n // may not be resolvable at the framework's compile time.\r\n interface EmailModule {\r\n sendEmail: (input: Record<string, unknown>) => Promise<{ ok: boolean; reason?: string }>;\r\n renderEmailLayout: (input: Record<string, unknown>) => { html: string; text: string };\r\n }\r\n //? Mirror testEmail's robustness: catch a failed dynamic import instead of\r\n //? throwing (an uncaught throw here would bubble out of the anti-enumeration\r\n //? wrapper as a generic 500). Surfaces the real reason in the server log.\r\n const emailModule = await (\r\n // @ts-expect-error optional peer dep — installed only when forgotPassword === 'framework'\r\n import('@luckystack/email') as Promise<EmailModule>\r\n ).catch((error: unknown) => {\r\n getLogger().warn('[forgotPassword] failed to load @luckystack/email — is it installed?', { error: String(error) });\r\n return null;\r\n });\r\n if (!emailModule) {\r\n return { ok: false, reason: 'email-module-load-failed' };\r\n }\r\n const { sendEmail, renderEmailLayout } = emailModule;\r\n\r\n const userAdapter = getUserAdapter();\r\n const user = await userAdapter.findByEmail({ email, provider: 'credentials' });\r\n getLogger().info('[forgotPassword] credentials user lookup', { email, found: Boolean(user), userId: user?.id ?? null });\r\n\r\n // Anti-enumeration: always pretend it succeeded if the email isn't found.\r\n if (!user) {\r\n //? The client always gets \"success\" (anti-enumeration). This server-side\r\n //? line is the ONLY signal that the address simply isn't a credentials\r\n //? (email+password) account — by far the most common reason a reset email\r\n //? never arrives (OAuth-only signups and unknown/typo'd addresses have no\r\n //? credentials row, so there is nothing to reset).\r\n getLogger().warn('[forgotPassword] no credentials account for this email — nothing sent. Use the email+password account you registered with.', { email });\r\n void dispatchHook('passwordResetRequested', {\r\n email,\r\n matched: false,\r\n });\r\n return { ok: true };\r\n }\r\n\r\n const token = await createPasswordResetToken(user.id);\r\n void dispatchHook('passwordResetRequested', {\r\n email,\r\n matched: true,\r\n userId: user.id,\r\n token,\r\n ttlSeconds: config.auth.passwordResetTtlSeconds,\r\n });\r\n const baseUrl = (config.app.publicUrl || '').replace(/\\/+$/, '');\r\n const resetUrl = `${baseUrl}/reset-password?token=${encodeURIComponent(token)}`;\r\n\r\n const ttlMinutes = Math.round(config.auth.passwordResetTtlSeconds / 60);\r\n const { html, text } = renderEmailLayout({\r\n brand: resolvedBrand,\r\n title: 'Reset your password',\r\n intro: `Hi ${user.name ?? 'there'}, we received a request to reset the password on your ${resolvedBrand} account. Click the button below to choose a new one. The link expires in ${String(ttlMinutes)} minutes.`,\r\n ctaLabel: 'Reset password',\r\n ctaUrl: resetUrl,\r\n outro: `If you didn't request this, you can safely ignore this email — your password will stay the same. The link: ${resetUrl}`,\r\n footer: `Sent by ${resolvedBrand}. If you have questions, reply to this email.`,\r\n });\r\n\r\n //? `adapterHint: 'transactional'` lets consumers who registered separate\r\n //? marketing + transactional senders via `registerEmailSenders({...})`\r\n //? route this through the transactional adapter automatically. Falls\r\n //? back to the default sender when only one is registered.\r\n const result = await sendEmail({\r\n to: user.email,\r\n subject: `Reset your ${resolvedBrand} password`,\r\n html,\r\n text,\r\n adapterHint: 'transactional',\r\n });\r\n\r\n if (result.ok) {\r\n getLogger().info('[forgotPassword] reset email dispatched', { userId: user.id, to: user.email });\r\n } else {\r\n getLogger().warn('[forgotPassword] reset email send FAILED', { userId: user.id, reason: result.reason });\r\n }\r\n\r\n return result.ok ? { ok: true } : { ok: false, reason: result.reason };\r\n};\r\n","//? Email-change token primitives. Mirrors `passwordReset.ts` for the\n//? confirm-via-email-link flow: user submits a new address, framework mails\n//? a tokenized URL to the NEW address, click-through confirms and updates\n//? `User.email` + revokes all sessions.\n//?\n//? Token format: 32 random bytes hex-encoded -> 64-char URL-safe string.\n//? Storage: `<projectName>-email-change:<token>` -> JSON payload\n//? `{ userId: string; newEmail: string }`\n//? TTL: `auth.emailChangeTtlSeconds` (default 3600 = 1 hour).\n\nimport { randomBytes } from 'node:crypto';\n\nimport { getProjectConfig, formatKey, redis } from '@luckystack/core';\n\nconst tokenKey = (token: string): string => formatKey('-email-change', token);\n\nexport interface EmailChangePayload {\n userId: string;\n newEmail: string;\n}\n\n/**\n * Create a one-time email-change token bound to a user id + the new email\n * address. Stored in Redis with the configured TTL. Returns the token string\n * — caller emails the token (embedded in a URL) to the NEW address so the\n * confirmation click proves ownership of the new mailbox.\n */\nexport const createEmailChangeToken = async (userId: string, newEmail: string): Promise<string> => {\n const token = randomBytes(32).toString('hex');\n const ttl = getProjectConfig().auth.emailChangeTtlSeconds;\n const payload = JSON.stringify({ userId, newEmail } satisfies EmailChangePayload);\n await redis.set(tokenKey(token), payload, 'EX', ttl);\n return token;\n};\n\n/**\n * Validate and consume an email-change token. Returns the bound\n * `{ userId, newEmail }` on success and removes the token (one-time use).\n * Returns null when the token is missing, expired, or malformed.\n */\nexport const consumeEmailChangeToken = async (token: string): Promise<EmailChangePayload | null> => {\n if (!token || typeof token !== 'string') return null;\n const key = tokenKey(token);\n const txResult = await redis.multi().get(key).del(key).exec();\n if (!txResult || txResult.length === 0) return null;\n const first = txResult[0];\n if (!first) return null;\n const [getErr, value] = first;\n if (getErr) return null;\n if (typeof value !== 'string' || value.length === 0) return null;\n try {\n const parsed = JSON.parse(value) as unknown;\n if (\n !parsed\n || typeof parsed !== 'object'\n || typeof (parsed as EmailChangePayload).userId !== 'string'\n || typeof (parsed as EmailChangePayload).newEmail !== 'string'\n ) {\n return null;\n }\n return parsed as EmailChangePayload;\n } catch {\n return null;\n }\n};\n","//? Framework-mode \"email change confirmation\" orchestration. Renders the\n//? confirmation email + sends it to the NEW address (proves the user can\n//? receive at the new mailbox). Mirrors the lazy-import pattern from\n//? `forgotPassword.ts` so `@luckystack/email` stays an optional peer.\n\nimport { getProjectConfig } from '@luckystack/core';\n\nimport { createEmailChangeToken } from './emailChange';\n\ninterface SendEmailChangeArgs {\n userId: string;\n newEmail: string;\n /** Optional display name used in the email greeting (falls back to \"there\"). */\n userName?: string | null;\n /**\n * App-facing display name used in the email greeting + subject. Falls back\n * to `projectConfig.auth.passwordResetBrand` (the shared brand label), then\n * to `'LuckyStack'` as the absolute fallback.\n */\n brand?: string;\n}\n\n/**\n * Mint a one-shot email-change token and send a confirmation email to the\n * NEW address with the tokenized URL. Returns the send result for diagnostics\n * + the token itself (callers can audit-log it through the\n * `postEmailChangeRequested` hook).\n */\nexport const sendEmailChangeConfirmation = async (\n { userId, newEmail, userName, brand }: SendEmailChangeArgs,\n): Promise<{ ok: boolean; reason?: string; token: string }> => {\n const config = getProjectConfig();\n const resolvedBrand = brand ?? config.auth.passwordResetBrand ?? 'LuckyStack';\n\n // Lazy import — `@luckystack/email` is an optional peer dep.\n interface EmailModule {\n sendEmail: (input: Record<string, unknown>) => Promise<{ ok: boolean; reason?: string }>;\n renderEmailLayout: (input: Record<string, unknown>) => { html: string; text: string };\n }\n const { sendEmail, renderEmailLayout } = await (\n // @ts-expect-error optional peer dep — installed only when the consumer wires email sending\n import('@luckystack/email') as Promise<EmailModule>\n );\n\n const token = await createEmailChangeToken(userId, newEmail);\n const baseUrl = (config.app.publicUrl || '').replace(/\\/+$/, '');\n const confirmUrl = `${baseUrl}/settings/confirm-email?token=${encodeURIComponent(token)}`;\n const ttlMinutes = Math.round(config.auth.emailChangeTtlSeconds / 60);\n\n const { html, text } = renderEmailLayout({\n brand: resolvedBrand,\n title: 'Confirm your new email address',\n intro: `Hi ${userName ?? 'there'}, you (or someone using your ${resolvedBrand} account) asked to change the account email to this address. Click the button below to confirm — the link expires in ${String(ttlMinutes)} minutes. If you confirm, all of your active sessions will be signed out as a security precaution.`,\n ctaLabel: 'Confirm new email',\n ctaUrl: confirmUrl,\n outro: `If you didn't request this, you can safely ignore this email — nothing will change. The link: ${confirmUrl}`,\n footer: `Sent by ${resolvedBrand}. If you have questions, reply to this email.`,\n });\n\n const result = await sendEmail({\n to: newEmail,\n subject: `Confirm your new ${resolvedBrand} email address`,\n html,\n text,\n adapterHint: 'transactional',\n });\n\n return result.ok ? { ok: true, token } : { ok: false, reason: result.reason, token };\n};\n"],"mappings":"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","names":["tryCatch","socketEventNames","dispatchHook","getLogger","formatKey","tryCatch","logout","tryCatch","emails","tryCatch","redisClient","dispatchHook","getLogger","formatKey","randomBytes","getProjectConfig","getProjectConfig","getProjectConfig","getProjectConfig","formatKey","randomBytes","redisClient","dispatchHook","tryCatch","getLogger","response","bcrypt","randomBytes","getProjectConfig","formatKey","redis","formatKey","randomBytes","getProjectConfig","redis","bcrypt","hash","getProjectConfig","dispatchHook","getLogger","getProjectConfig","getLogger","dispatchHook","randomBytes","getProjectConfig","formatKey","redis","tokenKey","getProjectConfig","getProjectConfig"]}
1
+ {"version":3,"sources":["../src/sessionAdapter.ts","../src/logout.ts","../src/session.ts","../src/index.ts","../src/oauthProviders.ts","../src/redirectResolver.ts","../src/login.ts","../src/userAdapter.ts","../src/passwordPolicy.ts","../src/data/commonPasswords.ts","../src/passwordReset.ts","../src/forgotPassword.ts","../src/emailChange.ts","../src/emailChangeNotification.ts"],"sourcesContent":["//? Session storage DI surface. The default adapter persists session\r\n//? records in Redis (same behaviour as before extraction). Consumers can\r\n//? plug in DynamoDB / Memcached / Postgres-table / signed-JWT-stateless\r\n//? adapters by implementing `SessionAdapter` and calling\r\n//? `registerSessionAdapter(...)` at boot.\r\n//?\r\n//? The adapter owns ONLY storage primitives. Framework-level concerns\r\n//? (CSRF minting, hook dispatch, socket emission, single-session\r\n//? enforcement) stay in session.ts and call the adapter to read/write.\r\n\r\nimport { redis, tryCatch, formatKey } from '@luckystack/core';\r\n\r\nexport interface SessionAdapter {\r\n /** Human-readable identifier (used in logs/diagnostics). */\r\n name: string;\r\n\r\n /** Read the raw stored session string for `token`. Null when missing. */\r\n getRaw(token: string): Promise<string | null>;\r\n\r\n /**\r\n * Write the raw session string for `token` with a TTL. Implementations\r\n * MUST honour the TTL — sessions without one would never expire.\r\n */\r\n setRaw(token: string, value: string, ttlSeconds: number): Promise<void>;\r\n\r\n /** Delete the session record for `token`. Idempotent. */\r\n delete(token: string): Promise<void>;\r\n\r\n /**\r\n * Refresh the TTL on an existing session record (sliding-window\r\n * expiration). Returns true when the underlying record existed and the\r\n * TTL was applied, false otherwise. Implementations must not create a\r\n * record on miss.\r\n */\r\n expire(token: string, ttlSeconds: number): Promise<boolean>;\r\n\r\n /** Remaining TTL in seconds (or null when the record is missing). */\r\n ttl(token: string): Promise<number | null>;\r\n\r\n // ---- Active-tokens-per-user (for single-session enforcement and\r\n // revokeUserSessions). Implementations need a set-like structure keyed\r\n // by userId. Memcached/edge-KV adapters can serialise as a comma-list\r\n // if no native set type is available. ----\r\n\r\n trackActive(userId: string, token: string, ttlSeconds: number): Promise<void>;\r\n untrackActive(userId: string, token: string): Promise<void>;\r\n listActive(userId: string): Promise<string[]>;\r\n\r\n /**\r\n * Admin walk: yield every active session record. Optional because\r\n * non-scannable backends (signed-JWT-stateless, log-only) cannot\r\n * enumerate. Callers fall back to a per-user listActive when omitted.\r\n */\r\n listAll?(): Promise<{ token: string; raw: string }[]>;\r\n}\r\n\r\n/**\r\n * Default adapter: Redis-backed. Key layout matches what the framework\r\n * has used since the first release, so registering this explicitly is\r\n * equivalent to not registering anything.\r\n *\r\n * Imports `redis` and `tryCatch` from `@luckystack/core`. Project name\r\n * + prefix are resolved at call time so projectConfig changes take\r\n * effect without reloading this module.\r\n */\r\n\r\nconst sessionKey = (token: string): string => formatKey('-session', token);\r\nconst activeUsersKey = (userId: string): string => formatKey('-activeUsers', userId);\r\n\r\nexport const redisSessionAdapter: SessionAdapter = {\r\n name: 'redis',\r\n\r\n async getRaw(token) {\r\n const [error, value] = await tryCatch(async () => await redis.get(sessionKey(token)));\r\n if (error) return null;\r\n return value ?? null;\r\n },\r\n\r\n async setRaw(token, value, ttlSeconds) {\r\n const key = sessionKey(token);\r\n await redis.set(key, value);\r\n await redis.expire(key, ttlSeconds);\r\n },\r\n\r\n async delete(token) {\r\n await redis.del(sessionKey(token));\r\n },\r\n\r\n async expire(token, ttlSeconds) {\r\n const result = await redis.expire(sessionKey(token), ttlSeconds);\r\n return result === 1;\r\n },\r\n\r\n async ttl(token) {\r\n const [error, value] = await tryCatch(async () => await redis.ttl(sessionKey(token)));\r\n if (error) return null;\r\n return typeof value === 'number' ? value : null;\r\n },\r\n\r\n async trackActive(userId, token, ttlSeconds) {\r\n const key = activeUsersKey(userId);\r\n await redis.sadd(key, token);\r\n await redis.expire(key, ttlSeconds);\r\n },\r\n\r\n async untrackActive(userId, token) {\r\n await redis.srem(activeUsersKey(userId), token);\r\n },\r\n\r\n async listActive(userId) {\r\n const [error, value] = await tryCatch(async () => await redis.smembers(activeUsersKey(userId)));\r\n if (error || !Array.isArray(value)) return [];\r\n return value;\r\n },\r\n\r\n async listAll() {\r\n const pattern = `${formatKey('-session', '')}:*`;\r\n const collected: { token: string; raw: string }[] = [];\r\n let cursor = '0';\r\n\r\n do {\r\n const [scanError, scanResponse] = await tryCatch(\r\n async () => await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100),\r\n );\r\n if (scanError || !scanResponse) break;\r\n\r\n cursor = scanResponse[0];\r\n const keys = scanResponse[1];\r\n\r\n if (Array.isArray(keys) && keys.length > 0) {\r\n const values = await Promise.all(keys.map((k) => redis.get(k)));\r\n for (const [idx, k] of keys.entries()) {\r\n const raw = values[idx];\r\n if (raw) {\r\n collected.push({\r\n token: k.replace(`${formatKey('-session', '')}:`, ''),\r\n raw,\r\n });\r\n }\r\n }\r\n }\r\n } while (cursor !== '0');\r\n\r\n return collected;\r\n },\r\n};\r\n\r\nlet activeAdapter: SessionAdapter = redisSessionAdapter;\r\n\r\n/**\r\n * Replace the active session storage backend. Call once at boot from\r\n * `luckystack/server/index.ts`, BEFORE the first login request. Common\r\n * use cases:\r\n *\r\n * - DynamoDB / Cosmos DB for serverless / edge deployments\r\n * - Postgres `sessions` table when you already operate Postgres\r\n * - signed-JWT-stateless (the adapter writes nothing, reads from the\r\n * JWT, and stores activeUsers per token in a small Redis hash)\r\n * - in-memory mock for integration tests\r\n *\r\n * Last-write-wins: subsequent calls overwrite the active adapter.\r\n */\r\nexport const registerSessionAdapter = (adapter: SessionAdapter): void => {\r\n activeAdapter = adapter;\r\n};\r\n\r\n/** Read the currently-active session adapter (defaults to Redis). */\r\nexport const getSessionAdapter = (): SessionAdapter => activeAdapter;\r\n","/* eslint-disable unicorn/no-abusive-eslint-disable */\r\n/* eslint-disable */\r\nimport { Socket } from \"socket.io\";\r\nimport { redis as redisClient, tryCatch, socketEventNames, dispatchHook, getLogger } from '@luckystack/core';\r\nimport { deleteSession, activeUsersKeyFor } from \"./session\";\r\n\r\nexport const logout = async ({ token, socket, userId, skipSessionDelete }: {\r\n token: string | null,\r\n socket: Socket | undefined,\r\n userId: string | null,\r\n skipSessionDelete?: boolean,\r\n}) => {\r\n const [error, result] = await tryCatch(async () => {\r\n if (!socket) {\r\n getLogger().warn('logout: invalid socket');\r\n return;\r\n }\r\n if (!token) {\r\n getLogger().warn('logout: no token provided');\r\n return;\r\n }\r\n\r\n const preLogoutResult = await dispatchHook('preLogout', { userId, token });\r\n if (preLogoutResult.stopped) {\r\n getLogger().warn('logout: aborted by preLogout hook', { errorCode: preLogoutResult.signal.errorCode });\r\n socket.emit(socketEventNames.logout, \"error\");\r\n return;\r\n }\r\n\r\n getLogger().debug(`logout: user ${userId ?? '?'}`, { token });\r\n\r\n if (!skipSessionDelete) {\r\n await deleteSession(token);\r\n }\r\n if (userId) {\r\n await redisClient.srem(activeUsersKeyFor(userId), token);\r\n }\r\n socket.leave(token);\r\n\r\n // Presence state (disconnectTimers, tempDisconnectedSockets) is cleaned up\r\n // by `@luckystack/presence`'s `postLogout` hook handler, registered at\r\n // server startup via `registerPresenceHooks()`. Keeps login → presence\r\n // a one-way dep: presence knows about login (uses getSession/deleteSession),\r\n // login does not know about presence.\r\n await dispatchHook('postLogout', { userId, token });\r\n return true;\r\n });\r\n if (error) {\r\n if (socket) {\r\n socket.emit(socketEventNames.logout, \"error\");\r\n }\r\n } else if (result && socket) {\r\n //? Opt-in logout tracing. This fires on EVERY successful logout, so an\r\n //? unconditional warn+stacktrace spams the server log. Set\r\n //? LUCKYSTACK_TRACE_SESSION_DELETES=1 when hunting a \"why was I logged out?\"\r\n //? bug to print the originating caller's stack; silent otherwise. The\r\n //? client still receives the success-emit and clears its session regardless.\r\n if (process.env.LUCKYSTACK_TRACE_SESSION_DELETES === '1') {\r\n getLogger().warn('[session] logout success — emitting logout to socket', {\r\n tokenPrefix: token ? token.slice(0, 8) : null,\r\n userId,\r\n socketId: socket.id,\r\n stack: new Error('logout invoked').stack,\r\n });\r\n }\r\n socket.emit(socketEventNames.logout, \"success\");\r\n }\r\n}\r\n","import type { BaseSessionLayout as SessionLayout } from \"./sessionLayout\";\r\nimport { randomBytes } from 'node:crypto';\r\nimport {\r\n socketEventNames,\r\n dispatchHook,\r\n getCsrfConfig,\r\n getLogger,\r\n getProjectConfig,\r\n formatKey,\r\n tryCatch,\r\n} from '@luckystack/core';\r\n\r\nimport { getSessionAdapter } from './sessionAdapter';\r\n\r\n//? Resolved at call time so project registration order doesn't matter.\r\n//? Session TTL comes from projectConfig and is honoured by every adapter\r\n//? (Redis default, or any custom adapter the consumer registers).\r\nconst getSessionTtl = (): number => 60 * 60 * 24 * getProjectConfig().session.expiryDays;\r\n\r\nconst isSessionLayout = (value: unknown): value is SessionLayout => {\r\n return value !== null && typeof value === 'object' && 'id' in value;\r\n};\r\n\r\nconst parseSessionLayout = (value: string): SessionLayout | null => {\r\n const parsed = JSON.parse(value) as unknown;\r\n return isSessionLayout(parsed) ? parsed : null;\r\n};\r\n\r\n/**\r\n * Save or update a user session through the active session adapter.\r\n *\r\n * @param token - The session token (unique identifier)\r\n * @param data - The session data to store\r\n * @param newUser - If true, this is a new login (triggers single-session enforcement)\r\n */\r\nconst saveSession = async (token: string, data: SessionLayout, newUser?: boolean): Promise<void> => {\r\n const [error] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n\r\n if (newUser) {\r\n const preSessionCreateResult = await dispatchHook('preSessionCreate', {\r\n token,\r\n user: data,\r\n persistent: !getProjectConfig().session.basedToken,\r\n });\r\n if (preSessionCreateResult.stopped) {\r\n getLogger().warn(`session create aborted by preSessionCreate hook`, { errorCode: preSessionCreateResult.signal.errorCode });\r\n return;\r\n }\r\n }\r\n\r\n //? Mint a CSRF token on first session write. Subsequent writes preserve\r\n //? the existing token so the client doesn't have to re-fetch on every\r\n //? session update. The token is rotated on logout via `deleteSession`.\r\n //? Token length is consumer-configurable via `registerCsrfConfig({ tokenLength })`.\r\n data.csrfToken ??= randomBytes(getCsrfConfig().tokenLength).toString('hex');\r\n\r\n const ttl = getSessionTtl();\r\n await adapter.setRaw(token, JSON.stringify(data), ttl);\r\n\r\n const userId = data.id;\r\n\r\n //? Always track active tokens BEFORE the io check — `trackActive` is a\r\n //? Redis-only write, independent of any live socket. Gating it behind a\r\n //? live io meant processes without a Socket.io instance (the test harness,\r\n //? background workers, CLI tasks) persisted the session but never populated\r\n //? the active-tokens set, so `listSessions` / `revokeUserSessions` saw\r\n //? nothing for that user.\r\n if (userId) {\r\n await adapter.trackActive(userId, token, ttl);\r\n }\r\n\r\n const { getIoInstance } = await import('@luckystack/core');\r\n const io = getIoInstance();\r\n //? Socket fanout + single-session enforcement below need a live io; skip\r\n //? them when there isn't one. The session is already persisted + tracked.\r\n if (!io) return;\r\n\r\n // Handle session-limit enforcement on new login. Logic:\r\n // 1. perUser = 'single' (default) OR legacy `allowMultiple: false` →\r\n // kick every prior session for this user.\r\n // 2. perUser = 'multiple' with `maxConcurrentPerUser` cap reached:\r\n // `onConflict === 'revokeOld'` kicks oldest until under cap,\r\n // `onConflict === 'rejectNew'` would refuse the new login (handled\r\n // at the login API layer, not here).\r\n // 3. perUser = 'multiple' with no cap → no kick.\r\n if (newUser && userId) {\r\n const sessionCfg = getProjectConfig().session;\r\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- BC shim: legacy `allowMultiple` still honored when set\r\n const effectivePerUser = sessionCfg.allowMultiple ? 'multiple' : sessionCfg.perUser;\r\n const cap = sessionCfg.maxConcurrentPerUser;\r\n\r\n const allTokens = await adapter.listActive(userId);\r\n // Exclude the current token — it was just added and has no socket room yet\r\n const previousTokens = allTokens.filter(t => t !== token);\r\n\r\n let tokensToKick: string[] = [];\r\n if (effectivePerUser === 'single') {\r\n tokensToKick = previousTokens;\r\n } else if (cap !== null && previousTokens.length + 1 > cap && sessionCfg.onConflict === 'revokeOld') {\r\n // Kick oldest until under cap. Without ordering metadata in the adapter,\r\n // we drop the head of the list — most adapters return insertion-order\r\n // for set-like structures, but consumers needing strict LRU should\r\n // implement a custom adapter that orders deterministically.\r\n const excess = previousTokens.length + 1 - cap;\r\n tokensToKick = previousTokens.slice(0, excess);\r\n }\r\n\r\n if (tokensToKick.length > 0) {\r\n const { logout } = await import('./logout');\r\n\r\n await Promise.all(tokensToKick.map(async (previousToken) => {\r\n const sockets = io.sockets.adapter.rooms.get(previousToken);\r\n if (sockets) {\r\n getLogger().debug(`Kicking previous session for user ${userId}`);\r\n // Optional UI notification before the disconnect lands.\r\n if (sessionCfg.notifyOldDeviceOnRevoke) {\r\n io.to(previousToken).emit(socketEventNames.sessionReplaced, JSON.stringify({\r\n reason: 'session-replaced',\r\n userId,\r\n }));\r\n }\r\n for (const socketId of sockets) {\r\n const socket = io.sockets.sockets.get(socketId);\r\n if (socket) {\r\n await logout({ token: previousToken, socket, userId });\r\n }\r\n }\r\n } else {\r\n // No active sockets, just clean up the adapter state\r\n await adapter.delete(previousToken);\r\n await adapter.untrackActive(userId, previousToken);\r\n }\r\n }));\r\n }\r\n }\r\n\r\n // Broadcast session updates to connected clients\r\n if (io.sockets.adapter.rooms.has(token)) {\r\n io.to(token).emit(socketEventNames.updateSession, JSON.stringify(data));\r\n }\r\n\r\n if (newUser) {\r\n await dispatchHook('postSessionCreate', { token, user: data, persistent: !getProjectConfig().session.basedToken });\r\n }\r\n }, undefined, { fn: 'saveSession', token });\r\n\r\n if (error) {\r\n getLogger().error('saveSession failed', error, { token });\r\n }\r\n};\r\n\r\n/**\r\n * Retrieve a user session through the active session adapter.\r\n *\r\n * @param token - The session token\r\n * @returns The session data or null if not found\r\n */\r\nconst getSession = async (token: string | null): Promise<SessionLayout | null> => {\r\n if (!token) return null;\r\n\r\n const [error, value] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n\r\n const raw = await adapter.getRaw(token);\r\n if (!raw) return null;\r\n\r\n const parsed = parseSessionLayout(raw);\r\n const userId = parsed?.id ?? null;\r\n\r\n // Sliding expiration: each successful authenticated access extends session lifetime.\r\n const newTtl = getSessionTtl();\r\n const oldTtl = await adapter.ttl(token);\r\n const preRefreshResult = await dispatchHook('preSessionRefresh', { token, userId, oldTtl, newTtl });\r\n\r\n //? A `preSessionRefresh` handler can stop the TTL extension (e.g. admin\r\n //? freezing a session pending review). When stopped, we skip the\r\n //? `adapter.expire` call and surface `applied: false` on the post payload\r\n //? so audit handlers see the same signal.\r\n let applied = false;\r\n if (!preRefreshResult.stopped) {\r\n applied = await adapter.expire(token, newTtl);\r\n }\r\n await dispatchHook('postSessionRefresh', {\r\n token,\r\n userId,\r\n oldTtl,\r\n newTtl,\r\n applied,\r\n });\r\n\r\n if (!parsed) return null;\r\n const merged: SessionLayout = { ...parsed, token };\r\n return merged;\r\n }, undefined, { fn: 'getSession', token });\r\n\r\n if (error) {\r\n getLogger().error('getSession failed', error, { token });\r\n return null;\r\n }\r\n return value;\r\n};\r\n\r\n/**\r\n * Delete a user session through the active adapter and notify connected\r\n * clients.\r\n *\r\n * @param token - The session token to delete\r\n * @returns true if successful\r\n */\r\nconst deleteSession = async (token: string): Promise<boolean> => {\r\n //? Opt-in spurious-delete tracing. `deleteSession` runs on EVERY legitimate\r\n //? logout / revokeSession / signOutEverywhere / deleteAccount, so an\r\n //? unconditional warn+stacktrace drowns the signal it's meant to surface and\r\n //? spams dev/test output with stacktrace-shaped lines that read like errors.\r\n //? Set LUCKYSTACK_TRACE_SESSION_DELETES=1 when hunting a \"why was I logged\r\n //? out?\" bug to print the originating caller's stack; silent otherwise.\r\n if (process.env.LUCKYSTACK_TRACE_SESSION_DELETES === '1') {\r\n getLogger().warn('[session] deleteSession invoked', {\r\n tokenPrefix: token.slice(0, 8),\r\n stack: new Error('deleteSession invoked').stack,\r\n });\r\n }\r\n\r\n const [error, ok] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n\r\n const raw = await adapter.getRaw(token);\r\n let resolvedUserId: string | null = null;\r\n\r\n if (raw) {\r\n resolvedUserId = parseSessionLayout(raw)?.id ?? null;\r\n }\r\n\r\n const preSessionDeleteResult = await dispatchHook('preSessionDelete', { token, userId: resolvedUserId });\r\n if (preSessionDeleteResult.stopped) {\r\n getLogger().warn(`session delete aborted by preSessionDelete hook`, { errorCode: preSessionDeleteResult.signal.errorCode });\r\n return false;\r\n }\r\n\r\n if (raw && resolvedUserId) {\r\n const { getIoInstance } = await import('@luckystack/core');\r\n const ioInstance = getIoInstance();\r\n\r\n // Reuse the same logout flow as single-session enforcement.\r\n if (ioInstance) {\r\n const { logout } = await import('./logout');\r\n const sockets = ioInstance.sockets.adapter.rooms.get(token);\r\n\r\n if (sockets) {\r\n await Promise.all([...sockets].map(async (socketId) => {\r\n const socket = ioInstance.sockets.sockets.get(socketId);\r\n if (!socket) return;\r\n\r\n await logout({\r\n token,\r\n socket,\r\n userId: resolvedUserId,\r\n skipSessionDelete: true,\r\n });\r\n }));\r\n }\r\n }\r\n\r\n await adapter.untrackActive(resolvedUserId, token);\r\n }\r\n\r\n await adapter.delete(token);\r\n await dispatchHook('postSessionDelete', { token, userId: resolvedUserId });\r\n return true;\r\n }, undefined, { fn: 'deleteSession', token });\r\n\r\n if (error) {\r\n getLogger().error('deleteSession failed', error, { token });\r\n return false;\r\n }\r\n return ok ?? false;\r\n};\r\n\r\n/**\r\n * Get all active sessions (admin utility). Returns empty when the active\r\n * adapter doesn't implement enumeration (signed-JWT-stateless, log-only).\r\n *\r\n * @returns Array of all session data\r\n */\r\nconst getAllSessions = async (): Promise<SessionLayout[]> => {\r\n const [error, sessions] = await tryCatch(async () => {\r\n const adapter = getSessionAdapter();\r\n if (!adapter.listAll) {\r\n getLogger().warn(`[session] getAllSessions: adapter '${adapter.name}' does not implement listAll — returning empty`);\r\n return [];\r\n }\r\n\r\n const rows = await adapter.listAll();\r\n const parsedSessions: SessionLayout[] = [];\r\n for (const { raw } of rows) {\r\n const parsed = parseSessionLayout(raw);\r\n if (parsed) parsedSessions.push(parsed);\r\n }\r\n return parsedSessions;\r\n }, undefined, { fn: 'getAllSessions' });\r\n\r\n if (error) {\r\n getLogger().error('getAllSessions failed', error);\r\n return [];\r\n }\r\n return sessions ?? [];\r\n};\r\n\r\n/**\r\n * Revoke every active session for a user, optionally keeping one alive.\r\n *\r\n * @param userId - The user whose sessions to revoke\r\n * @param exceptToken - If provided, this session is left untouched. Pass the\r\n * caller's own token when revoking after a password change so the user\r\n * isn't logged out of the device they just changed it from. Pass null/omit\r\n * to revoke everything (account deletion, \"sign out everywhere\", etc.).\r\n *\r\n * Each revocation goes through `deleteSession`, so connected sockets are\r\n * told to log out via the same flow as a normal logout. Returns the number\r\n * of sessions actually revoked.\r\n */\r\nconst revokeUserSessions = async (userId: string, exceptToken?: string | null): Promise<number> => {\r\n if (!userId) return 0;\r\n\r\n const adapter = getSessionAdapter();\r\n const tokens = await adapter.listActive(userId);\r\n const targets = exceptToken ? tokens.filter((t) => t !== exceptToken) : tokens;\r\n\r\n await Promise.all(targets.map((token) => deleteSession(token)));\r\n return targets.length;\r\n};\r\n\r\n//? Legacy key-builders preserved as exports for downstream code that\r\n//? assumed a stable Redis layout (admin tooling, dev REPL scripts). New\r\n//? callers should use the active SessionAdapter instead.\r\nconst sessionKeyFor = (token: string): string => formatKey('-session', token);\r\nconst activeUsersKeyFor = (userId: string): string => formatKey('-activeUsers', userId);\r\n\r\nexport { saveSession, getSession, deleteSession, getAllSessions, revokeUserSessions, sessionKeyFor, activeUsersKeyFor };\r\n","// Registers module augmentations on @luckystack/core for auth/session hooks.\r\nimport './hookPayloads';\r\n\r\nexport type { BaseSessionLayout, SessionLocation, AuthProps } from './sessionLayout';\r\nexport type {\r\n PreLoginPayload,\r\n PostLoginPayload,\r\n PreRegisterPayload,\r\n PostRegisterPayload,\r\n PreLogoutPayload,\r\n PostLogoutPayload,\r\n PreSessionCreatePayload,\r\n PostSessionCreatePayload,\r\n PreSessionDeletePayload,\r\n PostSessionDeletePayload,\r\n PreEmailChangePayload,\r\n PostEmailChangeRequestedPayload,\r\n PostEmailChangedPayload,\r\n} from './hookPayloads';\r\nexport { saveSession, getSession, deleteSession, getAllSessions, revokeUserSessions, sessionKeyFor, activeUsersKeyFor } from './session';\r\nexport { registerSessionAdapter, getSessionAdapter, redisSessionAdapter } from './sessionAdapter';\r\nexport type { SessionAdapter } from './sessionAdapter';\r\nexport { loginWithCredentials, loginCallback, createOAuthState, registerWithCredentials, loginWithCredentialsCore } from './login';\r\nexport { logout } from './logout';\r\n\r\n// Password-reset primitives. Used by the framework's `framework`-mode\r\n// forgot-password flow AND exported for consumers who picked `'custom'`.\r\nexport {\r\n createPasswordResetToken,\r\n consumePasswordResetToken,\r\n updatePasswordHash,\r\n verifyPassword,\r\n PasswordPolicyError,\r\n} from './passwordReset';\r\nexport { validatePassword } from './passwordPolicy';\r\nexport { sendPasswordResetEmail } from './forgotPassword';\r\n\r\n// Email-change primitives. Used by the framework's settings flow to confirm\r\n// a new email address before applying it (token mailed to NEW address proves\r\n// ownership of the new mailbox).\r\nexport {\r\n createEmailChangeToken,\r\n consumeEmailChangeToken,\r\n} from './emailChange';\r\nexport type { EmailChangePayload } from './emailChange';\r\nexport { sendEmailChangeConfirmation } from './emailChangeNotification';\r\n\r\n// OAuth provider registry + composable helpers.\r\nexport {\r\n registerOAuthProviders,\r\n getOAuthProviders,\r\n isFullOAuthProvider,\r\n credentialsProvider,\r\n googleProvider,\r\n githubProvider,\r\n discordProvider,\r\n facebookProvider,\r\n microsoftProvider,\r\n} from './oauthProviders';\r\nexport type {\r\n OAuthProvider,\r\n CredentialsProvider,\r\n FullOAuthProvider,\r\n} from './oauthProviders';\r\n\r\n// User adapter registry: lets consumers swap out the Prisma User model behind auth flows.\r\nexport {\r\n registerUserAdapter,\r\n getUserAdapter,\r\n isUserAdapterRegistered,\r\n defaultPrismaUserAdapter,\r\n} from './userAdapter';\r\nexport type {\r\n UserAdapter,\r\n UserAdapterCreateInput,\r\n UserRecord,\r\n} from './userAdapter';\r\n\r\n// Post-login redirect resolver — lets consumers compute the OAuth callback\r\n// destination dynamically (per-user, per-tenant, per-provider).\r\nexport {\r\n registerPostLoginRedirect,\r\n getPostLoginRedirect,\r\n} from './redirectResolver';\r\nexport type {\r\n PostLoginRedirectResolver,\r\n PostLoginRedirectInput,\r\n} from './redirectResolver';\r\n","//? OAuth provider registry. Replaces the previously hardcoded provider list\r\n//? in `loginConfig.ts`. Consumers register providers from their\r\n//? `luckystack/login/oauthProviders.ts` overlay file at boot.\r\n//?\r\n//? Two-layer surface:\r\n//? 1. `registerOAuthProviders([...])` — accept an array of provider definitions.\r\n//? 2. Built-in helpers (`googleProvider`, `githubProvider`, ...) so the common\r\n//? case is one function call per provider with only the secrets + callback.\r\n//?\r\n//? Custom providers (Okta, Apple, X, etc.) plug in as raw `OAuthProvider`\r\n//? objects alongside the helpers.\r\n\r\nimport { tryCatch } from '@luckystack/core';\r\n\r\ntype OAuthUserData = Record<string, unknown>;\r\n\r\nexport const asOAuthUserData = (value: unknown): OAuthUserData => {\r\n if (value && typeof value === 'object') {\r\n return value as OAuthUserData;\r\n }\r\n return {};\r\n};\r\n\r\nexport interface CredentialsProvider {\r\n name: 'credentials';\r\n}\r\n\r\nexport interface FullOAuthProvider {\r\n name: string;\r\n clientID: string;\r\n clientSecret: string;\r\n callbackURL: string;\r\n authorizationURL: string;\r\n tokenExchangeURL: string;\r\n tokenExchangeMethod: 'json' | 'form';\r\n userInfoURL: string;\r\n /**\r\n * OAuth scopes requested at the authorization step. Consumers can extend\r\n * a built-in provider's defaults to request additional permissions:\r\n *\r\n * ```ts\r\n * const customGoogle = googleProvider({...});\r\n * customGoogle.scope.push('https://www.googleapis.com/auth/calendar.readonly');\r\n * ```\r\n *\r\n * Or pass `extraScopes` to the built-in factory (see `OAuthHelperInput`).\r\n */\r\n scope: string[];\r\n nameKey: string;\r\n emailKey: string;\r\n avatarKey?: string;\r\n avatarCodeKey: string;\r\n getEmail?: (accessToken: string) => Promise<string | false | undefined>;\r\n getAvatar?: (params: { userData: OAuthUserData; avatarId?: string; accessToken: string }) => string | undefined | Promise<string | undefined>;\r\n /**\r\n * Optional per-provider hook to attach extra runtime-only data to the\r\n * session (NOT to the User row). Return key/value pairs that get merged\r\n * into `BaseSessionLayout` before `saveSession`. To get strict typing\r\n * downstream, extend `BaseSessionLayout` via module augmentation:\r\n *\r\n * ```ts\r\n * declare module '@luckystack/core' {\r\n * interface BaseSessionLayout {\r\n * googleCalendarToken?: string;\r\n * }\r\n * }\r\n * ```\r\n *\r\n * Data lives on the session in Redis and disappears on logout — no Prisma\r\n * schema change required. Don't store secrets you wouldn't want surfaced\r\n * to a user inspecting their own session.\r\n */\r\n extraSessionFields?: (params: {\r\n userData: OAuthUserData;\r\n accessToken: string;\r\n }) => Promise<Record<string, unknown>> | Record<string, unknown>;\r\n}\r\n\r\nexport type OAuthProvider = CredentialsProvider | FullOAuthProvider;\r\n\r\nexport const isFullOAuthProvider = (provider: OAuthProvider): provider is FullOAuthProvider =>\r\n provider.name !== 'credentials';\r\n\r\nexport interface OAuthEndpointOverrides {\r\n /** Override the provider's authorization URL (e.g. GitHub Enterprise host, Microsoft custom tenant). */\r\n authorizationURL?: string;\r\n /** Override the token exchange URL. */\r\n tokenExchangeURL?: string;\r\n /** Override the userInfo URL. */\r\n userInfoURL?: string;\r\n}\r\n\r\ninterface OAuthHelperInput {\r\n clientId: string | undefined;\r\n clientSecret: string | undefined;\r\n callbackUrl: string;\r\n /** Optional URL overrides for self-hosted / custom-tenant deployments. */\r\n endpoints?: OAuthEndpointOverrides;\r\n /**\r\n * Additional OAuth scopes to request alongside the built-in defaults. Use\r\n * for \"give me access to the user's calendar / drive / repos\" flows.\r\n * Merged with the helper's default scopes; duplicates are deduplicated.\r\n */\r\n extraScopes?: string[];\r\n /**\r\n * Per-provider hook for attaching runtime-only fields to the session.\r\n * See `FullOAuthProvider.extraSessionFields` for usage + module-augmentation\r\n * pattern.\r\n */\r\n extraSessionFields?: FullOAuthProvider['extraSessionFields'];\r\n}\r\n\r\nconst mergeScopes = (defaults: string[], extra: string[] | undefined): string[] => {\r\n if (!extra || extra.length === 0) return defaults;\r\n return [...new Set([...defaults, ...extra])];\r\n};\r\n\r\nconst requireString = (value: string | undefined, label: string): string => {\r\n if (!value || value.length === 0) {\r\n throw new Error(`OAuth provider configuration error: ${label} is empty.`);\r\n }\r\n return value;\r\n};\r\n\r\nexport const credentialsProvider = (): CredentialsProvider => ({ name: 'credentials' });\r\n\r\nexport const googleProvider = (input: OAuthHelperInput): FullOAuthProvider => ({\r\n name: 'google',\r\n clientID: requireString(input.clientId, 'google clientId'),\r\n clientSecret: requireString(input.clientSecret, 'google clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? 'https://accounts.google.com/o/oauth2/v2/auth',\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? 'https://oauth2.googleapis.com/token',\r\n tokenExchangeMethod: 'json',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://www.googleapis.com/oauth2/v1/userinfo',\r\n scope: mergeScopes([\r\n 'https://www.googleapis.com/auth/userinfo.profile',\r\n 'https://www.googleapis.com/auth/userinfo.email',\r\n ], input.extraScopes),\r\n nameKey: 'name',\r\n emailKey: 'email',\r\n avatarKey: 'picture',\r\n avatarCodeKey: '',\r\n extraSessionFields: input.extraSessionFields,\r\n});\r\n\r\nexport const githubProvider = (input: OAuthHelperInput): FullOAuthProvider => ({\r\n name: 'github',\r\n clientID: requireString(input.clientId, 'github clientId'),\r\n clientSecret: requireString(input.clientSecret, 'github clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? 'https://github.com/login/oauth/authorize',\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? 'https://github.com/login/oauth/access_token',\r\n tokenExchangeMethod: 'json',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://api.github.com/user',\r\n scope: mergeScopes(['read:user', 'user:email'], input.extraScopes),\r\n nameKey: 'login',\r\n emailKey: 'email',\r\n avatarKey: 'avatar_url',\r\n avatarCodeKey: '',\r\n extraSessionFields: input.extraSessionFields,\r\n getEmail: async (accessToken) => {\r\n const fetchEmails = async () => {\r\n const response = await fetch('https://api.github.com/user/emails', {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Accept: 'application/json',\r\n Authorization: `Bearer ${accessToken}`,\r\n },\r\n });\r\n if (!response.ok) return false;\r\n const emails: unknown = await response.json();\r\n if (!Array.isArray(emails)) return false;\r\n return emails as { primary?: boolean; email?: string }[];\r\n };\r\n\r\n const [error, emails] = await tryCatch(fetchEmails);\r\n if (error || !emails) return false;\r\n\r\n let mainEmail: string | undefined;\r\n for (const entry of emails) {\r\n if (entry.primary && typeof entry.email === 'string') mainEmail = entry.email;\r\n }\r\n mainEmail ??= emails[0]?.email;\r\n return mainEmail;\r\n },\r\n});\r\n\r\nexport const discordProvider = (input: OAuthHelperInput): FullOAuthProvider => ({\r\n name: 'discord',\r\n clientID: requireString(input.clientId, 'discord clientId'),\r\n clientSecret: requireString(input.clientSecret, 'discord clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? 'https://discord.com/oauth2/authorize',\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? 'https://discord.com/api/oauth2/token',\r\n tokenExchangeMethod: 'form',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://discord.com/api/users/@me',\r\n scope: mergeScopes(['identify', 'email'], input.extraScopes),\r\n nameKey: 'username',\r\n emailKey: 'email',\r\n avatarCodeKey: 'avatar',\r\n extraSessionFields: input.extraSessionFields,\r\n getAvatar: ({ userData, avatarId }) => {\r\n if (!avatarId) return;\r\n const userId = typeof userData.id === 'string' ? userData.id : '';\r\n if (!userId) return;\r\n const format = avatarId.startsWith('a_') ? 'gif' : 'png';\r\n return `https://cdn.discordapp.com/avatars/${userId}/${avatarId}.${format}`;\r\n },\r\n});\r\n\r\nexport interface FacebookProviderInput extends OAuthHelperInput {\r\n /** Graph API version segment used in the URLs. Default `'v18.0'` (current as of 2026 — bump as Meta deprecates older versions). */\r\n apiVersion?: string;\r\n}\r\n\r\nexport const facebookProvider = (input: FacebookProviderInput): FullOAuthProvider => {\r\n const v = input.apiVersion ?? 'v18.0';\r\n return {\r\n name: 'facebook',\r\n clientID: requireString(input.clientId, 'facebook clientId'),\r\n clientSecret: requireString(input.clientSecret, 'facebook clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? `https://www.facebook.com/${v}/dialog/oauth`,\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? `https://graph.facebook.com/${v}/oauth/access_token`,\r\n tokenExchangeMethod: 'form',\r\n userInfoURL: input.endpoints?.userInfoURL ?? 'https://graph.facebook.com/me?fields=id,name,email,picture.type(large)',\r\n scope: mergeScopes(['public_profile', 'email'], input.extraScopes),\r\n nameKey: 'name',\r\n emailKey: 'email',\r\n avatarCodeKey: '',\r\n extraSessionFields: input.extraSessionFields,\r\n getAvatar: ({ userData }) => {\r\n const picture = asOAuthUserData(userData.picture);\r\n const pictureData = asOAuthUserData(picture.data);\r\n const url = pictureData.url;\r\n return typeof url === 'string' ? url : undefined;\r\n },\r\n };\r\n};\r\n\r\nexport interface MicrosoftProviderInput extends OAuthHelperInput {\r\n /** Tenant id segment in the authorize/token URLs. Default `'common'`. */\r\n tenant?: string;\r\n /** OAuth API version segment. Default `'v2.0'`. */\r\n apiVersion?: string;\r\n /** Graph API version segment for /me. Default `'v1.0'`. */\r\n graphApiVersion?: string;\r\n}\r\n\r\n//? NOTE: Microsoft flow is implemented but has not been end-to-end verified\r\n//? against an Azure AD tenant as of 2026-05-14. The token-exchange URL, the\r\n//? Graph /me shape, and the photo data-URL pipeline are based on Microsoft\r\n//? docs and pattern-matching against Google/GitHub. First consumer to wire a\r\n//? real Azure tenant should report back so this note can be dropped.\r\nexport const microsoftProvider = (input: MicrosoftProviderInput): FullOAuthProvider => {\r\n const tenant = input.tenant ?? 'common';\r\n const oauthVersion = input.apiVersion ?? 'v2.0';\r\n const graphVersion = input.graphApiVersion ?? 'v1.0';\r\n return {\r\n name: 'microsoft',\r\n clientID: requireString(input.clientId, 'microsoft clientId'),\r\n clientSecret: requireString(input.clientSecret, 'microsoft clientSecret'),\r\n callbackURL: input.callbackUrl,\r\n authorizationURL: input.endpoints?.authorizationURL ?? `https://login.microsoftonline.com/${tenant}/oauth2/${oauthVersion}/authorize`,\r\n tokenExchangeURL: input.endpoints?.tokenExchangeURL ?? `https://login.microsoftonline.com/${tenant}/oauth2/${oauthVersion}/token`,\r\n tokenExchangeMethod: 'form',\r\n userInfoURL: input.endpoints?.userInfoURL ?? `https://graph.microsoft.com/${graphVersion}/me`,\r\n scope: mergeScopes(['openid', 'profile', 'email', 'User.Read'], input.extraScopes),\r\n nameKey: 'displayName',\r\n emailKey: 'mail',\r\n avatarCodeKey: 'id',\r\n extraSessionFields: input.extraSessionFields,\r\n //? Graph's /photo/$value requires bearer auth, so we can't store the URL —\r\n //? a browser <img> would 401. Fetch the bytes and inline as a data URL.\r\n getAvatar: async ({ avatarId, accessToken }) => {\r\n if (!avatarId) return;\r\n const fetchPhoto = async () => {\r\n const response = await fetch(\r\n `https://graph.microsoft.com/${graphVersion}/users/${avatarId}/photo/$value`,\r\n { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` } },\r\n );\r\n if (!response.ok) return;\r\n const contentType = response.headers.get('content-type') ?? 'image/jpeg';\r\n const buffer = Buffer.from(await response.arrayBuffer());\r\n return `data:${contentType};base64,${buffer.toString('base64')}`;\r\n };\r\n const [error, dataUrl] = await tryCatch(fetchPhoto);\r\n if (error) return;\r\n return dataUrl ?? undefined;\r\n },\r\n getEmail: async (accessToken) => {\r\n const fetchProfile = async () => {\r\n const response = await fetch(`https://graph.microsoft.com/${graphVersion}/me`, {\r\n method: 'GET',\r\n headers: {\r\n Authorization: `Bearer ${accessToken}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n });\r\n if (!response.ok) return false;\r\n const data: unknown = await response.json();\r\n if (!data || typeof data !== 'object') return false;\r\n return data as Record<string, unknown>;\r\n };\r\n\r\n const [error, profile] = await tryCatch(fetchProfile);\r\n if (error || !profile) return false;\r\n\r\n const record = asOAuthUserData(profile);\r\n const mail = typeof record.mail === 'string' ? record.mail : '';\r\n const userPrincipalName = typeof record.userPrincipalName === 'string' ? record.userPrincipalName : '';\r\n return (mail || userPrincipalName) || false;\r\n },\r\n };\r\n};\r\n\r\nlet registeredProviders: OAuthProvider[] = [{ name: 'credentials' }];\r\n\r\n//? Always returns the active list. The default value contains only the\r\n//? `credentials` entry so calls into `loginWithCredentials` keep working in\r\n//? environments that never register OAuth providers (tests, CLI, etc.).\r\nexport const getOAuthProviders = (): OAuthProvider[] => registeredProviders;\r\n\r\nexport const registerOAuthProviders = (providers: OAuthProvider[]): OAuthProvider[] => {\r\n registeredProviders = providers;\r\n return registeredProviders;\r\n};\r\n","//? Post-login redirect resolver. Lets consumers compute the OAuth callback\r\n//? redirect URL dynamically (per-user, per-tenant, per-provider).\r\n//?\r\n//? Why a dedicated registry instead of a hook:\r\n//? The framework's hooks are \"stop-or-continue\" — handlers return undefined\r\n//? or a stop signal. They don't carry a value. A redirect-resolution\r\n//? handler must RETURN a string, so it lives in its own registry.\r\n//?\r\n//? Validation: the resolved URL is checked against\r\n//? `ProjectConfig.http.cors.allowedOrigins` (plus localhost + the bound\r\n//? SERVER_IP) before being used. An invalid URL falls back to\r\n//? `ProjectConfig.loginRedirectUrl`.\r\n\r\nexport interface PostLoginRedirectInput {\r\n userId: string;\r\n provider: string;\r\n isNewUser: boolean;\r\n defaultUrl: string;\r\n}\r\n\r\nexport type PostLoginRedirectResolver = (\r\n input: PostLoginRedirectInput,\r\n) => string | Promise<string>;\r\n\r\nlet activeResolver: PostLoginRedirectResolver | null = null;\r\n\r\nexport const registerPostLoginRedirect = (\r\n resolver: PostLoginRedirectResolver,\r\n): PostLoginRedirectResolver => {\r\n activeResolver = resolver;\r\n return resolver;\r\n};\r\n\r\nexport const getPostLoginRedirect = (): PostLoginRedirectResolver | null => activeResolver;\r\n","/* eslint-disable unicorn/no-abusive-eslint-disable */\r\n/* eslint-disable */\r\nimport { asOAuthUserData, getOAuthProviders, isFullOAuthProvider, type FullOAuthProvider } from './oauthProviders';\r\nimport { getPostLoginRedirect } from './redirectResolver';\r\nimport { IncomingMessage, ServerResponse } from 'node:http';\r\nimport { URLSearchParams } from 'node:url';\r\nimport { tryCatch, redis as redisClient, getUploadsDir, dispatchHook, getLogger, formatKey } from '@luckystack/core';\r\nimport bcrypt from 'bcryptjs';\r\nimport { randomBytes } from 'node:crypto';\r\nimport { saveSession } from \"./session\"\r\nimport validator from 'validator';\r\nimport type { BaseSessionLayout as SessionLayout } from './sessionLayout';\r\nimport { getProjectConfig } from '@luckystack/core';\r\nimport { getUserAdapter } from './userAdapter';\r\nimport { validatePassword } from './passwordPolicy';\r\nimport path from 'node:path';\r\nimport { existsSync } from 'node:fs';\r\n\r\ninterface paramsType {\r\n email?: string,\r\n password?: string,\r\n name?: string,\r\n confirmPassword?: string,\r\n}\r\n\r\n//? Resolved at call time via getUploadsDir() so consumer path overrides win.\r\nconst uploadsFolder = (): string => getUploadsDir();\r\n//? Resolve at call time so dotenv/test-setup timing can't capture a stale\r\n//? value at module load. Routes through `projectConfig.logging.devLogs`\r\n//? which is the standard predicate everywhere else in the codebase.\r\nconst isDevMode = (): boolean => getProjectConfig().logging.devLogs;\r\nconst { compare, genSalt, hash } = bcrypt;\r\nconst { escape, isEmail } = validator;\r\n\r\n//? Use the shared `getProjectName()` helper so the OAuth state Redis key\r\n//? prefix matches the namespace used everywhere else (sessions, activeUsers,\r\n//? rate-limit, password-reset). Previously this used a separate\r\n//? `auth.oauthStateProjectNameFallback` config field which could silently\r\n//? drift from `session.projectName`.\r\nconst getOAuthStateKey = (providerName: string, state: string): string => {\r\n return formatKey('-oauth-state', `${providerName}:${state}`);\r\n};\r\n\r\nexport const createOAuthState = async (providerName: string): Promise<string | null> => {\r\n const state = randomBytes(32).toString('hex');\r\n const key = getOAuthStateKey(providerName, state);\r\n const result = await redisClient.set(key, '1', 'EX', getProjectConfig().auth.oauthStateTtlSeconds, 'NX');\r\n\r\n if (result !== 'OK') {\r\n return null;\r\n }\r\n\r\n return state;\r\n};\r\n\r\nconst consumeOAuthState = async (providerName: string, state: string): Promise<boolean> => {\r\n if (!state) {\r\n return false;\r\n }\r\n\r\n const key = getOAuthStateKey(providerName, state);\r\n const txResult = await redisClient.multi().get(key).del(key).exec();\r\n if (!txResult || txResult.length < 2) {\r\n return false;\r\n }\r\n\r\n const getResult = txResult[0];\r\n if (!getResult || getResult[0]) {\r\n return false;\r\n }\r\n\r\n return getResult[1] === '1';\r\n};\r\n\r\nconst asRecord = (value: unknown): Record<string, unknown> => {\r\n if (value && typeof value === 'object') {\r\n return value as Record<string, unknown>;\r\n }\r\n return {};\r\n};\r\n\r\nconst sanitizeUserForSession = <T extends { password?: unknown }>(user: T): Omit<T, 'password'> => {\r\n const { password: _password, ...safeUser } = user;\r\n return safeUser;\r\n};\r\n\r\nconst toReasonKey = (error: unknown, fallback = 'api.internalServerError'): string => {\r\n if (typeof error === 'string' && error.length > 0) {\r\n return error;\r\n }\r\n\r\n if (error instanceof Error && error.message) {\r\n const message = error.message.toLowerCase();\r\n if (message.includes('authentication failed') || message.includes('scram failure')) {\r\n return 'api.internalServerError';\r\n }\r\n }\r\n\r\n return fallback;\r\n};\r\n\r\ninterface NormalizedCredentials {\r\n email: string;\r\n password: string;\r\n name: string | undefined;\r\n confirmPassword: string | undefined;\r\n}\r\n\r\nconst normalizeCredentials = (params: paramsType): NormalizedCredentials => ({\r\n //? Don't HTML-escape password OR confirmPassword before bcrypt — neither\r\n //? reaches HTML, and they're memory-compared for equality during register.\r\n //? Escaping confirmPassword while leaving password raw made any password\r\n //? containing `& < > \" '` fail the `password !== confirmPassword` check\r\n //? (regression introduced in pass-1 fix #9 which only un-escaped password).\r\n //? Email is normalized (lowercase + trim) and validated via `isEmail`;\r\n //? HTML-escaping it would make legacy raw rows unfindable.\r\n email: (params.email || '').trim().toLowerCase(),\r\n password: params.password || '',\r\n name: params.name ? escape(params.name) : undefined,\r\n confirmPassword: params.confirmPassword || undefined,\r\n});\r\n\r\nconst validateCredentialsShape = (creds: NormalizedCredentials): { status: false; reason: string } | null => {\r\n const authLimits = getProjectConfig().auth;\r\n if (!creds.email || !creds.password) return { status: false, reason: 'login.empty' };\r\n if (creds.email.length > authLimits.emailMaxLength) return { status: false, reason: 'login.emailCharacterLimit' };\r\n if (creds.name && creds.name.length > authLimits.nameMaxLength) return { status: false, reason: 'login.nameCharacterLimit' };\r\n if (!isEmail(creds.email)) return { status: false, reason: 'login.invalidEmailFormat' };\r\n //? Full password-policy check (length, complexity, common-list, customValidator).\r\n //? Policy lives in `projectConfig.auth.passwordPolicy`; the deprecated\r\n //? `passwordMinLength`/`passwordMaxLength` top-level fields are still read\r\n //? from the policy's `minLength`/`maxLength` defaults.\r\n const passwordReason = validatePassword(creds.password);\r\n if (passwordReason) return { status: false, reason: passwordReason };\r\n return null;\r\n};\r\n\r\nconst registerWithCredentials = async ({\r\n email,\r\n password,\r\n name,\r\n confirmPassword,\r\n}: {\r\n email: string;\r\n password: string;\r\n name: string;\r\n confirmPassword: string;\r\n}) => {\r\n if (password !== confirmPassword) {\r\n return { status: false, reason: 'login.passwordNotMatch' };\r\n }\r\n\r\n const preRegisterResult = await dispatchHook('preRegister', { email, provider: 'credentials', name });\r\n if (preRegisterResult.stopped) {\r\n return { status: false, reason: preRegisterResult.signal.errorCode };\r\n }\r\n\r\n const userAdapter = getUserAdapter();\r\n const [checkEmailError, checkEmailResponse] = await tryCatch(() =>\r\n userAdapter.findByEmail({ email, provider: 'credentials' })\r\n );\r\n if (checkEmailError) {\r\n getLogger().error('login: findByEmail failed during register', checkEmailError);\r\n return { status: false, reason: toReasonKey(checkEmailError) };\r\n }\r\n if (checkEmailResponse) return { status: false, reason: 'login.emailExists' };\r\n\r\n const createNewUser = async () => {\r\n const salt = await genSalt(getProjectConfig().auth.bcryptRounds);\r\n const hashedPassword = await hash(password, salt);\r\n return await userAdapter.create({\r\n email,\r\n provider: 'credentials',\r\n name,\r\n password: hashedPassword,\r\n avatar: '',\r\n avatarFallback: `#${Math.floor(Math.random() * 0xFF_FF_FF).toString(16).padStart(6, \"0\")}`,\r\n language: getProjectConfig().defaultLanguage,\r\n });\r\n };\r\n\r\n const [createNewUserError, createNewUserResponse] = await tryCatch(createNewUser);\r\n if (createNewUserError) return { status: false, reason: toReasonKey(createNewUserError) };\r\n if (!createNewUserResponse) return { status: false, reason: 'login.createUserFailed' };\r\n\r\n await dispatchHook('postRegister', { userId: createNewUserResponse.id, provider: 'credentials' });\r\n return {\r\n status: true,\r\n reason: 'login.userCreated',\r\n session: sanitizeUserForSession(createNewUserResponse),\r\n };\r\n};\r\n\r\nconst loginWithCredentialsCore = async ({ email, password }: { email: string; password: string }) => {\r\n const preLoginResult = await dispatchHook('preLogin', { email, provider: 'credentials' });\r\n if (preLoginResult.stopped) {\r\n return { status: false, reason: preLoginResult.signal.errorCode };\r\n }\r\n\r\n const userAdapter = getUserAdapter();\r\n const [findUserError, findUserResponse] = await tryCatch(() =>\r\n userAdapter.findByEmail({ email, provider: 'credentials' })\r\n );\r\n if (findUserError) {\r\n getLogger().error('login: findByEmail failed', findUserError);\r\n return { status: false, reason: toReasonKey(findUserError) };\r\n }\r\n if (!findUserResponse) return { status: false, reason: 'login.userNotFound' };\r\n\r\n const [checkPasswordError, checkPasswordResponse] = await tryCatch(() =>\r\n compare(password, findUserResponse.password!)\r\n );\r\n if (checkPasswordError) {\r\n getLogger().error('login: bcrypt compare failed', checkPasswordError);\r\n return { status: false, reason: checkPasswordError };\r\n }\r\n if (!checkPasswordResponse) return { status: false, reason: 'login.wrongPassword' };\r\n\r\n const newToken = randomBytes(32).toString('hex');\r\n const previousLogin = (findUserResponse as { lastLogin?: Date | null }).lastLogin ?? null;\r\n const nowLogin = new Date();\r\n\r\n // Best-effort lastLogin update — silently no-ops if the user adapter\r\n // (or User schema) doesn't accept the field.\r\n await tryCatch(() => userAdapter.update(findUserResponse.id, { lastLogin: nowLogin } as never));\r\n\r\n const newUser: SessionLayout = {\r\n ...sanitizeUserForSession(findUserResponse),\r\n token: newToken,\r\n lastLogin: nowLogin,\r\n previousLogin,\r\n };\r\n\r\n const filePath = path.join(uploadsFolder(), `${newUser.id}.webp`);\r\n if (existsSync(filePath)) {\r\n newUser.avatar = `${newUser.id}.webp`;\r\n }\r\n\r\n await saveSession(newToken, newUser, true);\r\n await dispatchHook('postLogin', { userId: newUser.id, provider: 'credentials', isNewUser: false, token: newToken });\r\n if (isDevMode()) {\r\n getLogger().debug(`credentials login success for user ${newUser.id}`);\r\n }\r\n return { status: true, reason: 'login.loggedIn', newToken, session: newUser };\r\n};\r\n\r\n//? Thin dispatcher: keeps the existing single-entry HTTP surface but forwards\r\n//? to the dedicated register / login functions. `name && confirmPassword`\r\n//? present in the body means the client is registering; otherwise it's a\r\n//? login.\r\nconst loginWithCredentials = async (params: paramsType) => {\r\n const creds = normalizeCredentials(params);\r\n\r\n if (isDevMode()) {\r\n getLogger().debug(`credentials auth attempt for ${creds.email || 'unknown-email'}`);\r\n }\r\n\r\n const validationError = validateCredentialsShape(creds);\r\n if (validationError) return validationError;\r\n\r\n if (creds.name && creds.confirmPassword) {\r\n return registerWithCredentials({\r\n email: creds.email,\r\n password: creds.password,\r\n name: creds.name,\r\n confirmPassword: creds.confirmPassword,\r\n });\r\n }\r\n return loginWithCredentialsCore({ email: creds.email, password: creds.password });\r\n};\r\n\r\nexport interface OAuthCallbackResult {\r\n token: string;\r\n redirectUrl: string;\r\n userId: string;\r\n provider: string;\r\n isNewUser: boolean;\r\n}\r\n\r\nconst isAllowedRedirectUrl = (url: string): boolean => {\r\n if (!URL.canParse(url, 'http://placeholder')) return false;\r\n const parsed = new URL(url, 'http://placeholder');\r\n if (parsed.origin === 'http://placeholder') {\r\n // relative URL — same-origin, always safe\r\n return true;\r\n }\r\n //? `allowedOrigins` is `string[] | (origin: string) => boolean` since we\r\n //? added the function-resolver variant. Branch on the shape.\r\n const allowed = getProjectConfig().http.cors.allowedOrigins ?? [];\r\n if (typeof allowed === 'function') {\r\n return allowed(parsed.origin);\r\n }\r\n return allowed.some((origin) => {\r\n if (!URL.canParse(origin)) return false;\r\n return new URL(origin).origin === parsed.origin;\r\n });\r\n};\r\n\r\nconst exchangeOAuthToken = async (provider: FullOAuthProvider, code: string): Promise<string | null> => {\r\n const values = {\r\n code,\r\n client_id: provider.clientID,\r\n client_secret: provider.clientSecret,\r\n redirect_uri: provider.callbackURL,\r\n grant_type: 'authorization_code',\r\n };\r\n\r\n const getToken = async () => {\r\n if (provider.tokenExchangeMethod === 'json') {\r\n const response = await fetch(provider.tokenExchangeURL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },\r\n body: JSON.stringify(values),\r\n });\r\n return await response.json();\r\n }\r\n if (provider.tokenExchangeMethod === 'form') {\r\n const formParams = new URLSearchParams();\r\n formParams.append('client_id', provider.clientID);\r\n formParams.append('client_secret', provider.clientSecret);\r\n formParams.append('code', values.code);\r\n formParams.append('grant_type', 'authorization_code');\r\n formParams.append('redirect_uri', provider.callbackURL);\r\n\r\n if (isDevMode()) {\r\n getLogger().debug('oauth: token-exchange form params', { params: formParams.toString() });\r\n }\r\n const response = await fetch(provider.tokenExchangeURL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\r\n body: formParams.toString(),\r\n });\r\n return await response.json();\r\n }\r\n return null;\r\n };\r\n\r\n const [error, response] = await tryCatch(getToken);\r\n if (error) {\r\n getLogger().error('oauth: token exchange failed', error);\r\n return null;\r\n }\r\n const tokenData = asRecord(response);\r\n const accessToken = typeof tokenData.access_token === 'string' ? tokenData.access_token : '';\r\n if (!accessToken) {\r\n getLogger().warn('oauth: no access_token in token response', { provider: provider.name });\r\n return null;\r\n }\r\n return accessToken;\r\n};\r\n\r\ninterface OAuthProfile {\r\n email: string | undefined;\r\n name: string;\r\n avatar: string;\r\n}\r\n\r\nconst fetchOAuthProfile = async (\r\n provider: FullOAuthProvider,\r\n accessToken: string,\r\n): Promise<OAuthProfile | null> => {\r\n const getUserData = async () => {\r\n const response = await fetch(provider.userInfoURL, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Accept': 'application/json',\r\n 'Authorization': `Bearer ${accessToken}`,\r\n },\r\n });\r\n return await response.json();\r\n };\r\n\r\n const [error, response] = await tryCatch(getUserData);\r\n if (error) {\r\n getLogger().error('oauth: userInfo fetch failed', error);\r\n return null;\r\n }\r\n\r\n const userData = asRecord(response);\r\n const name = String(userData[provider.nameKey] || 'didnt find a name');\r\n const emailValue = userData[provider.emailKey];\r\n let email: string | undefined = typeof emailValue === 'string' ? emailValue : undefined;\r\n\r\n const avatarId = provider.avatarCodeKey ? userData[provider.avatarCodeKey] : undefined;\r\n const avatarValue = provider?.avatarKey\r\n ? String(userData[provider.avatarKey] || '')\r\n : (provider.getAvatar\r\n ? await provider.getAvatar({ userData, avatarId: typeof avatarId === 'string' ? avatarId : '', accessToken })\r\n : '');\r\n const avatar = typeof avatarValue === 'string' ? avatarValue : '';\r\n\r\n //? Some providers (GitHub) don't return email in /userinfo — fall back to the\r\n //? provider's `getEmail` accessor.\r\n if (!email && provider.getEmail) {\r\n const selectedEmail = await provider.getEmail(accessToken);\r\n if (!selectedEmail) {\r\n getLogger().warn('oauth: no email found via provider.getEmail', { provider: provider.name });\r\n return null;\r\n }\r\n email = selectedEmail;\r\n }\r\n\r\n return { email, name, avatar };\r\n};\r\n\r\ninterface ResolvedOAuthUser {\r\n user: SessionLayout;\r\n isNewUser: boolean;\r\n}\r\n\r\nconst findOrCreateOAuthUser = async (\r\n provider: FullOAuthProvider,\r\n profile: OAuthProfile,\r\n): Promise<ResolvedOAuthUser | null> => {\r\n const { email, name, avatar } = profile;\r\n if (!email) return null;\r\n\r\n const preLoginResult = await dispatchHook('preLogin', { email, provider: provider.name });\r\n if (preLoginResult.stopped) {\r\n getLogger().warn(`oauth login aborted by preLogin hook`, { errorCode: preLoginResult.signal.errorCode });\r\n return null;\r\n }\r\n\r\n const userAdapter = getUserAdapter();\r\n const [findError, findResponse] = await tryCatch(() =>\r\n userAdapter.findByEmail({ email, provider: provider.name })\r\n );\r\n if (findError) {\r\n getLogger().error('oauth: findByEmail failed', findError);\r\n return null;\r\n }\r\n\r\n if (findResponse?.id) {\r\n const filePath = path.join(uploadsFolder(), `${findResponse.id}.webp`);\r\n if (existsSync(filePath)) {\r\n findResponse.avatar = `${findResponse.id}.webp`;\r\n }\r\n\r\n const previousLogin = (findResponse as { lastLogin?: Date | null }).lastLogin ?? null;\r\n const nowLogin = new Date();\r\n await tryCatch(() => userAdapter.update(findResponse.id, { lastLogin: nowLogin } as never));\r\n\r\n return {\r\n user: {\r\n ...sanitizeUserForSession(findResponse),\r\n token: '',\r\n lastLogin: nowLogin,\r\n previousLogin,\r\n },\r\n isNewUser: false,\r\n };\r\n }\r\n\r\n const preRegisterResult = await dispatchHook('preRegister', { email, provider: provider.name, name });\r\n if (preRegisterResult.stopped) {\r\n getLogger().warn(`oauth register aborted by preRegister hook`, { errorCode: preRegisterResult.signal.errorCode });\r\n return null;\r\n }\r\n\r\n const createNewUser = async () => userAdapter.create({\r\n email,\r\n provider: provider.name,\r\n name,\r\n avatar,\r\n avatarFallback: `#${Math.floor(Math.random() * 0xFF_FF_FF).toString(16).padStart(6, \"0\")}`,\r\n language: getProjectConfig().defaultLanguage,\r\n });\r\n const [createError, createResponse] = await tryCatch(createNewUser);\r\n if (createError) {\r\n getLogger().error('oauth: user create failed', createError);\r\n return null;\r\n }\r\n if (!createResponse) return null;\r\n\r\n return {\r\n user: { ...sanitizeUserForSession(createResponse), token: '' },\r\n isNewUser: true,\r\n };\r\n};\r\n\r\nconst resolvePostLoginRedirect = async ({\r\n fallbackUrl,\r\n userId,\r\n providerName,\r\n isNewUser,\r\n}: {\r\n fallbackUrl: string;\r\n userId: string;\r\n providerName: string;\r\n isNewUser: boolean;\r\n}): Promise<string> => {\r\n const resolver = getPostLoginRedirect();\r\n if (!resolver) return fallbackUrl;\r\n\r\n const [resolverError, resolved] = await tryCatch(() => resolver({\r\n userId,\r\n provider: providerName,\r\n isNewUser,\r\n defaultUrl: fallbackUrl,\r\n }));\r\n if (resolverError) {\r\n getLogger().warn(`[oauth] postLoginRedirect resolver threw`, { message: (resolverError as Error).message });\r\n return fallbackUrl;\r\n }\r\n if (resolved && isAllowedRedirectUrl(resolved)) return resolved;\r\n if (resolved) {\r\n getLogger().warn(\r\n `[oauth] postLoginRedirect returned a URL not in allowed origins — falling back`,\r\n { resolved, fallbackUrl },\r\n );\r\n }\r\n return fallbackUrl;\r\n};\r\n\r\n// Route that handles the callback from the OAuth provider\r\nconst loginCallback = async (\r\n pathname: string,\r\n req: IncomingMessage,\r\n _res: ServerResponse,\r\n options: { defaultRedirectUrl?: string } = {},\r\n): Promise<OAuthCallbackResult | false> => {\r\n const providerName = pathname.split('/')[3]; // google/github/etc.\r\n if (!providerName) return false;\r\n const provider = getOAuthProviders().find(p => p.name === providerName);\r\n if (!provider || !req.url) return false;\r\n if (!isFullOAuthProvider(provider)) return false;\r\n\r\n const queryString = req.url.split('?')[1] ?? '';\r\n const params = new URLSearchParams(queryString);\r\n const code = params.get('code');\r\n const state = params.get('state');\r\n\r\n const stateIsValid = await consumeOAuthState(provider.name, state || '');\r\n if (!stateIsValid) {\r\n getLogger().warn('oauth: invalid or missing state', { provider: provider.name });\r\n return false;\r\n }\r\n\r\n if (!code) {\r\n getLogger().warn('oauth: no code provided in callback url', { provider: provider.name });\r\n return false;\r\n }\r\n\r\n const accessToken = await exchangeOAuthToken(provider, code);\r\n if (!accessToken) return false;\r\n\r\n const profile = await fetchOAuthProfile(provider, accessToken);\r\n if (!profile) return false;\r\n\r\n const resolved = await findOrCreateOAuthUser(provider, profile);\r\n if (!resolved) return false;\r\n\r\n const newToken = randomBytes(32).toString('hex');\r\n resolved.user.token = newToken;\r\n\r\n //? Per-provider runtime extras (calendar tokens, tenant ids, etc.) are\r\n //? merged into the session BEFORE save so saveSession + the resulting\r\n //? sessionStorage broadcast see the final shape. Errors are logged but\r\n //? do not block login — a missing extra is not worth keeping the user\r\n //? from signing in.\r\n const extraSessionFields = provider.extraSessionFields;\r\n if (extraSessionFields) {\r\n const [extraErr, extra] = await tryCatch(async () =>\r\n extraSessionFields({ userData: asOAuthUserData(profile), accessToken }),\r\n );\r\n if (extraErr) {\r\n getLogger().warn(`[oauth:${providerName}] extraSessionFields hook threw — continuing without extras`, { err: extraErr });\r\n } else if (extra) {\r\n Object.assign(resolved.user, extra);\r\n }\r\n }\r\n\r\n await saveSession(newToken, resolved.user, true);\r\n\r\n if (resolved.isNewUser) {\r\n await dispatchHook('postRegister', { userId: resolved.user.id, provider: providerName });\r\n }\r\n await dispatchHook('postLogin', {\r\n userId: resolved.user.id,\r\n provider: providerName,\r\n isNewUser: resolved.isNewUser,\r\n token: newToken,\r\n });\r\n\r\n if (isDevMode()) {\r\n getLogger().debug(`oauth login success for user ${resolved.user.id}`);\r\n }\r\n\r\n const fallbackUrl =\r\n options.defaultRedirectUrl\r\n ?? getProjectConfig().loginRedirectUrl\r\n ?? '/';\r\n\r\n const redirectUrl = await resolvePostLoginRedirect({\r\n fallbackUrl,\r\n userId: resolved.user.id,\r\n providerName,\r\n isNewUser: resolved.isNewUser,\r\n });\r\n\r\n return {\r\n token: newToken,\r\n redirectUrl,\r\n userId: resolved.user.id,\r\n provider: providerName,\r\n isNewUser: resolved.isNewUser,\r\n };\r\n};\r\n\r\nexport { loginWithCredentials, loginCallback, registerWithCredentials, loginWithCredentialsCore }\r\n","//? User adapter registry. Decouples auth flows from a specific Prisma User\r\n//? schema so consumers with different column names, additional fields, or a\r\n//? non-Prisma data layer can plug their own implementation.\r\n//?\r\n//? `defaultPrismaUserAdapter()` implements the framework's recommended schema\r\n//? (id, email, password?, provider, name, avatar, avatarFallback, admin,\r\n//? language) and is used automatically when no adapter has been registered.\r\n\r\nimport { prisma, type BaseSessionLayout } from '@luckystack/core';\r\n\r\nexport interface UserAdapterCreateInput {\r\n email: string;\r\n provider: string;\r\n name?: string | null;\r\n password?: string | null;\r\n avatar?: string | null;\r\n avatarFallback?: string | null;\r\n language?: string | null;\r\n}\r\n\r\nexport interface UserRecord extends BaseSessionLayout {\r\n password?: string | null;\r\n provider?: string | null;\r\n /** Most recent successful login. Mirrors the optional `lastLogin` column on User. */\r\n lastLogin?: Date | null;\r\n}\r\n\r\nexport interface UserAdapter {\r\n findByEmail: (params: { email: string; provider: string }) => Promise<UserRecord | null>;\r\n findById: (id: string) => Promise<UserRecord | null>;\r\n create: (input: UserAdapterCreateInput) => Promise<UserRecord>;\r\n update: (id: string, patch: Partial<UserRecord>) => Promise<UserRecord>;\r\n}\r\n\r\nlet registeredAdapter: UserAdapter | null = null;\r\n\r\nexport const registerUserAdapter = (adapter: UserAdapter): UserAdapter => {\r\n registeredAdapter = adapter;\r\n return registeredAdapter;\r\n};\r\n\r\nexport const isUserAdapterRegistered = (): boolean => registeredAdapter !== null;\r\n\r\n//? Default adapter: backed by the Prisma client exposed from `@luckystack/core`.\r\n//?\r\n//? The single `as unknown as PrismaUserDelegate` below is intentional and\r\n//? load-bearing — it is the abstraction boundary between the framework's\r\n//? loose `UserAdapterCreateInput` (string-typed fields) and the consumer's\r\n//? strict Prisma model (which may declare enums for `provider`, `language`,\r\n//? etc.). The framework cannot know those enum types statically, so it\r\n//? type-erases once at this seam and assumes the runtime `prisma.user`\r\n//? satisfies the documented `PrismaUserDelegate` shape. Consumers whose User\r\n//? schema diverges (different column names, mandatory extra fields) should\r\n//? register their own `UserAdapter` via `registerUserAdapter(...)` rather\r\n//? than relying on this default.\r\ninterface PrismaUserDelegate {\r\n findFirst: (args: { where: { email: string; provider: string } }) => Promise<UserRecord | null>;\r\n findUnique: (args: { where: { id: string } }) => Promise<UserRecord | null>;\r\n create: (args: { data: Record<string, unknown> }) => Promise<UserRecord>;\r\n update: (args: { where: { id: string }; data: Record<string, unknown> }) => Promise<UserRecord>;\r\n}\r\n\r\n//? Bridge between Prisma's generated client surface and the adapter contract.\r\n//? Structural cast required because Prisma's auto-generated delegate types\r\n//? don't structurally match our trimmed `PrismaUserDelegate` shape; this is a\r\n//? known framework boundary (see /docs/ARCHITECTURE_AUTH.md).\r\nconst getPrismaUser = (): PrismaUserDelegate =>\r\n // eslint-disable-next-line no-restricted-syntax -- structural Prisma boundary\r\n prisma.user as unknown as PrismaUserDelegate;\r\n\r\nexport const defaultPrismaUserAdapter = (): UserAdapter => {\r\n return {\r\n findByEmail: async ({ email, provider }) =>\r\n getPrismaUser().findFirst({ where: { email, provider } }),\r\n findById: async (id) =>\r\n getPrismaUser().findUnique({ where: { id } }),\r\n create: async (input) =>\r\n getPrismaUser().create({\r\n data: {\r\n email: input.email,\r\n provider: input.provider,\r\n name: input.name,\r\n password: input.password ?? null,\r\n avatar: input.avatar ?? '',\r\n avatarFallback: input.avatarFallback,\r\n language: input.language,\r\n },\r\n }),\r\n update: async (id, patch) =>\r\n getPrismaUser().update({ where: { id }, data: patch }),\r\n };\r\n};\r\n\r\nlet cachedDefaultAdapter: UserAdapter | null = null;\r\n\r\nexport const getUserAdapter = (): UserAdapter => {\r\n if (registeredAdapter) return registeredAdapter;\r\n cachedDefaultAdapter ??= defaultPrismaUserAdapter();\r\n return cachedDefaultAdapter;\r\n};\r\n","//? Password-policy enforcement for credentials auth. Read by\r\n//? `registerWithCredentials` and `updatePasswordHash` to validate plaintext\r\n//? passwords against the consumer-tunable `projectConfig.auth.passwordPolicy`.\r\n//?\r\n//? Returns null when the password passes every check, or a reason-key\r\n//? (i18n-style errorCode) describing the first failure. Callers surface\r\n//? the reason to the client; the framework's i18n layer handles\r\n//? translation.\r\n\r\nimport { getProjectConfig } from '@luckystack/core';\r\n\r\nimport { COMMON_PASSWORDS } from './data/commonPasswords';\r\n\r\nconst UPPERCASE_REGEX = /[A-Z]/;\r\nconst LOWERCASE_REGEX = /[a-z]/;\r\nconst NUMBER_REGEX = /\\d/;\r\nconst SPECIAL_REGEX = /[^A-Za-z0-9]/;\r\n\r\n/**\r\n * Validate `password` against the active project's password policy.\r\n * Returns null on success, a string reason-key on the first failure.\r\n *\r\n * The policy lives in `projectConfig.auth.passwordPolicy` and is read at\r\n * call time, so consumers can override defaults via `registerProjectConfig`.\r\n */\r\nexport const validatePassword = (password: string): string | null => {\r\n const policy = getProjectConfig().auth.passwordPolicy;\r\n\r\n if (password.length < policy.minLength) return 'login.passwordCharacterMinimum';\r\n if (password.length > policy.maxLength) return 'login.passwordCharacterLimit';\r\n if (policy.requireUppercase && !UPPERCASE_REGEX.test(password)) return 'login.passwordRequiresUppercase';\r\n if (policy.requireLowercase && !LOWERCASE_REGEX.test(password)) return 'login.passwordRequiresLowercase';\r\n if (policy.requireNumber && !NUMBER_REGEX.test(password)) return 'login.passwordRequiresNumber';\r\n if (policy.requireSpecial && !SPECIAL_REGEX.test(password)) return 'login.passwordRequiresSpecial';\r\n if (policy.forbidCommon && COMMON_PASSWORDS.has(password.toLowerCase())) return 'login.passwordTooCommon';\r\n if (policy.customValidator) {\r\n const customReason = policy.customValidator(password);\r\n if (customReason) return customReason;\r\n }\r\n\r\n return null;\r\n};\r\n","//? Built-in common-password blocklist. Sourced from the public-domain\r\n//? SecLists Top-passwords list (Daniel Miessler), trimmed to the 200 most\r\n//? common entries to keep the package size small. Consumers needing a\r\n//? larger list (10k+ entries) can supply one through\r\n//? `projectConfig.auth.passwordPolicy.customValidator` and check their\r\n//? own data source there.\r\n//?\r\n//? Lookups are case-insensitive (the validator lower-cases before\r\n//? checking). Adding additional entries here is a non-breaking change.\r\n\r\nconst COMMON_PASSWORD_LIST: readonly string[] = [\r\n '123456', 'password', '12345678', 'qwerty', '123456789', '12345', '1234', '111111',\r\n '1234567', 'dragon', '123123', 'baseball', 'abc123', 'football', 'monkey', 'letmein',\r\n '696969', 'shadow', 'master', '666666', 'qwertyuiop', '123321', 'mustang', '1234567890',\r\n 'michael', '654321', 'pussy', 'superman', '1qaz2wsx', '7777777', 'fuckyou', '121212',\r\n '000000', 'qazwsx', '123qwe', 'killer', 'trustno1', 'jordan', 'jennifer', 'zxcvbnm',\r\n 'asdfgh', 'hunter', 'buster', 'soccer', 'harley', 'batman', 'andrew', 'tigger',\r\n 'sunshine', 'iloveyou', 'fuckme', '2000', 'charlie', 'robert', 'thomas', 'hockey',\r\n 'ranger', 'daniel', 'starwars', 'klaster', '112233', 'george', 'asshole', 'computer',\r\n 'michelle', 'jessica', 'pepper', '1111', 'zxcvbn', '555555', '11111111', '131313',\r\n 'freedom', '777777', 'pass', 'fuck', 'maggie', '159753', 'aaaaaa', 'ginger',\r\n 'princess', 'joshua', 'cheese', 'amanda', 'summer', 'love', 'ashley', '6969',\r\n 'nicole', 'chelsea', 'biteme', 'matthew', 'access', 'yankees', '987654321', 'dallas',\r\n 'austin', 'thunder', 'taylor', 'matrix', 'william', 'corvette', 'hello', 'martin',\r\n 'heather', 'secret', 'fucker', 'merlin', 'diamond', '1234qwer', 'gfhjkm', 'hammer',\r\n 'silver', 'richard', 'samantha', 'iceman', 'tigers', 'purple', 'andrea', 'horney',\r\n 'dakota', 'aaaaa', 'player', 'sucker', 'mercedes', 'whatever', 'orange', 'qweqwe',\r\n 'mickey', 'panties', 'hannah', 'fucking', 'paul', 'pakistan', 'patrick', 'martha',\r\n 'morgan', 'iwantu', 'lakers', 'rachel', 'slayer', 'scott', '2112', 'fish',\r\n 'porn', 'matt', 'qwert', 'cookie', 'eagle1', 'london', 'phoenix', 'chicago',\r\n 'badboy', 'braves', 'yankee', 'lover', 'barney', 'edward', 'raiders', 'green',\r\n 'maverick', 'rangers', 'joseph', 'maddog', 'mike', 'jasmine', 'arsenal', 'beach',\r\n 'tiger', 'snoopy', 'natasha', 'matthews', 'jackson', 'cumshot', 'chevy', 'gateway',\r\n 'gators', 'angel', 'junior', 'samson', '6666', 'amateur', 'gemini', 'apples',\r\n 'august', 'biscuit', '5150', 'apollo', 'parker', 'qwerty123', 'rabbit', 'angela',\r\n 'love123', 'rocket', 'scooby', 'beaver', 'oliver', 'walter', 'blowme', 'spider',\r\n '4321', 'cumshot1', 'lucky', 'helpme', 'jackie', 'monica',\r\n];\r\n\r\n/** Frozen Set for O(1) `.has()` lookups; entries are pre-lowercased. */\r\nexport const COMMON_PASSWORDS: ReadonlySet<string> = new Set(\r\n COMMON_PASSWORD_LIST.map((p) => p.toLowerCase()),\r\n);\r\n","//? Password-reset primitives. Used by the framework's own forgot-password\r\n//? flow when `ProjectConfig.auth.forgotPassword === 'framework'`, AND\r\n//? exported as building blocks for consumers who picked `'custom'` and want\r\n//? to wire their own UI / email layer without re-implementing crypto + Redis.\r\n//?\r\n//? Token format: 32 random bytes hex-encoded → 64-char URL-safe string.\r\n//? Storage: `<projectName>-pwreset:<token> → userId` with a configurable TTL\r\n//? (`auth.passwordResetTtlSeconds`, default 3600 = 1 hour).\r\n\r\n// eslint-disable-next-line import-x/no-named-as-default -- bcryptjs ships both a default and namespace export; using the default mirrors its README\r\nimport bcrypt from 'bcryptjs';\r\n/* eslint-disable import-x/no-named-as-default-member -- accessing genSalt/hash/compare via the default import is the documented pattern */\r\nimport { randomBytes } from 'node:crypto';\r\n\r\nimport { getProjectConfig, formatKey, redis } from '@luckystack/core';\r\n\r\nimport { validatePassword } from './passwordPolicy';\r\nimport { getUserAdapter } from './userAdapter';\r\n\r\n//? Resolve at call time via the shared `getProjectName()` helper (single\r\n//? source of truth — see also session.ts, rateLimiter.ts, login.ts). Avoids\r\n//? the module-load capture bug where dotenv timing could put tokens under\r\n//? the wrong namespace.\r\nconst tokenKey = (token: string): string => formatKey('-pwreset', token);\r\n\r\n/**\r\n * Create a one-time password-reset token bound to a user id. Stored in Redis\r\n * with the configured TTL. Returns the token string — caller emails it to\r\n * the user (typically embedded in a URL).\r\n */\r\nexport const createPasswordResetToken = async (userId: string): Promise<string> => {\r\n const token = randomBytes(32).toString('hex');\r\n const ttl = getProjectConfig().auth.passwordResetTtlSeconds;\r\n await redis.set(tokenKey(token), userId, 'EX', ttl);\r\n return token;\r\n};\r\n\r\n/**\r\n * Validate and consume a password-reset token. Returns the bound userId on\r\n * success and removes the token (one-time use). Returns null when the token\r\n * is missing, expired, or malformed.\r\n */\r\nexport const consumePasswordResetToken = async (token: string): Promise<string | null> => {\r\n if (!token || typeof token !== 'string') return null;\r\n const key = tokenKey(token);\r\n const txResult = await redis.multi().get(key).del(key).exec();\r\n if (!txResult || txResult.length === 0) return null;\r\n const first = txResult[0];\r\n if (!first) return null;\r\n const [getErr, value] = first;\r\n if (getErr) return null;\r\n return typeof value === 'string' && value.length > 0 ? value : null;\r\n};\r\n\r\n/**\r\n * Bcrypt-hash a plaintext password and write it to the user record via the\r\n * registered user adapter. Used by the reset flow and by the settings\r\n * password-change flow. Validates against the active password policy\r\n * (`projectConfig.auth.passwordPolicy`) before hashing — throws a `PasswordPolicyError`\r\n * when the plaintext fails policy. Catch and surface the error code to the\r\n * client; framework-mode reset/change flows already do this.\r\n */\r\nexport const updatePasswordHash = async (userId: string, plaintext: string): Promise<void> => {\r\n const reason = validatePassword(plaintext);\r\n if (reason) {\r\n throw new PasswordPolicyError(reason);\r\n }\r\n const salt = await bcrypt.genSalt(getProjectConfig().auth.bcryptRounds);\r\n const hashedPassword = await bcrypt.hash(plaintext, salt);\r\n const patch: Record<string, unknown> = { password: hashedPassword };\r\n await getUserAdapter().update(userId, patch);\r\n};\r\n\r\n/**\r\n * Thrown by `updatePasswordHash` when the supplied plaintext violates the\r\n * active password policy. `errorCode` matches the i18n reason key the rest\r\n * of the login flow uses (e.g. `login.passwordRequiresUppercase`).\r\n */\r\nexport class PasswordPolicyError extends Error {\r\n readonly errorCode: string;\r\n constructor(errorCode: string) {\r\n super(`Password policy violation: ${errorCode}`);\r\n this.name = 'PasswordPolicyError';\r\n this.errorCode = errorCode;\r\n }\r\n}\r\n\r\n/** Verify a plaintext password against a stored bcrypt hash. */\r\nexport const verifyPassword = async (plaintext: string, hash: string): Promise<boolean> => {\r\n return bcrypt.compare(plaintext, hash);\r\n};\r\n","//? Framework-mode forgot-password orchestration. Used only when\r\n//? `ProjectConfig.auth.forgotPassword === 'framework'`. Pulls in\r\n//? `@luckystack/email` lazily so the login package doesn't list it as a\r\n//? hard dep — `'custom'` and `'disabled'` modes never reach this code.\r\n\r\nimport { getProjectConfig, dispatchHook, getLogger } from '@luckystack/core';\r\n\r\nimport { createPasswordResetToken } from './passwordReset';\r\nimport { getUserAdapter } from './userAdapter';\r\n\r\ninterface SendResetEmailArgs {\r\n email: string;\r\n /**\r\n * App-facing display name used in the email greeting + subject. If omitted,\r\n * falls back to `projectConfig.auth.passwordResetBrand`, then to `'LuckyStack'`\r\n * as the absolute fallback. Real consumers should set this on either the\r\n * call site OR via `projectConfig.auth.passwordResetBrand`.\r\n */\r\n brand?: string;\r\n}\r\n\r\n/**\r\n * Look up the user by credentials-provider email, generate a reset token,\r\n * and email them a reset link. Always resolves successfully regardless of\r\n * whether the email matched a user (anti-enumeration). Returns the result\r\n * of the email send for diagnostics.\r\n */\r\nexport const sendPasswordResetEmail = async ({ email, brand }: SendResetEmailArgs): Promise<{ ok: boolean; reason?: string }> => {\r\n const config = getProjectConfig();\r\n getLogger().info('[forgotPassword] start', { email, forgotPasswordMode: config.auth.forgotPassword });\r\n if (config.auth.forgotPassword !== 'framework') {\r\n getLogger().warn('[forgotPassword] auth.forgotPassword is not \"framework\" — nothing sent', { mode: config.auth.forgotPassword });\r\n return { ok: false, reason: 'forgotPassword-not-framework' };\r\n }\r\n\r\n const resolvedBrand = brand ?? config.auth.passwordResetBrand ?? 'LuckyStack';\r\n\r\n // Lazy import — the email package is an optional peer dep so its types\r\n // may not be resolvable at the framework's compile time.\r\n interface EmailModule {\r\n sendEmail: (input: Record<string, unknown>) => Promise<{ ok: boolean; reason?: string }>;\r\n renderEmailLayout: (input: Record<string, unknown>) => { html: string; text: string };\r\n }\r\n //? Mirror testEmail's robustness: catch a failed dynamic import instead of\r\n //? throwing (an uncaught throw here would bubble out of the anti-enumeration\r\n //? wrapper as a generic 500). Surfaces the real reason in the server log.\r\n const emailModule = await (\r\n // @ts-expect-error optional peer dep — installed only when forgotPassword === 'framework'\r\n import('@luckystack/email') as Promise<EmailModule>\r\n ).catch((error: unknown) => {\r\n getLogger().warn('[forgotPassword] failed to load @luckystack/email — is it installed?', { error: String(error) });\r\n return null;\r\n });\r\n if (!emailModule) {\r\n return { ok: false, reason: 'email-module-load-failed' };\r\n }\r\n const { sendEmail, renderEmailLayout } = emailModule;\r\n\r\n const userAdapter = getUserAdapter();\r\n const user = await userAdapter.findByEmail({ email, provider: 'credentials' });\r\n getLogger().info('[forgotPassword] credentials user lookup', { email, found: Boolean(user), userId: user?.id ?? null });\r\n\r\n // Anti-enumeration: always pretend it succeeded if the email isn't found.\r\n if (!user) {\r\n //? The client always gets \"success\" (anti-enumeration). This server-side\r\n //? line is the ONLY signal that the address simply isn't a credentials\r\n //? (email+password) account — by far the most common reason a reset email\r\n //? never arrives (OAuth-only signups and unknown/typo'd addresses have no\r\n //? credentials row, so there is nothing to reset).\r\n getLogger().warn('[forgotPassword] no credentials account for this email — nothing sent. Use the email+password account you registered with.', { email });\r\n void dispatchHook('passwordResetRequested', {\r\n email,\r\n matched: false,\r\n });\r\n return { ok: true };\r\n }\r\n\r\n const token = await createPasswordResetToken(user.id);\r\n void dispatchHook('passwordResetRequested', {\r\n email,\r\n matched: true,\r\n userId: user.id,\r\n token,\r\n ttlSeconds: config.auth.passwordResetTtlSeconds,\r\n });\r\n const baseUrl = (config.app.publicUrl || '').replace(/\\/+$/, '');\r\n const resetUrl = `${baseUrl}/reset-password?token=${encodeURIComponent(token)}`;\r\n\r\n const ttlMinutes = Math.round(config.auth.passwordResetTtlSeconds / 60);\r\n const { html, text } = renderEmailLayout({\r\n brand: resolvedBrand,\r\n title: 'Reset your password',\r\n intro: `Hi ${user.name ?? 'there'}, we received a request to reset the password on your ${resolvedBrand} account. Click the button below to choose a new one. The link expires in ${String(ttlMinutes)} minutes.`,\r\n ctaLabel: 'Reset password',\r\n ctaUrl: resetUrl,\r\n outro: `If you didn't request this, you can safely ignore this email — your password will stay the same. The link: ${resetUrl}`,\r\n footer: `Sent by ${resolvedBrand}. If you have questions, reply to this email.`,\r\n });\r\n\r\n //? `adapterHint: 'transactional'` lets consumers who registered separate\r\n //? marketing + transactional senders via `registerEmailSenders({...})`\r\n //? route this through the transactional adapter automatically. Falls\r\n //? back to the default sender when only one is registered.\r\n const result = await sendEmail({\r\n to: user.email,\r\n subject: `Reset your ${resolvedBrand} password`,\r\n html,\r\n text,\r\n adapterHint: 'transactional',\r\n });\r\n\r\n if (result.ok) {\r\n getLogger().info('[forgotPassword] reset email dispatched', { userId: user.id, to: user.email });\r\n } else {\r\n getLogger().warn('[forgotPassword] reset email send FAILED', { userId: user.id, reason: result.reason });\r\n }\r\n\r\n return result.ok ? { ok: true } : { ok: false, reason: result.reason };\r\n};\r\n","//? Email-change token primitives. Mirrors `passwordReset.ts` for the\r\n//? confirm-via-email-link flow: user submits a new address, framework mails\r\n//? a tokenized URL to the NEW address, click-through confirms and updates\r\n//? `User.email` + revokes all sessions.\r\n//?\r\n//? Token format: 32 random bytes hex-encoded -> 64-char URL-safe string.\r\n//? Storage: `<projectName>-email-change:<token>` -> JSON payload\r\n//? `{ userId: string; newEmail: string }`\r\n//? TTL: `auth.emailChangeTtlSeconds` (default 3600 = 1 hour).\r\n\r\nimport { randomBytes } from 'node:crypto';\r\n\r\nimport { getProjectConfig, formatKey, redis } from '@luckystack/core';\r\n\r\nconst tokenKey = (token: string): string => formatKey('-email-change', token);\r\n\r\nexport interface EmailChangePayload {\r\n userId: string;\r\n newEmail: string;\r\n}\r\n\r\n/**\r\n * Create a one-time email-change token bound to a user id + the new email\r\n * address. Stored in Redis with the configured TTL. Returns the token string\r\n * — caller emails the token (embedded in a URL) to the NEW address so the\r\n * confirmation click proves ownership of the new mailbox.\r\n */\r\nexport const createEmailChangeToken = async (userId: string, newEmail: string): Promise<string> => {\r\n const token = randomBytes(32).toString('hex');\r\n const ttl = getProjectConfig().auth.emailChangeTtlSeconds;\r\n const payload = JSON.stringify({ userId, newEmail } satisfies EmailChangePayload);\r\n await redis.set(tokenKey(token), payload, 'EX', ttl);\r\n return token;\r\n};\r\n\r\n/**\r\n * Validate and consume an email-change token. Returns the bound\r\n * `{ userId, newEmail }` on success and removes the token (one-time use).\r\n * Returns null when the token is missing, expired, or malformed.\r\n */\r\nexport const consumeEmailChangeToken = async (token: string): Promise<EmailChangePayload | null> => {\r\n if (!token || typeof token !== 'string') return null;\r\n const key = tokenKey(token);\r\n const txResult = await redis.multi().get(key).del(key).exec();\r\n if (!txResult || txResult.length === 0) return null;\r\n const first = txResult[0];\r\n if (!first) return null;\r\n const [getErr, value] = first;\r\n if (getErr) return null;\r\n if (typeof value !== 'string' || value.length === 0) return null;\r\n try {\r\n const parsed = JSON.parse(value) as unknown;\r\n if (\r\n !parsed\r\n || typeof parsed !== 'object'\r\n || typeof (parsed as EmailChangePayload).userId !== 'string'\r\n || typeof (parsed as EmailChangePayload).newEmail !== 'string'\r\n ) {\r\n return null;\r\n }\r\n return parsed as EmailChangePayload;\r\n } catch {\r\n return null;\r\n }\r\n};\r\n","//? Framework-mode \"email change confirmation\" orchestration. Renders the\r\n//? confirmation email + sends it to the NEW address (proves the user can\r\n//? receive at the new mailbox). Mirrors the lazy-import pattern from\r\n//? `forgotPassword.ts` so `@luckystack/email` stays an optional peer.\r\n\r\nimport { getProjectConfig } from '@luckystack/core';\r\n\r\nimport { createEmailChangeToken } from './emailChange';\r\n\r\ninterface SendEmailChangeArgs {\r\n userId: string;\r\n newEmail: string;\r\n /** Optional display name used in the email greeting (falls back to \"there\"). */\r\n userName?: string | null;\r\n /**\r\n * App-facing display name used in the email greeting + subject. Falls back\r\n * to `projectConfig.auth.passwordResetBrand` (the shared brand label), then\r\n * to `'LuckyStack'` as the absolute fallback.\r\n */\r\n brand?: string;\r\n}\r\n\r\n/**\r\n * Mint a one-shot email-change token and send a confirmation email to the\r\n * NEW address with the tokenized URL. Returns the send result for diagnostics\r\n * + the token itself (callers can audit-log it through the\r\n * `postEmailChangeRequested` hook).\r\n */\r\nexport const sendEmailChangeConfirmation = async (\r\n { userId, newEmail, userName, brand }: SendEmailChangeArgs,\r\n): Promise<{ ok: boolean; reason?: string; token: string }> => {\r\n const config = getProjectConfig();\r\n const resolvedBrand = brand ?? config.auth.passwordResetBrand ?? 'LuckyStack';\r\n\r\n // Lazy import — `@luckystack/email` is an optional peer dep.\r\n interface EmailModule {\r\n sendEmail: (input: Record<string, unknown>) => Promise<{ ok: boolean; reason?: string }>;\r\n renderEmailLayout: (input: Record<string, unknown>) => { html: string; text: string };\r\n }\r\n const { sendEmail, renderEmailLayout } = await (\r\n // @ts-expect-error optional peer dep — installed only when the consumer wires email sending\r\n import('@luckystack/email') as Promise<EmailModule>\r\n );\r\n\r\n const token = await createEmailChangeToken(userId, newEmail);\r\n const baseUrl = (config.app.publicUrl || '').replace(/\\/+$/, '');\r\n const confirmUrl = `${baseUrl}/settings/confirm-email?token=${encodeURIComponent(token)}`;\r\n const ttlMinutes = Math.round(config.auth.emailChangeTtlSeconds / 60);\r\n\r\n const { html, text } = renderEmailLayout({\r\n brand: resolvedBrand,\r\n title: 'Confirm your new email address',\r\n intro: `Hi ${userName ?? 'there'}, you (or someone using your ${resolvedBrand} account) asked to change the account email to this address. Click the button below to confirm — the link expires in ${String(ttlMinutes)} minutes. If you confirm, all of your active sessions will be signed out as a security precaution.`,\r\n ctaLabel: 'Confirm new email',\r\n ctaUrl: confirmUrl,\r\n outro: `If you didn't request this, you can safely ignore this email — nothing will change. The link: ${confirmUrl}`,\r\n footer: `Sent by ${resolvedBrand}. If you have questions, reply to this email.`,\r\n });\r\n\r\n const result = await sendEmail({\r\n to: newEmail,\r\n subject: `Confirm your new ${resolvedBrand} email address`,\r\n html,\r\n text,\r\n adapterHint: 'transactional',\r\n });\r\n\r\n return result.ok ? { ok: true, token } : { ok: false, reason: result.reason, token };\r\n};\r\n"],"mappings":"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","names":["tryCatch","socketEventNames","dispatchHook","getLogger","formatKey","tryCatch","logout","tryCatch","emails","tryCatch","redisClient","dispatchHook","getLogger","formatKey","randomBytes","getProjectConfig","getProjectConfig","getProjectConfig","getProjectConfig","formatKey","randomBytes","redisClient","dispatchHook","tryCatch","getLogger","response","bcrypt","randomBytes","getProjectConfig","formatKey","redis","formatKey","randomBytes","getProjectConfig","redis","bcrypt","hash","getProjectConfig","dispatchHook","getLogger","getProjectConfig","getLogger","dispatchHook","randomBytes","getProjectConfig","formatKey","redis","tokenKey","getProjectConfig","getProjectConfig"]}