@eminuckan/spine 0.2.2 → 0.3.0

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/CODE_OF_CONDUCT.md +39 -0
  3. package/CONTRIBUTING.md +78 -0
  4. package/MAINTAINERS.md +43 -0
  5. package/README.md +111 -55
  6. package/ROADMAP.md +58 -0
  7. package/SECURITY.md +37 -0
  8. package/dist/access/evaluator.d.ts +3 -0
  9. package/dist/access/evaluator.d.ts.map +1 -0
  10. package/dist/access/index.d.ts +3 -0
  11. package/dist/access/index.d.ts.map +1 -0
  12. package/dist/access/index.js +4 -0
  13. package/dist/access/types.d.ts +38 -0
  14. package/dist/access/types.d.ts.map +1 -0
  15. package/dist/access-CGrsDMwu.js +61 -0
  16. package/dist/access-CGrsDMwu.js.map +1 -0
  17. package/dist/api-client/api-config.server.d.ts +27 -0
  18. package/dist/api-client/api-config.server.d.ts.map +1 -1
  19. package/dist/api-client/error-codes.d.ts +6 -31
  20. package/dist/api-client/error-codes.d.ts.map +1 -1
  21. package/dist/api-client/index.js +3 -3
  22. package/dist/api-client/server.d.ts +1 -1
  23. package/dist/api-client/server.d.ts.map +1 -1
  24. package/dist/api-client/server.js +4 -4
  25. package/dist/{api-client-DsrxK05Z.js → api-client-CgZUMukO.js} +9 -26
  26. package/dist/api-client-CgZUMukO.js.map +1 -0
  27. package/dist/auth/auth.server.d.ts +21 -16
  28. package/dist/auth/auth.server.d.ts.map +1 -1
  29. package/dist/auth/authorization-parameters.d.ts +5 -0
  30. package/dist/auth/authorization-parameters.d.ts.map +1 -0
  31. package/dist/auth/index.d.ts +1 -1
  32. package/dist/auth/index.d.ts.map +1 -1
  33. package/dist/auth/redis-session-storage.server.d.ts +18 -1
  34. package/dist/auth/redis-session-storage.server.d.ts.map +1 -1
  35. package/dist/auth/route-protection.server.d.ts +20 -4
  36. package/dist/auth/route-protection.server.d.ts.map +1 -1
  37. package/dist/auth/server.d.ts +1 -1
  38. package/dist/auth/server.d.ts.map +1 -1
  39. package/dist/auth/server.js +5 -5
  40. package/dist/auth/types.d.ts +88 -21
  41. package/dist/auth/types.d.ts.map +1 -1
  42. package/dist/auth.server-D5oiz9aF.js +1066 -0
  43. package/dist/auth.server-D5oiz9aF.js.map +1 -0
  44. package/dist/auth.server-lC94lmlx.js +5 -0
  45. package/dist/identity/identity-context.server.d.ts +2 -0
  46. package/dist/identity/identity-context.server.d.ts.map +1 -1
  47. package/dist/identity/identity-store.d.ts +19 -2
  48. package/dist/identity/identity-store.d.ts.map +1 -1
  49. package/dist/identity/index.js +2 -2
  50. package/dist/identity/server.js +3 -3
  51. package/dist/identity/types.d.ts +3 -1
  52. package/dist/identity/types.d.ts.map +1 -1
  53. package/dist/{identity-CFa1txoj.js → identity-C5un-xAs.js} +104 -60
  54. package/dist/identity-C5un-xAs.js.map +1 -0
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +12 -10
  58. package/dist/logging/index.js +1 -1
  59. package/dist/{logging-_h2mi3L9.js → logging-DviiAkLx.js} +2 -2
  60. package/dist/{logging-_h2mi3L9.js.map → logging-DviiAkLx.js.map} +1 -1
  61. package/dist/permission-service-CvGpwI0d.js +95 -0
  62. package/dist/permission-service-CvGpwI0d.js.map +1 -0
  63. package/dist/permissions/constants.d.ts +3 -0
  64. package/dist/permissions/constants.d.ts.map +1 -1
  65. package/dist/permissions/index.js +2 -1
  66. package/dist/permissions/permission-store.d.ts +1 -1
  67. package/dist/permissions/types.d.ts +1 -1
  68. package/dist/permissions/types.d.ts.map +1 -1
  69. package/dist/{permissions-D5ScGGnd.js → permissions-_n1hIG7Q.js} +8 -106
  70. package/dist/permissions-_n1hIG7Q.js.map +1 -0
  71. package/dist/query-client/index.js +1 -1
  72. package/dist/query-client/types.d.ts +9 -9
  73. package/dist/query-client/types.d.ts.map +1 -1
  74. package/dist/{query-client-Bpe08pk9.js → query-client-Bh3TyB8W.js} +2 -2
  75. package/dist/query-client-Bh3TyB8W.js.map +1 -0
  76. package/dist/react-router/index.js +12 -10
  77. package/dist/react-router/server.js +19 -17
  78. package/dist/redis-session-storage.server-v2VcpFsQ.js +500 -0
  79. package/dist/redis-session-storage.server-v2VcpFsQ.js.map +1 -0
  80. package/dist/{server-BHfKHKF_.js → server-B6PCxc-T.js} +39 -87
  81. package/dist/server-B6PCxc-T.js.map +1 -0
  82. package/dist/{server-DZmcd2Lr.js → server-C96NMFBy.js} +18 -10
  83. package/dist/server-C96NMFBy.js.map +1 -0
  84. package/dist/{server-BxvRxjCK.js → server-Ca-3DJf2.js} +44 -11
  85. package/dist/server-Ca-3DJf2.js.map +1 -0
  86. package/dist/{server-C3L8J7iE.js → server-Dxsww9v9.js} +6 -5
  87. package/dist/server-Dxsww9v9.js.map +1 -0
  88. package/dist/{server-CouYoZiw.js → server-hGrElJMG.js} +2 -2
  89. package/dist/{server-CouYoZiw.js.map → server-hGrElJMG.js.map} +1 -1
  90. package/dist/server.js +19 -17
  91. package/dist/signalr/index.js +1 -1
  92. package/dist/signalr/types.d.ts +1 -1
  93. package/dist/{signalr-SJ-1sIxw.js → signalr-k-70VHKo.js} +3 -2
  94. package/dist/signalr-k-70VHKo.js.map +1 -0
  95. package/dist/tenant/index.js +2 -2
  96. package/dist/tenant/server.js +4 -4
  97. package/dist/tenant/tenant-context.d.ts +17 -1
  98. package/dist/tenant/tenant-context.d.ts.map +1 -1
  99. package/dist/tenant/tenant-cookie.server.d.ts.map +1 -1
  100. package/dist/tenant/tenant-store.d.ts +54 -9
  101. package/dist/tenant/tenant-store.d.ts.map +1 -1
  102. package/dist/tenant/tenant.server.d.ts +20 -1
  103. package/dist/tenant/tenant.server.d.ts.map +1 -1
  104. package/dist/tenant/types.d.ts +29 -9
  105. package/dist/tenant/types.d.ts.map +1 -1
  106. package/dist/tenant-DfLemb6n.js +384 -0
  107. package/dist/tenant-DfLemb6n.js.map +1 -0
  108. package/dist/{types-BnKaWdfe.js → types-BaaE6wjY.js} +1 -1
  109. package/dist/{types-BnKaWdfe.js.map → types-BaaE6wjY.js.map} +1 -1
  110. package/docs/adapters.md +87 -0
  111. package/docs/architecture.md +193 -0
  112. package/docs/backend-adaptation.md +249 -0
  113. package/docs/codex-for-oss-application.md +31 -0
  114. package/docs/codex-maintenance.md +47 -0
  115. package/docs/installation.md +213 -0
  116. package/docs/module-reference.md +207 -0
  117. package/docs/releasing.md +92 -0
  118. package/package.json +27 -4
  119. package/dist/api-client-DsrxK05Z.js.map +0 -1
  120. package/dist/auth.server-BPiCtlJE.js +0 -5
  121. package/dist/auth.server-Cx2h2qu3.js +0 -724
  122. package/dist/auth.server-Cx2h2qu3.js.map +0 -1
  123. package/dist/identity-CFa1txoj.js.map +0 -1
  124. package/dist/permissions-D5ScGGnd.js.map +0 -1
  125. package/dist/query-client-Bpe08pk9.js.map +0 -1
  126. package/dist/redis-session-storage.server-BR_0y-QO.js +0 -250
  127. package/dist/redis-session-storage.server-BR_0y-QO.js.map +0 -1
  128. package/dist/server-BHfKHKF_.js.map +0 -1
  129. package/dist/server-BxvRxjCK.js.map +0 -1
  130. package/dist/server-C3L8J7iE.js.map +0 -1
  131. package/dist/server-DZmcd2Lr.js.map +0 -1
  132. package/dist/signalr-SJ-1sIxw.js.map +0 -1
  133. package/dist/tenant-C6KPaSCX.js +0 -256
  134. package/dist/tenant-C6KPaSCX.js.map +0 -1
  135. /package/dist/{src-CJ2RLi94.js → src-B9rDUgSb.js} +0 -0
@@ -1,18 +1,20 @@
1
- import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-_h2mi3L9.js";
2
- import { PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "../permissions-D5ScGGnd.js";
3
- import { TenantProvider, configureTenantEndpoints, initializeTenantStore, useCurrentOrganization, useCurrentTenant, useOrganization, useTenant, useTenantStore, useTenantSwitcher } from "../tenant-C6KPaSCX.js";
4
- import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, resetIdentityStoreConfig, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity-CFa1txoj.js";
5
- import { isApiClientError } from "../types-BnKaWdfe.js";
6
- import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-DsrxK05Z.js";
7
- import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "../signalr-SJ-1sIxw.js";
8
- import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "../query-client-Bpe08pk9.js";
9
- import "../src-CJ2RLi94.js";
10
- import { AUTH_ERROR_COOKIE_PREFIX, cleanupExpiredOAuthStates, closeRedisConnection, createAuthSession, createOAuthState, deleteOAuthState, destroyAuthSession, getAuthSession, getOAuthState, isSessionValid, requireAuthSession, updateAuthSession } from "../redis-session-storage.server-BR_0y-QO.js";
11
- import { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, extractUserInfo, getAccessToken, getAuthError, getUser, handleCallback, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping } from "../auth.server-Cx2h2qu3.js";
12
- import { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, protectRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute } from "../server-BHfKHKF_.js";
13
- import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, setActiveTenant, setCurrentTenant } from "../server-BxvRxjCK.js";
14
- import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../server-C3L8J7iE.js";
15
- import { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission } from "../server-CouYoZiw.js";
16
- import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "../server-DZmcd2Lr.js";
1
+ import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-DviiAkLx.js";
2
+ import { PermissionChecker } from "../permission-service-CvGpwI0d.js";
3
+ import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "../permissions-_n1hIG7Q.js";
4
+ import { evaluateAccessRequirement } from "../access-CGrsDMwu.js";
5
+ import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "../tenant-DfLemb6n.js";
6
+ import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity-C5un-xAs.js";
7
+ import { isApiClientError } from "../types-BaaE6wjY.js";
8
+ import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-CgZUMukO.js";
9
+ import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "../signalr-k-70VHKo.js";
10
+ import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "../query-client-Bh3TyB8W.js";
11
+ import "../src-B9rDUgSb.js";
12
+ import { AUTH_ERROR_COOKIE_PREFIX, cleanupExpiredOAuthStates, closeRedisConnection, createAuthSession, createOAuthState, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, getAuthSession, getAuthSessionById, getCurrentAuthSessionId, getOAuthState, isSessionValid, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, requireAuthSession, updateAuthSession } from "../redis-session-storage.server-v2VcpFsQ.js";
13
+ import { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, extractUserInfo, getAccessToken, getAuthError, getUser, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping } from "../auth.server-D5oiz9aF.js";
14
+ import { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute } from "../server-B6PCxc-T.js";
15
+ import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "../server-Ca-3DJf2.js";
16
+ import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../server-Dxsww9v9.js";
17
+ import { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission } from "../server-hGrElJMG.js";
18
+ import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "../server-C96NMFBy.js";
17
19
 
18
- export { AUTH_ERROR_COOKIE_PREFIX, ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, attemptTokenRefresh, authRoute, cachePresets, calculateBackoffDelay, checkPermission, cleanupExpiredOAuthStates, cleanupSignalR, clearActiveTenant, clearAuthErrorHeaders, clearAuthServerCache, clearCurrentTenant, clearIdentityContextCache, closeRedisConnection, configureAuthClaimMapping, configureIdentityAPIFetcher, configureIdentityContextFetcher, configureIdentityEndpoints, configureIdentityStore, configurePermissionFetcher, configurePermissionRouteProtection, configureRouteProtection, configureTenantCookie, configureTenantEndpoints, contextToUserInfo, createAPIConfigFactory, createApiClient, createAuthSession, createFetchMiddleware, createIdentitySignalRClient, createLogger, createOAuthState, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, deleteOAuthState, destroyAuthSession, extractUserInfo, fetchIdentityContext, fetchUserPermissions, fetchWithTimeout, getAccessToken, getActiveTenant, getAuthError, getAuthSession, getAvailableTenants, getCurrentTenant, getIdentityContext, getIdentitySignalRClient, getOAuthState, getPermissionChecker, getPermissions, getTenantFromIdentityContext, getUser, handleApiError, handleCallback, hasAuthError, hasContextVersionChanged, initializePermissions, initializeSignalR, initializeTenant, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, isSessionValid, isTokenExpiredResponse, logger, login, logout, onboardingRoute, protectRoute, publicRoute, refreshTokens, requireAuth, requireAuthSession, requirePermission, resetAuthClaimMapping, resetIdentityStoreConfig, resetPermissionRouteProtection, resetRouteProtectionConfig, resetTenantCookieConfig, setActiveTenant, setCurrentTenant, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute, updateAuthSession, updateContextVersion, useAllPermissions, useCurrentOrganization, useCurrentTenant, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withPermission, withRetry };
20
+ export { AUTH_ERROR_COOKIE_PREFIX, ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, attemptTokenRefresh, authRoute, cachePresets, calculateBackoffDelay, checkPermission, cleanupExpiredOAuthStates, cleanupSignalR, clearActiveTenant, clearAuthErrorHeaders, clearAuthServerCache, clearCurrentTenant, clearIdentityContextCache, closeRedisConnection, configureAuthClaimMapping, configureIdentityAPIFetcher, configureIdentityContextFetcher, configureIdentityEndpoints, configureIdentityStore, configurePermissionFetcher, configurePermissionRouteProtection, configureRouteProtection, configureTenantClient, configureTenantCookie, configureTenantEndpoints, configureTenantResolution, contextToUserInfo, createAPIConfigFactory, createApiClient, createAuthSession, createFetchMiddleware, createIdentitySignalRClient, createLogger, createOAuthState, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, evaluateAccessRequirement, extractUserInfo, fetchIdentityContext, fetchUserPermissions, fetchWithTimeout, getAccessToken, getActiveTenant, getAuthError, getAuthSession, getAuthSessionById, getAvailableTenants, getCurrentAuthSessionId, getCurrentTenant, getIdentityContext, getIdentitySignalRClient, getOAuthState, getPermissionChecker, getPermissions, getTenantCookieName, getTenantFromIdentityContext, getUser, handleApiError, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, hasContextVersionChanged, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenant, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, isSessionValid, isTokenExpiredResponse, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, logger, login, logout, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, refreshTokens, requireAuth, requireAuthSession, requirePermission, resetAuthClaimMapping, resetIdentityStoreConfig, resetPermissionRouteProtection, resetRouteProtectionConfig, resetTenantClientConfig, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute, updateAuthSession, updateContextVersion, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withPermission, withRetry };
@@ -0,0 +1,500 @@
1
+ import { logger } from "./logging-DviiAkLx.js";
2
+ import Redis from "ioredis";
3
+ import { createCipheriv, createDecipheriv, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
4
+
5
+ //#region src/auth/redis-session-storage.server.ts
6
+ let redis = null;
7
+ function getRedis() {
8
+ if (!redis) {
9
+ redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379");
10
+ redis.on("error", (err) => {
11
+ logger.error("Redis Client Error", err);
12
+ });
13
+ redis.on("connect", () => {
14
+ logger.info("Redis connected successfully");
15
+ });
16
+ }
17
+ return redis;
18
+ }
19
+ const KEY_PREFIX = process.env.REDIS_KEY_PREFIX || "";
20
+ const REDIS_KEYS = {
21
+ oauthState: (stateId) => `${KEY_PREFIX}oauth:state:${stateId}`,
22
+ session: (sessionId) => `${KEY_PREFIX}session:${sessionId}`,
23
+ sessionByUser: (userId) => `${KEY_PREFIX}session:index:user:${userId}`,
24
+ sessionBySid: (sid) => `${KEY_PREFIX}session:index:sid:${sid}`,
25
+ sessionBySessionState: (sessionState) => `${KEY_PREFIX}session:index:session_state:${sessionState}`
26
+ };
27
+ const DEFAULT_SESSION_SECRET = "default-secret-change-in-production";
28
+ const DEFAULT_SESSION_TTL = 60 * 60 * 24 * 7;
29
+ const DEFAULT_OAUTH_STATE_TTL = 10 * 60;
30
+ const OAUTH_STATE_TTL = resolvePositiveIntegerEnv("OAUTH_STATE_TTL", DEFAULT_OAUTH_STATE_TTL);
31
+ const SESSION_TTL = resolvePositiveIntegerEnv("SESSION_DEFAULT_TTL", resolvePositiveIntegerEnv("SESSION_TTL", DEFAULT_SESSION_TTL));
32
+ const SESSION_COOKIE_NAME = process.env.SESSION_COOKIE_NAME || "__session_id";
33
+ const SESSION_SECRET = resolveSessionSecret();
34
+ const SESSION_COOKIE_SECURE = resolveCookieSecure();
35
+ const SESSION_COOKIE_PATH = "/";
36
+ const SESSION_COOKIE_SAME_SITE = resolveCookieSameSite();
37
+ const SESSION_COOKIE_DOMAIN = process.env.SESSION_COOKIE_DOMAIN?.trim();
38
+ const SESSION_ENCRYPTION_ENABLED = resolveSessionEncryptionEnabled();
39
+ const SESSION_ENCRYPTION_KEY = resolveSessionEncryptionKey(SESSION_ENCRYPTION_ENABLED);
40
+ const AUTH_ERROR_COOKIE_PREFIX = SESSION_COOKIE_NAME.replace("_session", "").replace("__", "");
41
+ function resolveBooleanEnv(name, defaultValue) {
42
+ const value = process.env[name]?.trim().toLowerCase();
43
+ if (!value) return defaultValue;
44
+ if ([
45
+ "1",
46
+ "true",
47
+ "yes",
48
+ "on"
49
+ ].includes(value)) return true;
50
+ if ([
51
+ "0",
52
+ "false",
53
+ "no",
54
+ "off"
55
+ ].includes(value)) return false;
56
+ throw new Error(`${name} must be a boolean value.`);
57
+ }
58
+ function resolvePositiveIntegerEnv(name, defaultValue) {
59
+ const value = process.env[name]?.trim();
60
+ if (!value) return defaultValue;
61
+ const parsed = Number(value);
62
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${name} must be a positive integer.`);
63
+ return parsed;
64
+ }
65
+ function resolveSessionSecret() {
66
+ const secret = process.env.SESSION_SECRET?.trim();
67
+ if (!secret || secret === DEFAULT_SESSION_SECRET) {
68
+ if (process.env.NODE_ENV === "production") throw new Error("SESSION_SECRET must be set to a unique random value in production.");
69
+ return DEFAULT_SESSION_SECRET;
70
+ }
71
+ if (secret.length < 32) throw new Error("SESSION_SECRET must be at least 32 characters long.");
72
+ return secret;
73
+ }
74
+ function resolveCookieSecure() {
75
+ return resolveBooleanEnv("SESSION_COOKIE_SECURE", process.env.NODE_ENV === "production");
76
+ }
77
+ function resolveCookieSameSite() {
78
+ const value = process.env.SESSION_COOKIE_SAME_SITE?.trim().toLowerCase();
79
+ const sameSite = value === "strict" ? "Strict" : value === "none" ? "None" : "Lax";
80
+ if (sameSite === "None" && !SESSION_COOKIE_SECURE) throw new Error("SESSION_COOKIE_SAME_SITE=None requires SESSION_COOKIE_SECURE=true.");
81
+ return sameSite;
82
+ }
83
+ function resolveSessionEncryptionEnabled() {
84
+ const enabled = resolveBooleanEnv("SESSION_ENCRYPTION", process.env.NODE_ENV === "production");
85
+ if (process.env.NODE_ENV === "production" && !enabled) throw new Error("SESSION_ENCRYPTION cannot be disabled in production.");
86
+ return enabled;
87
+ }
88
+ function decodeBase64Url(value) {
89
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
90
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
91
+ return Buffer.from(padded, "base64");
92
+ }
93
+ function resolveSessionEncryptionKey(enabled) {
94
+ if (!enabled) return null;
95
+ const value = process.env.SESSION_ENCRYPTION_KEY?.trim();
96
+ if (!value) throw new Error("SESSION_ENCRYPTION_KEY is required when session encryption is enabled.");
97
+ const key = /^[0-9a-f]{64}$/i.test(value) ? Buffer.from(value, "hex") : decodeBase64Url(value);
98
+ if (key.length !== 32) throw new Error("SESSION_ENCRYPTION_KEY must decode to exactly 32 bytes. Generate one with: openssl rand -base64 32");
99
+ return key;
100
+ }
101
+ function parseCookieHeader(cookieHeader) {
102
+ if (!cookieHeader) return {};
103
+ return cookieHeader.split(";").map((part) => part.trim()).filter(Boolean).reduce((cookies, part) => {
104
+ const separatorIndex = part.indexOf("=");
105
+ if (separatorIndex <= 0) return cookies;
106
+ const name = part.slice(0, separatorIndex).trim();
107
+ const value = part.slice(separatorIndex + 1).trim();
108
+ if (!name) return cookies;
109
+ cookies[name] = value;
110
+ return cookies;
111
+ }, {});
112
+ }
113
+ function createCookieSignature(value, secret) {
114
+ return createHmac("sha256", secret).update(value).digest("base64url");
115
+ }
116
+ function safelyCompareValues(left, right) {
117
+ const leftBuffer = Buffer.from(left);
118
+ const rightBuffer = Buffer.from(right);
119
+ if (leftBuffer.length !== rightBuffer.length) return false;
120
+ return timingSafeEqual(leftBuffer, rightBuffer);
121
+ }
122
+ function signSessionId(sessionId) {
123
+ const signature = createCookieSignature(sessionId, SESSION_SECRET);
124
+ return `${sessionId}.${signature}`;
125
+ }
126
+ function unsignSessionId(cookieValue) {
127
+ if (!cookieValue) return null;
128
+ let decodedValue;
129
+ try {
130
+ decodedValue = decodeURIComponent(cookieValue);
131
+ } catch {
132
+ return null;
133
+ }
134
+ const separatorIndex = decodedValue.lastIndexOf(".");
135
+ if (separatorIndex <= 0 || separatorIndex === decodedValue.length - 1) return null;
136
+ const sessionId = decodedValue.slice(0, separatorIndex);
137
+ const signature = decodedValue.slice(separatorIndex + 1);
138
+ const expectedSignature = createCookieSignature(sessionId, SESSION_SECRET);
139
+ return safelyCompareValues(signature, expectedSignature) ? sessionId : null;
140
+ }
141
+ function serializeSessionCookieValue(sessionId, options) {
142
+ const signedValue = encodeURIComponent(signSessionId(sessionId));
143
+ const segments = [
144
+ `${SESSION_COOKIE_NAME}=${signedValue}`,
145
+ `Max-Age=${options.maxAge}`,
146
+ `Path=${SESSION_COOKIE_PATH}`,
147
+ `SameSite=${SESSION_COOKIE_SAME_SITE}`,
148
+ "HttpOnly"
149
+ ];
150
+ if (SESSION_COOKIE_DOMAIN) segments.push(`Domain=${SESSION_COOKIE_DOMAIN}`);
151
+ if (SESSION_COOKIE_SECURE) segments.push("Secure");
152
+ return segments.join("; ");
153
+ }
154
+ function serializeExpiredSessionCookie() {
155
+ const segments = [
156
+ `${SESSION_COOKIE_NAME}=`,
157
+ "Max-Age=0",
158
+ `Path=${SESSION_COOKIE_PATH}`,
159
+ `SameSite=${SESSION_COOKIE_SAME_SITE}`,
160
+ "HttpOnly"
161
+ ];
162
+ if (SESSION_COOKIE_DOMAIN) segments.push(`Domain=${SESSION_COOKIE_DOMAIN}`);
163
+ if (SESSION_COOKIE_SECURE) segments.push("Secure");
164
+ return segments.join("; ");
165
+ }
166
+ async function getSessionIdFromRequest(request) {
167
+ const cookies = parseCookieHeader(request.headers.get("Cookie"));
168
+ return unsignSessionId(cookies[SESSION_COOKIE_NAME]);
169
+ }
170
+ function getSessionIndexKeys(data) {
171
+ const keys = new Set();
172
+ if (data.userId) keys.add(REDIS_KEYS.sessionByUser(data.userId));
173
+ if (data.sid) keys.add(REDIS_KEYS.sessionBySid(data.sid));
174
+ if (data.sessionState) keys.add(REDIS_KEYS.sessionBySessionState(data.sessionState));
175
+ return Array.from(keys);
176
+ }
177
+ async function registerSessionIndexes(sessionId, data) {
178
+ const indexKeys = getSessionIndexKeys(data);
179
+ if (indexKeys.length === 0) return;
180
+ const redisClient = getRedis();
181
+ const pipeline = redisClient.pipeline();
182
+ for (const indexKey of indexKeys) {
183
+ pipeline.sadd(indexKey, sessionId);
184
+ pipeline.expire(indexKey, SESSION_TTL);
185
+ }
186
+ await pipeline.exec();
187
+ }
188
+ async function unregisterSessionIndexes(sessionId, data) {
189
+ const indexKeys = getSessionIndexKeys(data);
190
+ if (indexKeys.length === 0) return;
191
+ const redisClient = getRedis();
192
+ const pipeline = redisClient.pipeline();
193
+ for (const indexKey of indexKeys) pipeline.srem(indexKey, sessionId);
194
+ await pipeline.exec();
195
+ }
196
+ function parseSessionData(sessionData) {
197
+ if (!sessionData) return null;
198
+ try {
199
+ return JSON.parse(decodeSessionData(sessionData));
200
+ } catch (error) {
201
+ logger.error("Failed to parse session data", error instanceof Error ? error : void 0);
202
+ return null;
203
+ }
204
+ }
205
+ function encodeSessionData(data) {
206
+ const plaintext = JSON.stringify(data);
207
+ if (!SESSION_ENCRYPTION_KEY) return plaintext;
208
+ const iv = randomBytes(12);
209
+ const cipher = createCipheriv("aes-256-gcm", SESSION_ENCRYPTION_KEY, iv);
210
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
211
+ const tag = cipher.getAuthTag();
212
+ return [
213
+ "enc:v1",
214
+ iv.toString("base64url"),
215
+ tag.toString("base64url"),
216
+ ciphertext.toString("base64url")
217
+ ].join(":");
218
+ }
219
+ function decodeSessionData(value) {
220
+ if (!value.startsWith("enc:v1:")) {
221
+ if (SESSION_ENCRYPTION_ENABLED) logger.warn("Reading legacy plaintext auth session payload while session encryption is enabled.");
222
+ return value;
223
+ }
224
+ if (!SESSION_ENCRYPTION_KEY) throw new Error("Cannot decrypt auth session payload because SESSION_ENCRYPTION_KEY is not configured.");
225
+ const [, version, ivValue, tagValue, ciphertextValue] = value.split(":");
226
+ if (version !== "v1" || !ivValue || !tagValue || !ciphertextValue) throw new Error("Invalid encrypted auth session payload format.");
227
+ const decipher = createDecipheriv("aes-256-gcm", SESSION_ENCRYPTION_KEY, Buffer.from(ivValue, "base64url"));
228
+ decipher.setAuthTag(Buffer.from(tagValue, "base64url"));
229
+ return Buffer.concat([decipher.update(Buffer.from(ciphertextValue, "base64url")), decipher.final()]).toString("utf8");
230
+ }
231
+ async function readSessionData(sessionId) {
232
+ try {
233
+ const sessionData = await getRedis().get(REDIS_KEYS.session(sessionId));
234
+ if (!sessionData) {
235
+ logger.debug("Session not found in Redis", { sessionId });
236
+ return null;
237
+ }
238
+ await getRedis().expire(REDIS_KEYS.session(sessionId), SESSION_TTL);
239
+ return parseSessionData(sessionData);
240
+ } catch (error) {
241
+ logger.error("Failed to read session from Redis", error instanceof Error ? error : void 0);
242
+ return null;
243
+ }
244
+ }
245
+ async function writeSessionData(sessionId, data) {
246
+ try {
247
+ const existingSessionData = parseSessionData(await getRedis().get(REDIS_KEYS.session(sessionId)));
248
+ if (existingSessionData) await unregisterSessionIndexes(sessionId, existingSessionData);
249
+ await getRedis().setex(REDIS_KEYS.session(sessionId), SESSION_TTL, encodeSessionData(data));
250
+ await registerSessionIndexes(sessionId, data);
251
+ logger.debug("Updated Redis session", { sessionId });
252
+ } catch (error) {
253
+ logger.error("Failed to update session in Redis", error instanceof Error ? error : void 0);
254
+ throw error;
255
+ }
256
+ }
257
+ async function deleteSessionData(sessionId) {
258
+ try {
259
+ const existingSessionData = parseSessionData(await getRedis().get(REDIS_KEYS.session(sessionId)));
260
+ if (existingSessionData) await unregisterSessionIndexes(sessionId, existingSessionData);
261
+ await getRedis().del(REDIS_KEYS.session(sessionId));
262
+ logger.debug("Deleted Redis session", { sessionId });
263
+ } catch (error) {
264
+ logger.error("Failed to delete session from Redis", error instanceof Error ? error : void 0);
265
+ }
266
+ }
267
+ function mergeSessionData(existingData, updates) {
268
+ const nextData = { ...existingData };
269
+ Object.entries(updates).forEach(([key, value]) => {
270
+ if (value === void 0) {
271
+ delete nextData[key];
272
+ return;
273
+ }
274
+ nextData[key] = value;
275
+ });
276
+ return nextData;
277
+ }
278
+ async function getSessionIdsFromIndex(indexKey) {
279
+ const sessionIds = await getRedis().smembers(indexKey);
280
+ const activeSessionIds = [];
281
+ const staleSessionIds = [];
282
+ for (const sessionId of sessionIds) {
283
+ const exists = await getRedis().exists(REDIS_KEYS.session(sessionId));
284
+ if (exists) activeSessionIds.push(sessionId);
285
+ else staleSessionIds.push(sessionId);
286
+ }
287
+ if (staleSessionIds.length > 0) await getRedis().srem(indexKey, ...staleSessionIds);
288
+ return activeSessionIds;
289
+ }
290
+ function toAuthSessionSummary(sessionId, session, currentSessionId) {
291
+ return {
292
+ sessionId,
293
+ userId: session.userId,
294
+ email: session.user?.email,
295
+ name: session.user?.name,
296
+ issuer: session.issuer,
297
+ sid: session.sid,
298
+ sessionState: session.sessionState,
299
+ clientId: session.clientId,
300
+ ipAddress: session.ipAddress,
301
+ userAgent: session.userAgent,
302
+ createdAt: session.createdAt,
303
+ lastActivity: session.lastActivity,
304
+ expiresAt: session.expiresAt,
305
+ isCurrent: currentSessionId ? sessionId === currentSessionId : void 0
306
+ };
307
+ }
308
+ async function listSessionDataByIds(sessionIds) {
309
+ const sessions = [];
310
+ for (const sessionId of sessionIds) {
311
+ const data = await readSessionData(sessionId);
312
+ if (data) sessions.push({
313
+ sessionId,
314
+ data
315
+ });
316
+ }
317
+ return sessions;
318
+ }
319
+ function getRequestIpAddress(request) {
320
+ const forwardedFor = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
321
+ return forwardedFor || request.headers.get("cf-connecting-ip")?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
322
+ }
323
+ async function createAuthSession(request, sessionData) {
324
+ const currentSessionId = await getSessionIdFromRequest(request);
325
+ const nextSessionId = crypto.randomUUID();
326
+ const nextSessionData = mergeSessionData({}, {
327
+ ...sessionData,
328
+ sessionId: nextSessionId,
329
+ ipAddress: sessionData.ipAddress ?? getRequestIpAddress(request),
330
+ userAgent: sessionData.userAgent ?? request.headers.get("user-agent") ?? void 0
331
+ });
332
+ logger.debug("Creating auth session", { dataKeys: Object.keys(sessionData) });
333
+ if (currentSessionId) await deleteSessionData(currentSessionId);
334
+ await writeSessionData(nextSessionId, nextSessionData);
335
+ return new Headers({ "Set-Cookie": serializeSessionCookieValue(nextSessionId, { maxAge: SESSION_TTL }) });
336
+ }
337
+ async function getAuthSession(request) {
338
+ const sessionId = await getSessionIdFromRequest(request);
339
+ if (!sessionId) return {};
340
+ const session = await readSessionData(sessionId);
341
+ if (!session) return {};
342
+ return {
343
+ sessionId,
344
+ userId: session.userId,
345
+ accessToken: session.accessToken,
346
+ refreshToken: session.refreshToken,
347
+ idToken: session.idToken,
348
+ expiresAt: session.expiresAt,
349
+ issuer: session.issuer,
350
+ sid: session.sid,
351
+ sessionState: session.sessionState,
352
+ clientId: session.clientId,
353
+ ipAddress: session.ipAddress,
354
+ userAgent: session.userAgent,
355
+ user: session.user,
356
+ createdAt: session.createdAt,
357
+ lastActivity: session.lastActivity
358
+ };
359
+ }
360
+ async function updateAuthSession(request, updates) {
361
+ const currentSessionId = await getSessionIdFromRequest(request);
362
+ const currentSessionData = currentSessionId ? await readSessionData(currentSessionId) ?? {} : {};
363
+ const nextSessionId = currentSessionId ?? crypto.randomUUID();
364
+ const nextSessionData = mergeSessionData(currentSessionData, {
365
+ ...updates,
366
+ sessionId: nextSessionId
367
+ });
368
+ await writeSessionData(nextSessionId, nextSessionData);
369
+ return new Headers({ "Set-Cookie": serializeSessionCookieValue(nextSessionId, { maxAge: SESSION_TTL }) });
370
+ }
371
+ async function destroyAuthSession(request) {
372
+ const sessionId = await getSessionIdFromRequest(request);
373
+ if (sessionId) await deleteSessionData(sessionId);
374
+ return new Headers({ "Set-Cookie": serializeExpiredSessionCookie() });
375
+ }
376
+ async function getCurrentAuthSessionId(request) {
377
+ return await getSessionIdFromRequest(request);
378
+ }
379
+ async function getAuthSessionById(sessionId) {
380
+ const session = await readSessionData(sessionId);
381
+ return session ? {
382
+ ...session,
383
+ sessionId
384
+ } : null;
385
+ }
386
+ async function listAuthSessionDataForUser(userId) {
387
+ const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionByUser(userId));
388
+ return await listSessionDataByIds(sessionIds);
389
+ }
390
+ async function listAuthSessionsForUser(userId, currentSessionId) {
391
+ const sessions = await listAuthSessionDataForUser(userId);
392
+ return sessions.map(({ sessionId, data }) => toAuthSessionSummary(sessionId, data, currentSessionId)).sort((left, right) => (right.lastActivity ?? right.createdAt ?? 0) - (left.lastActivity ?? left.createdAt ?? 0));
393
+ }
394
+ async function listCurrentUserAuthSessions(request) {
395
+ const currentSession = await getAuthSession(request);
396
+ if (!currentSession.userId) return [];
397
+ return await listAuthSessionsForUser(currentSession.userId, currentSession.sessionId);
398
+ }
399
+ async function destroyAuthSessionById(sessionId) {
400
+ const session = await readSessionData(sessionId);
401
+ if (!session) return false;
402
+ await deleteSessionData(sessionId);
403
+ return true;
404
+ }
405
+ async function destroyAuthSessionsForUser(userId) {
406
+ const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionByUser(userId));
407
+ let destroyedSessions = 0;
408
+ for (const sessionId of sessionIds) if (await destroyAuthSessionById(sessionId)) destroyedSessions += 1;
409
+ return destroyedSessions;
410
+ }
411
+ async function destroyAuthSessionsBySid(sid) {
412
+ const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionBySid(sid));
413
+ let destroyedSessions = 0;
414
+ for (const sessionId of sessionIds) if (await destroyAuthSessionById(sessionId)) destroyedSessions += 1;
415
+ return destroyedSessions;
416
+ }
417
+ async function destroyAuthSessionsBySessionState(sessionState) {
418
+ const sessionIds = await getSessionIdsFromIndex(REDIS_KEYS.sessionBySessionState(sessionState));
419
+ let destroyedSessions = 0;
420
+ for (const sessionId of sessionIds) if (await destroyAuthSessionById(sessionId)) destroyedSessions += 1;
421
+ return destroyedSessions;
422
+ }
423
+ async function destroyAuthSessionsByIdentitySession(params) {
424
+ if (params.sid) return await destroyAuthSessionsBySid(params.sid);
425
+ if (params.sessionState) return await destroyAuthSessionsBySessionState(params.sessionState);
426
+ if (params.userId) return await destroyAuthSessionsForUser(params.userId);
427
+ return 0;
428
+ }
429
+ async function requireAuthSession(request) {
430
+ const sessionData = await getAuthSession(request);
431
+ if (!sessionData.userId || !sessionData.accessToken) throw new Response("Unauthorized", { status: 401 });
432
+ return sessionData;
433
+ }
434
+ async function isSessionValid(request) {
435
+ try {
436
+ const sessionData = await getAuthSession(request);
437
+ if (!sessionData.user || !sessionData.accessToken) return false;
438
+ if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) return false;
439
+ return true;
440
+ } catch {
441
+ return false;
442
+ }
443
+ }
444
+ async function createOAuthState(state) {
445
+ const stateId = crypto.randomUUID();
446
+ const key = REDIS_KEYS.oauthState(stateId);
447
+ await getRedis().setex(key, OAUTH_STATE_TTL, JSON.stringify(state));
448
+ logger.debug("OAuth state created", {
449
+ stateId,
450
+ ttl: OAUTH_STATE_TTL
451
+ });
452
+ return stateId;
453
+ }
454
+ async function getOAuthState(stateId) {
455
+ const key = REDIS_KEYS.oauthState(stateId);
456
+ const data = await getRedis().get(key);
457
+ if (!data) {
458
+ logger.warn("OAuth state not found or expired", { stateId });
459
+ return null;
460
+ }
461
+ try {
462
+ return JSON.parse(data);
463
+ } catch (error) {
464
+ logger.error("Failed to parse OAuth state", error instanceof Error ? error : void 0);
465
+ return null;
466
+ }
467
+ }
468
+ async function deleteOAuthState(stateId) {
469
+ const key = REDIS_KEYS.oauthState(stateId);
470
+ await getRedis().del(key);
471
+ logger.debug("OAuth state deleted", { stateId });
472
+ }
473
+ async function cleanupExpiredOAuthStates() {
474
+ const pattern = REDIS_KEYS.oauthState("*");
475
+ const keys = await getRedis().keys(pattern);
476
+ let cleaned = 0;
477
+ for (const key of keys) {
478
+ const ttl = await getRedis().ttl(key);
479
+ if (ttl === -1) {
480
+ await getRedis().del(key);
481
+ cleaned++;
482
+ }
483
+ }
484
+ logger.info("OAuth state cleanup completed", {
485
+ cleaned,
486
+ total: keys.length
487
+ });
488
+ return cleaned;
489
+ }
490
+ async function closeRedisConnection() {
491
+ if (redis) {
492
+ await redis.quit();
493
+ redis = null;
494
+ logger.info("Redis connection closed");
495
+ }
496
+ }
497
+
498
+ //#endregion
499
+ export { AUTH_ERROR_COOKIE_PREFIX, cleanupExpiredOAuthStates, closeRedisConnection, createAuthSession, createOAuthState, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, getAuthSession, getAuthSessionById, getCurrentAuthSessionId, getOAuthState, isSessionValid, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, requireAuthSession, updateAuthSession };
500
+ //# sourceMappingURL=redis-session-storage.server-v2VcpFsQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-session-storage.server-v2VcpFsQ.js","names":["redis: Redis | null","stateId: string","sessionId: string","userId: string","sid: string","sessionState: string","name: string","defaultValue: boolean","defaultValue: number","value: string","enabled: boolean","cookieHeader: string | null","secret: string","left: string","right: string","cookieValue: string | undefined","decodedValue: string","options: SessionCookieOptions","request: Request","data: SessionData","sessionData: string | null","existingData: SessionData","updates: Partial<SessionData>","indexKey: string","activeSessionIds: string[]","staleSessionIds: string[]","session: SessionData","currentSessionId?: string | null","sessionIds: string[]","sessions: Array<{ sessionId: string; data: SessionData }>","sessionData: Partial<SessionData>","params: {\n sid?: string;\n sessionState?: string;\n userId?: string;\n}","state: OAuthState"],"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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"}