@eminuckan/spine 0.2.4 → 0.3.1

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 (112) hide show
  1. package/CHANGELOG.md +19 -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 +170 -135
  6. package/ROADMAP.md +58 -0
  7. package/SECURITY.md +37 -0
  8. package/dist/access/evaluator.d.ts.map +1 -1
  9. package/dist/access/index.js +1 -1
  10. package/dist/access/types.d.ts +10 -2
  11. package/dist/access/types.d.ts.map +1 -1
  12. package/dist/{access-DlAqsVGQ.js → access-CGrsDMwu.js} +9 -7
  13. package/dist/access-CGrsDMwu.js.map +1 -0
  14. package/dist/api-client/api-config.server.d.ts +27 -0
  15. package/dist/api-client/api-config.server.d.ts.map +1 -1
  16. package/dist/api-client/error-codes.d.ts +6 -31
  17. package/dist/api-client/error-codes.d.ts.map +1 -1
  18. package/dist/api-client/index.js +2 -2
  19. package/dist/api-client/server.d.ts +1 -1
  20. package/dist/api-client/server.d.ts.map +1 -1
  21. package/dist/api-client/server.js +3 -3
  22. package/dist/{api-client-CZapXIF8.js → api-client-CgZUMukO.js} +9 -26
  23. package/dist/api-client-CgZUMukO.js.map +1 -0
  24. package/dist/auth/auth.server.d.ts.map +1 -1
  25. package/dist/auth/authorization-parameters.d.ts +5 -0
  26. package/dist/auth/authorization-parameters.d.ts.map +1 -0
  27. package/dist/auth/route-protection.server.d.ts +20 -4
  28. package/dist/auth/route-protection.server.d.ts.map +1 -1
  29. package/dist/auth/server.d.ts +1 -1
  30. package/dist/auth/server.d.ts.map +1 -1
  31. package/dist/auth/server.js +5 -5
  32. package/dist/auth/types.d.ts +24 -2
  33. package/dist/auth/types.d.ts.map +1 -1
  34. package/dist/{auth.server-B3ODGIX4.js → auth.server-D5oiz9aF.js} +62 -53
  35. package/dist/auth.server-D5oiz9aF.js.map +1 -0
  36. package/dist/{auth.server-wdKsjQsn.js → auth.server-lC94lmlx.js} +3 -3
  37. package/dist/identity/identity-context.server.d.ts +2 -0
  38. package/dist/identity/identity-context.server.d.ts.map +1 -1
  39. package/dist/identity/identity-store.d.ts +19 -2
  40. package/dist/identity/identity-store.d.ts.map +1 -1
  41. package/dist/identity/index.js +2 -2
  42. package/dist/identity/server.js +2 -2
  43. package/dist/identity/types.d.ts +3 -1
  44. package/dist/identity/types.d.ts.map +1 -1
  45. package/dist/{identity-yT2ijZF0.js → identity-C5un-xAs.js} +104 -60
  46. package/dist/identity-C5un-xAs.js.map +1 -0
  47. package/dist/index.js +8 -8
  48. package/dist/logging/index.js +1 -1
  49. package/dist/{logging-_h2mi3L9.js → logging-DviiAkLx.js} +2 -2
  50. package/dist/{logging-_h2mi3L9.js.map → logging-DviiAkLx.js.map} +1 -1
  51. package/dist/permissions/constants.d.ts +3 -0
  52. package/dist/permissions/constants.d.ts.map +1 -1
  53. package/dist/permissions/index.js +1 -1
  54. package/dist/permissions/permission-store.d.ts +1 -1
  55. package/dist/permissions/types.d.ts +1 -1
  56. package/dist/permissions/types.d.ts.map +1 -1
  57. package/dist/{permissions-OUK8Eql2.js → permissions-_n1hIG7Q.js} +6 -12
  58. package/dist/permissions-_n1hIG7Q.js.map +1 -0
  59. package/dist/query-client/types.d.ts +9 -9
  60. package/dist/query-client/types.d.ts.map +1 -1
  61. package/dist/query-client-Bh3TyB8W.js.map +1 -1
  62. package/dist/react-router/index.js +8 -8
  63. package/dist/react-router/server.js +14 -14
  64. package/dist/{redis-session-storage.server-D0-72FV-.js → redis-session-storage.server-v2VcpFsQ.js} +2 -2
  65. package/dist/{redis-session-storage.server-D0-72FV-.js.map → redis-session-storage.server-v2VcpFsQ.js.map} +1 -1
  66. package/dist/{server-B7JN86_0.js → server-B6PCxc-T.js} +31 -83
  67. package/dist/server-B6PCxc-T.js.map +1 -0
  68. package/dist/{server-DeBlxoaW.js → server-C96NMFBy.js} +17 -9
  69. package/dist/server-C96NMFBy.js.map +1 -0
  70. package/dist/{server-Ce9jQRdL.js → server-Ca-3DJf2.js} +44 -11
  71. package/dist/server-Ca-3DJf2.js.map +1 -0
  72. package/dist/{server-BmiuV28c.js → server-Dxsww9v9.js} +5 -4
  73. package/dist/server-Dxsww9v9.js.map +1 -0
  74. package/dist/server.js +14 -14
  75. package/dist/signalr/index.js +1 -1
  76. package/dist/signalr/types.d.ts +1 -1
  77. package/dist/{signalr-CSfCz1Ik.js → signalr-k-70VHKo.js} +3 -2
  78. package/dist/signalr-k-70VHKo.js.map +1 -0
  79. package/dist/tenant/index.js +2 -2
  80. package/dist/tenant/server.js +4 -4
  81. package/dist/tenant/tenant-context.d.ts +17 -1
  82. package/dist/tenant/tenant-context.d.ts.map +1 -1
  83. package/dist/tenant/tenant-cookie.server.d.ts.map +1 -1
  84. package/dist/tenant/tenant-store.d.ts +54 -9
  85. package/dist/tenant/tenant-store.d.ts.map +1 -1
  86. package/dist/tenant/tenant.server.d.ts +20 -1
  87. package/dist/tenant/tenant.server.d.ts.map +1 -1
  88. package/dist/tenant/types.d.ts +29 -9
  89. package/dist/tenant/types.d.ts.map +1 -1
  90. package/dist/tenant-DfLemb6n.js +384 -0
  91. package/dist/tenant-DfLemb6n.js.map +1 -0
  92. package/docs/adapters.md +87 -0
  93. package/docs/architecture.md +193 -0
  94. package/docs/backend-adaptation.md +249 -0
  95. package/docs/codex-for-oss-application.md +39 -0
  96. package/docs/codex-maintenance.md +47 -0
  97. package/docs/installation.md +213 -0
  98. package/docs/module-reference.md +207 -0
  99. package/docs/releasing.md +92 -0
  100. package/package.json +21 -3
  101. package/dist/access-DlAqsVGQ.js.map +0 -1
  102. package/dist/api-client-CZapXIF8.js.map +0 -1
  103. package/dist/auth.server-B3ODGIX4.js.map +0 -1
  104. package/dist/identity-yT2ijZF0.js.map +0 -1
  105. package/dist/permissions-OUK8Eql2.js.map +0 -1
  106. package/dist/server-B7JN86_0.js.map +0 -1
  107. package/dist/server-BmiuV28c.js.map +0 -1
  108. package/dist/server-Ce9jQRdL.js.map +0 -1
  109. package/dist/server-DeBlxoaW.js.map +0 -1
  110. package/dist/signalr-CSfCz1Ik.js.map +0 -1
  111. package/dist/tenant-BErqEENp.js +0 -256
  112. package/dist/tenant-BErqEENp.js.map +0 -1
@@ -1,13 +1,13 @@
1
- import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-_h2mi3L9.js";
1
+ import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-DviiAkLx.js";
2
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-OUK8Eql2.js";
4
- import { evaluateAccessRequirement } from "../access-DlAqsVGQ.js";
5
- import { TenantProvider, configureTenantEndpoints, initializeTenantStore, useCurrentOrganization, useCurrentTenant, useOrganization, useTenant, useTenantStore, useTenantSwitcher } from "../tenant-BErqEENp.js";
6
- import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, resetIdentityStoreConfig, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity-yT2ijZF0.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
7
  import { isApiClientError } from "../types-BaaE6wjY.js";
8
- import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-CZapXIF8.js";
9
- import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "../signalr-CSfCz1Ik.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
10
  import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "../query-client-Bh3TyB8W.js";
11
11
  import "../src-B9rDUgSb.js";
12
12
 
13
- export { ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, cachePresets, calculateBackoffDelay, cleanupSignalR, configureIdentityEndpoints, configureIdentityStore, configureTenantEndpoints, createIdentitySignalRClient, createLogger, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, evaluateAccessRequirement, fetchWithTimeout, getIdentitySignalRClient, getPermissionChecker, getPermissions, handleApiError, initializePermissions, initializeSignalR, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, logger, resetIdentityStoreConfig, useAllPermissions, useCurrentOrganization, useCurrentTenant, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withRetry };
13
+ export { ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, cachePresets, calculateBackoffDelay, cleanupSignalR, configureIdentityEndpoints, configureIdentityStore, configureTenantClient, configureTenantEndpoints, createIdentitySignalRClient, createLogger, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, evaluateAccessRequirement, fetchWithTimeout, getIdentitySignalRClient, getPermissionChecker, getPermissions, getTenantCookieName, handleApiError, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, logger, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetIdentityStoreConfig, resetTenantClientConfig, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withRetry };
@@ -1,20 +1,20 @@
1
- import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-_h2mi3L9.js";
1
+ import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-DviiAkLx.js";
2
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-OUK8Eql2.js";
4
- import { evaluateAccessRequirement } from "../access-DlAqsVGQ.js";
5
- import { TenantProvider, configureTenantEndpoints, initializeTenantStore, useCurrentOrganization, useCurrentTenant, useOrganization, useTenant, useTenantStore, useTenantSwitcher } from "../tenant-BErqEENp.js";
6
- import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, resetIdentityStoreConfig, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity-yT2ijZF0.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
7
  import { isApiClientError } from "../types-BaaE6wjY.js";
8
- import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-CZapXIF8.js";
9
- import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "../signalr-CSfCz1Ik.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
10
  import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "../query-client-Bh3TyB8W.js";
11
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-D0-72FV-.js";
13
- import { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, extractUserInfo, getAccessToken, getAuthError, getUser, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping } from "../auth.server-B3ODGIX4.js";
14
- import { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, protectRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute } from "../server-B7JN86_0.js";
15
- import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, setActiveTenant, setCurrentTenant } from "../server-Ce9jQRdL.js";
16
- import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../server-BmiuV28c.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
17
  import { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission } from "../server-hGrElJMG.js";
18
- import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "../server-DeBlxoaW.js";
18
+ import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "../server-C96NMFBy.js";
19
19
 
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, configureTenantCookie, configureTenantEndpoints, 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, getTenantFromIdentityContext, getUser, handleApiError, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, hasContextVersionChanged, initializePermissions, initializeSignalR, initializeTenant, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, isSessionValid, isTokenExpiredResponse, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, 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 };
@@ -1,4 +1,4 @@
1
- import { logger } from "./logging-_h2mi3L9.js";
1
+ import { logger } from "./logging-DviiAkLx.js";
2
2
  import Redis from "ioredis";
3
3
  import { createCipheriv, createDecipheriv, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
4
4
 
@@ -497,4 +497,4 @@ async function closeRedisConnection() {
497
497
 
498
498
  //#endregion
499
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-D0-72FV-.js.map
500
+ //# sourceMappingURL=redis-session-storage.server-v2VcpFsQ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-session-storage.server-D0-72FV-.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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"}
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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"}
@@ -1,6 +1,6 @@
1
- import { logger } from "./logging-_h2mi3L9.js";
2
- import { getAuthSession } from "./redis-session-storage.server-D0-72FV-.js";
3
- import { createRedirectResponse, getUser, login, refreshTokens, requireAuth } from "./auth.server-B3ODGIX4.js";
1
+ import { logger } from "./logging-DviiAkLx.js";
2
+ import { getAuthSession } from "./redis-session-storage.server-v2VcpFsQ.js";
3
+ import { createRedirectResponse, getUser, login, refreshTokens, requireAuth } from "./auth.server-D5oiz9aF.js";
4
4
 
5
5
  //#region src/auth/route-protection.server.ts
6
6
  const REFRESH_THRESHOLD = resolveRefreshThreshold();
@@ -17,7 +17,7 @@ async function shouldRefreshToken(request) {
17
17
  if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {
18
18
  if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {
19
19
  logger.info("Token expired and no refresh token, forcing local application logout");
20
- const { logout } = await import("./auth.server-wdKsjQsn.js");
20
+ const { logout } = await import("./auth.server-lC94lmlx.js");
21
21
  const logoutRequest = await createAutomaticLogoutRequest(request, "expired-no-refresh-token");
22
22
  throw await logout(logoutRequest);
23
23
  }
@@ -55,7 +55,7 @@ async function autoRefreshTokens(request) {
55
55
  logger.error("Auto-refresh failed", void 0, { error: result.error });
56
56
  if (result.shouldLogout) {
57
57
  logger.error("Refresh token invalid, forcing local application logout");
58
- const { logout } = await import("./auth.server-wdKsjQsn.js");
58
+ const { logout } = await import("./auth.server-lC94lmlx.js");
59
59
  const logoutRequest = await createAutomaticLogoutRequest(request, "refresh-failed");
60
60
  throw await logout(logoutRequest);
61
61
  }
@@ -68,7 +68,7 @@ async function autoRefreshTokens(request) {
68
68
  }
69
69
  let protectionConfig = {};
70
70
  /**
71
- * Configure route protection with identity/tenant/subscription checkers
71
+ * Configure route protection with identity, tenant, and app policy checkers
72
72
  */
73
73
  function configureRouteProtection(config) {
74
74
  protectionConfig = config;
@@ -92,9 +92,9 @@ function getLoginReturnUrl(request, currentPath, protection) {
92
92
  protection
93
93
  });
94
94
  switch (protection) {
95
- case "onboarding-required": return "/onboarding";
96
- case "subscription-required": return "/subscription";
97
- case "public": return "/";
95
+ case "onboarding-required":
96
+ case "subscription-required":
97
+ case "public":
98
98
  case "auth":
99
99
  default: {
100
100
  const url = new URL(request.url);
@@ -158,62 +158,6 @@ async function applyConfiguredRouteResolution(request, user, currentPath, protec
158
158
  }
159
159
  }
160
160
  /**
161
- * Check user status (onboarding, subscription)
162
- */
163
- async function checkUserStatus(request, user, currentPath) {
164
- if (!protectionConfig.identityChecker) {
165
- logger.warn("Identity checker not configured, skipping status check");
166
- return null;
167
- }
168
- try {
169
- const context = await protectionConfig.identityChecker.getIdentityContext(request, user.sub);
170
- logger.debug("User status check", {
171
- userId: user.sub,
172
- currentPath,
173
- hasAnyMembership: context.hasAnyMembership,
174
- isOnboarded: context.isOnboarded,
175
- hasSubscription: context.hasSubscription
176
- });
177
- if (!context.hasAnyMembership) {
178
- if (currentPath !== "/onboarding") {
179
- logger.info("User has no memberships, redirecting to onboarding");
180
- throw createRedirectResponse("/onboarding");
181
- }
182
- return null;
183
- }
184
- if (context.hasAnyMembership) {
185
- if (currentPath === "/onboarding") {
186
- logger.info("User has memberships, redirecting from onboarding to subscription");
187
- throw createRedirectResponse("/subscription");
188
- }
189
- if (currentPath === "/subscription") return null;
190
- if (protectionConfig.tenantChecker && protectionConfig.subscriptionChecker) {
191
- let currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);
192
- if (!currentTenant) {
193
- const initResult = await protectionConfig.tenantChecker.initializeTenant(request);
194
- if (initResult) currentTenant = initResult.tenantId;
195
- }
196
- if (currentTenant) {
197
- const hasSubscription = await protectionConfig.subscriptionChecker.checkTenantSubscriptionStatus(request, currentTenant);
198
- if (!hasSubscription) {
199
- logger.info("Tenant has no subscription, redirecting to subscription");
200
- throw createRedirectResponse("/subscription");
201
- }
202
- } else {
203
- logger.info("Could not initialize tenant, redirecting to subscription");
204
- throw createRedirectResponse("/subscription");
205
- }
206
- }
207
- }
208
- return null;
209
- } catch (error) {
210
- if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
211
- logger.error("Error checking user status", error instanceof Error ? error : void 0);
212
- if (currentPath !== "/onboarding") throw createRedirectResponse("/onboarding");
213
- return null;
214
- }
215
- }
216
- /**
217
161
  * Main route protection function
218
162
  */
219
163
  async function protectRoute(request, protection, loaderFn) {
@@ -226,24 +170,20 @@ async function protectRoute(request, protection, loaderFn) {
226
170
  } catch {
227
171
  return loaderFn(void 0);
228
172
  }
229
- case "auth": try {
173
+ case "auth":
174
+ case "policy": try {
230
175
  await autoRefreshTokens(request);
231
176
  const user = await requireAuth(request);
232
- const handledByConfig = await applyConfiguredRouteResolution(request, user, url.pathname, "auth");
233
- if (!handledByConfig) await checkUserStatus(request, user, url.pathname);
177
+ await applyConfiguredRouteResolution(request, user, url.pathname, protection);
234
178
  return loaderFn(user);
235
179
  } catch (error) {
236
180
  if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
237
- throw await login(request, getLoginReturnUrl(request, url.pathname, "auth"));
181
+ throw await login(request, getLoginReturnUrl(request, url.pathname, protection));
238
182
  }
239
183
  case "onboarding-required": try {
240
184
  await autoRefreshTokens(request);
241
185
  const user = await requireAuth(request);
242
- const handledByConfig = await applyConfiguredRouteResolution(request, user, url.pathname, "onboarding-required");
243
- if (!handledByConfig && protectionConfig.identityChecker) {
244
- const context = await protectionConfig.identityChecker.getIdentityContext(request, user.sub);
245
- if (context.hasAnyMembership) throw createRedirectResponse("/subscription");
246
- }
186
+ await applyConfiguredRouteResolution(request, user, url.pathname, "onboarding-required");
247
187
  return loaderFn(user);
248
188
  } catch (error) {
249
189
  if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
@@ -252,11 +192,7 @@ async function protectRoute(request, protection, loaderFn) {
252
192
  case "subscription-required": try {
253
193
  await autoRefreshTokens(request);
254
194
  const user = await requireAuth(request);
255
- const handledByConfig = await applyConfiguredRouteResolution(request, user, url.pathname, "subscription-required");
256
- if (!handledByConfig && protectionConfig.identityChecker) {
257
- const context = await protectionConfig.identityChecker.getIdentityContext(request, user.sub);
258
- if (!context.hasAnyMembership) throw createRedirectResponse("/onboarding");
259
- }
195
+ await applyConfiguredRouteResolution(request, user, url.pathname, "subscription-required");
260
196
  if (protectionConfig.tenantChecker) {
261
197
  const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);
262
198
  if (!currentTenant) await protectionConfig.tenantChecker.initializeTenant(request);
@@ -278,11 +214,23 @@ const publicRoute = (request, loaderFn) => protectRoute(request, "public", loade
278
214
  */
279
215
  const authRoute = (request, loaderFn) => protectRoute(request, "auth", loaderFn);
280
216
  /**
281
- * Onboarding route
217
+ * Generic policy-protected route. Apps define policy in configureRouteProtection.
218
+ */
219
+ const policyRoute = (request, loaderFn) => protectRoute(request, "policy", loaderFn);
220
+ /**
221
+ * Alias for auth-protected routes.
222
+ */
223
+ const protectedRoute = authRoute;
224
+ /**
225
+ * Setup policy route
226
+ *
227
+ * @deprecated Use policyRoute with configureRouteProtection instead.
282
228
  */
283
229
  const onboardingRoute = (request, loaderFn) => protectRoute(request, "onboarding-required", loaderFn);
284
230
  /**
285
- * Subscription route
231
+ * Entitlement policy route
232
+ *
233
+ * @deprecated Use policyRoute with configureRouteProtection instead.
286
234
  */
287
235
  const subscriptionRoute = (request, loaderFn) => protectRoute(request, "subscription-required", loaderFn);
288
236
 
@@ -352,5 +300,5 @@ function shouldAttemptTokenRefresh(response, hasAuth) {
352
300
  }
353
301
 
354
302
  //#endregion
355
- export { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, protectRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute };
356
- //# sourceMappingURL=server-B7JN86_0.js.map
303
+ export { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute };
304
+ //# sourceMappingURL=server-B6PCxc-T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-B6PCxc-T.js","names":["request: Request","jwtExpiry: number | null","protectionConfig: RouteProtectionConfig<any>","config: RouteProtectionConfig<TIdentityContext>","redirect: RouteProtectionRedirect","currentPath: string","protection: ProtectionLevel","reason: AutomaticLogoutRequestContext['reason']","user: UserInfo","identityContextPromise: Promise<any | null> | null","currentTenantPromise: Promise<string | null> | null","initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null","loaderFn: (user?: UserInfo) => Promise<T> | T","loaderFn: (user: UserInfo) => Promise<T> | T","request: Request","response: Response","hasAuth: boolean"],"sources":["../src/auth/route-protection.server.ts","../src/auth/token-refresh.server.ts"],"sourcesContent":["/**\n * Route Protection Server Module\n * \n * Provides route protection utilities for different authentication levels.\n */\n\nimport { getUser, requireAuth, refreshTokens, login } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { ProtectionLevel, UserInfo } from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// Refresh shortly before expiry. Keep this below common 5-minute access-token\n// lifetimes so a freshly issued token does not refresh again immediately.\nconst REFRESH_THRESHOLD = resolveRefreshThreshold();\n\nfunction resolveRefreshThreshold(): number {\n const configured = Number(process.env.OIDC_REFRESH_THRESHOLD_MS);\n return Number.isFinite(configured) && configured > 0 ? configured : 60_000;\n}\n\n/**\n * Check if token needs refresh\n */\nexport async function shouldRefreshToken(request: Request): Promise<boolean> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {\n if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {\n logger.info('Token expired and no refresh token, forcing local application logout');\n const { logout } = await import('./auth.server');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'expired-no-refresh-token');\n throw await logout(logoutRequest);\n }\n return false;\n }\n\n if (!sessionData.expiresAt || !sessionData.accessToken) {\n return false;\n }\n\n const now = Date.now();\n\n // Check JWT expiry\n let jwtExpiry: number | null = null;\n try {\n const tokenParts = sessionData.accessToken.split('.');\n if (tokenParts.length === 3) {\n const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());\n if (payload.exp) {\n jwtExpiry = payload.exp * 1000;\n }\n }\n } catch {\n // Ignore JWT decode errors\n }\n\n const earliestExpiry = jwtExpiry && jwtExpiry < sessionData.expiresAt \n ? jwtExpiry \n : sessionData.expiresAt;\n const timeUntilEarliestExpiry = earliestExpiry - now;\n\n return timeUntilEarliestExpiry <= REFRESH_THRESHOLD;\n } catch (error) {\n logger.error('Error checking token refresh', error instanceof Error ? error : undefined);\n return false;\n }\n}\n\n/**\n * Auto-refresh tokens if needed\n */\nasync function autoRefreshTokens(request: Request): Promise<void> {\n try {\n const needsRefresh = await shouldRefreshToken(request);\n\n if (needsRefresh) {\n logger.info('Auto-refreshing tokens due to expiry');\n const result = await refreshTokens(request);\n\n if (result.success) {\n logger.info('Auto-refresh completed successfully');\n } else {\n logger.error('Auto-refresh failed', undefined, { error: result.error });\n\n if (result.shouldLogout) {\n logger.error('Refresh token invalid, forcing local application logout');\n const { logout } = await import('./auth.server');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'refresh-failed');\n throw await logout(logoutRequest);\n }\n }\n }\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw error;\n }\n}\n\n/**\n * Identity context check configuration\n */\nexport interface DefaultRouteIdentityContext {\n hasAnyMembership: boolean;\n hasEntitlement?: boolean;\n /** @deprecated Use hasEntitlement or app-specific fields instead. */\n hasSubscription: boolean;\n isOnboarded: boolean;\n}\n\nexport interface IdentityContextChecker<TIdentityContext = DefaultRouteIdentityContext> {\n getIdentityContext: (\n request: Request,\n userId: string,\n forceRefresh?: boolean\n ) => Promise<DefaultRouteIdentityContext & TIdentityContext>;\n}\n\n/**\n * Tenant checker configuration\n */\nexport interface TenantChecker {\n getCurrentTenant: (request: Request) => Promise<string | null>;\n initializeTenant: (request: Request) => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\n/**\n * Legacy subscription checker configuration\n *\n * @deprecated Put entitlement policy in resolveRoute or app-level route loaders.\n */\nexport interface SubscriptionChecker {\n checkTenantSubscriptionStatus: (request: Request, tenantId: string) => Promise<boolean>;\n}\n\nexport interface RouteProtectionRedirect {\n location: string;\n headers?: HeadersInit;\n status?: number;\n}\n\nexport interface AutomaticLogoutRequestContext {\n request: Request;\n reason: 'expired-no-refresh-token' | 'refresh-failed';\n}\n\nexport interface LoginReturnUrlContext {\n request: Request;\n currentPath: string;\n protection: ProtectionLevel;\n}\n\nexport interface RouteProtectionResolverContext<TIdentityContext = DefaultRouteIdentityContext> {\n request: Request;\n user: UserInfo;\n currentPath: string;\n protection: ProtectionLevel;\n getIdentityContext: (forceRefresh?: boolean) => Promise<TIdentityContext | null>;\n getCurrentTenant: () => Promise<string | null>;\n initializeTenant: () => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\nexport interface RouteProtectionErrorHandlerContext<TIdentityContext = DefaultRouteIdentityContext>\n extends RouteProtectionResolverContext<TIdentityContext> {\n error: unknown;\n}\n\n/**\n * Route protection configuration\n */\nexport interface RouteProtectionConfig<TIdentityContext = DefaultRouteIdentityContext> {\n identityChecker?: IdentityContextChecker<TIdentityContext>;\n tenantChecker?: TenantChecker;\n subscriptionChecker?: SubscriptionChecker;\n resolveRoute?: (\n context: RouteProtectionResolverContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n handleRouteError?: (\n context: RouteProtectionErrorHandlerContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n getLoginReturnUrl?: (context: LoginReturnUrlContext) => string;\n createAutomaticLogoutRequest?: (\n context: AutomaticLogoutRequestContext\n ) => Promise<Request> | Request;\n}\n\nlet protectionConfig: RouteProtectionConfig<any> = {};\n\n/**\n * Configure route protection with identity, tenant, and app policy checkers\n */\nexport function configureRouteProtection<TIdentityContext = DefaultRouteIdentityContext>(\n config: RouteProtectionConfig<TIdentityContext>\n): void {\n protectionConfig = config;\n}\n\n/**\n * Reset route protection to its built-in defaults.\n */\nexport function resetRouteProtectionConfig(): void {\n protectionConfig = {};\n}\n\nfunction createRedirectFromConfig(redirect: RouteProtectionRedirect): Response {\n return createRedirectResponse(redirect.location, {\n headers: redirect.headers,\n status: redirect.status,\n });\n}\n\nfunction getLoginReturnUrl(\n request: Request,\n currentPath: string,\n protection: ProtectionLevel\n): string {\n if (protectionConfig.getLoginReturnUrl) {\n return protectionConfig.getLoginReturnUrl({\n request,\n currentPath,\n protection,\n });\n }\n\n switch (protection) {\n case 'onboarding-required':\n case 'subscription-required':\n case 'public':\n case 'auth':\n default: {\n const url = new URL(request.url);\n return url.pathname === '/' ? '/' : `${url.pathname}${url.search}`;\n }\n }\n}\n\nasync function createAutomaticLogoutRequest(\n request: Request,\n reason: AutomaticLogoutRequestContext['reason']\n): Promise<Request> {\n if (protectionConfig.createAutomaticLogoutRequest) {\n return protectionConfig.createAutomaticLogoutRequest({ request, reason });\n }\n\n const logoutUrl = new URL(request.url);\n logoutUrl.pathname = '/auth/logout';\n logoutUrl.searchParams.set('logout', 'local');\n\n return new Request(logoutUrl.toString(), {\n headers: request.headers,\n });\n}\n\nfunction createRouteResolverContext(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): RouteProtectionResolverContext<any> {\n let identityContextPromise: Promise<any | null> | null = null;\n let currentTenantPromise: Promise<string | null> | null = null;\n let initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null = null;\n\n return {\n request,\n user,\n currentPath,\n protection,\n getIdentityContext: async (forceRefresh = false) => {\n if (!protectionConfig.identityChecker) {\n return null;\n }\n\n if (forceRefresh) {\n return protectionConfig.identityChecker.getIdentityContext(request, user.sub, true);\n }\n\n if (!identityContextPromise) {\n identityContextPromise = protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n }\n\n return identityContextPromise;\n },\n getCurrentTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!currentTenantPromise) {\n currentTenantPromise = protectionConfig.tenantChecker.getCurrentTenant(request);\n }\n\n return currentTenantPromise;\n },\n initializeTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!initializeTenantPromise) {\n initializeTenantPromise = protectionConfig.tenantChecker.initializeTenant(request);\n }\n\n return initializeTenantPromise;\n },\n };\n}\n\nasync function applyConfiguredRouteResolution(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): Promise<boolean> {\n if (!protectionConfig.resolveRoute) {\n return false;\n }\n\n const context = createRouteResolverContext(request, user, currentPath, protection);\n\n try {\n const result = await protectionConfig.resolveRoute(context);\n if (result) {\n throw createRedirectFromConfig(result);\n }\n\n return true;\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n\n if (!protectionConfig.handleRouteError) {\n throw error;\n }\n\n const recovery = await protectionConfig.handleRouteError({\n ...context,\n error,\n });\n\n if (recovery) {\n throw createRedirectFromConfig(recovery);\n }\n\n throw error;\n }\n}\n\n/**\n * Main route protection function\n */\nexport async function protectRoute<T>(\n request: Request,\n protection: ProtectionLevel,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n): Promise<T> {\n const url = new URL(request.url);\n\n switch (protection) {\n case 'public':\n try {\n await autoRefreshTokens(request);\n const user = await getUser(request);\n return loaderFn(user ?? undefined);\n } catch {\n return loaderFn(undefined);\n }\n\n case 'auth':\n case 'policy':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n await applyConfiguredRouteResolution(request, user, url.pathname, protection);\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, protection));\n }\n\n case 'onboarding-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'onboarding-required'\n );\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'onboarding-required'));\n }\n\n case 'subscription-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'subscription-required'\n );\n\n // Initialize tenant if needed\n if (protectionConfig.tenantChecker) {\n const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);\n if (!currentTenant) {\n await protectionConfig.tenantChecker.initializeTenant(request);\n }\n }\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'subscription-required'));\n }\n\n default:\n throw new Error(`Unknown protection level: ${protection}`);\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Public route (optional auth)\n */\nexport const publicRoute = <T>(\n request: Request,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'public', loaderFn);\n\n/**\n * Auth-protected route\n */\nexport const authRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'auth', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Generic policy-protected route. Apps define policy in configureRouteProtection.\n */\nexport const policyRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'policy', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Alias for auth-protected routes.\n */\nexport const protectedRoute = authRoute;\n\n/**\n * Setup policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const onboardingRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'onboarding-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Entitlement policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const subscriptionRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'subscription-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n","/**\n * Token Refresh Server Module\n */\n\nimport { refreshTokens } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { TokenRefreshResult } from './types';\nimport { logger } from '../logging';\n\n/**\n * Attempt to refresh the access token\n */\nexport async function attemptTokenRefresh(request: Request): Promise<TokenRefreshResult> {\n try {\n logger.info('Attempting token refresh due to 401 response');\n\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken) {\n logger.warn('No refresh token available in session');\n return {\n success: false,\n error: 'No refresh token available',\n shouldLogout: true,\n };\n }\n\n const refreshResult = await refreshTokens(request, sessionData.refreshToken);\n\n if (!refreshResult.success) {\n logger.error('Token refresh failed', undefined, {\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n });\n\n return {\n success: false,\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n };\n }\n\n const tokens = refreshResult.tokens;\n if (!tokens || !tokens.access_token) {\n logger.error('Token refresh succeeded but no access token returned');\n return {\n success: false,\n error: 'No access token in refresh response',\n shouldLogout: true,\n };\n }\n\n logger.info('Token refresh successful');\n\n return {\n success: true,\n newAccessToken: tokens.access_token,\n };\n } catch (error) {\n logger.error('Token refresh attempt failed', error instanceof Error ? error : undefined);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n shouldLogout: true,\n };\n }\n}\n\n/**\n * Check if a response indicates token expiration\n */\nexport function isTokenExpiredResponse(response: Response): boolean {\n return response.status === 401;\n}\n\n/**\n * Check if token refresh should be attempted\n */\nexport function shouldAttemptTokenRefresh(response: Response, hasAuth: boolean): boolean {\n return hasAuth && isTokenExpiredResponse(response);\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { isApiClientError } from "./types-BaaE6wjY.js";
2
- import { calculateBackoffDelay, fetchWithTimeout, isRetryableError } from "./api-client-CZapXIF8.js";
2
+ import { calculateBackoffDelay, fetchWithTimeout, isRetryableError } from "./api-client-CgZUMukO.js";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
 
5
5
  //#region src/api-client/api-config.server.ts
@@ -7,7 +7,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
7
7
  * Base URL Configuration
8
8
  * Single unified base URL for all API operations
9
9
  */
10
- const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || "https://localhost:5001";
10
+ const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || "";
11
11
  /**
12
12
  * Default Headers
13
13
  * Applied to all API requests
@@ -23,7 +23,7 @@ const DEFAULT_HEADERS = {
23
23
  * with the provided auth and tenant resolvers.
24
24
  */
25
25
  function createAPIConfigFactory(getAccessToken, getCurrentTenant, logger, factoryOptions = {}) {
26
- const { baseURL = DEFAULT_API_BASE_URL, defaultHeaders = DEFAULT_HEADERS, userAgent = "MimirCore/1.0", timeout = 3e4 } = factoryOptions;
26
+ const { baseURL = DEFAULT_API_BASE_URL, defaultHeaders = DEFAULT_HEADERS, userAgent = "Spine/1.0", timeout = 3e4, authHeaderName = "Authorization", authHeaderValue = (token) => `Bearer ${token}`, tenantHeaderName = "X-Tenant-Id", buildHeaders } = factoryOptions;
27
27
  /**
28
28
  * Resolve tenant ID from request or options
29
29
  */
@@ -45,9 +45,9 @@ function createAPIConfigFactory(getAccessToken, getCurrentTenant, logger, factor
45
45
  * Creates unified API configuration for all services.
46
46
  *
47
47
  * This function automatically:
48
- * - Adds authentication token to Authorization header
49
- * - Injects tenant context as X-Tenant-Id header
50
- * - Configures base URL from environment
48
+ * - Adds authentication token using the configured auth header strategy
49
+ * - Injects tenant context using the configured tenant header strategy
50
+ * - Configures base URL from options or environment
51
51
  * - Applies default headers
52
52
  *
53
53
  * @param request - The incoming request object
@@ -83,7 +83,7 @@ function createAPIConfigFactory(getAccessToken, getCurrentTenant, logger, factor
83
83
  throw new Error("No access token available");
84
84
  }
85
85
  accessToken = token;
86
- headers.Authorization = `Bearer ${token}`;
86
+ if (authHeaderName) headers[authHeaderName] = authHeaderValue(token);
87
87
  logger?.debug?.("Added authorization header to API config");
88
88
  }
89
89
  const tenantId = await resolveTenantId(request, options);
@@ -92,9 +92,17 @@ function createAPIConfigFactory(getAccessToken, getCurrentTenant, logger, factor
92
92
  logger?.error?.("Tenant context required but not available");
93
93
  throw new Error("Tenant context is required for this operation");
94
94
  }
95
- headers["X-Tenant-Id"] = tenantId;
95
+ if (tenantHeaderName) headers[tenantHeaderName] = tenantId;
96
96
  logger?.debug?.("Added tenant context header", { tenantId });
97
97
  }
98
+ if (buildHeaders) Object.assign(headers, buildHeaders({
99
+ request,
100
+ options,
101
+ accessToken,
102
+ tenantId,
103
+ includeAuth,
104
+ requireTenant
105
+ }));
98
106
  logger?.debug?.("API config created", {
99
107
  basePath: baseURL,
100
108
  hasAuth: includeAuth,
@@ -419,4 +427,4 @@ async function createApiClient(request, ClientClass, createAPIConfig, Configurat
419
427
 
420
428
  //#endregion
421
429
  export { createAPIConfigFactory, createApiClient, createFetchMiddleware };
422
- //# sourceMappingURL=server-DeBlxoaW.js.map
430
+ //# sourceMappingURL=server-C96NMFBy.js.map