@eminuckan/spine 0.3.1 → 0.3.2
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 +6 -0
- package/SECURITY.md +5 -15
- package/dist/access/index.mjs +2 -0
- package/dist/{access-CGrsDMwu.js → access-BHy1v5sr.mjs} +6 -8
- package/dist/access-BHy1v5sr.mjs.map +1 -0
- package/dist/api-client/index.mjs +3 -0
- package/dist/api-client/server.mjs +4 -0
- package/dist/{api-client-CgZUMukO.js → api-client-CB7XZaMg.mjs} +8 -10
- package/dist/api-client-CB7XZaMg.mjs.map +1 -0
- package/dist/auth/auth.server.d.ts +1 -0
- package/dist/auth/auth.server.d.ts.map +1 -1
- package/dist/auth/index.mjs +1 -0
- package/dist/auth/route-protection.server.d.ts.map +1 -1
- package/dist/auth/server.mjs +3 -0
- package/dist/{identity-C5un-xAs.js → identity/index.mjs} +24 -21
- package/dist/identity/index.mjs.map +1 -0
- package/dist/{server-Dxsww9v9.js → identity/server.mjs} +7 -8
- package/dist/identity/server.mjs.map +1 -0
- package/dist/{index.js → index.mjs} +11 -13
- package/dist/{logging-DviiAkLx.js → logging/index.mjs} +22 -24
- package/dist/logging/index.mjs.map +1 -0
- package/dist/{permission-service-CvGpwI0d.js → permission-service-DUyKomU3.mjs} +6 -10
- package/dist/permission-service-DUyKomU3.mjs.map +1 -0
- package/dist/permissions/index.mjs +3 -0
- package/dist/{permissions-_n1hIG7Q.js → permissions-C5dTP9y4.mjs} +23 -15
- package/dist/permissions-C5dTP9y4.mjs.map +1 -0
- package/dist/query-client/index.mjs +2 -0
- package/dist/{query-client-Bh3TyB8W.js → query-client-_xB300gc.mjs} +49 -15
- package/dist/query-client-_xB300gc.mjs.map +1 -0
- package/dist/react-router/{index.js → index.mjs} +11 -13
- package/dist/react-router/server.mjs +17 -0
- package/dist/{redis-session-storage.server-v2VcpFsQ.js → redis-session-storage.server-DGgQcJ9B.mjs} +30 -42
- package/dist/redis-session-storage.server-DGgQcJ9B.mjs.map +1 -0
- package/dist/{server-C96NMFBy.js → server-ByScGRXA.mjs} +39 -36
- package/dist/server-ByScGRXA.mjs.map +1 -0
- package/dist/{auth.server-D5oiz9aF.js → server-CqX2yCa4.mjs} +388 -119
- package/dist/server-CqX2yCa4.mjs.map +1 -0
- package/dist/server.mjs +90 -0
- package/dist/server.mjs.map +1 -0
- package/dist/signalr/index.mjs +2 -0
- package/dist/{signalr-k-70VHKo.js → signalr-C-TA8nEE.mjs} +11 -6
- package/dist/signalr-C-TA8nEE.mjs.map +1 -0
- package/dist/{tenant-DfLemb6n.js → tenant/index.mjs} +19 -12
- package/dist/tenant/index.mjs.map +1 -0
- package/dist/{server-Ca-3DJf2.js → tenant/server.mjs} +12 -12
- package/dist/tenant/server.mjs.map +1 -0
- package/dist/{types-BaaE6wjY.js → types-DqCXinAk.mjs} +3 -3
- package/dist/types-DqCXinAk.mjs.map +1 -0
- package/docs/codex-for-oss-application.md +4 -0
- package/package.json +14 -2
- package/dist/access/index.js +0 -4
- package/dist/access-CGrsDMwu.js.map +0 -1
- package/dist/api-client/index.js +0 -5
- package/dist/api-client/server.js +0 -6
- package/dist/api-client-CgZUMukO.js.map +0 -1
- package/dist/auth/index.js +0 -0
- package/dist/auth/server.js +0 -6
- package/dist/auth.server-D5oiz9aF.js.map +0 -1
- package/dist/auth.server-lC94lmlx.js +0 -5
- package/dist/identity/index.js +0 -3
- package/dist/identity/server.js +0 -5
- package/dist/identity-C5un-xAs.js.map +0 -1
- package/dist/logging/index.js +0 -3
- package/dist/logging-DviiAkLx.js.map +0 -1
- package/dist/permission-service-CvGpwI0d.js.map +0 -1
- package/dist/permissions/index.js +0 -4
- package/dist/permissions-_n1hIG7Q.js.map +0 -1
- package/dist/query-client/index.js +0 -3
- package/dist/query-client-Bh3TyB8W.js.map +0 -1
- package/dist/react-router/server.js +0 -20
- package/dist/redis-session-storage.server-v2VcpFsQ.js.map +0 -1
- package/dist/server-B6PCxc-T.js +0 -304
- package/dist/server-B6PCxc-T.js.map +0 -1
- package/dist/server-C96NMFBy.js.map +0 -1
- package/dist/server-Ca-3DJf2.js.map +0 -1
- package/dist/server-Dxsww9v9.js.map +0 -1
- package/dist/server-hGrElJMG.js +0 -78
- package/dist/server-hGrElJMG.js.map +0 -1
- package/dist/server.js +0 -20
- package/dist/signalr/index.js +0 -3
- package/dist/signalr-k-70VHKo.js.map +0 -1
- package/dist/src-B9rDUgSb.js +0 -0
- package/dist/tenant/index.js +0 -3
- package/dist/tenant/server.js +0 -5
- package/dist/tenant-DfLemb6n.js.map +0 -1
- package/dist/types-BaaE6wjY.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-session-storage.server-DGgQcJ9B.mjs","names":[],"sources":["../src/auth/redis-session-storage.server.ts"],"sourcesContent":["/**\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 { 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;\nconst DEFAULT_OAUTH_STATE_TTL = 10 * 60;\n\nconst OAUTH_STATE_TTL = resolvePositiveIntegerEnv('OAUTH_STATE_TTL', DEFAULT_OAUTH_STATE_TTL);\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.accessToken) {\n throw new Response('Unauthorized', { status: 401 });\n }\n\n return sessionData;\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) {\n return false;\n }\n\n if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {\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, JSON.stringify(state));\n\n logger.debug('OAuth state created', { stateId, ttl: OAUTH_STATE_TTL });\n\n return stateId;\n}\n\nexport async function getOAuthState(stateId: string): 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', { stateId });\n return null;\n }\n\n try {\n return JSON.parse(data);\n } catch (error) {\n logger.error('Failed to parse OAuth state', error instanceof Error ? error : undefined);\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', { stateId });\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,7 +1,6 @@
|
|
|
1
|
-
import { isApiClientError } from "./types-
|
|
2
|
-
import {
|
|
1
|
+
import { t as isApiClientError } from "./types-DqCXinAk.mjs";
|
|
2
|
+
import { i as isRetryableError, r as fetchWithTimeout, t as calculateBackoffDelay } from "./api-client-CB7XZaMg.mjs";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
-
|
|
5
4
|
//#region src/api-client/api-config.server.ts
|
|
6
5
|
/**
|
|
7
6
|
* Base URL Configuration
|
|
@@ -128,15 +127,24 @@ function createAPIConfigFactory(getAccessToken, getCurrentTenant, logger, factor
|
|
|
128
127
|
getAPIBaseURL
|
|
129
128
|
};
|
|
130
129
|
}
|
|
131
|
-
|
|
132
130
|
//#endregion
|
|
133
131
|
//#region src/api-client/fetch-client.server.ts
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Fetch-native API client setup for generated OpenAPI clients.
|
|
134
|
+
*
|
|
135
|
+
* This module preserves the shared request behaviors we need around fetch:
|
|
136
|
+
* - request ID enrichment
|
|
137
|
+
* - serialized token refresh on 401
|
|
138
|
+
* - retry with exponential backoff
|
|
139
|
+
* - ProblemDetails-aware error shaping
|
|
140
|
+
*
|
|
141
|
+
* It gives downstream apps a single fetch-native entry point for generated clients.
|
|
142
|
+
*/
|
|
143
|
+
const refreshPromiseMap = /* @__PURE__ */ new Map();
|
|
144
|
+
const responseRequestMeta = /* @__PURE__ */ new WeakMap();
|
|
136
145
|
const rawResponseStore = new AsyncLocalStorage();
|
|
137
146
|
function getSessionKeyFromRequest(request) {
|
|
138
|
-
|
|
139
|
-
return `session:${cookie.slice(0, 50)}`;
|
|
147
|
+
return `session:${(request.headers.get("cookie") || "").slice(0, 50)}`;
|
|
140
148
|
}
|
|
141
149
|
function getOrCreateRefreshPromise(sessionKey, refreshFn, logger) {
|
|
142
150
|
const existingPromise = refreshPromiseMap.get(sessionKey);
|
|
@@ -167,7 +175,7 @@ function cloneInit(init) {
|
|
|
167
175
|
};
|
|
168
176
|
}
|
|
169
177
|
function createTimeoutFetch(timeout) {
|
|
170
|
-
if (!timeout) return
|
|
178
|
+
if (!timeout) return;
|
|
171
179
|
return (input, init) => fetchWithTimeout(input, {
|
|
172
180
|
...init,
|
|
173
181
|
timeout
|
|
@@ -182,13 +190,12 @@ async function parseResponseBody(response) {
|
|
|
182
190
|
if (contentType.includes("application/json") || contentType.includes("+json")) try {
|
|
183
191
|
return await clone.json();
|
|
184
192
|
} catch {
|
|
185
|
-
return
|
|
193
|
+
return;
|
|
186
194
|
}
|
|
187
195
|
try {
|
|
188
|
-
|
|
189
|
-
return text || void 0;
|
|
196
|
+
return await clone.text() || void 0;
|
|
190
197
|
} catch {
|
|
191
|
-
return
|
|
198
|
+
return;
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
function createClientError(params) {
|
|
@@ -204,9 +211,8 @@ function createClientError(params) {
|
|
|
204
211
|
return error;
|
|
205
212
|
}
|
|
206
213
|
async function toAPIResponse(rawResponse, resolvedData) {
|
|
207
|
-
const data = resolvedData === void 0 ? await parseResponseBody(rawResponse) : resolvedData;
|
|
208
214
|
return {
|
|
209
|
-
data,
|
|
215
|
+
data: resolvedData === void 0 ? await parseResponseBody(rawResponse) : resolvedData,
|
|
210
216
|
status: rawResponse.status,
|
|
211
217
|
statusText: rawResponse.statusText,
|
|
212
218
|
headers: headersToRecord(rawResponse.headers),
|
|
@@ -240,10 +246,10 @@ function isRetryableFetchError(error) {
|
|
|
240
246
|
async function maybeRetryResponse(context, retryConfig, logger) {
|
|
241
247
|
const init = cloneInit(context.init);
|
|
242
248
|
const meta = ensureRequestMeta(init);
|
|
243
|
-
if (context.response.status === 401 || context.response.status === 409) return
|
|
244
|
-
if (!isRetryableError(context.response)) return
|
|
249
|
+
if (context.response.status === 401 || context.response.status === 409) return;
|
|
250
|
+
if (!isRetryableError(context.response)) return;
|
|
245
251
|
const retryCount = meta.retryCount ?? 0;
|
|
246
|
-
if (retryCount >= retryConfig.maxRetries) return
|
|
252
|
+
if (retryCount >= retryConfig.maxRetries) return;
|
|
247
253
|
meta.retryCount = retryCount + 1;
|
|
248
254
|
const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);
|
|
249
255
|
logger?.info?.("Retrying request", {
|
|
@@ -261,7 +267,7 @@ async function maybeRetryError(context, retryConfig, logger) {
|
|
|
261
267
|
const init = cloneInit(context.init);
|
|
262
268
|
const meta = ensureRequestMeta(init);
|
|
263
269
|
const retryCount = meta.retryCount ?? 0;
|
|
264
|
-
if (!isRetryableFetchError(context.error) || retryCount >= retryConfig.maxRetries) return
|
|
270
|
+
if (!isRetryableFetchError(context.error) || retryCount >= retryConfig.maxRetries) return;
|
|
265
271
|
meta.retryCount = retryCount + 1;
|
|
266
272
|
const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);
|
|
267
273
|
logger?.info?.("Retrying request after fetch failure", {
|
|
@@ -352,7 +358,6 @@ function createFetchMiddleware(request, options = {}) {
|
|
|
352
358
|
async onError(context) {
|
|
353
359
|
const retryResponse = await maybeRetryError(context, normalizedRetryConfig, logger);
|
|
354
360
|
if (retryResponse) return retryResponse;
|
|
355
|
-
return void 0;
|
|
356
361
|
}
|
|
357
362
|
}];
|
|
358
363
|
}
|
|
@@ -378,12 +383,12 @@ function instrumentRawMethods(client) {
|
|
|
378
383
|
}
|
|
379
384
|
function wrapClient(client) {
|
|
380
385
|
instrumentRawMethods(client);
|
|
381
|
-
const methodCache = new Map();
|
|
386
|
+
const methodCache = /* @__PURE__ */ new Map();
|
|
382
387
|
return new Proxy(client, { get(target, prop, receiver) {
|
|
383
388
|
const value = Reflect.get(target, prop, receiver);
|
|
384
389
|
if (typeof prop !== "string" || typeof value !== "function" || prop.endsWith("Raw")) return typeof value === "function" ? value.bind(target) : value;
|
|
385
390
|
if (methodCache.has(prop)) return methodCache.get(prop);
|
|
386
|
-
const wrapped = async (...args) => rawResponseStore.run(new Map(), async () => {
|
|
391
|
+
const wrapped = async (...args) => rawResponseStore.run(/* @__PURE__ */ new Map(), async () => {
|
|
387
392
|
try {
|
|
388
393
|
const resolvedData = await value.apply(target, args);
|
|
389
394
|
const rawResponse = rawResponseStore.getStore()?.get(`${prop}Raw`);
|
|
@@ -403,28 +408,26 @@ function wrapClient(client) {
|
|
|
403
408
|
} });
|
|
404
409
|
}
|
|
405
410
|
async function createApiClient(request, ClientClass, createAPIConfig, ConfigurationClass, options = {}) {
|
|
406
|
-
const { requireTenant, includeAuth, tenantId, customHeaders, credentials
|
|
411
|
+
const { requireTenant, includeAuth, tenantId, customHeaders, credentials, ...fetchOptions } = options;
|
|
407
412
|
const config = await createAPIConfig(request, {
|
|
408
413
|
requireTenant,
|
|
409
414
|
includeAuth,
|
|
410
415
|
tenantId,
|
|
411
416
|
customHeaders
|
|
412
417
|
});
|
|
413
|
-
const configuration = new ConfigurationClass({
|
|
414
|
-
basePath: config.basePath,
|
|
415
|
-
accessToken: config.accessToken,
|
|
416
|
-
headers: config.headers,
|
|
417
|
-
credentials: credentials ?? config.credentials,
|
|
418
|
-
middleware: createFetchMiddleware(request, fetchOptions),
|
|
419
|
-
fetchApi: createTimeoutFetch(config.baseOptions?.timeout)
|
|
420
|
-
});
|
|
421
|
-
const client = new ClientClass(configuration);
|
|
422
418
|
return {
|
|
423
|
-
client: wrapClient(
|
|
419
|
+
client: wrapClient(new ClientClass(new ConfigurationClass({
|
|
420
|
+
basePath: config.basePath,
|
|
421
|
+
accessToken: config.accessToken,
|
|
422
|
+
headers: config.headers,
|
|
423
|
+
credentials: credentials ?? config.credentials,
|
|
424
|
+
middleware: createFetchMiddleware(request, fetchOptions),
|
|
425
|
+
fetchApi: createTimeoutFetch(config.baseOptions?.timeout)
|
|
426
|
+
}))),
|
|
424
427
|
tenantId: config.tenantId
|
|
425
428
|
};
|
|
426
429
|
}
|
|
427
|
-
|
|
428
430
|
//#endregion
|
|
429
|
-
export { createAPIConfigFactory, createApiClient
|
|
430
|
-
|
|
431
|
+
export { createFetchMiddleware as n, createAPIConfigFactory as r, createApiClient as t };
|
|
432
|
+
|
|
433
|
+
//# sourceMappingURL=server-ByScGRXA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-ByScGRXA.mjs","names":[],"sources":["../src/api-client/api-config.server.ts","../src/api-client/fetch-client.server.ts"],"sourcesContent":["/**\n * API Configuration Server Module\n *\n * Centralized configuration for all API clients,\n * automatically handling authentication tokens and tenant context.\n */\n\nimport type { APIConfig, CreateAPIConfigOptions, GetAccessTokenFn, GetCurrentTenantFn, APILogger } from './types';\n\n/**\n * Base URL Configuration\n * Single unified base URL for all API operations\n */\nconst DEFAULT_API_BASE_URL = process.env.API_BASE_URL || '';\n\n/**\n * Default Headers\n * Applied to all API requests\n */\nconst DEFAULT_HEADERS: Record<string, string> = {\n Accept: 'application/json, application/problem+json',\n 'Content-Type': 'application/json',\n};\n\n/**\n * API Config Factory Options\n */\nexport interface APIConfigFactoryOptions {\n /**\n * Base URL for API requests\n */\n baseURL?: string;\n /**\n * Default headers applied to all requests\n */\n defaultHeaders?: Record<string, string>;\n /**\n * User agent string\n */\n userAgent?: string;\n /**\n * Default timeout in milliseconds\n */\n timeout?: number;\n /**\n * Header name used for bearer-style auth. Set to null when buildHeaders owns auth.\n * @default 'Authorization'\n */\n authHeaderName?: string | null;\n /**\n * Build an auth header value from the resolved access token.\n * @default token => `Bearer ${token}`\n */\n authHeaderValue?: (accessToken: string) => string;\n /**\n * Header name used for tenant context. Set to null when buildHeaders owns tenancy.\n * @default 'X-Tenant-Id'\n */\n tenantHeaderName?: string | null;\n /**\n * Final app-specific header hook. Returned headers are merged last.\n */\n buildHeaders?: (context: APIHeaderStrategyContext) => Record<string, string>;\n}\n\nexport interface APIHeaderStrategyContext {\n request: Request;\n options: CreateAPIConfigOptions;\n accessToken?: string;\n tenantId: string | null;\n includeAuth: boolean;\n requireTenant: boolean;\n}\n\n/**\n * Create API Config Factory\n *\n * Creates a factory function that generates API configurations\n * with the provided auth and tenant resolvers.\n */\nexport function createAPIConfigFactory(\n getAccessToken: GetAccessTokenFn,\n getCurrentTenant: GetCurrentTenantFn,\n logger?: APILogger,\n factoryOptions: APIConfigFactoryOptions = {}\n) {\n const {\n baseURL = DEFAULT_API_BASE_URL,\n defaultHeaders = DEFAULT_HEADERS,\n userAgent = 'Spine/1.0',\n timeout = 30000,\n authHeaderName = 'Authorization',\n authHeaderValue = (token: string) => `Bearer ${token}`,\n tenantHeaderName = 'X-Tenant-Id',\n buildHeaders,\n } = factoryOptions;\n\n /**\n * Resolve tenant ID from request or options\n */\n async function resolveTenantId(\n request: Request,\n options: CreateAPIConfigOptions\n ): Promise<string | null> {\n // Use explicit tenant ID if provided\n if (options.tenantId) {\n logger?.debug?.('Using explicit tenant ID', { tenantId: options.tenantId });\n return options.tenantId;\n }\n\n // Resolve from current tenant context\n try {\n const tenantId = await getCurrentTenant(request);\n if (tenantId) {\n logger?.debug?.('Resolved tenant from context', { tenantId });\n }\n return tenantId;\n } catch (error) {\n logger?.warn?.('Failed to resolve current tenant', error instanceof Error ? error : undefined);\n return null;\n }\n }\n\n /**\n * Creates unified API configuration for all services.\n *\n * This function automatically:\n * - Adds authentication token using the configured auth header strategy\n * - Injects tenant context using the configured tenant header strategy\n * - Configures base URL from options or environment\n * - Applies default headers\n *\n * @param request - The incoming request object\n * @param options - Configuration options\n * @returns API configuration object\n * @throws Error if required authentication or tenant context is missing\n *\n * @example\n * `\typescript\n * // Standard usage with auth and tenant\n * const config = await createAPIConfig(request);\n * const api = new IdentityApi(config);\n *\n * // Without tenant requirement\n * const config = await createAPIConfig(request, { requireTenant: false });\n *\n * // With explicit tenant override\n * const config = await createAPIConfig(request, { tenantId: 'specific-tenant-id' });\n * `\n */\n async function createAPIConfig(\n request: Request,\n options: CreateAPIConfigOptions = {}\n ): Promise<APIConfig> {\n const {\n requireTenant = true,\n includeAuth = true,\n customHeaders = {},\n } = options;\n\n // Start with default headers\n const headers: Record<string, string> = {\n ...defaultHeaders,\n 'User-Agent': userAgent,\n ...customHeaders,\n };\n\n let accessToken: string | undefined;\n\n // Add authentication if required\n if (includeAuth) {\n const token = await getAccessToken(request);\n if (!token) {\n logger?.error?.('No access token available for API request');\n throw new Error('No access token available');\n }\n\n accessToken = token;\n if (authHeaderName) {\n headers[authHeaderName] = authHeaderValue(token);\n }\n logger?.debug?.('Added authorization header to API config');\n }\n\n // Resolve tenant context\n const tenantId = await resolveTenantId(request, options);\n\n // Add tenant header if required\n if (requireTenant) {\n if (!tenantId) {\n logger?.error?.('Tenant context required but not available');\n throw new Error('Tenant context is required for this operation');\n }\n\n if (tenantHeaderName) {\n headers[tenantHeaderName] = tenantId;\n }\n logger?.debug?.('Added tenant context header', { tenantId });\n }\n\n if (buildHeaders) {\n Object.assign(headers, buildHeaders({\n request,\n options,\n accessToken,\n tenantId,\n includeAuth,\n requireTenant,\n }));\n }\n\n logger?.debug?.('API config created', {\n basePath: baseURL,\n hasAuth: includeAuth,\n hasTenant: !!tenantId,\n requireTenant,\n });\n\n return {\n basePath: baseURL,\n accessToken,\n headers,\n tenantId: tenantId || '',\n baseOptions: {\n timeout,\n },\n };\n }\n\n /**\n * Get the configured API base URL\n */\n function getAPIBaseURL(): string {\n return baseURL;\n }\n\n return {\n createAPIConfig,\n getAPIBaseURL,\n };\n}\n\nexport type APIConfigFactory = ReturnType<typeof createAPIConfigFactory>;\n","/**\n * Fetch-native API client setup for generated OpenAPI clients.\n *\n * This module preserves the shared request behaviors we need around fetch:\n * - request ID enrichment\n * - serialized token refresh on 401\n * - retry with exponential backoff\n * - ProblemDetails-aware error shaping\n *\n * It gives downstream apps a single fetch-native entry point for generated clients.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type {\n APIClientError,\n APIConfig,\n APIResponse,\n APILogger,\n CreateAPIConfigOptions,\n EnhancedAPIClient,\n FetchErrorContext,\n FetchMiddleware,\n FetchResponseContext,\n ProblemDetails,\n RetryConfig,\n TokenRefreshFn,\n TokenRefreshResult,\n} from './types';\nimport { isApiClientError } from './types';\nimport { calculateBackoffDelay, fetchWithTimeout, isRetryableError } from './retry-handler';\n\ntype RuntimeApiResponse<T> = {\n raw: Response;\n value(): Promise<T>;\n};\n\ntype GeneratedFetchClient = object;\n\ntype SpineRequestInit = RequestInit & {\n __spineMeta?: {\n requestId?: string;\n retryCount?: number;\n refreshed?: boolean;\n };\n};\n\ntype ResponseRequestMeta = {\n url: string;\n method: string;\n headers: Record<string, string>;\n};\n\nconst refreshPromiseMap = new Map<string, Promise<TokenRefreshResult>>();\nconst responseRequestMeta = new WeakMap<Response, ResponseRequestMeta>();\nconst rawResponseStore = new AsyncLocalStorage<Map<string, RuntimeApiResponse<unknown>>>();\n\nfunction getSessionKeyFromRequest(request: Request): string {\n const cookie = request.headers.get('cookie') || '';\n return `session:${cookie.slice(0, 50)}`;\n}\n\nfunction getOrCreateRefreshPromise(\n sessionKey: string,\n refreshFn: () => Promise<TokenRefreshResult>,\n logger?: APILogger\n): Promise<TokenRefreshResult> {\n const existingPromise = refreshPromiseMap.get(sessionKey);\n if (existingPromise) {\n logger?.info?.('Token refresh already in progress, waiting for existing refresh');\n return existingPromise;\n }\n\n logger?.info?.('Starting new token refresh');\n\n const refreshPromise = refreshFn().finally(() => {\n refreshPromiseMap.delete(sessionKey);\n });\n\n refreshPromiseMap.set(sessionKey, refreshPromise);\n return refreshPromise;\n}\n\nfunction ensureRequestMeta(init: SpineRequestInit): NonNullable<SpineRequestInit['__spineMeta']> {\n if (!init.__spineMeta) {\n init.__spineMeta = {};\n }\n\n return init.__spineMeta;\n}\n\nfunction headersToRecord(headersInit?: HeadersInit): Record<string, string> {\n return Object.fromEntries(new Headers(headersInit).entries());\n}\n\nfunction cloneInit(init: RequestInit): SpineRequestInit {\n const source = init as SpineRequestInit;\n\n return {\n ...source,\n headers: headersToRecord(source.headers),\n __spineMeta: source.__spineMeta ? { ...source.__spineMeta } : undefined,\n };\n}\n\nfunction createTimeoutFetch(timeout?: number): typeof fetch | undefined {\n if (!timeout) {\n return undefined;\n }\n\n return (input, init) => fetchWithTimeout(input, { ...init, timeout });\n}\n\nfunction isProblemDetails(value: unknown): value is ProblemDetails {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'title' in value &&\n 'status' in value &&\n 'detail' in value\n );\n}\n\nasync function parseResponseBody(response: Response): Promise<unknown> {\n const clone = response.clone();\n const contentType = clone.headers.get('content-type') || '';\n\n if (contentType.includes('application/json') || contentType.includes('+json')) {\n try {\n return await clone.json();\n } catch {\n return undefined;\n }\n }\n\n try {\n const text = await clone.text();\n return text || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction createClientError<T = unknown>(params: {\n message: string;\n response?: APIResponse<T>;\n config?: ResponseRequestMeta;\n problemDetails?: ProblemDetails;\n shouldLogout?: boolean;\n originalError?: unknown;\n cause?: unknown;\n}): APIClientError<T> {\n const error = new Error(params.message) as APIClientError<T>;\n\n error.name = 'APIClientError';\n error.isApiClientError = true;\n error.response = params.response;\n error.config = params.config;\n error.problemDetails = params.problemDetails;\n error.shouldLogout = params.shouldLogout;\n error.originalError = params.originalError;\n error.cause = params.cause;\n\n return error;\n}\n\nasync function toAPIResponse<T>(\n rawResponse: Response,\n resolvedData?: T\n): Promise<APIResponse<T>> {\n const data = resolvedData === undefined ? ((await parseResponseBody(rawResponse)) as T) : resolvedData;\n\n return {\n data,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: headersToRecord(rawResponse.headers),\n raw: rawResponse,\n };\n}\n\nasync function normalizeClientError(error: unknown): Promise<APIClientError> {\n if (isApiClientError(error)) {\n return error;\n }\n\n if (error && typeof error === 'object' && 'response' in error && (error as { response?: unknown }).response instanceof Response) {\n const response = (error as { response: Response }).response;\n const responseData = await parseResponseBody(response);\n const problemDetails = isProblemDetails(responseData) ? responseData : undefined;\n\n return createClientError({\n message:\n problemDetails?.detail ||\n problemDetails?.title ||\n response.statusText ||\n 'API request failed',\n response: await toAPIResponse(response, responseData),\n config: responseRequestMeta.get(response),\n problemDetails,\n cause: error,\n });\n }\n\n return createClientError({\n message: error instanceof Error ? error.message : 'API request failed',\n cause: error,\n });\n}\n\nfunction isRetryableFetchError(error: unknown): boolean {\n if (isRetryableError(error)) {\n return true;\n }\n\n if (error && typeof error === 'object' && 'cause' in error) {\n return isRetryableError((error as { cause?: unknown }).cause);\n }\n\n return false;\n}\n\nasync function maybeRetryResponse(\n context: FetchResponseContext,\n retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>,\n logger?: APILogger\n): Promise<Response | undefined> {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n\n if (context.response.status === 401 || context.response.status === 409) {\n return undefined;\n }\n\n if (!isRetryableError(context.response)) {\n return undefined;\n }\n\n const retryCount = meta.retryCount ?? 0;\n if (retryCount >= retryConfig.maxRetries) {\n return undefined;\n }\n\n meta.retryCount = retryCount + 1;\n\n const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);\n logger?.info?.('Retrying request', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n attempt: meta.retryCount,\n maxRetries: retryConfig.maxRetries,\n delay,\n status: context.response.status,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return context.fetch(context.url, init);\n}\n\nasync function maybeRetryError(\n context: FetchErrorContext,\n retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>,\n logger?: APILogger\n): Promise<Response | undefined> {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n const retryCount = meta.retryCount ?? 0;\n\n if (!isRetryableFetchError(context.error) || retryCount >= retryConfig.maxRetries) {\n return undefined;\n }\n\n meta.retryCount = retryCount + 1;\n\n const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);\n logger?.info?.('Retrying request after fetch failure', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n attempt: meta.retryCount,\n maxRetries: retryConfig.maxRetries,\n delay,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return context.fetch(context.url, init);\n}\n\nexport interface FetchSetupOptions {\n logger?: APILogger;\n attemptTokenRefresh?: TokenRefreshFn;\n retryConfig?: RetryConfig;\n addRequestId?: boolean;\n}\n\nexport function createFetchMiddleware(\n request: Request,\n options: FetchSetupOptions = {}\n): FetchMiddleware[] {\n const {\n logger,\n attemptTokenRefresh,\n retryConfig = {},\n addRequestId = true,\n } = options;\n\n const normalizedRetryConfig = {\n maxRetries: retryConfig.maxRetries ?? 3,\n baseDelay: retryConfig.baseDelay ?? 1000,\n maxDelay: retryConfig.maxDelay ?? 10000,\n };\n\n return [\n {\n async pre(context) {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n const headers = new Headers(init.headers);\n\n if (addRequestId) {\n const requestId = headers.get('X-Request-Id') || crypto.randomUUID();\n headers.set('X-Request-Id', requestId);\n meta.requestId = requestId;\n }\n\n init.headers = headersToRecord(headers);\n\n logger?.debug?.('API request initiated', {\n method: (init.method ?? 'GET').toUpperCase(),\n url: context.url,\n requestId: meta.requestId,\n });\n\n return {\n url: context.url,\n init,\n };\n },\n\n async post(context) {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n\n responseRequestMeta.set(context.response, {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n headers: headersToRecord(init.headers),\n });\n\n if (context.response.status === 401 && attemptTokenRefresh && !meta.refreshed) {\n meta.refreshed = true;\n\n logger?.info?.('Received 401 from backend, attempting token refresh', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n });\n\n const refreshResult = await getOrCreateRefreshPromise(\n getSessionKeyFromRequest(request),\n () => attemptTokenRefresh(request),\n logger\n );\n\n if (refreshResult.success && refreshResult.newAccessToken) {\n const headers = new Headers(init.headers);\n headers.set('Authorization', `Bearer ${refreshResult.newAccessToken}`);\n init.headers = headersToRecord(headers);\n\n logger?.info?.('Token refresh successful, retrying original request');\n\n return context.fetch(context.url, init);\n }\n\n if (refreshResult.shouldLogout) {\n throw createClientError({\n message: 'REFRESH_TOKEN_EXPIRED',\n response: await toAPIResponse(context.response),\n config: responseRequestMeta.get(context.response),\n shouldLogout: true,\n originalError: refreshResult,\n cause: refreshResult,\n });\n }\n }\n\n const retryResponse = await maybeRetryResponse(context, normalizedRetryConfig, logger);\n if (retryResponse) {\n return retryResponse;\n }\n\n if (context.response.status >= 400) {\n const responseData = await parseResponseBody(context.response);\n const problemDetails = isProblemDetails(responseData) ? responseData : undefined;\n\n logger?.error?.('API error response received', undefined, {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n status: context.response.status,\n code: problemDetails?.title,\n detail: problemDetails?.detail,\n traceId: problemDetails?.traceId,\n });\n }\n\n return context.response;\n },\n\n async onError(context) {\n const retryResponse = await maybeRetryError(context, normalizedRetryConfig, logger);\n if (retryResponse) {\n return retryResponse;\n }\n\n return undefined;\n },\n },\n ];\n}\n\nexport interface CreateApiClientOptions extends FetchSetupOptions, CreateAPIConfigOptions {\n credentials?: RequestCredentials;\n}\n\nfunction instrumentRawMethods<T extends GeneratedFetchClient>(client: T): void {\n if ((client as { __spineRawMethodsInstrumented?: boolean }).__spineRawMethodsInstrumented) {\n return;\n }\n\n const prototype = Object.getPrototypeOf(client) as Record<string, unknown>;\n\n for (const key of Object.getOwnPropertyNames(prototype)) {\n if (!key.endsWith('Raw')) {\n continue;\n }\n\n const original = prototype[key];\n if (typeof original !== 'function') {\n continue;\n }\n\n Object.defineProperty(client, key, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: async (...args: unknown[]) => {\n const result = await (original as (...innerArgs: unknown[]) => Promise<RuntimeApiResponse<unknown>>).apply(client, args);\n rawResponseStore.getStore()?.set(key, result);\n return result;\n },\n });\n }\n\n (client as { __spineRawMethodsInstrumented?: boolean }).__spineRawMethodsInstrumented = true;\n}\n\nfunction wrapClient<T extends GeneratedFetchClient>(client: T): EnhancedAPIClient<T> {\n instrumentRawMethods(client);\n\n const methodCache = new Map<PropertyKey, unknown>();\n\n return new Proxy(client, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop !== 'string' || typeof value !== 'function' || prop.endsWith('Raw')) {\n return typeof value === 'function' ? value.bind(target) : value;\n }\n\n if (methodCache.has(prop)) {\n return methodCache.get(prop);\n }\n\n const wrapped = async (...args: unknown[]) =>\n rawResponseStore.run(new Map(), async () => {\n try {\n const resolvedData = await (value as (...innerArgs: unknown[]) => Promise<unknown>).apply(target, args);\n const rawResponse = rawResponseStore.getStore()?.get(`${prop}Raw`);\n\n if (rawResponse) {\n return toAPIResponse(rawResponse.raw, resolvedData);\n }\n\n return {\n data: resolvedData,\n status: 200,\n statusText: 'OK',\n headers: {},\n };\n } catch (error) {\n throw await normalizeClientError(error);\n }\n });\n\n methodCache.set(prop, wrapped);\n return wrapped;\n },\n }) as EnhancedAPIClient<T>;\n}\n\nexport async function createApiClient<T extends GeneratedFetchClient>(\n request: Request,\n ClientClass: new (configuration?: unknown) => T,\n createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>,\n ConfigurationClass: new (params?: Record<string, unknown>) => unknown,\n options: CreateApiClientOptions = {}\n): Promise<{ client: EnhancedAPIClient<T>; tenantId: string }> {\n const {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n credentials,\n ...fetchOptions\n } = options;\n\n const config = await createAPIConfig(request, {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n });\n\n const configuration = new ConfigurationClass({\n basePath: config.basePath,\n accessToken: config.accessToken,\n headers: config.headers,\n credentials: credentials ?? config.credentials,\n middleware: createFetchMiddleware(request, fetchOptions),\n fetchApi: createTimeoutFetch(config.baseOptions?.timeout),\n });\n\n const client = new ClientClass(configuration);\n\n return {\n client: wrapClient(client),\n tenantId: config.tenantId,\n };\n}\n"],"mappings":"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"}
|