@eminuckan/spine 0.3.1 → 0.3.3

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 (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/SECURITY.md +5 -15
  3. package/dist/access/index.mjs +2 -0
  4. package/dist/{access-CGrsDMwu.js → access-BHy1v5sr.mjs} +6 -8
  5. package/dist/access-BHy1v5sr.mjs.map +1 -0
  6. package/dist/api-client/index.mjs +3 -0
  7. package/dist/api-client/server.mjs +4 -0
  8. package/dist/{api-client-CgZUMukO.js → api-client-CB7XZaMg.mjs} +8 -10
  9. package/dist/api-client-CB7XZaMg.mjs.map +1 -0
  10. package/dist/auth/auth.server.d.ts +1 -0
  11. package/dist/auth/auth.server.d.ts.map +1 -1
  12. package/dist/auth/index.mjs +1 -0
  13. package/dist/auth/route-protection.server.d.ts.map +1 -1
  14. package/dist/auth/server.mjs +3 -0
  15. package/dist/{identity-C5un-xAs.js → identity/index.mjs} +24 -21
  16. package/dist/identity/index.mjs.map +1 -0
  17. package/dist/{server-Dxsww9v9.js → identity/server.mjs} +7 -8
  18. package/dist/identity/server.mjs.map +1 -0
  19. package/dist/{index.js → index.mjs} +11 -13
  20. package/dist/{logging-DviiAkLx.js → logging/index.mjs} +22 -24
  21. package/dist/logging/index.mjs.map +1 -0
  22. package/dist/{permission-service-CvGpwI0d.js → permission-service-DUyKomU3.mjs} +6 -10
  23. package/dist/permission-service-DUyKomU3.mjs.map +1 -0
  24. package/dist/permissions/index.mjs +3 -0
  25. package/dist/{permissions-_n1hIG7Q.js → permissions-C5dTP9y4.mjs} +23 -15
  26. package/dist/permissions-C5dTP9y4.mjs.map +1 -0
  27. package/dist/query-client/index.mjs +2 -0
  28. package/dist/{query-client-Bh3TyB8W.js → query-client-_xB300gc.mjs} +49 -15
  29. package/dist/query-client-_xB300gc.mjs.map +1 -0
  30. package/dist/react-router/{index.js → index.mjs} +11 -13
  31. package/dist/react-router/server.mjs +17 -0
  32. package/dist/{redis-session-storage.server-v2VcpFsQ.js → redis-session-storage.server-DGgQcJ9B.mjs} +30 -42
  33. package/dist/redis-session-storage.server-DGgQcJ9B.mjs.map +1 -0
  34. package/dist/{server-C96NMFBy.js → server-ByScGRXA.mjs} +39 -36
  35. package/dist/server-ByScGRXA.mjs.map +1 -0
  36. package/dist/{auth.server-D5oiz9aF.js → server-CqX2yCa4.mjs} +388 -119
  37. package/dist/server-CqX2yCa4.mjs.map +1 -0
  38. package/dist/server.mjs +90 -0
  39. package/dist/server.mjs.map +1 -0
  40. package/dist/signalr/index.mjs +2 -0
  41. package/dist/{signalr-k-70VHKo.js → signalr-C-TA8nEE.mjs} +11 -6
  42. package/dist/signalr-C-TA8nEE.mjs.map +1 -0
  43. package/dist/{tenant-DfLemb6n.js → tenant/index.mjs} +19 -12
  44. package/dist/tenant/index.mjs.map +1 -0
  45. package/dist/{server-Ca-3DJf2.js → tenant/server.mjs} +12 -12
  46. package/dist/tenant/server.mjs.map +1 -0
  47. package/dist/{types-BaaE6wjY.js → types-DqCXinAk.mjs} +3 -3
  48. package/dist/types-DqCXinAk.mjs.map +1 -0
  49. package/docs/codex-for-oss-application.md +4 -0
  50. package/package.json +33 -21
  51. package/dist/access/index.js +0 -4
  52. package/dist/access-CGrsDMwu.js.map +0 -1
  53. package/dist/api-client/index.js +0 -5
  54. package/dist/api-client/server.js +0 -6
  55. package/dist/api-client-CgZUMukO.js.map +0 -1
  56. package/dist/auth/index.js +0 -0
  57. package/dist/auth/server.js +0 -6
  58. package/dist/auth.server-D5oiz9aF.js.map +0 -1
  59. package/dist/auth.server-lC94lmlx.js +0 -5
  60. package/dist/identity/index.js +0 -3
  61. package/dist/identity/server.js +0 -5
  62. package/dist/identity-C5un-xAs.js.map +0 -1
  63. package/dist/logging/index.js +0 -3
  64. package/dist/logging-DviiAkLx.js.map +0 -1
  65. package/dist/permission-service-CvGpwI0d.js.map +0 -1
  66. package/dist/permissions/index.js +0 -4
  67. package/dist/permissions-_n1hIG7Q.js.map +0 -1
  68. package/dist/query-client/index.js +0 -3
  69. package/dist/query-client-Bh3TyB8W.js.map +0 -1
  70. package/dist/react-router/server.js +0 -20
  71. package/dist/redis-session-storage.server-v2VcpFsQ.js.map +0 -1
  72. package/dist/server-B6PCxc-T.js +0 -304
  73. package/dist/server-B6PCxc-T.js.map +0 -1
  74. package/dist/server-C96NMFBy.js.map +0 -1
  75. package/dist/server-Ca-3DJf2.js.map +0 -1
  76. package/dist/server-Dxsww9v9.js.map +0 -1
  77. package/dist/server-hGrElJMG.js +0 -78
  78. package/dist/server-hGrElJMG.js.map +0 -1
  79. package/dist/server.js +0 -20
  80. package/dist/signalr/index.js +0 -3
  81. package/dist/signalr-k-70VHKo.js.map +0 -1
  82. package/dist/src-B9rDUgSb.js +0 -0
  83. package/dist/tenant/index.js +0 -3
  84. package/dist/tenant/server.js +0 -5
  85. package/dist/tenant-DfLemb6n.js.map +0 -1
  86. package/dist/types-BaaE6wjY.js.map +0 -1
@@ -1,5 +0,0 @@
1
- import "./logging-DviiAkLx.js";
2
- import { isSessionValid } from "./redis-session-storage.server-v2VcpFsQ.js";
3
- import { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, extractUserInfo, getAccessToken, getAuthError, getUser, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping } from "./auth.server-D5oiz9aF.js";
4
-
5
- export { logout };
@@ -1,3 +0,0 @@
1
- import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity-C5un-xAs.js";
2
-
3
- export { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants };
@@ -1,5 +0,0 @@
1
- import "../logging-DviiAkLx.js";
2
- import "../types-BaaE6wjY.js";
3
- import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../server-Dxsww9v9.js";
4
-
5
- export { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion };
@@ -1 +0,0 @@
1
- {"version":3,"file":"identity-C5un-xAs.js","names":["initialIdentityContext: IdentityContextData","DEFAULT_IDENTITY_STORE_CONFIG: Required<\n Pick<IdentityStoreConfig, 'contextEndpoint' | 'permissionsEndpoint' | 'logoutPath' | 'fetcher'>\n>","identityStoreConfig: IdentityStoreConfig","reason: IdentityUnauthorizedDetails['reason']","response?: Response","value: unknown","endpoint: string","forceRefresh: boolean","response: Response","payload: unknown","details: Omit<IdentityUnauthorizedDetails, 'logoutPath'>","unauthorizedDetails: IdentityUnauthorizedDetails","config: IdentityStoreConfig","config: {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n}","event: IdentityContextChangedEvent"],"sources":["../src/identity/identity-store.ts","../src/identity/identity-context-provider.tsx"],"sourcesContent":["/**\n * Identity Store (Zustand)\n * \n * Centralized state management for identity context.\n */\n\nimport { create } from 'zustand';\nimport type { IdentityContextData } from './types';\n\nexport const initialIdentityContext: IdentityContextData = {\n hasAnyMembership: false,\n hasEntitlement: false,\n hasSubscription: false,\n isOnboarded: false,\n tenants: [],\n currentTenant: undefined,\n permissions: [],\n contextVersion: 0,\n isLoading: true,\n userId: undefined,\n email: undefined,\n firstName: undefined,\n lastName: undefined,\n displayName: undefined,\n profileImageUrl: undefined,\n phoneNumber: undefined,\n timeZone: undefined,\n addresses: undefined,\n hasOwnerMembership: false,\n memberships: undefined,\n};\n\nexport interface IdentityContextFetchContext {\n endpoint: string;\n fetch: typeof fetch;\n forceRefresh: boolean;\n currentContext: IdentityContextData;\n}\n\nexport interface IdentityPermissionsFetchContext {\n endpoint: string;\n fetch: typeof fetch;\n currentContext: IdentityContextData;\n}\n\nexport interface IdentityState {\n context: IdentityContextData;\n\n // Actions\n setContext: (context: Partial<IdentityContextData>) => void;\n refreshContext: () => Promise<void>;\n refreshPermissions: () => Promise<void>;\n reset: () => void;\n}\n\nexport interface IdentityUnauthorizedDetails {\n reason: 'current-user-missing' | 'unauthorized';\n logoutPath: string;\n response?: Response;\n}\n\nexport interface IdentityStoreConfig {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n logoutPath?: string;\n fetcher?: typeof fetch;\n fetchContext?: (context: IdentityContextFetchContext) => Promise<Partial<IdentityContextData> | null>;\n fetchPermissions?: (context: IdentityPermissionsFetchContext) => Promise<string[]>;\n onUnauthorized?: (details: IdentityUnauthorizedDetails) => void;\n}\n\nconst DEFAULT_IDENTITY_STORE_CONFIG: Required<\n Pick<IdentityStoreConfig, 'contextEndpoint' | 'permissionsEndpoint' | 'logoutPath' | 'fetcher'>\n> = {\n contextEndpoint: '/api/identity/context',\n permissionsEndpoint: '/api/identity/permissions',\n logoutPath: '/auth/logout',\n fetcher: (input, init) => fetch(input, init),\n};\n\nlet identityStoreConfig: IdentityStoreConfig = {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n};\n\nclass IdentityUnauthorizedError extends Error {\n constructor(\n readonly reason: IdentityUnauthorizedDetails['reason'],\n readonly response?: Response\n ) {\n super(reason);\n this.name = 'IdentityUnauthorizedError';\n }\n}\n\nfunction getIdentityStoreConfig(): IdentityStoreConfig & typeof DEFAULT_IDENTITY_STORE_CONFIG {\n return {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n ...identityStoreConfig,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction appendRefreshParam(endpoint: string, forceRefresh: boolean): string {\n if (!forceRefresh) {\n return endpoint;\n }\n\n const separator = endpoint.includes('?') ? '&' : '?';\n return `${endpoint}${separator}refresh=true`;\n}\n\nasync function readJsonResponse(response: Response): Promise<unknown> {\n const text = await response.text();\n return text.trim().length > 0 ? JSON.parse(text) : null;\n}\n\nexport function normalizeIdentityContextPayload(payload: unknown): Partial<IdentityContextData> | null {\n if (!isRecord(payload)) {\n return null;\n }\n\n if (payload.success === false) {\n return null;\n }\n\n const nestedContext = payload.context ?? payload.data ?? payload.identity ?? payload.user;\n if (isRecord(nestedContext)) {\n return nestedContext as Partial<IdentityContextData>;\n }\n\n return payload as Partial<IdentityContextData>;\n}\n\nexport function normalizeIdentityPermissionsPayload(payload: unknown): string[] {\n if (Array.isArray(payload)) {\n return payload.filter((permission): permission is string => typeof permission === 'string');\n }\n\n if (!isRecord(payload)) {\n return [];\n }\n\n const nestedPermissions =\n payload.permissions ??\n (isRecord(payload.data) ? payload.data.permissions : payload.data) ??\n (isRecord(payload.context) ? payload.context.permissions : undefined);\n\n return Array.isArray(nestedPermissions)\n ? nestedPermissions.filter((permission): permission is string => typeof permission === 'string')\n : [];\n}\n\nasync function defaultFetchIdentityContext({\n endpoint,\n fetch: fetchFn,\n forceRefresh,\n}: IdentityContextFetchContext): Promise<Partial<IdentityContextData> | null> {\n const response = await fetchFn(appendRefreshParam(endpoint, forceRefresh), {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (response.ok) {\n return normalizeIdentityContextPayload(await readJsonResponse(response));\n }\n\n if (response.status === 404) {\n const errorData = await readJsonResponse(response).catch(() => ({}));\n if (\n isRecord(errorData) &&\n (errorData.code === 'CurrentUserMissing' || errorData.title === 'CurrentUserMissing')\n ) {\n throw new IdentityUnauthorizedError('current-user-missing', response);\n }\n }\n\n if (response.status === 401) {\n throw new IdentityUnauthorizedError('unauthorized', response);\n }\n\n console.error('Failed to refresh identity context:', response.status);\n return null;\n}\n\nasync function defaultFetchIdentityPermissions({\n endpoint,\n fetch: fetchFn,\n}: IdentityPermissionsFetchContext): Promise<string[]> {\n const response = await fetchFn(endpoint, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (response.ok) {\n return normalizeIdentityPermissionsPayload(await readJsonResponse(response));\n }\n\n if (response.status === 401) {\n throw new IdentityUnauthorizedError('unauthorized', response);\n }\n\n console.error('Failed to refresh permissions:', response.status);\n return [];\n}\n\nfunction handleUnauthorized(details: Omit<IdentityUnauthorizedDetails, 'logoutPath'>): void {\n const config = getIdentityStoreConfig();\n const unauthorizedDetails: IdentityUnauthorizedDetails = {\n ...details,\n logoutPath: config.logoutPath,\n };\n\n if (config.onUnauthorized) {\n config.onUnauthorized(unauthorizedDetails);\n return;\n }\n\n if (typeof window !== 'undefined') {\n window.location.href = config.logoutPath;\n }\n}\n\n/**\n * Configure the identity store for different app/back-end conventions.\n */\nexport function configureIdentityStore(config: IdentityStoreConfig): void {\n identityStoreConfig = {\n ...identityStoreConfig,\n ...config,\n };\n}\n\n/**\n * Reset identity store configuration to the built-in defaults.\n */\nexport function resetIdentityStoreConfig(): void {\n identityStoreConfig = {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n };\n}\n\n/**\n * Backward-compatible endpoint-only configuration helper.\n */\nexport function configureIdentityEndpoints(config: {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n}): void {\n configureIdentityStore(config);\n}\n\nexport const useIdentityStore = create<IdentityState>((set, get) => ({\n context: initialIdentityContext,\n\n setContext: (newContext) => {\n set((state) => ({\n context: {\n ...state.context,\n ...newContext,\n isLoading: false,\n lastUpdated: new Date(),\n },\n }));\n },\n\n refreshContext: async () => {\n try {\n const config = getIdentityStoreConfig();\n\n set((state) => ({\n context: { ...state.context, isLoading: true },\n }));\n\n const fetchContext = config.fetchContext ?? defaultFetchIdentityContext;\n const newContext = await fetchContext({\n endpoint: config.contextEndpoint,\n fetch: config.fetcher,\n forceRefresh: true,\n currentContext: get().context,\n });\n\n if (newContext) {\n set({\n context: {\n ...get().context,\n ...newContext,\n isLoading: false,\n lastUpdated: new Date(),\n },\n });\n console.log('Identity context refreshed:', newContext);\n } else {\n set((state) => ({\n context: { ...state.context, isLoading: false },\n }));\n }\n } catch (error) {\n if (error instanceof IdentityUnauthorizedError) {\n handleUnauthorized({\n reason: error.reason,\n response: error.response,\n });\n return;\n }\n\n console.error('Error refreshing identity context:', error);\n set((state) => ({\n context: { ...state.context, isLoading: false },\n }));\n }\n },\n\n refreshPermissions: async () => {\n try {\n const config = getIdentityStoreConfig();\n\n console.log('Refreshing permissions...');\n\n const fetchPermissions = config.fetchPermissions ?? defaultFetchIdentityPermissions;\n const permissions = await fetchPermissions({\n endpoint: config.permissionsEndpoint,\n fetch: config.fetcher,\n currentContext: get().context,\n });\n\n set((state) => ({\n context: {\n ...state.context,\n permissions,\n lastUpdated: new Date(),\n },\n }));\n\n console.log('Permissions refreshed:', {\n count: permissions.length,\n });\n } catch (error) {\n if (error instanceof IdentityUnauthorizedError) {\n handleUnauthorized({\n reason: error.reason,\n response: error.response,\n });\n return;\n }\n\n console.error('Error refreshing permissions:', error);\n }\n },\n\n reset: () => {\n set({ context: initialIdentityContext });\n },\n}));\n\n// Convenience selectors\nexport const useIsOnboarded = () => useIdentityStore((state) => state.context.isOnboarded);\nexport const useHasEntitlement = () => useIdentityStore((state) => state.context.hasEntitlement ?? state.context.hasSubscription);\n/** @deprecated Use useHasEntitlement or an app-specific selector instead. */\nexport const useHasSubscription = () => useIdentityStore((state) => state.context.hasSubscription);\nexport const useUserTenants = () =>\n useIdentityStore((state) => ({\n tenants: state.context.tenants,\n currentTenant: state.context.currentTenant,\n }));\nexport const useUserPermissions = () => useIdentityStore((state) => state.context.permissions);\nexport const useIdentityContext = () => {\n const context = useIdentityStore((state) => state.context);\n const refreshContext = useIdentityStore((state) => state.refreshContext);\n const refreshPermissions = useIdentityStore((state) => state.refreshPermissions);\n\n return { context, refreshContext, refreshPermissions };\n};\n","/**\n * Identity Context Provider\n * \n * Initializes the identity store and listens to SignalR events.\n */\n\nimport { useEffect, type ReactNode } from 'react';\nimport { useIdentityStore } from './identity-store';\nimport type { IdentityContextData, IdentityContextChangedEvent } from './types';\n\ninterface IdentityContextProviderProps {\n children: ReactNode;\n initialContext?: Partial<IdentityContextData>;\n accessToken?: string | null;\n signalRClient?: {\n onIdentityContextChanged: (handler: (event: IdentityContextChangedEvent) => void) => () => void;\n disconnect: () => Promise<void>;\n };\n}\n\nexport function IdentityContextProvider({\n children,\n initialContext,\n accessToken,\n signalRClient,\n}: IdentityContextProviderProps) {\n const setContext = useIdentityStore((state) => state.setContext);\n const refreshContext = useIdentityStore((state) => state.refreshContext);\n const refreshPermissions = useIdentityStore((state) => state.refreshPermissions);\n const contextVersion = useIdentityStore((state) => state.context.contextVersion);\n\n // Initialize store with initial context from server\n useEffect(() => {\n if (initialContext) {\n setContext(initialContext);\n }\n }, [initialContext, setContext]);\n\n // Handle SignalR identity context changes\n useEffect(() => {\n if (!signalRClient) return;\n\n const handleContextChange = (event: IdentityContextChangedEvent) => {\n console.log('Identity context changed via SignalR:', event);\n\n if (event.contextVersion > (contextVersion ?? 0)) {\n console.log('Refreshing context due to SignalR event...');\n\n if (event.reason === 'PermissionsChanged' || event.reason === 'RoleAssignmentChanged') {\n console.log('Permission-specific change detected, refreshing permissions only...');\n refreshPermissions();\n } else {\n refreshContext();\n }\n }\n };\n\n const unsubscribe = signalRClient.onIdentityContextChanged(handleContextChange);\n\n return unsubscribe;\n }, [accessToken, contextVersion, refreshContext, refreshPermissions, signalRClient]);\n\n // Initial context load if not provided\n useEffect(() => {\n if (!initialContext) {\n refreshContext();\n }\n }, [initialContext, refreshContext]);\n\n // Cleanup SignalR on unmount\n useEffect(() => {\n return () => {\n if (signalRClient) {\n signalRClient.disconnect().catch((error) => {\n console.error('Error disconnecting SignalR:', error);\n });\n }\n };\n }, [signalRClient]);\n\n return <>{children}</>;\n}\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-DviiAkLx.js";
2
-
3
- export { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger };
@@ -1 +0,0 @@
1
- {"version":3,"file":"logging-DviiAkLx.js","names":["LOG_LEVEL_PRIORITY: Record<LogLevel, number>","defaultLoggerConfig: LoggerConfig","config?: Partial<LoggerConfig>","message: string","context?: LogContext","error?: Error","level: LogLevel","entry: LogEntry"],"sources":["../src/logging/types.ts","../src/logging/config.ts","../src/logging/logger.ts"],"sourcesContent":["/**\n * Logging Types\n */\n\nexport enum LogLevel {\n DEBUG = 'debug',\n INFO = 'info',\n WARN = 'warn',\n ERROR = 'error',\n}\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: LogContext;\n error?: Error;\n}\n\nexport interface LoggerConfig {\n minLevel: LogLevel;\n enableConsole: boolean;\n enableRemote: boolean;\n remoteEndpoint?: string;\n serviceName?: string;\n}\n","/**\n * Logger Configuration\n */\n\nimport { LogLevel, type LoggerConfig } from './types';\n\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n [LogLevel.DEBUG]: 0,\n [LogLevel.INFO]: 1,\n [LogLevel.WARN]: 2,\n [LogLevel.ERROR]: 3,\n};\n\nexport const defaultLoggerConfig: LoggerConfig = {\n minLevel: process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG,\n enableConsole: true,\n enableRemote: false,\n serviceName: 'spine',\n};\n","/**\n * Logger Service\n * \n * Centralized logging implementation with structured logging format.\n */\n\nimport { LogLevel, type LogContext, type LogEntry, type LoggerConfig } from './types';\nimport { defaultLoggerConfig, LOG_LEVEL_PRIORITY } from './config';\n\nexport class Logger {\n private config: LoggerConfig;\n\n constructor(config?: Partial<LoggerConfig>) {\n this.config = {\n ...defaultLoggerConfig,\n ...config,\n };\n }\n\n debug(message: string, context?: LogContext): void {\n this.log(LogLevel.DEBUG, message, context);\n }\n\n info(message: string, context?: LogContext): void {\n this.log(LogLevel.INFO, message, context);\n }\n\n warn(message: string, context?: LogContext): void {\n this.log(LogLevel.WARN, message, context);\n }\n\n error(message: string, error?: Error, context?: LogContext): void {\n this.log(LogLevel.ERROR, message, context, error);\n }\n\n private log(\n level: LogLevel,\n message: string,\n context?: LogContext,\n error?: Error\n ): void {\n if (!this.shouldLog(level)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n context,\n error,\n };\n\n if (this.config.enableConsole) {\n this.logToConsole(entry);\n }\n\n if (this.config.enableRemote && this.config.remoteEndpoint) {\n this.sendToRemote(entry).catch((err) => {\n console.error('Failed to send log to remote endpoint:', err);\n });\n }\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.config.minLevel];\n }\n\n private logToConsole(entry: LogEntry): void {\n const formatted = this.formatEntry(entry);\n\n switch (entry.level) {\n case LogLevel.DEBUG:\n console.debug(formatted);\n break;\n case LogLevel.INFO:\n console.info(formatted);\n break;\n case LogLevel.WARN:\n console.warn(formatted);\n break;\n case LogLevel.ERROR:\n console.error(formatted);\n if (entry.error) {\n console.error(entry.error);\n }\n break;\n }\n }\n\n private formatEntry(entry: LogEntry): string {\n const parts = [\n `[${entry.timestamp}]`,\n `[${entry.level.toUpperCase()}]`,\n entry.message,\n ];\n\n if (entry.context && Object.keys(entry.context).length > 0) {\n parts.push(JSON.stringify(entry.context));\n }\n\n return parts.join(' ');\n }\n\n private async sendToRemote(entry: LogEntry): Promise<void> {\n if (!this.config.remoteEndpoint) return;\n\n await fetch(this.config.remoteEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...entry,\n serviceName: this.config.serviceName,\n error: entry.error\n ? {\n name: entry.error.name,\n message: entry.error.message,\n stack: entry.error.stack,\n }\n : undefined,\n }),\n });\n }\n}\n\n// Default logger instance\nexport const logger = new Logger();\n\n// Create logger with custom config\nexport function createLogger(config?: Partial<LoggerConfig>): Logger {\n return new Logger(config);\n}\n"],"mappings":";;;;AAIA,IAAY,gDAAL;AACL;AACA;AACA;AACA;;AACD;;;;ACHD,MAAaA,qBAA+C;EACzD,SAAS,QAAQ;EACjB,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,SAAS,QAAQ;AACnB;AAED,MAAaC,sBAAoC;CAC/C,UAAU,QAAQ,IAAI,aAAa,eAAe,SAAS,OAAO,SAAS;CAC3E,eAAe;CACf,cAAc;CACd,aAAa;AACd;;;;ACTD,IAAa,SAAb,MAAoB;CAClB,AAAQ;CAER,YAAYC,QAAgC;AAC1C,OAAK,SAAS;GACZ,GAAG;GACH,GAAG;EACJ;CACF;CAED,MAAMC,SAAiBC,SAA4B;AACjD,OAAK,IAAI,SAAS,OAAO,SAAS,QAAQ;CAC3C;CAED,KAAKD,SAAiBC,SAA4B;AAChD,OAAK,IAAI,SAAS,MAAM,SAAS,QAAQ;CAC1C;CAED,KAAKD,SAAiBC,SAA4B;AAChD,OAAK,IAAI,SAAS,MAAM,SAAS,QAAQ;CAC1C;CAED,MAAMD,SAAiBE,OAAeD,SAA4B;AAChE,OAAK,IAAI,SAAS,OAAO,SAAS,SAAS,MAAM;CAClD;CAED,AAAQ,IACNE,OACAH,SACAC,SACAC,OACM;AACN,OAAK,KAAK,UAAU,MAAM,CACxB;EAGF,MAAME,QAAkB;GACtB,WAAW,IAAI,OAAO,aAAa;GACnC;GACA;GACA;GACA;EACD;AAED,MAAI,KAAK,OAAO,cACd,MAAK,aAAa,MAAM;AAG1B,MAAI,KAAK,OAAO,gBAAgB,KAAK,OAAO,eAC1C,MAAK,aAAa,MAAM,CAAC,MAAM,CAAC,QAAQ;AACtC,WAAQ,MAAM,0CAA0C,IAAI;EAC7D,EAAC;CAEL;CAED,AAAQ,UAAUD,OAA0B;AAC1C,SAAO,mBAAmB,UAAU,mBAAmB,KAAK,OAAO;CACpE;CAED,AAAQ,aAAaC,OAAuB;EAC1C,MAAM,YAAY,KAAK,YAAY,MAAM;AAEzC,UAAQ,MAAM,OAAd;GACE,KAAK,SAAS;AACZ,YAAQ,MAAM,UAAU;AACxB;GACF,KAAK,SAAS;AACZ,YAAQ,KAAK,UAAU;AACvB;GACF,KAAK,SAAS;AACZ,YAAQ,KAAK,UAAU;AACvB;GACF,KAAK,SAAS;AACZ,YAAQ,MAAM,UAAU;AACxB,QAAI,MAAM,MACR,SAAQ,MAAM,MAAM,MAAM;AAE5B;EACH;CACF;CAED,AAAQ,YAAYA,OAAyB;EAC3C,MAAM,QAAQ;IACX,GAAG,MAAM,UAAU;IACnB,GAAG,MAAM,MAAM,aAAa,CAAC;GAC9B,MAAM;EACP;AAED,MAAI,MAAM,WAAW,OAAO,KAAK,MAAM,QAAQ,CAAC,SAAS,EACvD,OAAM,KAAK,KAAK,UAAU,MAAM,QAAQ,CAAC;AAG3C,SAAO,MAAM,KAAK,IAAI;CACvB;CAED,MAAc,aAAaA,OAAgC;AACzD,OAAK,KAAK,OAAO,eAAgB;AAEjC,QAAM,MAAM,KAAK,OAAO,gBAAgB;GACtC,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAoB;GAC/C,MAAM,KAAK,UAAU;IACnB,GAAG;IACH,aAAa,KAAK,OAAO;IACzB,OAAO,MAAM,QACT;KACE,MAAM,MAAM,MAAM;KAClB,SAAS,MAAM,MAAM;KACrB,OAAO,MAAM,MAAM;IACpB;GAEN,EAAC;EACH,EAAC;CACH;AACF;AAGD,MAAa,SAAS,IAAI;AAG1B,SAAgB,aAAaL,QAAwC;AACnE,QAAO,IAAI,OAAO;AACnB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"permission-service-CvGpwI0d.js","names":["permissions: PermissionCode[]","permission: PermissionCode","options: PermissionCheckOptions","matchedPermissions: PermissionCode[]","missingPermissions: PermissionCode[]","pattern: PermissionCode","module: PermissionModule","entity: string"],"sources":["../src/permissions/permission-service.ts"],"sourcesContent":["/**\n * Permission Service\n * \n * Core permission checking logic.\n */\n\nimport type {\n PermissionCode,\n PermissionModule,\n PermissionCheckOptions,\n PermissionCheckResult,\n FieldPermissionResult,\n} from './types';\n\n/**\n * Permission Checker Class\n * \n * Provides methods for checking user permissions.\n */\nexport class PermissionChecker {\n private permissions: Set<PermissionCode>;\n private permissionArray: PermissionCode[];\n\n constructor(permissions: PermissionCode[]) {\n this.permissionArray = permissions;\n this.permissions = new Set(permissions);\n }\n\n /**\n * Check if user has a single permission\n */\n hasPermission(permission: PermissionCode): boolean {\n return this.permissions.has(permission);\n }\n\n /**\n * Check multiple permissions with AND/OR logic\n */\n hasPermissions(\n permissions: PermissionCode[],\n options: PermissionCheckOptions = {}\n ): PermissionCheckResult {\n const { operator = 'OR', wildcard = false } = options;\n\n const matchedPermissions: PermissionCode[] = [];\n const missingPermissions: PermissionCode[] = [];\n\n for (const permission of permissions) {\n const hasIt = wildcard\n ? this.hasWildcardPermission(permission)\n : this.hasPermission(permission);\n\n if (hasIt) {\n matchedPermissions.push(permission);\n } else {\n missingPermissions.push(permission);\n }\n }\n\n const hasPermission =\n operator === 'AND'\n ? missingPermissions.length === 0\n : matchedPermissions.length > 0;\n\n return {\n hasPermission,\n matchedPermissions,\n missingPermissions,\n };\n }\n\n /**\n * Check wildcard permission pattern\n */\n private hasWildcardPermission(pattern: PermissionCode): boolean {\n if (!pattern.includes('*')) {\n return this.hasPermission(pattern);\n }\n\n const regex = new RegExp(\n '^' + pattern.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*') + '$'\n );\n\n return this.permissionArray.some((p) => regex.test(p));\n }\n\n /**\n * Check if user has any permission in a module\n */\n hasModulePermission(module: PermissionModule): boolean {\n const prefix = `${module}.`;\n return this.permissionArray.some((p) => p.startsWith(prefix));\n }\n\n /**\n * Get field-level permissions for an entity\n */\n getFieldPermissions(\n module: PermissionModule,\n entity: string\n ): FieldPermissionResult {\n const base = `${module}.${entity}`;\n\n return {\n canView: this.hasPermission(`${base}.View`),\n canEdit: this.hasPermission(`${base}.Edit`),\n canCreate: this.hasPermission(`${base}.Create`),\n canDelete: this.hasPermission(`${base}.Delete`),\n };\n }\n\n /**\n * Get all permissions\n */\n getAllPermissions(): readonly PermissionCode[] {\n return Object.freeze([...this.permissionArray]);\n }\n\n /**\n * Get permissions for a specific module\n */\n getModulePermissions(module: PermissionModule): PermissionCode[] {\n const prefix = `${module}.`;\n return this.permissionArray.filter((p) => p.startsWith(prefix));\n }\n\n /**\n * Check if user has any permissions\n */\n hasAnyPermissions(): boolean {\n return this.permissionArray.length > 0;\n }\n\n /**\n * Get permission count\n */\n getPermissionCount(): number {\n return this.permissionArray.length;\n }\n}\n"],"mappings":";;;;;;AAmBA,IAAa,oBAAb,MAA+B;CAC7B,AAAQ;CACR,AAAQ;CAER,YAAYA,aAA+B;AACzC,OAAK,kBAAkB;AACvB,OAAK,cAAc,IAAI,IAAI;CAC5B;;;;CAKD,cAAcC,YAAqC;AACjD,SAAO,KAAK,YAAY,IAAI,WAAW;CACxC;;;;CAKD,eACED,aACAE,UAAkC,CAAE,GACb;EACvB,MAAM,EAAE,WAAW,MAAM,WAAW,OAAO,GAAG;EAE9C,MAAMC,qBAAuC,CAAE;EAC/C,MAAMC,qBAAuC,CAAE;AAE/C,OAAK,MAAM,cAAc,aAAa;GACpC,MAAM,QAAQ,WACV,KAAK,sBAAsB,WAAW,GACtC,KAAK,cAAc,WAAW;AAElC,OAAI,MACF,oBAAmB,KAAK,WAAW;OAEnC,oBAAmB,KAAK,WAAW;EAEtC;EAED,MAAM,gBACJ,aAAa,QACT,mBAAmB,WAAW,IAC9B,mBAAmB,SAAS;AAElC,SAAO;GACL;GACA;GACA;EACD;CACF;;;;CAKD,AAAQ,sBAAsBC,SAAkC;AAC9D,OAAK,QAAQ,SAAS,IAAI,CACxB,QAAO,KAAK,cAAc,QAAQ;EAGpC,MAAM,QAAQ,IAAI,OAChB,MAAM,QAAQ,QAAQ,OAAO,MAAM,CAAC,QAAQ,OAAO,KAAK,GAAG;AAG7D,SAAO,KAAK,gBAAgB,KAAK,CAAC,MAAM,MAAM,KAAK,EAAE,CAAC;CACvD;;;;CAKD,oBAAoBC,QAAmC;EACrD,MAAM,UAAU,EAAE,OAAO;AACzB,SAAO,KAAK,gBAAgB,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC;CAC9D;;;;CAKD,oBACEA,QACAC,QACuB;EACvB,MAAM,QAAQ,EAAE,OAAO,GAAG,OAAO;AAEjC,SAAO;GACL,SAAS,KAAK,eAAe,EAAE,KAAK,OAAO;GAC3C,SAAS,KAAK,eAAe,EAAE,KAAK,OAAO;GAC3C,WAAW,KAAK,eAAe,EAAE,KAAK,SAAS;GAC/C,WAAW,KAAK,eAAe,EAAE,KAAK,SAAS;EAChD;CACF;;;;CAKD,oBAA+C;AAC7C,SAAO,OAAO,OAAO,CAAC,GAAG,KAAK,eAAgB,EAAC;CAChD;;;;CAKD,qBAAqBD,QAA4C;EAC/D,MAAM,UAAU,EAAE,OAAO;AACzB,SAAO,KAAK,gBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC;CAChE;;;;CAKD,oBAA6B;AAC3B,SAAO,KAAK,gBAAgB,SAAS;CACtC;;;;CAKD,qBAA6B;AAC3B,SAAO,KAAK,gBAAgB;CAC7B;AACF"}
@@ -1,4 +0,0 @@
1
- import { PermissionChecker } from "../permission-service-CvGpwI0d.js";
2
- import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "../permissions-_n1hIG7Q.js";
3
-
4
- export { PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions };
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissions-_n1hIG7Q.js","names":["state: PermissionState","permissions: PermissionCode[]","isLoading: boolean","permission: PermissionCode","permissions: PermissionCode[]","options?: PermissionCheckOptions","module: PermissionModule","PERMISSION_MODULES: PermissionModule[]"],"sources":["../src/permissions/permission-store.ts","../src/permissions/permission-initializer.tsx","../src/permissions/hooks.ts","../src/permissions/components.tsx","../src/permissions/constants.ts"],"sourcesContent":["/**\n * Permission Store (Zustand)\n * \n * State management for permissions.\n */\n\nimport { create } from 'zustand';\nimport { PermissionChecker } from './permission-service';\nimport type { PermissionCode } from './types';\n\n/**\n * Permission store state interface\n */\ninterface PermissionState {\n checker: PermissionChecker;\n permissions: PermissionCode[];\n isLoading: boolean;\n setPermissions: (permissions: PermissionCode[]) => void;\n setLoading: (isLoading: boolean) => void;\n reset: () => void;\n}\n\n/**\n * Selectors for focused store reads\n */\nexport const selectChecker = (state: PermissionState) => state.checker;\nexport const selectPermissions = (state: PermissionState) => state.permissions;\nexport const selectIsLoading = (state: PermissionState) => state.isLoading;\n\n/**\n * Initial state\n */\nconst initialState = {\n checker: new PermissionChecker([]),\n permissions: [] as PermissionCode[],\n isLoading: false,\n};\n\n/**\n * Permission store\n */\nexport const usePermissionStore = create<PermissionState>((set) => ({\n ...initialState,\n\n setPermissions: (permissions: PermissionCode[]) => {\n set({\n permissions,\n checker: new PermissionChecker(permissions),\n });\n },\n\n setLoading: (isLoading: boolean) => {\n set({ isLoading });\n },\n\n reset: () => {\n set(initialState);\n },\n}));\n\n/**\n * Initialize permission store\n */\nexport function initializePermissions(\n permissions: PermissionCode[],\n isLoading: boolean = false\n): void {\n usePermissionStore.getState().setPermissions(permissions);\n usePermissionStore.getState().setLoading(isLoading);\n}\n\n/**\n * Get permission checker outside React components\n */\nexport function getPermissionChecker(): PermissionChecker {\n return usePermissionStore.getState().checker;\n}\n\n/**\n * Get all permissions outside React components\n */\nexport function getPermissions(): PermissionCode[] {\n return usePermissionStore.getState().permissions;\n}\n","/**\n * Permission Initializer Component\n * \n * Initializes permission store from identity context.\n */\n\nimport { useEffect, type ReactNode } from 'react';\nimport { initializePermissions } from './permission-store';\nimport type { PermissionCode } from './types';\n\ninterface PermissionInitializerProps {\n permissions: PermissionCode[];\n isLoading?: boolean;\n children: ReactNode;\n}\n\n/**\n * Component that initializes permissions from props\n */\nexport function PermissionInitializer({\n permissions,\n isLoading = false,\n children,\n}: PermissionInitializerProps) {\n useEffect(() => {\n initializePermissions(permissions, isLoading);\n }, [permissions, isLoading]);\n\n return <>{children}</>;\n}\n","/**\n * Permission Hooks\n * \n * React hooks for permission checks.\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { usePermissionStore, selectChecker } from './permission-store';\nimport type { PermissionCode, PermissionCheckOptions, PermissionModule } from './types';\n\n/**\n * Hook for checking a single permission\n */\nexport function usePermission(permission: PermissionCode): boolean {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.hasPermission(permission), [checker, permission]);\n}\n\n/**\n * Alias for usePermission\n */\nexport const useHasPermission = usePermission;\n\n/**\n * Hook for checking multiple permissions\n */\nexport function usePermissions(\n permissions: PermissionCode[],\n options?: PermissionCheckOptions\n) {\n const checker = usePermissionStore(selectChecker);\n return useMemo(\n () => checker.hasPermissions(permissions, options),\n [checker, permissions, options]\n );\n}\n\n/**\n * Alias for usePermissions\n */\nexport const useHasPermissions = usePermissions;\n\n/**\n * Hook for checking module-level permissions\n */\nexport function useModulePermission(module: PermissionModule): boolean {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.hasModulePermission(module), [checker, module]);\n}\n\n/**\n * Alias for useModulePermission\n */\nexport const useHasModulePermission = useModulePermission;\n\n/**\n * Hook for getting a memoized permission checker function\n */\nexport function usePermissionChecker() {\n const checker = usePermissionStore(selectChecker);\n return useCallback(\n (permission: PermissionCode) => checker.hasPermission(permission),\n [checker]\n );\n}\n\n/**\n * Hook for getting all user permissions\n */\nexport function useAllPermissions(): readonly PermissionCode[] {\n const checker = usePermissionStore(selectChecker);\n return useMemo(() => checker.getAllPermissions(), [checker]);\n}\n\n/**\n * Hook for field-level permissions\n */\nexport function useFieldPermissions(module: PermissionModule, entity: string) {\n const checker = usePermissionStore(selectChecker);\n return useMemo(\n () => checker.getFieldPermissions(module, entity),\n [checker, module, entity]\n );\n}\n\n/**\n * Hook for permission loading state\n */\nexport function usePermissionLoading(): boolean {\n return usePermissionStore((state) => state.isLoading);\n}\n","/**\n * Permission Components\n * \n * Protected UI components that conditionally render based on permissions.\n */\n\nimport React, { type ReactNode, type ComponentProps } from 'react';\nimport { usePermission, usePermissions } from './hooks';\nimport type { PermissionCode, PermissionCheckOptions } from './types';\n\n// ============================================================================\n// ProtectedButton\n// ============================================================================\n\ninterface ProtectedButtonProps extends ComponentProps<'button'> {\n permission: PermissionCode;\n fallback?: ReactNode;\n}\n\n/**\n * Button that only renders if user has the required permission\n */\nexport function ProtectedButton({\n permission,\n fallback = null,\n children,\n ...props\n}: ProtectedButtonProps) {\n const hasPermission = usePermission(permission);\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <button {...props}>{children}</button>;\n}\n\n// ============================================================================\n// ProtectedLink\n// ============================================================================\n\ninterface ProtectedLinkProps extends ComponentProps<'a'> {\n permission: PermissionCode;\n fallback?: ReactNode;\n}\n\n/**\n * Link that only renders if user has the required permission\n */\nexport function ProtectedLink({\n permission,\n fallback = null,\n children,\n ...props\n}: ProtectedLinkProps) {\n const hasPermission = usePermission(permission);\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <a {...props}>{children}</a>;\n}\n\n// ============================================================================\n// ProtectedSection\n// ============================================================================\n\ninterface ProtectedSectionProps {\n permission?: PermissionCode;\n permissions?: PermissionCode[];\n options?: PermissionCheckOptions;\n fallback?: ReactNode;\n children: ReactNode;\n}\n\n/**\n * Section that only renders if user has the required permission(s)\n */\nexport function ProtectedSection({\n permission,\n permissions,\n options,\n fallback = null,\n children,\n}: ProtectedSectionProps) {\n // Single permission check\n const singlePermissionResult = usePermission(permission || '');\n \n // Multiple permissions check\n const multiplePermissionsResult = usePermissions(permissions || [], options);\n\n // Determine if we should render\n let hasPermission = false;\n \n if (permission) {\n hasPermission = singlePermissionResult;\n } else if (permissions && permissions.length > 0) {\n hasPermission = multiplePermissionsResult.hasPermission;\n } else {\n // No permission specified, render by default\n hasPermission = true;\n }\n\n if (!hasPermission) {\n return <>{fallback}</>;\n }\n\n return <>{children}</>;\n}\n\n// ============================================================================\n// ProtectedAction\n// ============================================================================\n\ninterface ProtectedActionProps {\n permission: PermissionCode;\n onAction: () => void;\n children: (props: { onClick: () => void; disabled: boolean }) => ReactNode;\n}\n\n/**\n * Render prop component for protected actions\n */\nexport function ProtectedAction({\n permission,\n onAction,\n children,\n}: ProtectedActionProps) {\n const hasPermission = usePermission(permission);\n\n return (\n <>\n {children({\n onClick: hasPermission ? onAction : () => {},\n disabled: !hasPermission,\n })}\n </>\n );\n}\n","/**\n * Permission Constants\n */\n\nimport type { PermissionModule } from './types';\n\n/**\n * Available permission modules\n *\n * @deprecated Spine does not own an application's permission taxonomy. Define\n * module constants in the consuming app instead.\n */\nexport const PERMISSION_MODULES: PermissionModule[] = [];\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "../query-client-Bh3TyB8W.js";
2
-
3
- export { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers };
@@ -1 +0,0 @@
1
- {"version":3,"file":"query-client-Bh3TyB8W.js","names":["DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>>","error: unknown","returnUrl?: string","config: QueryClientConfig","cachePresets: CachePresets","module: T","filters?: Record<string, unknown>","id: string","queryClient: QueryClient","module: string","queryKey: readonly unknown[]"],"sources":["../src/query-client/query-config.ts"],"sourcesContent":["/**\n * Query Client Configuration\n * \n * Optimized caching strategy for different types of data:\n * - Static data (rarely changes): Long staleTime and gcTime\n * - Dynamic data (changes frequently): Short staleTime\n * - Critical data (needs to be fresh): Very short staleTime with prefetching\n */\n\nimport { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';\nimport type { QueryClientConfig, CachePresets } from './types';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<QueryClientConfig, 'logger' | 'onLogout'>> = {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n maxRetries: 3,\n maxMutationRetries: 1,\n refetchOnWindowFocus: true,\n refetchOnReconnect: false,\n refetchOnMount: false,\n};\n\n/**\n * Check if error is a 401 Unauthorized error\n */\nfunction is401Error(error: unknown): boolean {\n if (error instanceof Response) {\n return error.status === 401;\n }\n\n if (error && typeof error === 'object' && 'status' in error) {\n return (error as any).status === 401;\n }\n\n return false;\n}\n\n/**\n * Check if error requires logout (refresh token expired)\n */\nfunction shouldLogout(error: unknown): boolean {\n if (error && typeof error === 'object') {\n // Check for shouldLogout flag from server\n if ('shouldLogout' in error && (error as any).shouldLogout === true) {\n return true;\n }\n\n // Check for SESSION_EXPIRED code\n if ('code' in error && (error as any).code === 'SESSION_EXPIRED') {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Create default logout handler\n */\nfunction createDefaultLogoutHandler(): (returnUrl?: string) => void {\n return (returnUrl?: string) => {\n if (typeof window !== 'undefined') {\n const currentUrl = returnUrl || window.location.pathname + window.location.search;\n window.location.href = `/auth/logout?logout=local&returnUrl=${encodeURIComponent(currentUrl)}`;\n }\n };\n}\n\n/**\n * Create optimized QueryClient instance\n * \n * @param config - Configuration options\n * @returns Configured QueryClient with optimized defaults\n */\nexport function createQueryClient(config: QueryClientConfig = {}): QueryClient {\n const {\n staleTime,\n gcTime,\n maxRetries,\n maxMutationRetries,\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n logger,\n onLogout,\n } = { ...DEFAULT_CONFIG, ...config };\n\n const handleLogout = onLogout || createDefaultLogoutHandler();\n\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime,\n gcTime,\n retry: (failureCount, error) => {\n // Don't retry 401 errors - the fetch client handles refresh flows separately\n if (is401Error(error)) {\n logger?.debug?.('Query failed with 401, not retrying');\n return false;\n }\n return failureCount < maxRetries;\n },\n retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),\n refetchOnWindowFocus,\n refetchOnReconnect,\n refetchOnMount,\n },\n mutations: {\n retry: (failureCount, error) => {\n if (is401Error(error)) {\n logger?.debug?.('Mutation failed with 401, not retrying');\n return false;\n }\n return failureCount < maxMutationRetries;\n },\n retryDelay: 1000,\n },\n },\n queryCache: new QueryCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Query error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n mutationCache: new MutationCache({\n onError: (error) => {\n if (shouldLogout(error)) {\n handleLogout();\n return;\n }\n logger?.error?.('Mutation error', error instanceof Error ? error : undefined, {\n is401: is401Error(error),\n });\n },\n }),\n });\n\n return queryClient;\n}\n\n/**\n * Cache Presets for different data types\n * \n * Use these presets for consistent caching across the application.\n */\nexport const cachePresets: CachePresets = {\n /**\n * Static data - rarely changes\n * Good for: permissions, roles, system settings\n */\n static: {\n staleTime: 30 * 60 * 1000, // 30 minutes\n gcTime: 60 * 60 * 1000, // 1 hour\n refetchOnWindowFocus: false,\n },\n\n /**\n * Normal data - changes occasionally\n * Good for: resources, users, tenant data\n */\n normal: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n refetchOnWindowFocus: true,\n },\n\n /**\n * Dynamic data - changes frequently\n * Good for: transactions, activities, notifications\n */\n dynamic: {\n staleTime: 1 * 60 * 1000, // 1 minute\n gcTime: 5 * 60 * 1000, // 5 minutes\n refetchOnWindowFocus: true,\n },\n\n /**\n * Realtime data - always fresh\n * Good for: live status, counters, dashboards\n */\n realtime: {\n staleTime: 0, // Always stale\n gcTime: 1 * 60 * 1000, // 1 minute\n refetchOnWindowFocus: true,\n },\n};\n\n/**\n * Create base query keys for a module\n */\nexport function createQueryKeyFactory<T extends string>(\n module: T\n): {\n all: readonly [T];\n lists: () => readonly [T, 'list'];\n list: (filters?: Record<string, unknown>) => readonly [T, 'list', Record<string, unknown>?];\n details: () => readonly [T, 'detail'];\n detail: (id: string) => readonly [T, 'detail', string];\n} {\n return {\n all: [module] as const,\n lists: () => [module, 'list'] as const,\n list: (filters?: Record<string, unknown>) => [module, 'list', filters] as const,\n details: () => [module, 'detail'] as const,\n detail: (id: string) => [module, 'detail', id] as const,\n };\n}\n\n/**\n * Invalidation helpers for common patterns\n */\nexport const invalidationHelpers = {\n /**\n * Invalidate all queries for a module\n */\n invalidateModule: async (queryClient: QueryClient, module: string): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module] });\n },\n\n /**\n * Invalidate all list queries for a module\n */\n invalidateLists: async (queryClient: QueryClient, module: string): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module, 'list'] });\n },\n\n /**\n * Invalidate a specific detail query\n */\n invalidateDetail: async (\n queryClient: QueryClient,\n module: string,\n id: string\n ): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey: [module, 'detail', id] });\n },\n\n /**\n * Invalidate and refetch (useful for critical data)\n */\n invalidateAndRefetch: async (queryClient: QueryClient, queryKey: readonly unknown[]): Promise<void> => {\n await queryClient.invalidateQueries({ queryKey });\n await queryClient.refetchQueries({ queryKey });\n },\n};\n"],"mappings":"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"}
@@ -1,20 +0,0 @@
1
- import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "../logging-DviiAkLx.js";
2
- import { PermissionChecker } from "../permission-service-CvGpwI0d.js";
3
- import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "../permissions-_n1hIG7Q.js";
4
- import { evaluateAccessRequirement } from "../access-CGrsDMwu.js";
5
- import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "../tenant-DfLemb6n.js";
6
- import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "../identity-C5un-xAs.js";
7
- import { isApiClientError } from "../types-BaaE6wjY.js";
8
- import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "../api-client-CgZUMukO.js";
9
- import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "../signalr-k-70VHKo.js";
10
- import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "../query-client-Bh3TyB8W.js";
11
- import "../src-B9rDUgSb.js";
12
- import { AUTH_ERROR_COOKIE_PREFIX, cleanupExpiredOAuthStates, closeRedisConnection, createAuthSession, createOAuthState, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, getAuthSession, getAuthSessionById, getCurrentAuthSessionId, getOAuthState, isSessionValid, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, requireAuthSession, updateAuthSession } from "../redis-session-storage.server-v2VcpFsQ.js";
13
- import { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, extractUserInfo, getAccessToken, getAuthError, getUser, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping } from "../auth.server-D5oiz9aF.js";
14
- import { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute } from "../server-B6PCxc-T.js";
15
- import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "../server-Ca-3DJf2.js";
16
- import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../server-Dxsww9v9.js";
17
- import { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission } from "../server-hGrElJMG.js";
18
- import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "../server-C96NMFBy.js";
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, 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 +0,0 @@
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,304 +0,0 @@
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
-
5
- //#region src/auth/route-protection.server.ts
6
- const REFRESH_THRESHOLD = resolveRefreshThreshold();
7
- function resolveRefreshThreshold() {
8
- const configured = Number(process.env.OIDC_REFRESH_THRESHOLD_MS);
9
- return Number.isFinite(configured) && configured > 0 ? configured : 6e4;
10
- }
11
- /**
12
- * Check if token needs refresh
13
- */
14
- async function shouldRefreshToken(request) {
15
- try {
16
- const sessionData = await getAuthSession(request);
17
- if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {
18
- if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {
19
- logger.info("Token expired and no refresh token, forcing local application logout");
20
- const { logout } = await import("./auth.server-lC94lmlx.js");
21
- const logoutRequest = await createAutomaticLogoutRequest(request, "expired-no-refresh-token");
22
- throw await logout(logoutRequest);
23
- }
24
- return false;
25
- }
26
- if (!sessionData.expiresAt || !sessionData.accessToken) return false;
27
- const now = Date.now();
28
- let jwtExpiry = null;
29
- try {
30
- const tokenParts = sessionData.accessToken.split(".");
31
- if (tokenParts.length === 3) {
32
- const payload = JSON.parse(Buffer.from(tokenParts[1], "base64").toString());
33
- if (payload.exp) jwtExpiry = payload.exp * 1e3;
34
- }
35
- } catch {}
36
- const earliestExpiry = jwtExpiry && jwtExpiry < sessionData.expiresAt ? jwtExpiry : sessionData.expiresAt;
37
- const timeUntilEarliestExpiry = earliestExpiry - now;
38
- return timeUntilEarliestExpiry <= REFRESH_THRESHOLD;
39
- } catch (error) {
40
- logger.error("Error checking token refresh", error instanceof Error ? error : void 0);
41
- return false;
42
- }
43
- }
44
- /**
45
- * Auto-refresh tokens if needed
46
- */
47
- async function autoRefreshTokens(request) {
48
- try {
49
- const needsRefresh = await shouldRefreshToken(request);
50
- if (needsRefresh) {
51
- logger.info("Auto-refreshing tokens due to expiry");
52
- const result = await refreshTokens(request);
53
- if (result.success) logger.info("Auto-refresh completed successfully");
54
- else {
55
- logger.error("Auto-refresh failed", void 0, { error: result.error });
56
- if (result.shouldLogout) {
57
- logger.error("Refresh token invalid, forcing local application logout");
58
- const { logout } = await import("./auth.server-lC94lmlx.js");
59
- const logoutRequest = await createAutomaticLogoutRequest(request, "refresh-failed");
60
- throw await logout(logoutRequest);
61
- }
62
- }
63
- }
64
- } catch (error) {
65
- if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
66
- throw error;
67
- }
68
- }
69
- let protectionConfig = {};
70
- /**
71
- * Configure route protection with identity, tenant, and app policy checkers
72
- */
73
- function configureRouteProtection(config) {
74
- protectionConfig = config;
75
- }
76
- /**
77
- * Reset route protection to its built-in defaults.
78
- */
79
- function resetRouteProtectionConfig() {
80
- protectionConfig = {};
81
- }
82
- function createRedirectFromConfig(redirect) {
83
- return createRedirectResponse(redirect.location, {
84
- headers: redirect.headers,
85
- status: redirect.status
86
- });
87
- }
88
- function getLoginReturnUrl(request, currentPath, protection) {
89
- if (protectionConfig.getLoginReturnUrl) return protectionConfig.getLoginReturnUrl({
90
- request,
91
- currentPath,
92
- protection
93
- });
94
- switch (protection) {
95
- case "onboarding-required":
96
- case "subscription-required":
97
- case "public":
98
- case "auth":
99
- default: {
100
- const url = new URL(request.url);
101
- return url.pathname === "/" ? "/" : `${url.pathname}${url.search}`;
102
- }
103
- }
104
- }
105
- async function createAutomaticLogoutRequest(request, reason) {
106
- if (protectionConfig.createAutomaticLogoutRequest) return protectionConfig.createAutomaticLogoutRequest({
107
- request,
108
- reason
109
- });
110
- const logoutUrl = new URL(request.url);
111
- logoutUrl.pathname = "/auth/logout";
112
- logoutUrl.searchParams.set("logout", "local");
113
- return new Request(logoutUrl.toString(), { headers: request.headers });
114
- }
115
- function createRouteResolverContext(request, user, currentPath, protection) {
116
- let identityContextPromise = null;
117
- let currentTenantPromise = null;
118
- let initializeTenantPromise = null;
119
- return {
120
- request,
121
- user,
122
- currentPath,
123
- protection,
124
- getIdentityContext: async (forceRefresh = false) => {
125
- if (!protectionConfig.identityChecker) return null;
126
- if (forceRefresh) return protectionConfig.identityChecker.getIdentityContext(request, user.sub, true);
127
- if (!identityContextPromise) identityContextPromise = protectionConfig.identityChecker.getIdentityContext(request, user.sub);
128
- return identityContextPromise;
129
- },
130
- getCurrentTenant: async () => {
131
- if (!protectionConfig.tenantChecker) return null;
132
- if (!currentTenantPromise) currentTenantPromise = protectionConfig.tenantChecker.getCurrentTenant(request);
133
- return currentTenantPromise;
134
- },
135
- initializeTenant: async () => {
136
- if (!protectionConfig.tenantChecker) return null;
137
- if (!initializeTenantPromise) initializeTenantPromise = protectionConfig.tenantChecker.initializeTenant(request);
138
- return initializeTenantPromise;
139
- }
140
- };
141
- }
142
- async function applyConfiguredRouteResolution(request, user, currentPath, protection) {
143
- if (!protectionConfig.resolveRoute) return false;
144
- const context = createRouteResolverContext(request, user, currentPath, protection);
145
- try {
146
- const result = await protectionConfig.resolveRoute(context);
147
- if (result) throw createRedirectFromConfig(result);
148
- return true;
149
- } catch (error) {
150
- if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
151
- if (!protectionConfig.handleRouteError) throw error;
152
- const recovery = await protectionConfig.handleRouteError({
153
- ...context,
154
- error
155
- });
156
- if (recovery) throw createRedirectFromConfig(recovery);
157
- throw error;
158
- }
159
- }
160
- /**
161
- * Main route protection function
162
- */
163
- async function protectRoute(request, protection, loaderFn) {
164
- const url = new URL(request.url);
165
- switch (protection) {
166
- case "public": try {
167
- await autoRefreshTokens(request);
168
- const user = await getUser(request);
169
- return loaderFn(user ?? void 0);
170
- } catch {
171
- return loaderFn(void 0);
172
- }
173
- case "auth":
174
- case "policy": try {
175
- await autoRefreshTokens(request);
176
- const user = await requireAuth(request);
177
- await applyConfiguredRouteResolution(request, user, url.pathname, protection);
178
- return loaderFn(user);
179
- } catch (error) {
180
- if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
181
- throw await login(request, getLoginReturnUrl(request, url.pathname, protection));
182
- }
183
- case "onboarding-required": try {
184
- await autoRefreshTokens(request);
185
- const user = await requireAuth(request);
186
- await applyConfiguredRouteResolution(request, user, url.pathname, "onboarding-required");
187
- return loaderFn(user);
188
- } catch (error) {
189
- if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
190
- throw await login(request, getLoginReturnUrl(request, url.pathname, "onboarding-required"));
191
- }
192
- case "subscription-required": try {
193
- await autoRefreshTokens(request);
194
- const user = await requireAuth(request);
195
- await applyConfiguredRouteResolution(request, user, url.pathname, "subscription-required");
196
- if (protectionConfig.tenantChecker) {
197
- const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);
198
- if (!currentTenant) await protectionConfig.tenantChecker.initializeTenant(request);
199
- }
200
- return loaderFn(user);
201
- } catch (error) {
202
- if (error instanceof Response && error.status >= 300 && error.status < 400) throw error;
203
- throw await login(request, getLoginReturnUrl(request, url.pathname, "subscription-required"));
204
- }
205
- default: throw new Error(`Unknown protection level: ${protection}`);
206
- }
207
- }
208
- /**
209
- * Public route (optional auth)
210
- */
211
- const publicRoute = (request, loaderFn) => protectRoute(request, "public", loaderFn);
212
- /**
213
- * Auth-protected route
214
- */
215
- const authRoute = (request, loaderFn) => protectRoute(request, "auth", loaderFn);
216
- /**
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.
228
- */
229
- const onboardingRoute = (request, loaderFn) => protectRoute(request, "onboarding-required", loaderFn);
230
- /**
231
- * Entitlement policy route
232
- *
233
- * @deprecated Use policyRoute with configureRouteProtection instead.
234
- */
235
- const subscriptionRoute = (request, loaderFn) => protectRoute(request, "subscription-required", loaderFn);
236
-
237
- //#endregion
238
- //#region src/auth/token-refresh.server.ts
239
- /**
240
- * Attempt to refresh the access token
241
- */
242
- async function attemptTokenRefresh(request) {
243
- try {
244
- logger.info("Attempting token refresh due to 401 response");
245
- const sessionData = await getAuthSession(request);
246
- if (!sessionData.refreshToken) {
247
- logger.warn("No refresh token available in session");
248
- return {
249
- success: false,
250
- error: "No refresh token available",
251
- shouldLogout: true
252
- };
253
- }
254
- const refreshResult = await refreshTokens(request, sessionData.refreshToken);
255
- if (!refreshResult.success) {
256
- logger.error("Token refresh failed", void 0, {
257
- error: refreshResult.error,
258
- shouldLogout: refreshResult.shouldLogout
259
- });
260
- return {
261
- success: false,
262
- error: refreshResult.error,
263
- shouldLogout: refreshResult.shouldLogout
264
- };
265
- }
266
- const tokens = refreshResult.tokens;
267
- if (!tokens || !tokens.access_token) {
268
- logger.error("Token refresh succeeded but no access token returned");
269
- return {
270
- success: false,
271
- error: "No access token in refresh response",
272
- shouldLogout: true
273
- };
274
- }
275
- logger.info("Token refresh successful");
276
- return {
277
- success: true,
278
- newAccessToken: tokens.access_token
279
- };
280
- } catch (error) {
281
- logger.error("Token refresh attempt failed", error instanceof Error ? error : void 0);
282
- return {
283
- success: false,
284
- error: error instanceof Error ? error.message : "Unknown error",
285
- shouldLogout: true
286
- };
287
- }
288
- }
289
- /**
290
- * Check if a response indicates token expiration
291
- */
292
- function isTokenExpiredResponse(response) {
293
- return response.status === 401;
294
- }
295
- /**
296
- * Check if token refresh should be attempted
297
- */
298
- function shouldAttemptTokenRefresh(response, hasAuth) {
299
- return hasAuth && isTokenExpiredResponse(response);
300
- }
301
-
302
- //#endregion
303
- export { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute };
304
- //# sourceMappingURL=server-B6PCxc-T.js.map