@eminuckan/spine 0.3.6 → 0.3.9
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/CHANGELOG.md +38 -0
- package/README.md +39 -6
- package/dist/api-client/api-config.server.d.ts +11 -1
- package/dist/api-client/api-config.server.d.ts.map +1 -1
- package/dist/api-client/fetch-client.server.d.ts.map +1 -1
- package/dist/api-client/index.d.ts +1 -1
- package/dist/api-client/index.d.ts.map +1 -1
- package/dist/api-client/server.mjs +1 -1
- package/dist/api-client/types.d.ts +9 -1
- package/dist/api-client/types.d.ts.map +1 -1
- package/dist/auth/auth.server.d.ts.map +1 -1
- package/dist/auth/callback-errors.d.ts +42 -0
- package/dist/auth/callback-errors.d.ts.map +1 -0
- package/dist/auth/oauth-state-config.d.ts +7 -0
- package/dist/auth/oauth-state-config.d.ts.map +1 -0
- package/dist/auth/redis-session-storage.server.d.ts +3 -1
- package/dist/auth/redis-session-storage.server.d.ts.map +1 -1
- package/dist/auth/route-protection.server.d.ts.map +1 -1
- package/dist/auth/server.d.ts +1 -0
- package/dist/auth/server.d.ts.map +1 -1
- package/dist/auth/server.mjs +3 -3
- package/dist/auth/token-refresh.server.d.ts.map +1 -1
- package/dist/react-router/server.mjs +5 -5
- package/dist/{redis-session-storage.server-DGgQcJ9B.mjs → redis-session-storage.server-CI_IYIn8.mjs} +46 -15
- package/dist/redis-session-storage.server-CI_IYIn8.mjs.map +1 -0
- package/dist/{server-DPCu40ud.mjs → server-Cw86FdT-.mjs} +273 -64
- package/dist/server-Cw86FdT-.mjs.map +1 -0
- package/dist/{server-ByScGRXA.mjs → server-pnsLybIH.mjs} +63 -16
- package/dist/server-pnsLybIH.mjs.map +1 -0
- package/dist/server.mjs +5 -5
- package/dist/tanstack-start/index.d.ts +9 -0
- package/dist/tanstack-start/index.d.ts.map +1 -0
- package/dist/tanstack-start/index.mjs +11 -0
- package/dist/tanstack-start/server.d.ts +9 -0
- package/dist/tanstack-start/server.d.ts.map +1 -0
- package/dist/tanstack-start/server.mjs +17 -0
- package/dist/tenant/server.mjs +47 -7
- package/dist/tenant/server.mjs.map +1 -1
- package/dist/tenant/tenant-cookie.server.d.ts +5 -1
- package/dist/tenant/tenant-cookie.server.d.ts.map +1 -1
- package/dist/tenant/tenant.server.d.ts +9 -1
- package/dist/tenant/tenant.server.d.ts.map +1 -1
- package/dist/types-DqCXinAk.mjs.map +1 -1
- package/docs/adapters.md +64 -1
- package/docs/architecture.md +6 -1
- package/docs/module-reference.md +5 -0
- package/package.json +13 -4
- package/dist/redis-session-storage.server-DGgQcJ9B.mjs.map +0 -1
- package/dist/server-ByScGRXA.mjs.map +0 -1
- package/dist/server-DPCu40ud.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-session-storage.server-CI_IYIn8.mjs","names":["resolvePositiveIntegerEnv"],"sources":["../src/auth/oauth-state-config.ts","../src/auth/redis-session-storage.server.ts"],"sourcesContent":["const DEFAULT_OAUTH_STATE_TTL_SECONDS = 10 * 60;\n\nfunction resolvePositiveIntegerEnv(name: string, defaultValue: number): number {\n const value = process.env[name]?.trim();\n if (!value) {\n return defaultValue;\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new Error(`${name} must be a positive integer.`);\n }\n\n return parsed;\n}\n\n/**\n * OAuth state lifetime shared by Redis expiry and callback age validation.\n * This module is internal and is not part of a package export entry point.\n */\nexport const OAUTH_STATE_TTL_SECONDS = resolvePositiveIntegerEnv(\n 'OAUTH_STATE_TTL',\n DEFAULT_OAUTH_STATE_TTL_SECONDS,\n);\n\nexport const OAUTH_STATE_TTL_MS = OAUTH_STATE_TTL_SECONDS * 1000;\n","/**\n * Redis Session Storage Server Module\n *\n * Redis-backed session storage for authentication.\n * Provides secure session management with automatic expiration,\n * without depending on a framework-specific session implementation.\n */\n\nimport Redis from 'ioredis';\nimport { createCipheriv, createDecipheriv, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport type { AuthSessionSummary, SessionData, OAuthState } from './types';\nimport { OAUTH_STATE_TTL_SECONDS } from './oauth-state-config';\nimport { logger } from '../logging';\n\n// ============================================================================\n// Redis Client Setup\n// ============================================================================\n\nlet redis: Redis | null = null;\n\nfunction getRedis(): Redis {\n if (!redis) {\n redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');\n\n redis.on('error', (err) => {\n logger.error('Redis Client Error', err);\n });\n\n redis.on('connect', () => {\n logger.info('Redis connected successfully');\n });\n }\n return redis;\n}\n\n// ============================================================================\n// Redis Key Patterns\n// ============================================================================\n\nconst KEY_PREFIX = process.env.REDIS_KEY_PREFIX || '';\n\nconst REDIS_KEYS = {\n oauthState: (stateId: string) => `${KEY_PREFIX}oauth:state:${stateId}`,\n session: (sessionId: string) => `${KEY_PREFIX}session:${sessionId}`,\n sessionByUser: (userId: string) => `${KEY_PREFIX}session:index:user:${userId}`,\n sessionBySid: (sid: string) => `${KEY_PREFIX}session:index:sid:${sid}`,\n sessionBySessionState: (sessionState: string) => `${KEY_PREFIX}session:index:session_state:${sessionState}`,\n};\n\nconst DEFAULT_SESSION_SECRET = 'default-secret-change-in-production';\nconst DEFAULT_SESSION_TTL = 60 * 60 * 24 * 7;\n\nconst SESSION_TTL = resolvePositiveIntegerEnv('SESSION_DEFAULT_TTL', resolvePositiveIntegerEnv('SESSION_TTL', DEFAULT_SESSION_TTL));\n\nconst SESSION_COOKIE_NAME = process.env.SESSION_COOKIE_NAME || '__session_id';\nconst SESSION_SECRET = resolveSessionSecret();\nconst SESSION_COOKIE_SECURE = resolveCookieSecure();\nconst SESSION_COOKIE_PATH = '/';\nconst SESSION_COOKIE_SAME_SITE = resolveCookieSameSite();\nconst SESSION_COOKIE_DOMAIN = process.env.SESSION_COOKIE_DOMAIN?.trim();\nconst SESSION_ENCRYPTION_ENABLED = resolveSessionEncryptionEnabled();\nconst SESSION_ENCRYPTION_KEY = resolveSessionEncryptionKey(SESSION_ENCRYPTION_ENABLED);\n\nexport const AUTH_ERROR_COOKIE_PREFIX = SESSION_COOKIE_NAME.replace('_session', '').replace('__', '');\n\nfunction resolveBooleanEnv(name: string, defaultValue: boolean): boolean {\n const value = process.env[name]?.trim().toLowerCase();\n if (!value) {\n return defaultValue;\n }\n\n if (['1', 'true', 'yes', 'on'].includes(value)) {\n return true;\n }\n\n if (['0', 'false', 'no', 'off'].includes(value)) {\n return false;\n }\n\n throw new Error(`${name} must be a boolean value.`);\n}\n\nfunction resolvePositiveIntegerEnv(name: string, defaultValue: number): number {\n const value = process.env[name]?.trim();\n if (!value) {\n return defaultValue;\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new Error(`${name} must be a positive integer.`);\n }\n\n return parsed;\n}\n\nfunction resolveSessionSecret(): string {\n const secret = process.env.SESSION_SECRET?.trim();\n if (!secret || secret === DEFAULT_SESSION_SECRET) {\n if (process.env.NODE_ENV === 'production') {\n throw new Error('SESSION_SECRET must be set to a unique random value in production.');\n }\n\n return DEFAULT_SESSION_SECRET;\n }\n\n if (secret.length < 32) {\n throw new Error('SESSION_SECRET must be at least 32 characters long.');\n }\n\n return secret;\n}\n\nfunction resolveCookieSecure(): boolean {\n return resolveBooleanEnv('SESSION_COOKIE_SECURE', process.env.NODE_ENV === 'production');\n}\n\nfunction resolveCookieSameSite(): 'Lax' | 'Strict' | 'None' {\n const value = process.env.SESSION_COOKIE_SAME_SITE?.trim().toLowerCase();\n const sameSite = value === 'strict' ? 'Strict' : value === 'none' ? 'None' : 'Lax';\n\n if (sameSite === 'None' && !SESSION_COOKIE_SECURE) {\n throw new Error('SESSION_COOKIE_SAME_SITE=None requires SESSION_COOKIE_SECURE=true.');\n }\n\n return sameSite;\n}\n\nfunction resolveSessionEncryptionEnabled(): boolean {\n const enabled = resolveBooleanEnv('SESSION_ENCRYPTION', process.env.NODE_ENV === 'production');\n if (process.env.NODE_ENV === 'production' && !enabled) {\n throw new Error('SESSION_ENCRYPTION cannot be disabled in production.');\n }\n\n return enabled;\n}\n\nfunction decodeBase64Url(value: string): Buffer {\n const normalized = value.replace(/-/g, '+').replace(/_/g, '/');\n const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=');\n return Buffer.from(padded, 'base64');\n}\n\nfunction resolveSessionEncryptionKey(enabled: boolean): Buffer | null {\n if (!enabled) {\n return null;\n }\n\n const value = process.env.SESSION_ENCRYPTION_KEY?.trim();\n if (!value) {\n throw new Error('SESSION_ENCRYPTION_KEY is required when session encryption is enabled.');\n }\n\n const key = /^[0-9a-f]{64}$/i.test(value)\n ? Buffer.from(value, 'hex')\n : decodeBase64Url(value);\n\n if (key.length !== 32) {\n throw new Error('SESSION_ENCRYPTION_KEY must decode to exactly 32 bytes. Generate one with: openssl rand -base64 32');\n }\n\n return key;\n}\n\ntype SessionCookieOptions = {\n maxAge: number;\n};\n\nfunction parseCookieHeader(cookieHeader: string | null): Record<string, string> {\n if (!cookieHeader) {\n return {};\n }\n\n return cookieHeader\n .split(';')\n .map((part) => part.trim())\n .filter(Boolean)\n .reduce<Record<string, string>>((cookies, part) => {\n const separatorIndex = part.indexOf('=');\n if (separatorIndex <= 0) {\n return cookies;\n }\n\n const name = part.slice(0, separatorIndex).trim();\n const value = part.slice(separatorIndex + 1).trim();\n if (!name) {\n return cookies;\n }\n\n cookies[name] = value;\n return cookies;\n }, {});\n}\n\nfunction createCookieSignature(value: string, secret: string): string {\n return createHmac('sha256', secret).update(value).digest('base64url');\n}\n\nfunction safelyCompareValues(left: string, right: string): boolean {\n const leftBuffer = Buffer.from(left);\n const rightBuffer = Buffer.from(right);\n\n if (leftBuffer.length !== rightBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(leftBuffer, rightBuffer);\n}\n\nfunction signSessionId(sessionId: string): string {\n const signature = createCookieSignature(sessionId, SESSION_SECRET);\n return `${sessionId}.${signature}`;\n}\n\nfunction unsignSessionId(cookieValue: string | undefined): string | null {\n if (!cookieValue) {\n return null;\n }\n\n let decodedValue: string;\n try {\n decodedValue = decodeURIComponent(cookieValue);\n } catch {\n return null;\n }\n\n const separatorIndex = decodedValue.lastIndexOf('.');\n if (separatorIndex <= 0 || separatorIndex === decodedValue.length - 1) {\n return null;\n }\n\n const sessionId = decodedValue.slice(0, separatorIndex);\n const signature = decodedValue.slice(separatorIndex + 1);\n const expectedSignature = createCookieSignature(sessionId, SESSION_SECRET);\n\n return safelyCompareValues(signature, expectedSignature) ? sessionId : null;\n}\n\nfunction serializeSessionCookieValue(sessionId: string, options: SessionCookieOptions): string {\n const signedValue = encodeURIComponent(signSessionId(sessionId));\n const segments = [\n `${SESSION_COOKIE_NAME}=${signedValue}`,\n `Max-Age=${options.maxAge}`,\n `Path=${SESSION_COOKIE_PATH}`,\n `SameSite=${SESSION_COOKIE_SAME_SITE}`,\n 'HttpOnly',\n ];\n\n if (SESSION_COOKIE_DOMAIN) {\n segments.push(`Domain=${SESSION_COOKIE_DOMAIN}`);\n }\n\n if (SESSION_COOKIE_SECURE) {\n segments.push('Secure');\n }\n\n return segments.join('; ');\n}\n\nfunction serializeExpiredSessionCookie(): string {\n const segments = [\n `${SESSION_COOKIE_NAME}=`,\n 'Max-Age=0',\n `Path=${SESSION_COOKIE_PATH}`,\n `SameSite=${SESSION_COOKIE_SAME_SITE}`,\n 'HttpOnly',\n ];\n\n if (SESSION_COOKIE_DOMAIN) {\n segments.push(`Domain=${SESSION_COOKIE_DOMAIN}`);\n }\n\n if (SESSION_COOKIE_SECURE) {\n segments.push('Secure');\n }\n\n return segments.join('; ');\n}\n\nasync function getSessionIdFromRequest(request: Request): Promise<string | null> {\n const cookies = parseCookieHeader(request.headers.get('Cookie'));\n return unsignSessionId(cookies[SESSION_COOKIE_NAME]);\n}\n\nfunction getSessionIndexKeys(data: SessionData): string[] {\n const keys = new Set<string>();\n\n if (data.userId) {\n keys.add(REDIS_KEYS.sessionByUser(data.userId));\n }\n\n if (data.sid) {\n keys.add(REDIS_KEYS.sessionBySid(data.sid));\n }\n\n if (data.sessionState) {\n keys.add(REDIS_KEYS.sessionBySessionState(data.sessionState));\n }\n\n return Array.from(keys);\n}\n\nasync function registerSessionIndexes(sessionId: string, data: SessionData): Promise<void> {\n const indexKeys = getSessionIndexKeys(data);\n if (indexKeys.length === 0) {\n return;\n }\n\n const redisClient = getRedis();\n const pipeline = redisClient.pipeline();\n\n for (const indexKey of indexKeys) {\n pipeline.sadd(indexKey, sessionId);\n pipeline.expire(indexKey, SESSION_TTL);\n }\n\n await pipeline.exec();\n}\n\nasync function unregisterSessionIndexes(sessionId: string, data: SessionData): Promise<void> {\n const indexKeys = getSessionIndexKeys(data);\n if (indexKeys.length === 0) {\n return;\n }\n\n const redisClient = getRedis();\n const pipeline = redisClient.pipeline();\n\n for (const indexKey of indexKeys) {\n pipeline.srem(indexKey, sessionId);\n }\n\n await pipeline.exec();\n}\n\nfunction parseSessionData(sessionData: string | null): SessionData | null {\n if (!sessionData) {\n return null;\n }\n\n try {\n return JSON.parse(decodeSessionData(sessionData)) as SessionData;\n } catch (error) {\n logger.error('Failed to parse session data', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\nfunction encodeSessionData(data: SessionData): string {\n const plaintext = JSON.stringify(data);\n if (!SESSION_ENCRYPTION_KEY) {\n return plaintext;\n }\n\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', SESSION_ENCRYPTION_KEY, iv);\n const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n const tag = cipher.getAuthTag();\n\n return [\n 'enc:v1',\n iv.toString('base64url'),\n tag.toString('base64url'),\n ciphertext.toString('base64url'),\n ].join(':');\n}\n\nfunction decodeSessionData(value: string): string {\n if (!value.startsWith('enc:v1:')) {\n if (SESSION_ENCRYPTION_ENABLED) {\n logger.warn('Reading legacy plaintext auth session payload while session encryption is enabled.');\n }\n\n return value;\n }\n\n if (!SESSION_ENCRYPTION_KEY) {\n throw new Error('Cannot decrypt auth session payload because SESSION_ENCRYPTION_KEY is not configured.');\n }\n\n const [, version, ivValue, tagValue, ciphertextValue] = value.split(':');\n if (version !== 'v1' || !ivValue || !tagValue || !ciphertextValue) {\n throw new Error('Invalid encrypted auth session payload format.');\n }\n\n const decipher = createDecipheriv('aes-256-gcm', SESSION_ENCRYPTION_KEY, Buffer.from(ivValue, 'base64url'));\n decipher.setAuthTag(Buffer.from(tagValue, 'base64url'));\n\n return Buffer.concat([\n decipher.update(Buffer.from(ciphertextValue, 'base64url')),\n decipher.final(),\n ]).toString('utf8');\n}\n\nasync function readSessionData(sessionId: string): Promise<SessionData | null> {\n try {\n const sessionData = await getRedis().get(REDIS_KEYS.session(sessionId));\n if (!sessionData) {\n logger.debug('Session not found in Redis', { sessionId });\n return null;\n }\n\n await getRedis().expire(REDIS_KEYS.session(sessionId), SESSION_TTL);\n return parseSessionData(sessionData);\n } catch (error) {\n logger.error('Failed to read session from Redis', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\nasync function writeSessionData(sessionId: string, data: SessionData): Promise<void> {\n try {\n const existingSessionData = parseSessionData(await getRedis().get(REDIS_KEYS.session(sessionId)));\n if (existingSessionData) {\n await unregisterSessionIndexes(sessionId, existingSessionData);\n }\n\n await getRedis().setex(REDIS_KEYS.session(sessionId), SESSION_TTL, encodeSessionData(data));\n await registerSessionIndexes(sessionId, data);\n logger.debug('Updated Redis session', { sessionId });\n } catch (error) {\n logger.error('Failed to update session in Redis', error instanceof Error ? error : undefined);\n throw error;\n }\n}\n\nasync function deleteSessionData(sessionId: string): Promise<void> {\n try {\n const existingSessionData = parseSessionData(await getRedis().get(REDIS_KEYS.session(sessionId)));\n if (existingSessionData) {\n await unregisterSessionIndexes(sessionId, existingSessionData);\n }\n\n await getRedis().del(REDIS_KEYS.session(sessionId));\n logger.debug('Deleted Redis session', { sessionId });\n } catch (error) {\n logger.error('Failed to delete session from Redis', error instanceof Error ? error : undefined);\n }\n}\n\nfunction mergeSessionData(existingData: SessionData, updates: Partial<SessionData>): SessionData {\n const nextData = { ...existingData } as Record<string, unknown>;\n\n Object.entries(updates).forEach(([key, value]) => {\n if (value === undefined) {\n delete nextData[key];\n return;\n }\n\n nextData[key] = value;\n });\n\n return nextData as SessionData;\n}\n\nasync function getSessionIdsFromIndex(indexKey: string): Promise<string[]> {\n const sessionIds = await getRedis().smembers(indexKey);\n const activeSessionIds: string[] = [];\n const staleSessionIds: string[] = [];\n\n for (const sessionId of sessionIds) {\n const exists = await getRedis().exists(REDIS_KEYS.session(sessionId));\n if (exists) {\n activeSessionIds.push(sessionId);\n } else {\n staleSessionIds.push(sessionId);\n }\n }\n\n if (staleSessionIds.length > 0) {\n await getRedis().srem(indexKey, ...staleSessionIds);\n }\n\n return activeSessionIds;\n}\n\nfunction toAuthSessionSummary(\n sessionId: string,\n session: SessionData,\n currentSessionId?: string | null\n): AuthSessionSummary {\n return {\n sessionId,\n userId: session.userId,\n email: session.user?.email,\n name: session.user?.name,\n issuer: session.issuer,\n sid: session.sid,\n sessionState: session.sessionState,\n clientId: session.clientId,\n ipAddress: session.ipAddress,\n userAgent: session.userAgent,\n createdAt: session.createdAt,\n lastActivity: session.lastActivity,\n expiresAt: session.expiresAt,\n isCurrent: currentSessionId ? sessionId === currentSessionId : undefined,\n };\n}\n\nasync function listSessionDataByIds(sessionIds: string[]): Promise<Array<{ sessionId: string; data: SessionData }>> {\n const sessions: Array<{ sessionId: string; data: SessionData }> = [];\n\n for (const sessionId of sessionIds) {\n const data = await readSessionData(sessionId);\n if (data) {\n sessions.push({ sessionId, data });\n }\n }\n\n return sessions;\n}\n\nfunction getRequestIpAddress(request: Request): string | undefined {\n const forwardedFor = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();\n return (\n forwardedFor ||\n request.headers.get('cf-connecting-ip')?.trim() ||\n request.headers.get('x-real-ip')?.trim() ||\n undefined\n );\n}\n\n// ============================================================================\n// Session Helper Functions\n// ============================================================================\n\nexport async function createAuthSession(\n request: Request,\n sessionData: Partial<SessionData>\n): Promise<Headers> {\n const currentSessionId = await getSessionIdFromRequest(request);\n const nextSessionId = crypto.randomUUID();\n const nextSessionData = mergeSessionData({}, {\n ...sessionData,\n sessionId: nextSessionId,\n ipAddress: sessionData.ipAddress ?? getRequestIpAddress(request),\n userAgent: sessionData.userAgent ?? request.headers.get('user-agent') ?? undefined,\n });\n\n logger.debug('Creating auth session', { dataKeys: Object.keys(sessionData) });\n\n if (currentSessionId) {\n await deleteSessionData(currentSessionId);\n }\n\n await writeSessionData(nextSessionId, nextSessionData);\n\n return new Headers({\n 'Set-Cookie': serializeSessionCookieValue(nextSessionId, { maxAge: SESSION_TTL }),\n });\n}\n\nexport async function getAuthSession(request: Request): Promise<SessionData> {\n const sessionId = await getSessionIdFromRequest(request);\n if (!sessionId) {\n return {};\n }\n\n const session = await readSessionData(sessionId);\n if (!session) {\n return {};\n }\n\n return {\n sessionId,\n userId: session.userId,\n accessToken: session.accessToken,\n refreshToken: session.refreshToken,\n idToken: session.idToken,\n expiresAt: session.expiresAt,\n issuer: session.issuer,\n sid: session.sid,\n sessionState: session.sessionState,\n clientId: session.clientId,\n ipAddress: session.ipAddress,\n userAgent: session.userAgent,\n user: session.user,\n createdAt: session.createdAt,\n lastActivity: session.lastActivity,\n };\n}\n\nexport async function updateAuthSession(\n request: Request,\n updates: Partial<SessionData>\n): Promise<Headers> {\n const currentSessionId = await getSessionIdFromRequest(request);\n const currentSessionData = currentSessionId\n ? (await readSessionData(currentSessionId)) ?? {}\n : {};\n const nextSessionId = currentSessionId ?? crypto.randomUUID();\n const nextSessionData = mergeSessionData(currentSessionData, {\n ...updates,\n sessionId: nextSessionId,\n });\n\n await writeSessionData(nextSessionId, nextSessionData);\n\n return new Headers({\n 'Set-Cookie': serializeSessionCookieValue(nextSessionId, { maxAge: SESSION_TTL }),\n });\n}\n\nexport async function destroyAuthSession(request: Request): Promise<Headers> {\n const sessionId = await getSessionIdFromRequest(request);\n if (sessionId) {\n await deleteSessionData(sessionId);\n }\n\n return new Headers({\n 'Set-Cookie': serializeExpiredSessionCookie(),\n });\n}\n\nexport async function getCurrentAuthSessionId(request: Request): Promise<string | null> {\n return await getSessionIdFromRequest(request);\n}\n\nexport async function getAuthSessionById(sessionId: string): Promise<SessionData | null> {\n const session = await readSessionData(sessionId);\n return session ? { ...session, sessionId } : null;\n}\n\nexport async function listAuthSessionDataForUser(\n userId: string\n): Promise<Array<{ sessionId: string; data: SessionData }>> {\n const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionByUser(userId));\n return await listSessionDataByIds(sessionIds);\n}\n\nexport async function listAuthSessionsForUser(\n userId: string,\n currentSessionId?: string | null\n): Promise<AuthSessionSummary[]> {\n const sessions = await listAuthSessionDataForUser(userId);\n\n return sessions\n .map(({ sessionId, data }) => toAuthSessionSummary(sessionId, data, currentSessionId))\n .sort((left, right) => (right.lastActivity ?? right.createdAt ?? 0) - (left.lastActivity ?? left.createdAt ?? 0));\n}\n\nexport async function listCurrentUserAuthSessions(request: Request): Promise<AuthSessionSummary[]> {\n const currentSession = await getAuthSession(request);\n if (!currentSession.userId) {\n return [];\n }\n\n return await listAuthSessionsForUser(currentSession.userId, currentSession.sessionId);\n}\n\nexport async function destroyAuthSessionById(sessionId: string): Promise<boolean> {\n const session = await readSessionData(sessionId);\n if (!session) {\n return false;\n }\n\n await deleteSessionData(sessionId);\n return true;\n}\n\nexport async function destroyAuthSessionsForUser(userId: string): Promise<number> {\n const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionByUser(userId));\n let destroyedSessions = 0;\n\n for (const sessionId of sessionIds) {\n if (await destroyAuthSessionById(sessionId)) {\n destroyedSessions += 1;\n }\n }\n\n return destroyedSessions;\n}\n\nexport async function destroyAuthSessionsBySid(sid: string): Promise<number> {\n const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionBySid(sid));\n let destroyedSessions = 0;\n\n for (const sessionId of sessionIds) {\n if (await destroyAuthSessionById(sessionId)) {\n destroyedSessions += 1;\n }\n }\n\n return destroyedSessions;\n}\n\nexport async function destroyAuthSessionsBySessionState(sessionState: string): Promise<number> {\n const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionBySessionState(sessionState));\n let destroyedSessions = 0;\n\n for (const sessionId of sessionIds) {\n if (await destroyAuthSessionById(sessionId)) {\n destroyedSessions += 1;\n }\n }\n\n return destroyedSessions;\n}\n\nexport async function destroyAuthSessionsByIdentitySession(params: {\n sid?: string;\n sessionState?: string;\n userId?: string;\n}): Promise<number> {\n if (params.sid) {\n return await destroyAuthSessionsBySid(params.sid);\n }\n\n if (params.sessionState) {\n return await destroyAuthSessionsBySessionState(params.sessionState);\n }\n\n if (params.userId) {\n return await destroyAuthSessionsForUser(params.userId);\n }\n\n return 0;\n}\n\nexport async function requireAuthSession(request: Request): Promise<SessionData> {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.userId || !sessionData.user || !sessionData.accessToken || isSessionDataExpired(sessionData)) {\n throw new Response('Unauthorized', { status: 401 });\n }\n\n return sessionData;\n}\n\nfunction getJwtExpiry(accessToken: string | undefined): number | null {\n if (!accessToken) {\n return null;\n }\n\n try {\n const [, encodedPayload] = accessToken.split('.');\n if (!encodedPayload) {\n return null;\n }\n\n const payload = JSON.parse(\n Buffer.from(encodedPayload, 'base64url').toString('utf8'),\n ) as { exp?: unknown };\n\n return typeof payload.exp === 'number' && Number.isFinite(payload.exp)\n ? payload.exp * 1000\n : null;\n } catch {\n return null;\n }\n}\n\nfunction isSessionDataExpired(sessionData: SessionData): boolean {\n const now = Date.now();\n if (\n typeof sessionData.expiresAt === 'number' &&\n (!Number.isFinite(sessionData.expiresAt) || now >= sessionData.expiresAt)\n ) {\n return true;\n }\n\n const jwtExpiry = getJwtExpiry(sessionData.accessToken);\n return jwtExpiry !== null && now >= jwtExpiry;\n}\n\nexport async function isSessionValid(request: Request): Promise<boolean> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.user || !sessionData.accessToken || isSessionDataExpired(sessionData)) {\n return false;\n }\n\n return true;\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// OAuth State Management\n// ============================================================================\n\nexport async function createOAuthState(state: OAuthState): Promise<string> {\n const stateId = crypto.randomUUID();\n const key = REDIS_KEYS.oauthState(stateId);\n\n await getRedis().setex(key, OAUTH_STATE_TTL_SECONDS, JSON.stringify(state));\n\n logger.debug('OAuth state created', { hasStateId: true, ttl: OAUTH_STATE_TTL_SECONDS });\n\n return stateId;\n}\n\nexport async function getOAuthState(\n stateId: string,\n options?: { readonly throwOnMalformed?: boolean },\n): Promise<OAuthState | null> {\n const key = REDIS_KEYS.oauthState(stateId);\n const data = await getRedis().get(key);\n\n if (!data) {\n logger.warn('OAuth state not found or expired', { hasStateId: true });\n return null;\n }\n\n try {\n return JSON.parse(data);\n } catch (error) {\n logger.error('Failed to parse OAuth state', undefined, {\n errorType: error instanceof Error ? error.name : 'UnknownError',\n });\n if (options?.throwOnMalformed) {\n throw new Error('OAuth state storage failure');\n }\n return null;\n }\n}\n\nexport async function deleteOAuthState(stateId: string): Promise<void> {\n const key = REDIS_KEYS.oauthState(stateId);\n await getRedis().del(key);\n logger.debug('OAuth state deleted', { hasStateId: true });\n}\n\nexport async function cleanupExpiredOAuthStates(): Promise<number> {\n const pattern = REDIS_KEYS.oauthState('*');\n const keys = await getRedis().keys(pattern);\n\n let cleaned = 0;\n for (const key of keys) {\n const ttl = await getRedis().ttl(key);\n if (ttl === -1) {\n await getRedis().del(key);\n cleaned++;\n }\n }\n\n logger.info('OAuth state cleanup completed', { cleaned, total: keys.length });\n return cleaned;\n}\n\n// ============================================================================\n// Cleanup\n// ============================================================================\n\nexport async function closeRedisConnection(): Promise<void> {\n if (redis) {\n await redis.quit();\n redis = null;\n logger.info('Redis connection closed');\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from "./logging/index.mjs";
|
|
2
|
-
import { S as updateAuthSession, a as createOAuthState, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, o as deleteOAuthState, p as getAuthSession, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, v as listAuthSessionDataForUser } from "./redis-session-storage.server-
|
|
2
|
+
import { C as OAUTH_STATE_TTL_MS, S as updateAuthSession, a as createOAuthState, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, o as deleteOAuthState, p as getAuthSession, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, v as listAuthSessionDataForUser, w as OAUTH_STATE_TTL_SECONDS } from "./redis-session-storage.server-CI_IYIn8.mjs";
|
|
3
3
|
import * as oidc from "openid-client";
|
|
4
4
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
5
5
|
//#region src/http/response.ts
|
|
@@ -132,6 +132,75 @@ function serializeTemporaryAuthCookie(name, value, options) {
|
|
|
132
132
|
return segments.join("; ");
|
|
133
133
|
}
|
|
134
134
|
//#endregion
|
|
135
|
+
//#region src/auth/callback-errors.ts
|
|
136
|
+
const OAuthCallbackFailureCodes = {
|
|
137
|
+
StaleTransaction: "stale_transaction",
|
|
138
|
+
StateMismatch: "state_mismatch",
|
|
139
|
+
MalformedCallback: "malformed_callback",
|
|
140
|
+
ApplicationActionFailed: "application_action_failed",
|
|
141
|
+
TokenExchangeFailed: "token_exchange_failed",
|
|
142
|
+
SessionCreationFailed: "session_creation_failed",
|
|
143
|
+
StorageError: "storage_error",
|
|
144
|
+
ConfigurationError: "configuration_error"
|
|
145
|
+
};
|
|
146
|
+
const OAUTH_CALLBACK_ERROR_BRAND = Symbol.for("@eminuckan/spine/OAuthCallbackError");
|
|
147
|
+
function isOAuthCallbackFailureCode(value) {
|
|
148
|
+
return typeof value === "string" && Object.values(OAuthCallbackFailureCodes).includes(value);
|
|
149
|
+
}
|
|
150
|
+
function isHeadersLike(value) {
|
|
151
|
+
return value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.append === "function";
|
|
152
|
+
}
|
|
153
|
+
function getSetCookieValues(headers) {
|
|
154
|
+
const getSetCookie = headers.getSetCookie;
|
|
155
|
+
if (getSetCookie) return getSetCookie.call(headers);
|
|
156
|
+
return Array.from(headers).reduce((cookies, [name, value]) => {
|
|
157
|
+
if (name.toLowerCase() === "set-cookie") cookies.push(value);
|
|
158
|
+
return cookies;
|
|
159
|
+
}, []);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* A callback failure that consumers can classify without parsing messages.
|
|
163
|
+
*
|
|
164
|
+
* The message is intentionally constant. Consumers should branch on
|
|
165
|
+
* `code`, not on `message`, and should merge `cleanupHeaders` (or the
|
|
166
|
+
* lossless `cleanupSetCookies` values) with any response they create using
|
|
167
|
+
* repeated `Set-Cookie` appends.
|
|
168
|
+
*/
|
|
169
|
+
var OAuthCallbackError = class extends Error {
|
|
170
|
+
code;
|
|
171
|
+
cleanupHeaders;
|
|
172
|
+
cleanupSetCookies;
|
|
173
|
+
constructor(code, cleanupHeaders = {}) {
|
|
174
|
+
super("Authentication failed");
|
|
175
|
+
this.name = "OAuthCallbackError";
|
|
176
|
+
this.code = code;
|
|
177
|
+
this.cleanupHeaders = new Headers(cleanupHeaders);
|
|
178
|
+
this.cleanupSetCookies = getSetCookieValues(this.cleanupHeaders);
|
|
179
|
+
Object.defineProperty(this, OAUTH_CALLBACK_ERROR_BRAND, {
|
|
180
|
+
configurable: false,
|
|
181
|
+
enumerable: false,
|
|
182
|
+
value: true,
|
|
183
|
+
writable: false
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Cross-package-copy safe type guard for callback failures.
|
|
189
|
+
*/
|
|
190
|
+
function isOAuthCallbackError(error) {
|
|
191
|
+
if (error instanceof OAuthCallbackError) return true;
|
|
192
|
+
if (error === null || typeof error !== "object") return false;
|
|
193
|
+
const candidate = error;
|
|
194
|
+
return candidate[OAUTH_CALLBACK_ERROR_BRAND] === true && isOAuthCallbackFailureCode(candidate.code) && isHeadersLike(candidate.cleanupHeaders);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Only a normal authorization callback whose local transaction is absent or
|
|
198
|
+
* expired may be retried. All other callback failures fail closed.
|
|
199
|
+
*/
|
|
200
|
+
function isRecoverableOAuthCallbackError(error) {
|
|
201
|
+
return isOAuthCallbackError(error) && error.code === "stale_transaction";
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
135
204
|
//#region src/auth/auth.server.ts
|
|
136
205
|
/**
|
|
137
206
|
* OpenID Connect Authentication Server Module
|
|
@@ -314,11 +383,6 @@ function getOAuthErrorMessage(error) {
|
|
|
314
383
|
if (error instanceof Error) return error.message;
|
|
315
384
|
return "Unknown error";
|
|
316
385
|
}
|
|
317
|
-
function shouldLogoutAfterOAuthError(error) {
|
|
318
|
-
if (error instanceof oidc.ResponseBodyError) return error.status === 400 || error.status === 401 || error.error === "invalid_grant" || error.error === "invalid_token";
|
|
319
|
-
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
320
|
-
return message.includes("invalid_grant") || message.includes("invalid_token") || message.includes("unauthorized");
|
|
321
|
-
}
|
|
322
386
|
function getLogoutScope(url) {
|
|
323
387
|
const normalized = (url.searchParams.get("logout") ?? url.searchParams.get("logout_scope") ?? url.searchParams.get("scope"))?.trim().toLowerCase();
|
|
324
388
|
if (normalized === "local" || normalized === "application" || normalized === "app" || normalized === "current-device") return "local";
|
|
@@ -389,11 +453,12 @@ async function getOAuthConfig() {
|
|
|
389
453
|
});
|
|
390
454
|
return configuration;
|
|
391
455
|
} catch (error) {
|
|
392
|
-
logger.error("OIDC discovery failed",
|
|
456
|
+
logger.error("OIDC discovery failed", void 0, {
|
|
393
457
|
authority: config.authority,
|
|
394
|
-
clientId: config.clientId
|
|
458
|
+
clientId: config.clientId,
|
|
459
|
+
errorType: error instanceof Error ? error.name : "UnknownError"
|
|
395
460
|
});
|
|
396
|
-
throw new Error(
|
|
461
|
+
throw new Error("OIDC discovery failed");
|
|
397
462
|
}
|
|
398
463
|
}
|
|
399
464
|
function getJwks(jwksUri) {
|
|
@@ -666,6 +731,74 @@ function clearAuthErrorHeaders() {
|
|
|
666
731
|
function hasAuthError(request) {
|
|
667
732
|
return request.headers.get("Cookie")?.includes(`${AUTH_ERROR_COOKIE}=`) ?? false;
|
|
668
733
|
}
|
|
734
|
+
function escapeRegExp(value) {
|
|
735
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
736
|
+
}
|
|
737
|
+
function getOAuthStateIdFromRequest(request) {
|
|
738
|
+
const cookies = request.headers.get("Cookie");
|
|
739
|
+
if (!cookies) return null;
|
|
740
|
+
const cookieRegex = new RegExp(`(?:^|;\\s*)${escapeRegExp(OAUTH_STATE_COOKIE)}=([^;]*)`);
|
|
741
|
+
const cookieValue = cookies.match(cookieRegex)?.[1];
|
|
742
|
+
if (!cookieValue) return null;
|
|
743
|
+
try {
|
|
744
|
+
const stateId = decodeURIComponent(cookieValue);
|
|
745
|
+
return stateId.length > 0 ? stateId : null;
|
|
746
|
+
} catch {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function hasNonEmptyCallbackValue(value) {
|
|
751
|
+
return value !== null && value.trim().length > 0;
|
|
752
|
+
}
|
|
753
|
+
function appendHeadersPreservingSetCookie(target, source) {
|
|
754
|
+
const sourceHeaders = new Headers(source);
|
|
755
|
+
const setCookies = sourceHeaders.getSetCookie?.call(sourceHeaders) ?? Array.from(sourceHeaders).filter(([name]) => name.toLowerCase() === "set-cookie").map(([, value]) => value);
|
|
756
|
+
if (setCookies.length > 0) for (const cookie of setCookies) target.append("Set-Cookie", cookie);
|
|
757
|
+
else {
|
|
758
|
+
const setCookie = sourceHeaders.get("Set-Cookie");
|
|
759
|
+
if (setCookie) target.append("Set-Cookie", setCookie);
|
|
760
|
+
}
|
|
761
|
+
for (const [name, value] of sourceHeaders) if (name.toLowerCase() !== "set-cookie") target.set(name, value);
|
|
762
|
+
}
|
|
763
|
+
function appendOAuthStateCookieClear(headers) {
|
|
764
|
+
headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, "", { maxAge: 0 }));
|
|
765
|
+
}
|
|
766
|
+
function isUsableOAuthState(state, requireNonce) {
|
|
767
|
+
return state !== null && typeof state.state === "string" && state.state.length > 0 && typeof state.codeVerifier === "string" && state.codeVerifier.length > 0 && (state.nonce === void 0 ? !requireNonce : typeof state.nonce === "string" && state.nonce.length > 0) && typeof state.createdAt === "number" && Number.isFinite(state.createdAt) && (state.returnUrl === void 0 || typeof state.returnUrl === "string") && (state.kcAction === void 0 || typeof state.kcAction === "string");
|
|
768
|
+
}
|
|
769
|
+
function getCallbackFailureCode(error, stage) {
|
|
770
|
+
if (isOAuthCallbackError(error)) return error.code;
|
|
771
|
+
switch (stage) {
|
|
772
|
+
case "configuration": return OAuthCallbackFailureCodes.ConfigurationError;
|
|
773
|
+
case "state":
|
|
774
|
+
case "storage": return OAuthCallbackFailureCodes.StorageError;
|
|
775
|
+
case "application_action": return OAuthCallbackFailureCodes.ApplicationActionFailed;
|
|
776
|
+
case "token_exchange": return OAuthCallbackFailureCodes.TokenExchangeFailed;
|
|
777
|
+
case "session_creation": return OAuthCallbackFailureCodes.SessionCreationFailed;
|
|
778
|
+
default: return OAuthCallbackFailureCodes.StorageError;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
async function createOAuthCallbackFailure(request, stateId, failureCode) {
|
|
782
|
+
const cleanupHeaders = new Headers();
|
|
783
|
+
let cleanupError;
|
|
784
|
+
if (stateId) try {
|
|
785
|
+
await deleteOAuthState(stateId);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
cleanupError = error;
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
appendHeadersPreservingSetCookie(cleanupHeaders, await destroyAuthSession(request));
|
|
791
|
+
} catch (error) {
|
|
792
|
+
cleanupError ??= error;
|
|
793
|
+
}
|
|
794
|
+
appendOAuthStateCookieClear(cleanupHeaders);
|
|
795
|
+
const effectiveFailureCode = cleanupError ? OAuthCallbackFailureCodes.StorageError : failureCode;
|
|
796
|
+
if (cleanupError) logger.warn("OAuth callback cleanup failed", {
|
|
797
|
+
failureCode: effectiveFailureCode,
|
|
798
|
+
errorType: cleanupError instanceof Error ? cleanupError.name : "UnknownError"
|
|
799
|
+
});
|
|
800
|
+
return new OAuthCallbackError(effectiveFailureCode, cleanupHeaders);
|
|
801
|
+
}
|
|
669
802
|
/**
|
|
670
803
|
* Initiate OAuth login flow
|
|
671
804
|
*
|
|
@@ -718,7 +851,7 @@ async function login(request, returnUrlOrOptions) {
|
|
|
718
851
|
if (options.kcAction) authorizationParameters.kc_action = options.kcAction;
|
|
719
852
|
const authorizationUrl = oidc.buildAuthorizationUrl(oidcConfig, authorizationParameters);
|
|
720
853
|
const headers = new Headers();
|
|
721
|
-
headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, stateId, { maxAge:
|
|
854
|
+
headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, stateId, { maxAge: OAUTH_STATE_TTL_SECONDS }));
|
|
722
855
|
logger.info("OIDC authorization flow initiated", {
|
|
723
856
|
clientId: config.clientId,
|
|
724
857
|
redirectUri: config.redirectUri,
|
|
@@ -747,9 +880,13 @@ async function login(request, returnUrlOrOptions) {
|
|
|
747
880
|
* - Redirect to home to start fresh login
|
|
748
881
|
*/
|
|
749
882
|
async function handleCallback(request) {
|
|
883
|
+
let stateId = null;
|
|
884
|
+
let callbackStage = "configuration";
|
|
750
885
|
try {
|
|
886
|
+
stateId = getOAuthStateIdFromRequest(request);
|
|
751
887
|
const config = getAuthConfig();
|
|
752
888
|
const hasLandingPage = getEffectiveHasLandingPage(config);
|
|
889
|
+
const requiresNonce = config.scope.split(/\s+/).includes("openid");
|
|
753
890
|
const url = new URL(request.url);
|
|
754
891
|
const code = url.searchParams.get("code");
|
|
755
892
|
const state = url.searchParams.get("state");
|
|
@@ -757,28 +894,31 @@ async function handleCallback(request) {
|
|
|
757
894
|
const errorDescription = url.searchParams.get("error_description");
|
|
758
895
|
const kcAction = url.searchParams.get("kc_action");
|
|
759
896
|
const kcActionStatus = url.searchParams.get("kc_action_status");
|
|
897
|
+
const hasCode = hasNonEmptyCallbackValue(code);
|
|
898
|
+
const hasState = hasNonEmptyCallbackValue(state);
|
|
899
|
+
const hasApplicationAction = Boolean(hasNonEmptyCallbackValue(kcAction) || hasNonEmptyCallbackValue(kcActionStatus));
|
|
760
900
|
logger.info("OIDC callback received", {
|
|
761
|
-
hasCode
|
|
762
|
-
hasState
|
|
901
|
+
hasCode,
|
|
902
|
+
hasState,
|
|
763
903
|
hasError: Boolean(error),
|
|
764
904
|
hasErrorDescription: Boolean(errorDescription),
|
|
765
|
-
hasKcAction:
|
|
766
|
-
hasKcActionStatus:
|
|
905
|
+
hasKcAction: hasNonEmptyCallbackValue(kcAction),
|
|
906
|
+
hasKcActionStatus: hasNonEmptyCallbackValue(kcActionStatus),
|
|
767
907
|
clientId: config.clientId,
|
|
768
908
|
redirectUri: config.redirectUri
|
|
769
909
|
});
|
|
770
910
|
if (error) {
|
|
771
|
-
if (kcActionStatus &&
|
|
772
|
-
|
|
773
|
-
const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);
|
|
774
|
-
const stateId = (cookies?.match(oauthStateRegex))?.[1];
|
|
911
|
+
if (kcActionStatus && hasState) {
|
|
912
|
+
callbackStage = "state";
|
|
775
913
|
if (stateId) {
|
|
776
|
-
const oauthState = await getOAuthState(stateId);
|
|
914
|
+
const oauthState = await getOAuthState(stateId, { throwOnMalformed: true });
|
|
777
915
|
const stateAge = oauthState ? Date.now() - oauthState.createdAt : Number.POSITIVE_INFINITY;
|
|
778
|
-
if (oauthState && oauthState.state === state && stateAge <=
|
|
916
|
+
if (oauthState && isUsableOAuthState(oauthState, requiresNonce) && oauthState.state === state && stateAge <= OAUTH_STATE_TTL_MS) {
|
|
917
|
+
callbackStage = "storage";
|
|
779
918
|
await deleteOAuthState(stateId);
|
|
919
|
+
callbackStage = "application_action";
|
|
780
920
|
const headers = new Headers();
|
|
781
|
-
headers
|
|
921
|
+
appendOAuthStateCookieClear(headers);
|
|
782
922
|
const redirectUrl = appendApplicationInitiatedActionResult(sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? "/", kcAction || oauthState.kcAction, kcActionStatus);
|
|
783
923
|
logger.info("Application initiated action returned with OAuth error", {
|
|
784
924
|
hasError: Boolean(error),
|
|
@@ -799,9 +939,12 @@ async function handleCallback(request) {
|
|
|
799
939
|
});
|
|
800
940
|
if (error === "access_denied") {
|
|
801
941
|
logger.warn("Access denied for this application", { clientId: config.clientId });
|
|
942
|
+
callbackStage = "storage";
|
|
943
|
+
if (stateId) await deleteOAuthState(stateId);
|
|
802
944
|
const headers = await destroyAuthSession(request);
|
|
945
|
+
appendOAuthStateCookieClear(headers);
|
|
803
946
|
headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, "access_denied", { maxAge: 300 }));
|
|
804
|
-
headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE,
|
|
947
|
+
headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, "You do not have access to this application.", { maxAge: 300 }));
|
|
805
948
|
const baseUrl = new URL(config.redirectUri).origin;
|
|
806
949
|
logger.info("Redirecting to /auth/login to show access_denied error", {
|
|
807
950
|
hasLandingPage,
|
|
@@ -812,30 +955,47 @@ async function handleCallback(request) {
|
|
|
812
955
|
}
|
|
813
956
|
if (error === "login_required") {
|
|
814
957
|
logger.info("Login required, user session may have expired");
|
|
958
|
+
callbackStage = "storage";
|
|
959
|
+
if (stateId) await deleteOAuthState(stateId);
|
|
815
960
|
const headers = await destroyAuthSession(request);
|
|
961
|
+
appendOAuthStateCookieClear(headers);
|
|
816
962
|
const baseUrl = new URL(config.redirectUri).origin;
|
|
817
963
|
return createRedirectResponse(baseUrl, { headers });
|
|
818
964
|
}
|
|
965
|
+
callbackStage = "storage";
|
|
966
|
+
if (stateId) await deleteOAuthState(stateId);
|
|
819
967
|
const headers = await destroyAuthSession(request);
|
|
968
|
+
appendOAuthStateCookieClear(headers);
|
|
820
969
|
headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, error, { maxAge: 300 }));
|
|
821
|
-
if (errorDescription) headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, errorDescription, { maxAge: 300 }));
|
|
822
970
|
const baseUrl = new URL(config.redirectUri).origin;
|
|
823
971
|
return createRedirectResponse(baseUrl, { headers });
|
|
824
972
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
if (
|
|
834
|
-
|
|
973
|
+
if (!hasState) {
|
|
974
|
+
callbackStage = hasApplicationAction ? "application_action" : "state";
|
|
975
|
+
throw new OAuthCallbackError(hasApplicationAction ? OAuthCallbackFailureCodes.ApplicationActionFailed : OAuthCallbackFailureCodes.MalformedCallback);
|
|
976
|
+
}
|
|
977
|
+
if (!hasCode && !hasApplicationAction) {
|
|
978
|
+
callbackStage = "state";
|
|
979
|
+
throw new OAuthCallbackError(OAuthCallbackFailureCodes.MalformedCallback);
|
|
980
|
+
}
|
|
981
|
+
if (!stateId) {
|
|
982
|
+
callbackStage = hasApplicationAction ? "application_action" : "state";
|
|
983
|
+
throw new OAuthCallbackError(hasApplicationAction ? OAuthCallbackFailureCodes.ApplicationActionFailed : OAuthCallbackFailureCodes.StaleTransaction);
|
|
984
|
+
}
|
|
985
|
+
callbackStage = "state";
|
|
986
|
+
const oauthState = await getOAuthState(stateId, { throwOnMalformed: true });
|
|
987
|
+
if (!oauthState) throw new OAuthCallbackError(hasApplicationAction ? OAuthCallbackFailureCodes.ApplicationActionFailed : OAuthCallbackFailureCodes.StaleTransaction);
|
|
988
|
+
if (!isUsableOAuthState(oauthState, requiresNonce)) throw new OAuthCallbackError(OAuthCallbackFailureCodes.StorageError);
|
|
989
|
+
if (oauthState.state !== state) throw new OAuthCallbackError(OAuthCallbackFailureCodes.StateMismatch);
|
|
990
|
+
const isApplicationAction = Boolean(hasApplicationAction || oauthState.kcAction);
|
|
991
|
+
if (Date.now() - oauthState.createdAt > OAUTH_STATE_TTL_MS) throw new OAuthCallbackError(isApplicationAction ? OAuthCallbackFailureCodes.ApplicationActionFailed : OAuthCallbackFailureCodes.StaleTransaction);
|
|
992
|
+
if (!hasCode) {
|
|
835
993
|
if (kcActionStatus) {
|
|
994
|
+
callbackStage = "storage";
|
|
836
995
|
await deleteOAuthState(stateId);
|
|
996
|
+
callbackStage = "application_action";
|
|
837
997
|
const headers = new Headers();
|
|
838
|
-
headers
|
|
998
|
+
appendOAuthStateCookieClear(headers);
|
|
839
999
|
const redirectUrl = appendApplicationInitiatedActionResult(sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? "/", kcAction || oauthState.kcAction, kcActionStatus);
|
|
840
1000
|
logger.info("Application initiated action returned without authorization code", {
|
|
841
1001
|
hasKcAction: Boolean(kcAction || oauthState.kcAction),
|
|
@@ -843,11 +1003,13 @@ async function handleCallback(request) {
|
|
|
843
1003
|
});
|
|
844
1004
|
return createRedirectResponse(redirectUrl, { headers });
|
|
845
1005
|
}
|
|
846
|
-
throw new
|
|
1006
|
+
throw new OAuthCallbackError(isApplicationAction ? OAuthCallbackFailureCodes.ApplicationActionFailed : OAuthCallbackFailureCodes.MalformedCallback);
|
|
847
1007
|
}
|
|
848
1008
|
logger.info("Initiating token exchange");
|
|
1009
|
+
callbackStage = "configuration";
|
|
849
1010
|
const oidcConfig = await getOAuthConfig();
|
|
850
1011
|
const authorizationResponseUrl = createAuthorizationResponseUrl(request.url, config.redirectUri);
|
|
1012
|
+
callbackStage = "token_exchange";
|
|
851
1013
|
const tokenResult = await oidc.authorizationCodeGrant(oidcConfig, authorizationResponseUrl, {
|
|
852
1014
|
pkceCodeVerifier: oauthState.codeVerifier,
|
|
853
1015
|
expectedState: oauthState.state,
|
|
@@ -855,22 +1017,24 @@ async function handleCallback(request) {
|
|
|
855
1017
|
idTokenExpected: config.scope.split(/\s+/).includes("openid")
|
|
856
1018
|
});
|
|
857
1019
|
logger.info("Token exchange completed successfully");
|
|
858
|
-
|
|
1020
|
+
callbackStage = "session_creation";
|
|
1021
|
+
const newSessionData = await createSessionData(tokenResult, getClaimsFromTokenResponse(tokenResult));
|
|
1022
|
+
callbackStage = "session_creation";
|
|
1023
|
+
const sessionHeaders = await createAuthSession(request, newSessionData);
|
|
1024
|
+
callbackStage = "storage";
|
|
859
1025
|
await deleteOAuthState(stateId);
|
|
860
|
-
const headers = new Headers(
|
|
861
|
-
headers
|
|
1026
|
+
const headers = new Headers();
|
|
1027
|
+
appendHeadersPreservingSetCookie(headers, sessionHeaders);
|
|
1028
|
+
appendOAuthStateCookieClear(headers);
|
|
862
1029
|
logger.info("OAuth callback processed successfully");
|
|
863
1030
|
return createRedirectResponse(appendApplicationInitiatedActionResult(sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? "/", kcAction || oauthState.kcAction, kcActionStatus), { headers });
|
|
864
1031
|
} catch (error) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
} catch {}
|
|
872
|
-
await destroyAuthSession(request);
|
|
873
|
-
throw new Error("Authentication failed");
|
|
1032
|
+
const failureCode = getCallbackFailureCode(error, callbackStage);
|
|
1033
|
+
logger.error("OAuth callback failed", void 0, {
|
|
1034
|
+
failureCode,
|
|
1035
|
+
errorType: error instanceof Error ? error.name : "UnknownError"
|
|
1036
|
+
});
|
|
1037
|
+
throw await createOAuthCallbackFailure(request, stateId, failureCode);
|
|
874
1038
|
}
|
|
875
1039
|
}
|
|
876
1040
|
/**
|
|
@@ -1019,12 +1183,31 @@ async function handleFrontChannelLogout(request) {
|
|
|
1019
1183
|
return new Response(null, { status: 204 });
|
|
1020
1184
|
}
|
|
1021
1185
|
}
|
|
1186
|
+
function getAccessTokenExpiry(accessToken) {
|
|
1187
|
+
if (!accessToken) return null;
|
|
1188
|
+
try {
|
|
1189
|
+
const [, encodedPayload] = accessToken.split(".");
|
|
1190
|
+
if (!encodedPayload) return null;
|
|
1191
|
+
const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
|
|
1192
|
+
return typeof payload.exp === "number" && Number.isFinite(payload.exp) ? payload.exp * 1e3 : null;
|
|
1193
|
+
} catch {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function isSessionExpired(sessionData) {
|
|
1198
|
+
const now = Date.now();
|
|
1199
|
+
if (typeof sessionData.expiresAt === "number" && (!Number.isFinite(sessionData.expiresAt) || now >= sessionData.expiresAt)) return true;
|
|
1200
|
+
const accessTokenExpiry = getAccessTokenExpiry(sessionData.accessToken);
|
|
1201
|
+
return accessTokenExpiry !== null && now >= accessTokenExpiry;
|
|
1202
|
+
}
|
|
1022
1203
|
/**
|
|
1023
1204
|
* Get current user from session
|
|
1024
1205
|
*/
|
|
1025
1206
|
async function getUser(request) {
|
|
1026
1207
|
try {
|
|
1027
|
-
|
|
1208
|
+
const sessionData = await getAuthSession(request);
|
|
1209
|
+
if (!sessionData.user || !sessionData.accessToken || isSessionExpired(sessionData)) return null;
|
|
1210
|
+
return sessionData.user;
|
|
1028
1211
|
} catch (error) {
|
|
1029
1212
|
logger.error("Failed to get user", error instanceof Error ? error : void 0);
|
|
1030
1213
|
return null;
|
|
@@ -1060,8 +1243,16 @@ async function getAccessToken(request) {
|
|
|
1060
1243
|
async function refreshTokens(request, refreshToken) {
|
|
1061
1244
|
try {
|
|
1062
1245
|
const sessionData = await getAuthSession(request);
|
|
1063
|
-
|
|
1064
|
-
|
|
1246
|
+
if (!sessionData.sessionId) {
|
|
1247
|
+
logger.warn("Cannot refresh tokens without an active Redis session");
|
|
1248
|
+
return {
|
|
1249
|
+
success: false,
|
|
1250
|
+
error: "No active session",
|
|
1251
|
+
shouldLogout: true
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const sessionRefreshToken = sessionData.refreshToken;
|
|
1255
|
+
if (!sessionRefreshToken) {
|
|
1065
1256
|
logger.warn("No refresh token available");
|
|
1066
1257
|
return {
|
|
1067
1258
|
success: false,
|
|
@@ -1069,10 +1260,27 @@ async function refreshTokens(request, refreshToken) {
|
|
|
1069
1260
|
shouldLogout: true
|
|
1070
1261
|
};
|
|
1071
1262
|
}
|
|
1263
|
+
if (refreshToken && refreshToken !== sessionRefreshToken) {
|
|
1264
|
+
logger.warn("Rejected refresh token that does not match the active Redis session");
|
|
1265
|
+
return {
|
|
1266
|
+
success: false,
|
|
1267
|
+
error: "Refresh token does not match active session",
|
|
1268
|
+
shouldLogout: true
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const tokenToRefresh = sessionRefreshToken;
|
|
1072
1272
|
logger.info("Initiating token refresh");
|
|
1073
1273
|
const oidcConfig = await getOAuthConfig();
|
|
1074
1274
|
const refreshResult = await oidc.refreshTokenGrant(oidcConfig, tokenToRefresh);
|
|
1075
1275
|
logger.info("Token refresh successful");
|
|
1276
|
+
if (!refreshResult.access_token) {
|
|
1277
|
+
logger.error("Token refresh returned no access token");
|
|
1278
|
+
return {
|
|
1279
|
+
success: false,
|
|
1280
|
+
error: "No access token in refresh response",
|
|
1281
|
+
shouldLogout: true
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1076
1284
|
const newRefreshToken = refreshResult.refresh_token || tokenToRefresh;
|
|
1077
1285
|
const expiresAt = refreshResult.expires_in ? Date.now() + refreshResult.expires_in * 1e3 : sessionData.expiresAt;
|
|
1078
1286
|
await updateAuthSession(request, {
|
|
@@ -1096,7 +1304,7 @@ async function refreshTokens(request, refreshToken) {
|
|
|
1096
1304
|
return {
|
|
1097
1305
|
success: false,
|
|
1098
1306
|
error: getOAuthErrorMessage(error),
|
|
1099
|
-
shouldLogout:
|
|
1307
|
+
shouldLogout: true
|
|
1100
1308
|
};
|
|
1101
1309
|
}
|
|
1102
1310
|
}
|
|
@@ -1125,15 +1333,16 @@ function resolveRefreshThreshold() {
|
|
|
1125
1333
|
async function shouldRefreshToken(request) {
|
|
1126
1334
|
try {
|
|
1127
1335
|
const sessionData = await getAuthSession(request);
|
|
1336
|
+
const now = Date.now();
|
|
1337
|
+
const sessionExpired = typeof sessionData.expiresAt === "number" && now >= sessionData.expiresAt;
|
|
1128
1338
|
if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {
|
|
1129
|
-
if (
|
|
1339
|
+
if (sessionExpired) {
|
|
1130
1340
|
logger.info("Token expired and no refresh token, forcing local application logout");
|
|
1131
1341
|
throw await logout(await createAutomaticLogoutRequest(request, "expired-no-refresh-token"));
|
|
1132
1342
|
}
|
|
1133
1343
|
return false;
|
|
1134
1344
|
}
|
|
1135
1345
|
if (!sessionData.expiresAt || !sessionData.accessToken) return false;
|
|
1136
|
-
const now = Date.now();
|
|
1137
1346
|
let jwtExpiry = null;
|
|
1138
1347
|
try {
|
|
1139
1348
|
const tokenParts = sessionData.accessToken.split(".");
|
|
@@ -1144,8 +1353,9 @@ async function shouldRefreshToken(request) {
|
|
|
1144
1353
|
} catch {}
|
|
1145
1354
|
return (jwtExpiry && jwtExpiry < sessionData.expiresAt ? jwtExpiry : sessionData.expiresAt) - now <= REFRESH_THRESHOLD;
|
|
1146
1355
|
} catch (error) {
|
|
1356
|
+
if (error instanceof Response) throw error;
|
|
1147
1357
|
logger.error("Error checking token refresh", error instanceof Error ? error : void 0);
|
|
1148
|
-
|
|
1358
|
+
throw error;
|
|
1149
1359
|
}
|
|
1150
1360
|
}
|
|
1151
1361
|
/**
|
|
@@ -1156,13 +1366,11 @@ async function autoRefreshTokens(request) {
|
|
|
1156
1366
|
if (await shouldRefreshToken(request)) {
|
|
1157
1367
|
logger.info("Auto-refreshing tokens due to expiry");
|
|
1158
1368
|
const result = await refreshTokens(request);
|
|
1159
|
-
if (result.success) logger.info("Auto-refresh completed successfully");
|
|
1369
|
+
if (result.success && result.tokens?.access_token) logger.info("Auto-refresh completed successfully");
|
|
1160
1370
|
else {
|
|
1161
|
-
logger.error("Auto-refresh failed", void 0, { error: result.error });
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
throw await logout(await createAutomaticLogoutRequest(request, "refresh-failed"));
|
|
1165
|
-
}
|
|
1371
|
+
logger.error("Auto-refresh failed", void 0, { error: result.error ?? "Refresh response did not include an access token" });
|
|
1372
|
+
logger.error("Refresh token unusable, forcing local application logout");
|
|
1373
|
+
throw await logout(await createAutomaticLogoutRequest(request, "refresh-failed"));
|
|
1166
1374
|
}
|
|
1167
1375
|
}
|
|
1168
1376
|
} catch (error) {
|
|
@@ -1266,7 +1474,8 @@ async function protectRoute(request, protection, loaderFn) {
|
|
|
1266
1474
|
case "public": try {
|
|
1267
1475
|
await autoRefreshTokens(request);
|
|
1268
1476
|
return loaderFn(await getUser(request) ?? void 0);
|
|
1269
|
-
} catch {
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
|
|
1270
1479
|
return loaderFn(void 0);
|
|
1271
1480
|
}
|
|
1272
1481
|
case "auth":
|
|
@@ -1360,7 +1569,7 @@ async function attemptTokenRefresh(request) {
|
|
|
1360
1569
|
return {
|
|
1361
1570
|
success: false,
|
|
1362
1571
|
error: refreshResult.error,
|
|
1363
|
-
shouldLogout:
|
|
1572
|
+
shouldLogout: true
|
|
1364
1573
|
};
|
|
1365
1574
|
}
|
|
1366
1575
|
const tokens = refreshResult.tokens;
|
|
@@ -1399,6 +1608,6 @@ function shouldAttemptTokenRefresh(response, hasAuth) {
|
|
|
1399
1608
|
return hasAuth && isTokenExpiredResponse(response);
|
|
1400
1609
|
}
|
|
1401
1610
|
//#endregion
|
|
1402
|
-
export { resetAuthClaimMapping as A, handleCallback as C, logout as D, login as E, refreshTokens as O, handleBackChannelLogout as S, hasAuthError as T, createAuthorizationResponseUrl as _, configureRouteProtection as a, getAuthError as b, protectRoute as c, resetRouteProtectionConfig as d, shouldRefreshToken as f, configureAuthClaimMapping as g, clearAuthServerCache as h, authRoute as i, requireAuth as k, protectedRoute as l, clearAuthErrorHeaders as m, isTokenExpiredResponse as n, onboardingRoute as o, subscriptionRoute as p, shouldAttemptTokenRefresh as r, policyRoute as s, attemptTokenRefresh as t, publicRoute as u, extractUserInfo as v, handleFrontChannelLogout as w, getUser as x, getAccessToken as y };
|
|
1611
|
+
export { resetAuthClaimMapping as A, handleCallback as C, logout as D, login as E, OAuthCallbackFailureCodes as M, isOAuthCallbackError as N, refreshTokens as O, isRecoverableOAuthCallbackError as P, handleBackChannelLogout as S, hasAuthError as T, createAuthorizationResponseUrl as _, configureRouteProtection as a, getAuthError as b, protectRoute as c, resetRouteProtectionConfig as d, shouldRefreshToken as f, configureAuthClaimMapping as g, clearAuthServerCache as h, authRoute as i, OAuthCallbackError as j, requireAuth as k, protectedRoute as l, clearAuthErrorHeaders as m, isTokenExpiredResponse as n, onboardingRoute as o, subscriptionRoute as p, shouldAttemptTokenRefresh as r, policyRoute as s, attemptTokenRefresh as t, publicRoute as u, extractUserInfo as v, handleFrontChannelLogout as w, getUser as x, getAccessToken as y };
|
|
1403
1612
|
|
|
1404
|
-
//# sourceMappingURL=server-
|
|
1613
|
+
//# sourceMappingURL=server-Cw86FdT-.mjs.map
|