@eminuckan/spine 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/CODE_OF_CONDUCT.md +39 -0
  3. package/CONTRIBUTING.md +78 -0
  4. package/MAINTAINERS.md +43 -0
  5. package/README.md +170 -135
  6. package/ROADMAP.md +58 -0
  7. package/SECURITY.md +37 -0
  8. package/dist/access/evaluator.d.ts.map +1 -1
  9. package/dist/access/index.js +1 -1
  10. package/dist/access/types.d.ts +10 -2
  11. package/dist/access/types.d.ts.map +1 -1
  12. package/dist/{access-DlAqsVGQ.js → access-CGrsDMwu.js} +9 -7
  13. package/dist/access-CGrsDMwu.js.map +1 -0
  14. package/dist/api-client/api-config.server.d.ts +27 -0
  15. package/dist/api-client/api-config.server.d.ts.map +1 -1
  16. package/dist/api-client/error-codes.d.ts +6 -31
  17. package/dist/api-client/error-codes.d.ts.map +1 -1
  18. package/dist/api-client/index.js +2 -2
  19. package/dist/api-client/server.d.ts +1 -1
  20. package/dist/api-client/server.d.ts.map +1 -1
  21. package/dist/api-client/server.js +3 -3
  22. package/dist/{api-client-CZapXIF8.js → api-client-CgZUMukO.js} +9 -26
  23. package/dist/api-client-CgZUMukO.js.map +1 -0
  24. package/dist/auth/auth.server.d.ts.map +1 -1
  25. package/dist/auth/authorization-parameters.d.ts +5 -0
  26. package/dist/auth/authorization-parameters.d.ts.map +1 -0
  27. package/dist/auth/route-protection.server.d.ts +20 -4
  28. package/dist/auth/route-protection.server.d.ts.map +1 -1
  29. package/dist/auth/server.d.ts +1 -1
  30. package/dist/auth/server.d.ts.map +1 -1
  31. package/dist/auth/server.js +5 -5
  32. package/dist/auth/types.d.ts +24 -2
  33. package/dist/auth/types.d.ts.map +1 -1
  34. package/dist/{auth.server-B3ODGIX4.js → auth.server-D5oiz9aF.js} +62 -53
  35. package/dist/auth.server-D5oiz9aF.js.map +1 -0
  36. package/dist/{auth.server-wdKsjQsn.js → auth.server-lC94lmlx.js} +3 -3
  37. package/dist/identity/identity-context.server.d.ts +2 -0
  38. package/dist/identity/identity-context.server.d.ts.map +1 -1
  39. package/dist/identity/identity-store.d.ts +19 -2
  40. package/dist/identity/identity-store.d.ts.map +1 -1
  41. package/dist/identity/index.js +2 -2
  42. package/dist/identity/server.js +2 -2
  43. package/dist/identity/types.d.ts +3 -1
  44. package/dist/identity/types.d.ts.map +1 -1
  45. package/dist/{identity-yT2ijZF0.js → identity-C5un-xAs.js} +104 -60
  46. package/dist/identity-C5un-xAs.js.map +1 -0
  47. package/dist/index.js +8 -8
  48. package/dist/logging/index.js +1 -1
  49. package/dist/{logging-_h2mi3L9.js → logging-DviiAkLx.js} +2 -2
  50. package/dist/{logging-_h2mi3L9.js.map → logging-DviiAkLx.js.map} +1 -1
  51. package/dist/permissions/constants.d.ts +3 -0
  52. package/dist/permissions/constants.d.ts.map +1 -1
  53. package/dist/permissions/index.js +1 -1
  54. package/dist/permissions/permission-store.d.ts +1 -1
  55. package/dist/permissions/types.d.ts +1 -1
  56. package/dist/permissions/types.d.ts.map +1 -1
  57. package/dist/{permissions-OUK8Eql2.js → permissions-_n1hIG7Q.js} +6 -12
  58. package/dist/permissions-_n1hIG7Q.js.map +1 -0
  59. package/dist/query-client/types.d.ts +9 -9
  60. package/dist/query-client/types.d.ts.map +1 -1
  61. package/dist/query-client-Bh3TyB8W.js.map +1 -1
  62. package/dist/react-router/index.js +8 -8
  63. package/dist/react-router/server.js +14 -14
  64. package/dist/{redis-session-storage.server-D0-72FV-.js → redis-session-storage.server-v2VcpFsQ.js} +2 -2
  65. package/dist/{redis-session-storage.server-D0-72FV-.js.map → redis-session-storage.server-v2VcpFsQ.js.map} +1 -1
  66. package/dist/{server-B7JN86_0.js → server-B6PCxc-T.js} +31 -83
  67. package/dist/server-B6PCxc-T.js.map +1 -0
  68. package/dist/{server-DeBlxoaW.js → server-C96NMFBy.js} +17 -9
  69. package/dist/server-C96NMFBy.js.map +1 -0
  70. package/dist/{server-Ce9jQRdL.js → server-Ca-3DJf2.js} +44 -11
  71. package/dist/server-Ca-3DJf2.js.map +1 -0
  72. package/dist/{server-BmiuV28c.js → server-Dxsww9v9.js} +5 -4
  73. package/dist/server-Dxsww9v9.js.map +1 -0
  74. package/dist/server.js +14 -14
  75. package/dist/signalr/index.js +1 -1
  76. package/dist/signalr/types.d.ts +1 -1
  77. package/dist/{signalr-CSfCz1Ik.js → signalr-k-70VHKo.js} +3 -2
  78. package/dist/signalr-k-70VHKo.js.map +1 -0
  79. package/dist/tenant/index.js +2 -2
  80. package/dist/tenant/server.js +4 -4
  81. package/dist/tenant/tenant-context.d.ts +17 -1
  82. package/dist/tenant/tenant-context.d.ts.map +1 -1
  83. package/dist/tenant/tenant-cookie.server.d.ts.map +1 -1
  84. package/dist/tenant/tenant-store.d.ts +54 -9
  85. package/dist/tenant/tenant-store.d.ts.map +1 -1
  86. package/dist/tenant/tenant.server.d.ts +20 -1
  87. package/dist/tenant/tenant.server.d.ts.map +1 -1
  88. package/dist/tenant/types.d.ts +29 -9
  89. package/dist/tenant/types.d.ts.map +1 -1
  90. package/dist/tenant-DfLemb6n.js +384 -0
  91. package/dist/tenant-DfLemb6n.js.map +1 -0
  92. package/docs/adapters.md +87 -0
  93. package/docs/architecture.md +193 -0
  94. package/docs/backend-adaptation.md +249 -0
  95. package/docs/codex-for-oss-application.md +39 -0
  96. package/docs/codex-maintenance.md +47 -0
  97. package/docs/installation.md +213 -0
  98. package/docs/module-reference.md +207 -0
  99. package/docs/releasing.md +92 -0
  100. package/package.json +21 -3
  101. package/dist/access-DlAqsVGQ.js.map +0 -1
  102. package/dist/api-client-CZapXIF8.js.map +0 -1
  103. package/dist/auth.server-B3ODGIX4.js.map +0 -1
  104. package/dist/identity-yT2ijZF0.js.map +0 -1
  105. package/dist/permissions-OUK8Eql2.js.map +0 -1
  106. package/dist/server-B7JN86_0.js.map +0 -1
  107. package/dist/server-BmiuV28c.js.map +0 -1
  108. package/dist/server-Ce9jQRdL.js.map +0 -1
  109. package/dist/server-DeBlxoaW.js.map +0 -1
  110. package/dist/signalr-CSfCz1Ik.js.map +0 -1
  111. package/dist/tenant-BErqEENp.js +0 -256
  112. package/dist/tenant-BErqEENp.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.server-B3ODGIX4.js","names":["location: string","init: RedirectResponseInit","config: AuthConfig","applicationType: ApplicationType","value: string | undefined","fallback: OidcClientAuthMethod","OIDC_CONFIG: AuthConfig | null","authServerCache: {\n configuration: oidc.Configuration;\n cachedAt: number;\n} | null","token: string | undefined","idToken: string | undefined","expectedIssuer: string","returnUrl: string | null","request: Request","returnUrl: string","action: string | null | undefined","status: string | null | undefined","path: string","authority: string","params: LoginOptions['extraAuthParams'] | undefined","sanitized: Record<string, string>","key: string","baseState: string","extraParams: Record<string, string>","context: Record<string, string>","error: unknown","url: URL","redirectUrl: string","configuration: oidc.Configuration","tokenTypeHint: 'access_token' | 'refresh_token'","sessionData: SessionData","userId: string","tokenResult: OidcTokenResponse","jwksUri: string","payload: Record<string, unknown>","clientId: string","DEFAULT_AUTH_CLAIM_MAPPING: Required<AuthClaimMapping>","authClaimMapping: Required<AuthClaimMapping>","keys?: string[]","mapping: AuthClaimMapping","claims: Record<string, unknown>","claimKeys: string[]","value: string","value: unknown","tenants: string[]","tenantRoles: Record<string, string[]>","parsedRoles: unknown","baseUser: UserInfo","returnUrlOrOptions?: string | LoginOptions","options: LoginOptions","headers","oauthState: OAuthState","authorizationParameters: Record<string, string>","cookies","oauthStateRegex","cookieMatch","stateId","oauthState","stateAge","redirectUrl","redirectUri","baseUrl","logoutParameters: Record<string, string>","result: BackChannelLogoutResult","result: FrontChannelLogoutResult","refreshToken?: string"],"sources":["../src/http/response.ts","../src/auth/auth.server.ts"],"sourcesContent":["/**\n * HTTP response helpers that stay framework-agnostic.\n */\n\nexport interface RedirectResponseInit {\n headers?: HeadersInit;\n status?: number;\n}\n\n/**\n * Create a redirect response without relying on a router/framework helper.\n */\nexport function createRedirectResponse(\n location: string,\n init: RedirectResponseInit = {}\n): Response {\n const headers = new Headers(init.headers);\n headers.set('Location', location);\n\n return new Response(null, {\n status: init.status ?? 302,\n headers,\n });\n}\n","/**\n * OpenID Connect Authentication Server Module\n *\n * Handles:\n * - Login flow (PKCE)\n * - Token exchange\n * - Token refresh\n * - Logout\n * - Session management\n */\n\nimport * as oidc from 'openid-client';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\n\nimport {\n createAuthSession,\n getAuthSession,\n updateAuthSession,\n destroyAuthSession,\n isSessionValid,\n listAuthSessionDataForUser,\n destroyAuthSessionsByIdentitySession,\n destroyAuthSessionsBySid,\n destroyAuthSessionsForUser,\n createOAuthState,\n getOAuthState,\n deleteOAuthState,\n AUTH_ERROR_COOKIE_PREFIX,\n} from './redis-session-storage.server';\nimport type { \n AuthConfig, \n AuthClaimMapping,\n UserInfo, \n SessionData, \n OAuthState,\n TokenRefreshResult,\n LoginOptions,\n ApplicationType,\n OidcClientAuthMethod,\n BackChannelLogoutResult,\n FrontChannelLogoutResult,\n AuthError\n} from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\ntype OidcTokenResponse = oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\ntype LogoutScope = 'identity' | 'local' | 'all';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Get effective hasLandingPage setting based on config\n */\nfunction getEffectiveHasLandingPage(config: AuthConfig): boolean {\n // Explicit setting takes precedence\n if (config.hasLandingPage !== undefined) {\n return config.hasLandingPage;\n }\n \n // Determine by application type\n switch (config.applicationType) {\n case 'no-landing-page':\n case 'dashboard':\n return false; // Dashboard has no landing page\n case 'landing-page':\n case 'tenant-app':\n return true; // Tenant app has landing page\n default:\n return true; // Default to having a landing page\n }\n}\n\n/**\n * Create OAuth configuration from environment variables\n */\nfunction createAuthConfig(): AuthConfig {\n const requiredEnvVars = {\n authority: process.env.OIDC_AUTHORITY,\n clientId: process.env.OIDC_CLIENT_ID,\n redirectUri: process.env.OIDC_REDIRECT_URI,\n };\n\n for (const [key, value] of Object.entries(requiredEnvVars)) {\n if (!value) {\n throw new Error(`Missing required environment variable: OIDC_${key.toUpperCase()}`);\n }\n }\n\n // Parse application type from env\n const appTypeEnv = process.env.OIDC_APPLICATION_TYPE?.toLowerCase();\n let applicationType: ApplicationType = 'custom';\n if (appTypeEnv === 'no-landing-page' || appTypeEnv === 'no_landing_page') {\n applicationType = 'no-landing-page';\n } else if (appTypeEnv === 'landing-page' || appTypeEnv === 'landing_page') {\n applicationType = 'landing-page';\n } else if (appTypeEnv === 'dashboard') {\n applicationType = 'dashboard';\n } else if (appTypeEnv === 'tenant-app' || appTypeEnv === 'tenant_app') {\n applicationType = 'tenant-app';\n }\n\n const clientAuthMethod = parseOidcClientAuthMethod(\n process.env.OIDC_CLIENT_AUTH_METHOD,\n process.env.OIDC_CLIENT_SECRET ? 'client_secret_post' : 'none'\n );\n\n const hasLandingPage = process.env.OIDC_HAS_LANDING_PAGE !== undefined\n ? process.env.OIDC_HAS_LANDING_PAGE === 'true'\n : undefined;\n\n return {\n authority: requiredEnvVars.authority!,\n clientId: requiredEnvVars.clientId!,\n clientSecret: process.env.OIDC_CLIENT_SECRET,\n clientAuthMethod,\n redirectUri: requiredEnvVars.redirectUri!,\n scope: process.env.OIDC_SCOPE || 'openid profile email api',\n postLogoutRedirectUri: process.env.OIDC_POST_LOGOUT_REDIRECT_URI,\n applicationType,\n hasLandingPage,\n };\n}\n\nfunction parseOidcClientAuthMethod(\n value: string | undefined,\n fallback: OidcClientAuthMethod\n): OidcClientAuthMethod {\n const normalized = value?.trim().toLowerCase();\n\n switch (normalized) {\n case undefined:\n case '':\n return fallback;\n case 'none':\n return 'none';\n case 'client_secret_post':\n case 'post':\n return 'client_secret_post';\n case 'client_secret_basic':\n case 'basic':\n return 'client_secret_basic';\n default:\n throw new Error(`Unsupported OIDC_CLIENT_AUTH_METHOD: ${value}`);\n }\n}\n\nlet OIDC_CONFIG: AuthConfig | null = null;\n\nfunction getAuthConfig(): AuthConfig {\n if (!OIDC_CONFIG) {\n OIDC_CONFIG = createAuthConfig();\n }\n return OIDC_CONFIG;\n}\n\n// ============================================================================\n// OAuth Discovery Cache\n// ============================================================================\n\nlet authServerCache: {\n configuration: oidc.Configuration;\n cachedAt: number;\n} | null = null;\n\nconst CACHE_TTL = 60 * 60 * 1000; // 1 hour\nconst jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\nconst RESERVED_AUTHORIZATION_PARAMETER_NAMES = new Set([\n 'client_id',\n 'code_challenge',\n 'code_challenge_method',\n 'kc_action',\n 'nonce',\n 'prompt',\n 'redirect_uri',\n 'response_mode',\n 'response_type',\n 'scope',\n 'state',\n]);\nconst ALLOWED_EXTRA_AUTHORIZATION_PARAMETER_NAMES = new Set([\n 'acr_values',\n 'kc_idp_hint',\n 'login_hint',\n 'ui_locales',\n]);\n\nfunction normalizeIssuer(value: string | undefined): string | null {\n const normalized = value?.trim().replace(/\\/+$/, '');\n return normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction getJwtClaims(token: string | undefined): Record<string, unknown> | null {\n if (!token) {\n return null;\n }\n\n try {\n const [, payload] = token.split('.');\n if (!payload) {\n return null;\n }\n\n return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction getJwtIssuer(token: string | undefined): string | null {\n const issuer = getJwtClaims(token)?.iss;\n return typeof issuer === 'string' ? normalizeIssuer(issuer) : null;\n}\n\nfunction canUseIdTokenHint(idToken: string | undefined, expectedIssuer: string): boolean {\n if (!idToken) {\n return false;\n }\n\n const tokenIssuer = getJwtIssuer(idToken);\n return tokenIssuer !== null && tokenIssuer === normalizeIssuer(expectedIssuer);\n}\n\nfunction sanitizeLogoutReturnUrl(returnUrl: string | null, request: Request): string | null {\n if (!returnUrl) {\n return null;\n }\n\n try {\n const requestUrl = new URL(request.url);\n const decodedReturnUrl = decodeURIComponent(returnUrl);\n\n if (decodedReturnUrl.startsWith('/') && !decodedReturnUrl.startsWith('//')) {\n return isAuthLoopPath(decodedReturnUrl) ? null : decodedReturnUrl;\n }\n\n const candidateUrl = new URL(decodedReturnUrl);\n if (candidateUrl.origin !== requestUrl.origin) {\n return null;\n }\n\n const sameOriginPath = `${candidateUrl.pathname}${candidateUrl.search}${candidateUrl.hash}`;\n return isAuthLoopPath(sameOriginPath) ? null : sameOriginPath;\n } catch {\n return null;\n }\n}\n\nfunction appendApplicationInitiatedActionResult(\n returnUrl: string,\n action: string | null | undefined,\n status: string | null | undefined\n): string {\n if (!action && !status) {\n return returnUrl;\n }\n\n try {\n const isRelative = returnUrl.startsWith('/') && !returnUrl.startsWith('//');\n const url = new URL(returnUrl, isRelative ? 'http://spine.local' : undefined);\n\n if (action) {\n url.searchParams.set('kc_action', action);\n }\n\n if (status) {\n url.searchParams.set('kc_action_status', status);\n }\n\n return isRelative ? `${url.pathname}${url.search}${url.hash}` : url.toString();\n } catch {\n const params = new URLSearchParams();\n if (action) params.set('kc_action', action);\n if (status) params.set('kc_action_status', status);\n const separator = returnUrl.includes('?') ? '&' : '?';\n return `${returnUrl}${separator}${params.toString()}`;\n }\n}\n\nfunction isAuthLoopPath(path: string): boolean {\n const normalized = path.split('#')[0].split('?')[0].replace(/\\/+$/, '') || '/';\n return normalized === '/auth/logout' || normalized === '/auth/callback';\n}\n\nfunction isLocalInsecureIssuer(authority: string): boolean {\n try {\n const issuer = new URL(authority);\n return (\n issuer.protocol === 'http:' &&\n ['localhost', '127.0.0.1', '::1', '[::1]'].includes(issuer.hostname)\n );\n } catch {\n return false;\n }\n}\n\nfunction shouldAllowInsecureOidcRequests(authority: string): boolean {\n if (process.env.NODE_ENV === 'production') {\n return false;\n }\n\n if (process.env.OIDC_ALLOW_INSECURE_REQUESTS === 'true') {\n return true;\n }\n\n return isLocalInsecureIssuer(authority);\n}\n\nfunction getDiscoveryOptions(): oidc.DiscoveryRequestOptions {\n const config = getAuthConfig();\n\n return shouldAllowInsecureOidcRequests(config.authority)\n ? { algorithm: 'oidc', execute: [oidc.allowInsecureRequests] }\n : { algorithm: 'oidc' };\n}\n\nfunction getClientMetadata(config: AuthConfig): string | undefined {\n return config.clientAuthMethod === 'none' ? undefined : config.clientSecret;\n}\n\nfunction sanitizeExtraAuthorizationParameters(\n params: LoginOptions['extraAuthParams'] | undefined\n): Record<string, string> {\n if (!params) {\n return {};\n }\n\n const sanitized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(params)) {\n const normalizedKey = key.trim();\n if (\n normalizedKey.length === 0 ||\n RESERVED_AUTHORIZATION_PARAMETER_NAMES.has(normalizedKey.toLowerCase()) ||\n !isAllowedExtraAuthorizationParameter(normalizedKey) ||\n value === null ||\n value === undefined\n ) {\n continue;\n }\n\n const normalizedValue = String(value).trim();\n if (normalizedValue.length === 0) {\n continue;\n }\n\n sanitized[normalizedKey] = normalizedValue;\n }\n\n return sanitized;\n}\n\nfunction isAllowedExtraAuthorizationParameter(key: string): boolean {\n const normalized = key.toLowerCase();\n return (\n ALLOWED_EXTRA_AUTHORIZATION_PARAMETER_NAMES.has(normalized) ||\n normalized.startsWith('uf_')\n );\n}\n\nfunction buildAuthorizationState(baseState: string, extraParams: Record<string, string>): string {\n const uiContext = buildPublicAuthorizationUiContext(extraParams);\n if (!uiContext) {\n return baseState;\n }\n\n return `${baseState}.${Buffer.from(JSON.stringify(uiContext), 'utf8').toString('base64url')}`;\n}\n\nfunction buildPublicAuthorizationUiContext(\n extraParams: Record<string, string>\n): Record<string, string> | null {\n const inviteKind = extraParams.uf_invite;\n if (inviteKind !== 'organization') {\n return null;\n }\n\n const context: Record<string, string> = {\n uf_invite: inviteKind,\n };\n\n if (extraParams.uf_invite_mode) {\n context.uf_invite_mode = extraParams.uf_invite_mode;\n }\n\n if (extraParams.login_hint) {\n context.login_hint = extraParams.login_hint;\n }\n\n return context;\n}\n\nfunction getClientAuthentication(config: AuthConfig): oidc.ClientAuth {\n switch (config.clientAuthMethod) {\n case 'client_secret_basic':\n if (!config.clientSecret) {\n throw new Error('OIDC_CLIENT_SECRET is required for client_secret_basic authentication');\n }\n\n return oidc.ClientSecretBasic(config.clientSecret);\n case 'client_secret_post':\n if (!config.clientSecret) {\n throw new Error('OIDC_CLIENT_SECRET is required for client_secret_post authentication');\n }\n\n return oidc.ClientSecretPost(config.clientSecret);\n case 'none':\n default:\n return oidc.None();\n }\n}\n\nfunction getOAuthErrorMessage(error: unknown): string {\n if (error instanceof oidc.ResponseBodyError) {\n return error.error_description || error.error || error.message;\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'Unknown error';\n}\n\nfunction shouldLogoutAfterOAuthError(error: unknown): boolean {\n if (error instanceof oidc.ResponseBodyError) {\n return (\n error.status === 400 ||\n error.status === 401 ||\n error.error === 'invalid_grant' ||\n error.error === 'invalid_token'\n );\n }\n\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return message.includes('invalid_grant') || message.includes('invalid_token') || message.includes('unauthorized');\n}\n\nfunction getLogoutScope(url: URL): LogoutScope {\n const requestedScope =\n url.searchParams.get('logout') ??\n url.searchParams.get('logout_scope') ??\n url.searchParams.get('scope');\n const normalized = requestedScope?.trim().toLowerCase();\n\n if (\n normalized === 'local' ||\n normalized === 'application' ||\n normalized === 'app' ||\n normalized === 'current-device'\n ) {\n return 'local';\n }\n\n if (\n normalized === 'all' ||\n normalized === 'all-local' ||\n normalized === 'all-devices' ||\n normalized === 'everywhere'\n ) {\n return 'all';\n }\n\n if (url.searchParams.get('local_only') === 'true' || url.searchParams.get('client_id_only') === 'true') {\n return 'local';\n }\n\n return 'identity';\n}\n\nfunction getAbsoluteRedirectUrl(request: Request, redirectUrl: string): string {\n if (/^https?:\\/\\//i.test(redirectUrl)) {\n return redirectUrl;\n }\n\n return new URL(redirectUrl, new URL(request.url).origin).toString();\n}\n\nasync function revokeTokenBestEffort(\n configuration: oidc.Configuration,\n token: string | undefined,\n tokenTypeHint: 'access_token' | 'refresh_token'\n): Promise<void> {\n if (!token) {\n return;\n }\n\n try {\n await oidc.tokenRevocation(configuration, token, {\n token_type_hint: tokenTypeHint,\n });\n } catch (error) {\n logger.warn('OIDC token revocation failed', {\n tokenTypeHint,\n error: getOAuthErrorMessage(error),\n });\n }\n}\n\nasync function revokeSessionTokensBestEffort(sessionData: SessionData): Promise<void> {\n if (!sessionData.refreshToken && !sessionData.accessToken) {\n return;\n }\n\n try {\n const configuration = await getOAuthConfig();\n await revokeTokenBestEffort(configuration, sessionData.refreshToken, 'refresh_token');\n await revokeTokenBestEffort(configuration, sessionData.accessToken, 'access_token');\n } catch (error) {\n logger.warn('Skipping OIDC token revocation because provider configuration is unavailable', {\n error: getOAuthErrorMessage(error),\n });\n }\n}\n\nasync function revokeAndDestroyAllUserSessionsBestEffort(userId: string): Promise<number> {\n const sessions = await listAuthSessionDataForUser(userId);\n\n try {\n const configuration = await getOAuthConfig();\n for (const { data } of sessions) {\n await revokeTokenBestEffort(configuration, data.refreshToken, 'refresh_token');\n await revokeTokenBestEffort(configuration, data.accessToken, 'access_token');\n }\n } catch (error) {\n logger.warn('Skipping all-session token revocation because provider configuration is unavailable', {\n error: getOAuthErrorMessage(error),\n });\n }\n\n return await destroyAuthSessionsForUser(userId);\n}\n\nfunction getClaimsFromTokenResponse(tokenResult: OidcTokenResponse): Record<string, unknown> {\n const claims = tokenResult.claims();\n\n if (claims) {\n return claims as Record<string, unknown>;\n }\n\n return getJwtClaims(tokenResult.id_token) ?? {};\n}\n\n/**\n * Get OIDC configuration with caching.\n */\nasync function getOAuthConfig(): Promise<oidc.Configuration> {\n const config = getAuthConfig();\n const now = Date.now();\n\n if (authServerCache && now - authServerCache.cachedAt < CACHE_TTL) {\n return authServerCache.configuration;\n }\n\n try {\n const configuration = await oidc.discovery(\n new URL(config.authority),\n config.clientId,\n getClientMetadata(config),\n getClientAuthentication(config),\n getDiscoveryOptions()\n );\n\n authServerCache = {\n configuration,\n cachedAt: now,\n };\n\n logger.info('OIDC discovery completed successfully', {\n issuer: configuration.serverMetadata().issuer,\n clientId: config.clientId,\n clientAuthMethod: config.clientAuthMethod,\n });\n return configuration;\n } catch (error) {\n logger.error('OIDC discovery failed', error instanceof Error ? error : undefined, {\n authority: config.authority,\n clientId: config.clientId,\n });\n throw new Error(`OIDC discovery failed: ${getOAuthErrorMessage(error)}`);\n }\n}\n\nfunction getJwks(jwksUri: string): ReturnType<typeof createRemoteJWKSet> {\n const cachedJwks = jwksCache.get(jwksUri);\n if (cachedJwks) {\n return cachedJwks;\n }\n\n const jwks = createRemoteJWKSet(new URL(jwksUri));\n jwksCache.set(jwksUri, jwks);\n return jwks;\n}\n\nasync function getLogoutTokenFromRequest(request: Request): Promise<string | null> {\n const contentType = request.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json')) {\n const body = await request.json().catch(() => null) as { logout_token?: unknown } | null;\n return typeof body?.logout_token === 'string' ? body.logout_token : null;\n }\n\n const formData = await request.formData().catch(() => null);\n const logoutToken = formData?.get('logout_token');\n return typeof logoutToken === 'string' ? logoutToken : null;\n}\n\nfunction validateBackChannelLogoutClaims(payload: Record<string, unknown>, clientId: string): {\n issuer?: string;\n subject?: string;\n sid?: string;\n} {\n const events = payload.events;\n const hasBackChannelEvent =\n events &&\n typeof events === 'object' &&\n !Array.isArray(events) &&\n 'http://schemas.openid.net/event/backchannel-logout' in events;\n\n if (!hasBackChannelEvent) {\n throw new Error('Invalid back-channel logout token: missing back-channel logout event');\n }\n\n if ('nonce' in payload) {\n throw new Error('Invalid back-channel logout token: nonce is not allowed');\n }\n\n const audience = payload.aud;\n const hasClientAudience = Array.isArray(audience)\n ? audience.includes(clientId)\n : audience === clientId;\n\n if (!hasClientAudience) {\n throw new Error('Invalid back-channel logout token: audience mismatch');\n }\n\n const subject = typeof payload.sub === 'string' ? payload.sub : undefined;\n const sid = typeof payload.sid === 'string' ? payload.sid : undefined;\n\n if (!subject && !sid) {\n throw new Error('Invalid back-channel logout token: subject or sid is required');\n }\n\n return {\n issuer: typeof payload.iss === 'string' ? payload.iss : undefined,\n subject,\n sid,\n };\n}\n\n// ============================================================================\n// Claim Extraction\n// ============================================================================\n\nconst DEFAULT_AUTH_CLAIM_MAPPING: Required<AuthClaimMapping> = {\n subject: ['sub'],\n name: ['name', 'preferred_username', 'email'],\n email: ['email'],\n givenName: ['given_name', 'givenName'],\n familyName: ['family_name', 'familyName'],\n picture: ['picture', 'avatar_url'],\n locale: ['locale'],\n zoneinfo: ['zoneinfo'],\n updatedAt: ['updated_at'],\n tenantIds: ['tenant_ids'],\n tenantRoles: ['tenant_roles'],\n permissions: ['app_perms', 'permissions', 'scope'],\n isOnboarded: ['is_onboarded'],\n};\n\nlet authClaimMapping: Required<AuthClaimMapping> = {\n ...DEFAULT_AUTH_CLAIM_MAPPING,\n};\n\nfunction normalizeClaimKeys(keys?: string[]): string[] {\n if (!Array.isArray(keys)) {\n return [];\n }\n\n return Array.from(\n new Set(\n keys\n .map((key) => key.trim())\n .filter((key) => key.length > 0)\n )\n );\n}\n\n/**\n * Configure claim mapping so different providers/backends can project their\n * own claim names onto the shared auth/session primitives.\n */\nexport function configureAuthClaimMapping(mapping: AuthClaimMapping): void {\n authClaimMapping = {\n subject: mapping.subject !== undefined ? normalizeClaimKeys(mapping.subject) : DEFAULT_AUTH_CLAIM_MAPPING.subject,\n name: mapping.name !== undefined ? normalizeClaimKeys(mapping.name) : DEFAULT_AUTH_CLAIM_MAPPING.name,\n email: mapping.email !== undefined ? normalizeClaimKeys(mapping.email) : DEFAULT_AUTH_CLAIM_MAPPING.email,\n givenName: mapping.givenName !== undefined ? normalizeClaimKeys(mapping.givenName) : DEFAULT_AUTH_CLAIM_MAPPING.givenName,\n familyName: mapping.familyName !== undefined ? normalizeClaimKeys(mapping.familyName) : DEFAULT_AUTH_CLAIM_MAPPING.familyName,\n picture: mapping.picture !== undefined ? normalizeClaimKeys(mapping.picture) : DEFAULT_AUTH_CLAIM_MAPPING.picture,\n locale: mapping.locale !== undefined ? normalizeClaimKeys(mapping.locale) : DEFAULT_AUTH_CLAIM_MAPPING.locale,\n zoneinfo: mapping.zoneinfo !== undefined ? normalizeClaimKeys(mapping.zoneinfo) : DEFAULT_AUTH_CLAIM_MAPPING.zoneinfo,\n updatedAt: mapping.updatedAt !== undefined ? normalizeClaimKeys(mapping.updatedAt) : DEFAULT_AUTH_CLAIM_MAPPING.updatedAt,\n tenantIds: mapping.tenantIds !== undefined ? normalizeClaimKeys(mapping.tenantIds) : DEFAULT_AUTH_CLAIM_MAPPING.tenantIds,\n tenantRoles: mapping.tenantRoles !== undefined ? normalizeClaimKeys(mapping.tenantRoles) : DEFAULT_AUTH_CLAIM_MAPPING.tenantRoles,\n permissions: mapping.permissions !== undefined ? normalizeClaimKeys(mapping.permissions) : DEFAULT_AUTH_CLAIM_MAPPING.permissions,\n isOnboarded: mapping.isOnboarded !== undefined ? normalizeClaimKeys(mapping.isOnboarded) : DEFAULT_AUTH_CLAIM_MAPPING.isOnboarded,\n };\n}\n\n/**\n * Reset claim mapping to the built-in defaults.\n */\nexport function resetAuthClaimMapping(): void {\n authClaimMapping = {\n ...DEFAULT_AUTH_CLAIM_MAPPING,\n };\n}\n\nfunction getConfiguredAuthClaimMapping(): Required<AuthClaimMapping> {\n return authClaimMapping;\n}\n\nfunction getFirstClaimValue(\n claims: Record<string, unknown>,\n claimKeys: string[]\n): { key?: string; value?: unknown } {\n for (const claimKey of claimKeys) {\n if (claimKey in claims) {\n return {\n key: claimKey,\n value: claims[claimKey],\n };\n }\n }\n\n return {};\n}\n\nfunction getStringClaim(claims: Record<string, unknown>, claimKeys: string[]): string | undefined {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return `${value}`;\n }\n\n return undefined;\n}\n\nfunction getNumberClaim(claims: Record<string, unknown>, claimKeys: string[]): number | undefined {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === 'string') {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n return undefined;\n}\n\nfunction getBooleanClaim(\n claims: Record<string, unknown>,\n claimKeys: string[],\n defaultValue = false\n): boolean {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'boolean') {\n return value;\n }\n\n if (typeof value === 'string') {\n return value.toLowerCase() === 'true';\n }\n\n return defaultValue;\n}\n\n/**\n * Parse JSON safely\n */\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\n/**\n * Convert value to string array\n */\nfunction toStringArray(value: unknown): string[] {\n if (!value) return [];\n\n if (Array.isArray(value)) {\n return value\n .map((item) => (typeof item === 'string' ? item.trim() : `${item}`.trim()))\n .filter((item) => item.length > 0);\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (!trimmed) return [];\n\n if (trimmed.startsWith('[')) {\n const parsed = parseJson(trimmed);\n if (Array.isArray(parsed)) {\n return parsed\n .map((item) => (typeof item === 'string' ? item.trim() : `${item}`.trim()))\n .filter((item) => item.length > 0);\n }\n }\n\n if (trimmed.includes(',')) {\n return trimmed\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item.length > 0);\n }\n\n return [trimmed];\n }\n\n return [];\n}\n\nfunction toScopeArray(value: unknown): string[] {\n if (typeof value === 'string') {\n return value\n .trim()\n .split(/\\s+/)\n .filter((item) => item.length > 0);\n }\n\n return toStringArray(value);\n}\n\n/**\n * Extract user info from ID token claims\n */\nexport function extractUserInfo(claims: Record<string, unknown>): {\n tenants: string[];\n currentTenant?: string;\n isOnboarded: boolean;\n tenantRoles: Record<string, string[]>;\n permissions: string[];\n} {\n const claimMapping = getConfiguredAuthClaimMapping();\n\n // Extract tenant IDs\n let tenants: string[] = [];\n const { value: rawTenantIds } = getFirstClaimValue(claims, claimMapping.tenantIds);\n if (rawTenantIds !== undefined) {\n if (typeof rawTenantIds === 'string' && rawTenantIds.trim().startsWith('[')) {\n const parsed = parseJson(rawTenantIds.trim());\n if (Array.isArray(parsed)) {\n tenants = parsed\n .map((id) => (typeof id === 'string' ? id.trim() : `${id}`.trim()))\n .filter((id) => id.length > 0);\n }\n } else {\n tenants = toStringArray(rawTenantIds);\n }\n }\n\n // Extract tenant roles\n const tenantRoles: Record<string, string[]> = {};\n const { value: rawRoles } = getFirstClaimValue(claims, claimMapping.tenantRoles);\n if (rawRoles !== undefined) {\n let parsedRoles: unknown = rawRoles;\n\n if (typeof rawRoles === 'string' && rawRoles.trim().startsWith('{')) {\n parsedRoles = parseJson(rawRoles.trim()) ?? rawRoles;\n }\n\n if (parsedRoles && typeof parsedRoles === 'object' && !Array.isArray(parsedRoles)) {\n for (const [tenantId, roleValue] of Object.entries(parsedRoles as Record<string, unknown>)) {\n const roleIds = toStringArray(roleValue);\n if (roleIds.length > 0) {\n tenantRoles[tenantId] = roleIds;\n }\n }\n }\n }\n\n // Extract permissions\n const permissionClaim = getFirstClaimValue(claims, claimMapping.permissions);\n const permissions =\n permissionClaim.key === 'scope'\n ? toScopeArray(permissionClaim.value)\n : toStringArray(permissionClaim.value);\n\n // Extract onboarding status\n const isOnboarded = getBooleanClaim(claims, claimMapping.isOnboarded);\n\n return { tenants, isOnboarded, tenantRoles, permissions };\n}\n\n// ============================================================================\n// Session Data Creation\n// ============================================================================\n\n/**\n * Create session data from token result\n */\nasync function createSessionData(\n tokenResult: OidcTokenResponse,\n claims: Record<string, unknown>\n): Promise<Partial<SessionData>> {\n const claimMapping = getConfiguredAuthClaimMapping();\n const baseUser: UserInfo = {\n sub: getStringClaim(claims, claimMapping.subject) || 'unknown',\n name: getStringClaim(claims, claimMapping.name) || getStringClaim(claims, claimMapping.email) || 'User',\n email: getStringClaim(claims, claimMapping.email),\n givenName: getStringClaim(claims, claimMapping.givenName),\n familyName: getStringClaim(claims, claimMapping.familyName),\n picture: getStringClaim(claims, claimMapping.picture),\n locale: getStringClaim(claims, claimMapping.locale),\n zoneinfo: getStringClaim(claims, claimMapping.zoneinfo),\n updated_at: getNumberClaim(claims, claimMapping.updatedAt),\n };\n\n const expiresAt = tokenResult.expires_in\n ? Date.now() + tokenResult.expires_in * 1000\n : undefined;\n const now = Date.now();\n const sessionState = (tokenResult as Record<string, unknown>).session_state;\n\n return {\n userId: baseUser.sub,\n accessToken: tokenResult.access_token,\n refreshToken: tokenResult.refresh_token,\n idToken: tokenResult.id_token,\n expiresAt,\n issuer: getStringClaim(claims, ['iss']),\n sid: getStringClaim(claims, ['sid']),\n sessionState: typeof sessionState === 'string' ? sessionState : undefined,\n clientId: getAuthConfig().clientId,\n user: baseUser,\n createdAt: now,\n lastActivity: now,\n };\n}\n\n// ============================================================================\n// Auth Error Handling\n// ============================================================================\n\n// Auth error cookie names - unique per application to prevent conflicts\nconst AUTH_ERROR_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error`;\nconst AUTH_ERROR_DESC_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error_desc`;\nconst OAUTH_STATE_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_oauth_state_id`;\n\n/**\n * Check if there's an auth error in cookies\n * \n * This should be called by apps (especially those without landing pages like Dashboard)\n * BEFORE starting OAuth flow to prevent redirect loops.\n * \n * @example\n * ```ts\n * // In dashboard's index route loader\n * const authError = getAuthError(request);\n * if (authError) {\n * return { authError }; // Show error to user\n * }\n * // No error, proceed with session check\n * const user = await getUser(request);\n * if (!user) {\n * return login(request);\n * }\n * ```\n */\nexport function getAuthError(request: Request): AuthError | null {\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n \n // Parse auth_error cookie (app-specific)\n const errorRegex = new RegExp(`${AUTH_ERROR_COOKIE}=([^;]+)`);\n const errorMatch = cookies.match(errorRegex);\n if (!errorMatch) return null;\n \n const error = decodeURIComponent(errorMatch[1]);\n if (!error) return null;\n \n // Parse auth_error_description cookie (app-specific)\n const descRegex = new RegExp(`${AUTH_ERROR_DESC_COOKIE}=([^;]+)`);\n const descMatch = cookies.match(descRegex);\n const description = descMatch ? decodeURIComponent(descMatch[1]) : undefined;\n \n return { error, description };\n}\n\n/**\n * Clear auth error cookies\n * \n * Call this after displaying the error to the user\n */\nexport function clearAuthErrorHeaders(): Headers {\n const headers = new Headers();\n headers.append('Set-Cookie', `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);\n headers.append('Set-Cookie', `${AUTH_ERROR_DESC_COOKIE}=; Path=/; Max-Age=0`);\n return headers;\n}\n\n/**\n * Check if request has auth error (quick check without parsing)\n */\nexport function hasAuthError(request: Request): boolean {\n const cookies = request.headers.get('Cookie');\n return cookies?.includes(`${AUTH_ERROR_COOKIE}=`) ?? false;\n}\n\n// ============================================================================\n// Auth Functions\n// ============================================================================\n\n/**\n * Initiate OAuth login flow\n * \n * @param request - The incoming request\n * @param returnUrlOrOptions - Either a return URL string or LoginOptions object\n */\nexport async function login(\n request: Request, \n returnUrlOrOptions?: string | LoginOptions\n): Promise<Response> {\n try {\n const config = getAuthConfig();\n \n // Parse options\n const options: LoginOptions = typeof returnUrlOrOptions === 'string' \n ? { returnUrl: returnUrlOrOptions }\n : returnUrlOrOptions || {};\n\n // Check for auth_error cookie to prevent redirect loops\n const cookies = request.headers.get('Cookie');\n const hasError = cookies?.includes(`${AUTH_ERROR_COOKIE}=`);\n if (hasError) {\n logger.warn('Auth error cookie detected, preventing redirect loop');\n // Clear the error cookie and redirect to home\n const headers = new Headers();\n headers.append('Set-Cookie', `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n\n const oidcConfig = await getOAuthConfig();\n\n // Generate PKCE challenge\n const codeVerifier = oidc.randomPKCECodeVerifier();\n const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);\n\n const extraAuthParams = sanitizeExtraAuthorizationParameters(options.extraAuthParams);\n\n // Generate secure state and nonce. The state may carry a non-secret UI\n // hint for Keycloak themes, but callback validation still compares the\n // exact value stored server-side.\n const state = buildAuthorizationState(oidc.randomState(), extraAuthParams);\n const nonce = oidc.randomNonce();\n\n // Create OAuth state object\n const oauthState: OAuthState = {\n state,\n codeVerifier,\n nonce,\n returnUrl: options.returnUrl,\n kcAction: options.kcAction,\n createdAt: Date.now(),\n };\n\n logger.info('Creating OAuth state', {\n returnUrl: options.returnUrl,\n prompt: options.prompt,\n kcAction: options.kcAction,\n extraAuthParamKeys: Object.keys(extraAuthParams),\n });\n\n // Store OAuth state in Redis\n const stateId = await createOAuthState(oauthState);\n\n const authorizationParameters: Record<string, string> = {\n redirect_uri: config.redirectUri,\n scope: config.scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...extraAuthParams,\n };\n\n if (options.prompt) {\n authorizationParameters.prompt = options.prompt;\n }\n\n if (options.kcAction) {\n authorizationParameters.kc_action = options.kcAction;\n }\n\n const authorizationUrl = oidc.buildAuthorizationUrl(oidcConfig, authorizationParameters);\n\n // Store stateId in cookie for callback (app-specific to prevent conflicts)\n const headers = new Headers();\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=${stateId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=600`);\n\n logger.info('OIDC authorization flow initiated', {\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scope: config.scope,\n authorizationEndpoint: oidcConfig.serverMetadata().authorization_endpoint,\n stateId,\n prompt: options.prompt,\n kcAction: options.kcAction,\n });\n\n return createRedirectResponse(authorizationUrl.toString(), { headers });\n } catch (error) {\n logger.error('Failed to initiate OAuth login', error instanceof Error ? error : undefined);\n throw new Error('Login failed');\n }\n}\n\n/**\n * Handle OAuth callback\n * \n * Handles OAuth authorization response including success and error cases.\n * \n * Error Handling:\n * - `access_denied`: User doesn't have access to this application\n * - For apps with landing page: Redirect home with error cookie\n * - For apps without landing page: Redirect home with error cookie (must handle specially)\n * - `login_required`: No active identity session\n * - Redirect to home to start fresh login\n */\nexport async function handleCallback(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const hasLandingPage = getEffectiveHasLandingPage(config);\n const url = new URL(request.url);\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n const kcAction = url.searchParams.get('kc_action');\n const kcActionStatus = url.searchParams.get('kc_action_status');\n\n logger.info('OIDC callback received', {\n hasCode: !!code,\n hasState: !!state,\n error,\n errorDescription,\n kcAction,\n kcActionStatus,\n clientId: config.clientId,\n redirectUri: config.redirectUri\n });\n\n // Handle OAuth errors\n if (error) {\n if (kcActionStatus && state) {\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n\n if (stateId) {\n const oauthState = await getOAuthState(stateId);\n const stateAge = oauthState ? Date.now() - oauthState.createdAt : Number.POSITIVE_INFINITY;\n\n if (oauthState && oauthState.state === state && stateAge <= 10 * 60 * 1000) {\n await deleteOAuthState(stateId);\n const headers = new Headers();\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);\n\n const redirectUrl = appendApplicationInitiatedActionResult(\n oauthState.returnUrl || '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n\n logger.info('Application initiated action returned with OAuth error', {\n error,\n errorDescription,\n kcAction: kcAction || oauthState.kcAction,\n kcActionStatus,\n });\n\n return createRedirectResponse(redirectUrl, { headers });\n }\n }\n }\n\n logger.error('OAuth callback error', undefined, { \n error, \n errorDescription,\n clientId: config.clientId,\n applicationType: config.applicationType,\n hasLandingPage\n });\n \n // Handle access_denied specially - user doesn't have access to this app\n // This can happen when identity cookie is valid but user has no access to this client\n if (error === 'access_denied') {\n logger.warn('Access denied for this application', { clientId: config.clientId });\n // Clear any existing session and redirect to home without triggering new login\n const headers = await destroyAuthSession(request);\n // Set error cookies to:\n // 1. Prevent redirect loop (auth_error)\n // 2. Allow UI to show appropriate message (auth_error_type, auth_error_description)\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_COOKIE}=access_denied; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription || 'You do not have access to this application.')}; Path=/; SameSite=Lax; Max-Age=300`\n );\n // Use redirectUri to get the correct base URL with port\n // request.url.origin can lose the port in some cases\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n logger.info('Redirecting to /auth/login to show access_denied error', { \n hasLandingPage,\n baseUrl,\n configRedirectUri: config.redirectUri\n });\n // Redirect to /auth/login so user immediately sees the access denied page\n // instead of landing page (which doesn't show the error)\n return createRedirectResponse(`${baseUrl}/auth/login`, { headers });\n }\n \n // Handle login_required - session expired or user not logged in\n if (error === 'login_required') {\n logger.info('Login required, user session may have expired');\n const headers = await destroyAuthSession(request);\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n \n // Handle other errors\n const headers = await destroyAuthSession(request);\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_COOKIE}=${encodeURIComponent(error)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n if (errorDescription) {\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription)}; Path=/; SameSite=Lax; Max-Age=300`\n );\n }\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n\n // Get stateId from cookie (app-specific)\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n\n if (!stateId) {\n throw new Error('OAuth state ID not found in cookie');\n }\n\n // Retrieve OAuth state from Redis\n const oauthState = await getOAuthState(stateId);\n\n if (!oauthState) {\n throw new Error('No OAuth state found - state may have expired');\n }\n\n if (!state) {\n throw new Error('Missing required callback state parameter');\n }\n\n if (oauthState.state !== state) {\n throw new Error(`OAuth state mismatch`);\n }\n\n // Check state age (10 minutes max)\n const stateAge = Date.now() - oauthState.createdAt;\n if (stateAge > 10 * 60 * 1000) {\n throw new Error('OAuth state expired');\n }\n\n if (!code) {\n if (kcActionStatus) {\n await deleteOAuthState(stateId);\n const headers = new Headers();\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);\n\n const redirectUrl = appendApplicationInitiatedActionResult(\n oauthState.returnUrl || '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n\n logger.info('Application initiated action returned without authorization code', {\n kcAction: kcAction || oauthState.kcAction,\n kcActionStatus,\n });\n\n return createRedirectResponse(redirectUrl, { headers });\n }\n\n throw new Error('Missing required callback code parameter');\n }\n\n logger.info('Initiating token exchange');\n\n const oidcConfig = await getOAuthConfig();\n const tokenResult = await oidc.authorizationCodeGrant(oidcConfig, url, {\n pkceCodeVerifier: oauthState.codeVerifier,\n expectedState: oauthState.state,\n expectedNonce: oauthState.nonce,\n idTokenExpected: config.scope.split(/\\s+/).includes('openid'),\n });\n\n logger.info('Token exchange completed successfully');\n\n const claims = getClaimsFromTokenResponse(tokenResult);\n\n // Create session data\n const newSessionData = await createSessionData(tokenResult, claims);\n\n // Create new session\n const sessionHeaders = await createAuthSession(request, newSessionData);\n\n // Clean up OAuth state from Redis\n await deleteOAuthState(stateId);\n\n // Combine headers and clear OAuth state cookie (app-specific)\n const headers = new Headers(sessionHeaders);\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);\n\n logger.info('OAuth callback processed successfully');\n\n // Redirect to return URL or default location\n const redirectUrl = appendApplicationInitiatedActionResult(\n oauthState.returnUrl || '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n return createRedirectResponse(redirectUrl, { headers });\n } catch (error) {\n logger.error('OAuth callback failed', error instanceof Error ? error : undefined);\n\n // Try to clean up OAuth state on error\n try {\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n if (stateId) {\n await deleteOAuthState(stateId);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n await destroyAuthSession(request);\n throw new Error('Authentication failed');\n }\n}\n\n/**\n * Logout user.\n *\n * Default logout follows OIDC RP-Initiated Logout and ends the identity\n * provider session. Automatic/session-expiry cleanup can request\n * `/auth/logout?logout=local` to clear only this app's Redis session without\n * bouncing the browser through the identity provider or revoking SSO tokens.\n */\nexport async function logout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const url = new URL(request.url);\n \n const logoutScope = getLogoutScope(url);\n const returnUrl = sanitizeLogoutReturnUrl(url.searchParams.get('returnUrl'), request);\n \n logger.info('Logout initiated', { \n clientId: config.clientId,\n logoutScope,\n });\n \n const sessionData = await getAuthSession(request);\n const idToken = sessionData.idToken;\n\n if (logoutScope === 'local') {\n // Local logout is RP-only cleanup. Tokens are server-side and become\n // unreachable once the Redis session is destroyed; revoking the refresh\n // token can also terminate the Keycloak SSO session for this user.\n const headers = await destroyAuthSession(request);\n const redirectUrl = returnUrl || '/';\n const absoluteRedirectUrl = getAbsoluteRedirectUrl(request, redirectUrl);\n\n headers.append('Set-Cookie', 'logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');\n logger.info('Local application logout completed', { redirectUrl: absoluteRedirectUrl });\n return createRedirectResponse(absoluteRedirectUrl, { headers });\n }\n\n const sessionHeaders = new Headers();\n\n if (logoutScope === 'all' && sessionData.userId) {\n await revokeAndDestroyAllUserSessionsBestEffort(sessionData.userId);\n sessionHeaders.append('Set-Cookie', 'logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');\n const expiredHeaders = await destroyAuthSession(request);\n expiredHeaders.forEach((value, key) => sessionHeaders.append(key, value));\n } else {\n await revokeSessionTokensBestEffort(sessionData);\n const expiredHeaders = await destroyAuthSession(request);\n expiredHeaders.forEach((value, key) => sessionHeaders.append(key, value));\n }\n\n const headers = new Headers(sessionHeaders);\n\n // Store returnUrl in cookie if provided for the post-logout landing redirect.\n if (returnUrl) {\n headers.append(\n 'Set-Cookie',\n `logout_return_url=${encodeURIComponent(returnUrl)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n } else {\n headers.append('Set-Cookie', 'logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');\n }\n\n try {\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const logoutParameters: Record<string, string> = {};\n const expectedIssuer = serverMetadata.issuer ?? config.authority;\n\n if (idToken && canUseIdTokenHint(idToken, expectedIssuer)) {\n logoutParameters.id_token_hint = idToken;\n } else if (idToken) {\n logger.warn('Skipping id_token_hint because the session token issuer does not match the active OIDC provider', {\n tokenIssuer: getJwtIssuer(idToken),\n expectedIssuer: normalizeIssuer(expectedIssuer),\n clientId: config.clientId,\n });\n }\n\n if (config.postLogoutRedirectUri) {\n logoutParameters.post_logout_redirect_uri = config.postLogoutRedirectUri;\n }\n\n const endSessionUrl = oidc.buildEndSessionUrl(oidcConfig, logoutParameters);\n logger.info('RP-Initiated Logout redirect created', {\n endSessionEndpoint: serverMetadata.end_session_endpoint,\n hasIdTokenHint: Boolean(logoutParameters.id_token_hint),\n });\n\n return createRedirectResponse(endSessionUrl.toString(), { headers });\n } catch (error) {\n logger.warn('OIDC end_session endpoint unavailable, falling back to local redirect', {\n error: getOAuthErrorMessage(error),\n });\n }\n\n // Fallback redirect\n const registeredPath = config.postLogoutRedirectUri || '/';\n const finalRedirectUrl = getAbsoluteRedirectUrl(request, registeredPath);\n\n logger.info('Logout completed', { redirectUrl: finalRedirectUrl });\n return createRedirectResponse(finalRedirectUrl, { headers });\n } catch (error) {\n logger.error('Logout failed', error instanceof Error ? error : undefined);\n const headers = await destroyAuthSession(request);\n return createRedirectResponse('/', { headers });\n }\n}\n\n/**\n * Handle an OIDC Back-Channel Logout request from Keycloak or another OP.\n *\n * Configure the client Backchannel logout URL to this endpoint. The logout\n * token is signature-verified against the provider JWKS, issuer/audience are\n * checked, and all matching local Redis sessions are destroyed by `sid` first,\n * falling back to `sub` when the OP omits a session id.\n */\nexport async function handleBackChannelLogout(request: Request): Promise<Response> {\n if (request.method !== 'POST') {\n return Response.json({ error: 'method_not_allowed' }, { status: 405 });\n }\n\n try {\n const config = getAuthConfig();\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const logoutToken = await getLogoutTokenFromRequest(request);\n\n if (!logoutToken) {\n return Response.json({ error: 'missing_logout_token' }, { status: 400 });\n }\n\n if (!serverMetadata.jwks_uri) {\n throw new Error('OIDC provider does not expose jwks_uri');\n }\n\n const { payload } = await jwtVerify(logoutToken, getJwks(serverMetadata.jwks_uri), {\n issuer: serverMetadata.issuer ?? config.authority,\n audience: config.clientId,\n clockTolerance: 60,\n });\n\n const claims = validateBackChannelLogoutClaims(payload as Record<string, unknown>, config.clientId);\n const destroyedSessions = await destroyAuthSessionsByIdentitySession({\n sid: claims.sid,\n userId: claims.subject,\n });\n const result: BackChannelLogoutResult = {\n destroyedSessions,\n issuer: claims.issuer,\n subject: claims.subject,\n sid: claims.sid,\n };\n\n logger.info('Back-channel logout processed', { ...result });\n return Response.json({ success: true, ...result });\n } catch (error) {\n logger.error('Back-channel logout failed', error instanceof Error ? error : undefined);\n return Response.json(\n { error: 'invalid_logout_token', error_description: getOAuthErrorMessage(error) },\n { status: 400 }\n );\n }\n}\n\n/**\n * Handle OIDC Front-Channel Logout iframe/browser requests.\n *\n * Front-channel logout cannot carry a signed logout token, so this validates the\n * issuer and applies local cleanup only when a `sid` is present.\n */\nexport async function handleFrontChannelLogout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const url = new URL(request.url);\n const issuer = url.searchParams.get('iss');\n const sid = url.searchParams.get('sid');\n const expectedIssuer = normalizeIssuer(serverMetadata.issuer ?? config.authority);\n\n if (!issuer || normalizeIssuer(issuer) !== expectedIssuer) {\n return new Response(null, { status: 204 });\n }\n\n const destroyedSessions = sid ? await destroyAuthSessionsBySid(sid) : 0;\n const result: FrontChannelLogoutResult = {\n destroyedSessions,\n issuer,\n sid: sid ?? undefined,\n };\n\n logger.info('Front-channel logout processed', { ...result });\n return new Response(null, {\n status: 204,\n headers: {\n 'Cache-Control': 'no-store',\n },\n });\n } catch (error) {\n logger.warn('Front-channel logout ignored', {\n error: getOAuthErrorMessage(error),\n });\n\n return new Response(null, { status: 204 });\n }\n}\n\n/**\n * Get current user from session\n */\nexport async function getUser(request: Request): Promise<UserInfo | null> {\n try {\n const sessionData = await getAuthSession(request);\n return sessionData.user || null;\n } catch (error) {\n logger.error('Failed to get user', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\n/**\n * Require authenticated user (throws redirect to login if not authenticated)\n */\nexport async function requireAuth(request: Request): Promise<UserInfo> {\n const user = await getUser(request);\n\n if (!user) {\n const url = new URL(request.url);\n const returnUrl = `${url.pathname}${url.search}`;\n logger.info('No user found, starting OAuth flow', { returnUrl });\n throw await login(request, returnUrl);\n }\n\n return user;\n}\n\n/**\n * Get access token from session\n */\nexport async function getAccessToken(request: Request): Promise<string | null> {\n try {\n const sessionData = await getAuthSession(request);\n return sessionData.accessToken || null;\n } catch (error) {\n logger.error('Failed to get access token', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\n/**\n * Refresh tokens\n */\nexport async function refreshTokens(\n request: Request,\n refreshToken?: string\n): Promise<TokenRefreshResult> {\n try {\n const sessionData = await getAuthSession(request);\n const tokenToRefresh = refreshToken || sessionData.refreshToken;\n\n if (!tokenToRefresh) {\n logger.warn('No refresh token available');\n return { success: false, error: 'No refresh token available', shouldLogout: true };\n }\n\n logger.info('Initiating token refresh');\n\n const oidcConfig = await getOAuthConfig();\n const refreshResult = await oidc.refreshTokenGrant(oidcConfig, tokenToRefresh);\n\n logger.info('Token refresh successful');\n\n const newRefreshToken = refreshResult.refresh_token || tokenToRefresh;\n const expiresAt = refreshResult.expires_in\n ? Date.now() + refreshResult.expires_in * 1000\n : sessionData.expiresAt;\n\n // Update session\n await updateAuthSession(request, {\n accessToken: refreshResult.access_token,\n refreshToken: newRefreshToken,\n idToken: refreshResult.id_token,\n expiresAt,\n lastActivity: Date.now(),\n });\n\n return {\n success: true,\n tokens: {\n access_token: refreshResult.access_token,\n refresh_token: newRefreshToken,\n id_token: refreshResult.id_token,\n expires_in: refreshResult.expires_in,\n },\n };\n } catch (error) {\n logger.error('Token refresh failed with exception', error instanceof Error ? error : undefined);\n\n return {\n success: false,\n error: getOAuthErrorMessage(error),\n shouldLogout: shouldLogoutAfterOAuthError(error),\n };\n }\n}\n\n// Re-export session validation\nexport { isSessionValid };\n\n/**\n * Clear auth server cache (useful for testing)\n */\nexport function clearAuthServerCache(): void {\n authServerCache = null;\n jwksCache.clear();\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"identity-yT2ijZF0.js","names":["initialContext: IdentityContextData","DEFAULT_IDENTITY_STORE_CONFIG: Required<\n Pick<IdentityStoreConfig, 'contextEndpoint' | 'permissionsEndpoint' | 'logoutPath' | 'fetcher'>\n>","identityStoreConfig: IdentityStoreConfig","details: Omit<IdentityUnauthorizedDetails, 'logoutPath'>","unauthorizedDetails: IdentityUnauthorizedDetails","config: IdentityStoreConfig","config: {\n contextEndpoint?: string;\n permissionsEndpoint?: string;\n}","initialContext","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\nconst initialContext: IdentityContextData = {\n hasAnyMembership: 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\ninterface 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 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\nfunction getIdentityStoreConfig(): IdentityStoreConfig & typeof DEFAULT_IDENTITY_STORE_CONFIG {\n return {\n ...DEFAULT_IDENTITY_STORE_CONFIG,\n ...identityStoreConfig,\n };\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) => ({\n context: initialContext,\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 response = await config.fetcher(`${config.contextEndpoint}?refresh=true`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (response.ok) {\n const newContext = await response.json();\n set({\n context: {\n ...newContext,\n isLoading: false,\n lastUpdated: new Date(),\n },\n });\n console.log('Identity context refreshed:', newContext);\n } else if (response.status === 404) {\n const errorData = await response.json().catch(() => ({}));\n if (errorData.code === 'CurrentUserMissing' || errorData.title === 'CurrentUserMissing') {\n console.error('CurrentUserMissing error - session is invalid, redirecting to logout');\n handleUnauthorized({\n reason: 'current-user-missing',\n response,\n });\n return;\n }\n console.error('Failed to refresh identity context:', response.status);\n set((state) => ({\n context: { ...state.context, isLoading: false },\n }));\n } else if (response.status === 401) {\n console.error('Identity context refresh returned 401, redirecting to logout');\n handleUnauthorized({\n reason: 'unauthorized',\n response,\n });\n return;\n } else {\n console.error('Failed to refresh identity context:', response.status);\n set((state) => ({\n context: { ...state.context, isLoading: false },\n }));\n }\n } catch (error) {\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 response = await config.fetcher(config.permissionsEndpoint, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (response.ok) {\n const result = await response.json();\n const permissions = result.data?.permissions || [];\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 } else if (response.status === 401) {\n console.error('Permissions refresh returned 401, redirecting to logout');\n handleUnauthorized({\n reason: 'unauthorized',\n response,\n });\n } else {\n console.error('Failed to refresh permissions:', response.status);\n }\n } catch (error) {\n console.error('Error refreshing permissions:', error);\n }\n },\n\n reset: () => {\n set({ context: initialContext });\n },\n}));\n\n// Convenience selectors\nexport const useIsOnboarded = () => useIdentityStore((state) => state.context.isOnboarded);\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 +0,0 @@
1
- {"version":3,"file":"permissions-OUK8Eql2.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 selective subscription\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 */\nexport const PERMISSION_MODULES: PermissionModule[] = [\n 'Identity',\n 'PropertyManagement',\n 'Leasing',\n 'Accounting',\n 'Tasks',\n 'People',\n 'Organization',\n 'Subscription',\n];\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-B7JN86_0.js","names":["request: Request","jwtExpiry: number | null","protectionConfig: RouteProtectionConfig<any>","config: RouteProtectionConfig<TIdentityContext>","redirect: RouteProtectionRedirect","currentPath: string","protection: ProtectionLevel","reason: AutomaticLogoutRequestContext['reason']","user: UserInfo","identityContextPromise: Promise<any | null> | null","currentTenantPromise: Promise<string | null> | null","initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null","loaderFn: (user?: UserInfo) => Promise<T> | T","loaderFn: (user: UserInfo) => Promise<T> | T","request: Request","response: Response","hasAuth: boolean"],"sources":["../src/auth/route-protection.server.ts","../src/auth/token-refresh.server.ts"],"sourcesContent":["/**\n * Route Protection Server Module\n * \n * Provides route protection utilities for different authentication levels.\n */\n\nimport { getUser, requireAuth, refreshTokens, login } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { ProtectionLevel, UserInfo } from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// Refresh shortly before expiry. Keep this below common 5-minute access-token\n// lifetimes so a freshly issued token does not refresh again immediately.\nconst REFRESH_THRESHOLD = resolveRefreshThreshold();\n\nfunction resolveRefreshThreshold(): number {\n const configured = Number(process.env.OIDC_REFRESH_THRESHOLD_MS);\n return Number.isFinite(configured) && configured > 0 ? configured : 60_000;\n}\n\n/**\n * Check if token needs refresh\n */\nexport async function shouldRefreshToken(request: Request): Promise<boolean> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {\n if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {\n logger.info('Token expired and no refresh token, forcing local application logout');\n const { logout } = await import('./auth.server');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'expired-no-refresh-token');\n throw await logout(logoutRequest);\n }\n return false;\n }\n\n if (!sessionData.expiresAt || !sessionData.accessToken) {\n return false;\n }\n\n const now = Date.now();\n\n // Check JWT expiry\n let jwtExpiry: number | null = null;\n try {\n const tokenParts = sessionData.accessToken.split('.');\n if (tokenParts.length === 3) {\n const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());\n if (payload.exp) {\n jwtExpiry = payload.exp * 1000;\n }\n }\n } catch {\n // Ignore JWT decode errors\n }\n\n const earliestExpiry = jwtExpiry && jwtExpiry < sessionData.expiresAt \n ? jwtExpiry \n : sessionData.expiresAt;\n const timeUntilEarliestExpiry = earliestExpiry - now;\n\n return timeUntilEarliestExpiry <= REFRESH_THRESHOLD;\n } catch (error) {\n logger.error('Error checking token refresh', error instanceof Error ? error : undefined);\n return false;\n }\n}\n\n/**\n * Auto-refresh tokens if needed\n */\nasync function autoRefreshTokens(request: Request): Promise<void> {\n try {\n const needsRefresh = await shouldRefreshToken(request);\n\n if (needsRefresh) {\n logger.info('Auto-refreshing tokens due to expiry');\n const result = await refreshTokens(request);\n\n if (result.success) {\n logger.info('Auto-refresh completed successfully');\n } else {\n logger.error('Auto-refresh failed', undefined, { error: result.error });\n\n if (result.shouldLogout) {\n logger.error('Refresh token invalid, forcing local application logout');\n const { logout } = await import('./auth.server');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'refresh-failed');\n throw await logout(logoutRequest);\n }\n }\n }\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw error;\n }\n}\n\n/**\n * Identity context check configuration\n */\nexport interface DefaultRouteIdentityContext {\n hasAnyMembership: boolean;\n hasSubscription: boolean;\n isOnboarded: boolean;\n}\n\nexport interface IdentityContextChecker<TIdentityContext = DefaultRouteIdentityContext> {\n getIdentityContext: (\n request: Request,\n userId: string,\n forceRefresh?: boolean\n ) => Promise<DefaultRouteIdentityContext & TIdentityContext>;\n}\n\n/**\n * Tenant checker configuration\n */\nexport interface TenantChecker {\n getCurrentTenant: (request: Request) => Promise<string | null>;\n initializeTenant: (request: Request) => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\n/**\n * Subscription checker configuration\n */\nexport interface SubscriptionChecker {\n checkTenantSubscriptionStatus: (request: Request, tenantId: string) => Promise<boolean>;\n}\n\nexport interface RouteProtectionRedirect {\n location: string;\n headers?: HeadersInit;\n status?: number;\n}\n\nexport interface AutomaticLogoutRequestContext {\n request: Request;\n reason: 'expired-no-refresh-token' | 'refresh-failed';\n}\n\nexport interface LoginReturnUrlContext {\n request: Request;\n currentPath: string;\n protection: ProtectionLevel;\n}\n\nexport interface RouteProtectionResolverContext<TIdentityContext = DefaultRouteIdentityContext> {\n request: Request;\n user: UserInfo;\n currentPath: string;\n protection: ProtectionLevel;\n getIdentityContext: (forceRefresh?: boolean) => Promise<TIdentityContext | null>;\n getCurrentTenant: () => Promise<string | null>;\n initializeTenant: () => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\nexport interface RouteProtectionErrorHandlerContext<TIdentityContext = DefaultRouteIdentityContext>\n extends RouteProtectionResolverContext<TIdentityContext> {\n error: unknown;\n}\n\n/**\n * Route protection configuration\n */\nexport interface RouteProtectionConfig<TIdentityContext = DefaultRouteIdentityContext> {\n identityChecker?: IdentityContextChecker<TIdentityContext>;\n tenantChecker?: TenantChecker;\n subscriptionChecker?: SubscriptionChecker;\n resolveRoute?: (\n context: RouteProtectionResolverContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n handleRouteError?: (\n context: RouteProtectionErrorHandlerContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n getLoginReturnUrl?: (context: LoginReturnUrlContext) => string;\n createAutomaticLogoutRequest?: (\n context: AutomaticLogoutRequestContext\n ) => Promise<Request> | Request;\n}\n\nlet protectionConfig: RouteProtectionConfig<any> = {};\n\n/**\n * Configure route protection with identity/tenant/subscription checkers\n */\nexport function configureRouteProtection<TIdentityContext = DefaultRouteIdentityContext>(\n config: RouteProtectionConfig<TIdentityContext>\n): void {\n protectionConfig = config;\n}\n\n/**\n * Reset route protection to its built-in defaults.\n */\nexport function resetRouteProtectionConfig(): void {\n protectionConfig = {};\n}\n\nfunction createRedirectFromConfig(redirect: RouteProtectionRedirect): Response {\n return createRedirectResponse(redirect.location, {\n headers: redirect.headers,\n status: redirect.status,\n });\n}\n\nfunction getLoginReturnUrl(\n request: Request,\n currentPath: string,\n protection: ProtectionLevel\n): string {\n if (protectionConfig.getLoginReturnUrl) {\n return protectionConfig.getLoginReturnUrl({\n request,\n currentPath,\n protection,\n });\n }\n\n switch (protection) {\n case 'onboarding-required':\n return '/onboarding';\n case 'subscription-required':\n return '/subscription';\n case 'public':\n return '/';\n case 'auth':\n default: {\n const url = new URL(request.url);\n return url.pathname === '/' ? '/' : `${url.pathname}${url.search}`;\n }\n }\n}\n\nasync function createAutomaticLogoutRequest(\n request: Request,\n reason: AutomaticLogoutRequestContext['reason']\n): Promise<Request> {\n if (protectionConfig.createAutomaticLogoutRequest) {\n return protectionConfig.createAutomaticLogoutRequest({ request, reason });\n }\n\n const logoutUrl = new URL(request.url);\n logoutUrl.pathname = '/auth/logout';\n logoutUrl.searchParams.set('logout', 'local');\n\n return new Request(logoutUrl.toString(), {\n headers: request.headers,\n });\n}\n\nfunction createRouteResolverContext(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): RouteProtectionResolverContext<any> {\n let identityContextPromise: Promise<any | null> | null = null;\n let currentTenantPromise: Promise<string | null> | null = null;\n let initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null = null;\n\n return {\n request,\n user,\n currentPath,\n protection,\n getIdentityContext: async (forceRefresh = false) => {\n if (!protectionConfig.identityChecker) {\n return null;\n }\n\n if (forceRefresh) {\n return protectionConfig.identityChecker.getIdentityContext(request, user.sub, true);\n }\n\n if (!identityContextPromise) {\n identityContextPromise = protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n }\n\n return identityContextPromise;\n },\n getCurrentTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!currentTenantPromise) {\n currentTenantPromise = protectionConfig.tenantChecker.getCurrentTenant(request);\n }\n\n return currentTenantPromise;\n },\n initializeTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!initializeTenantPromise) {\n initializeTenantPromise = protectionConfig.tenantChecker.initializeTenant(request);\n }\n\n return initializeTenantPromise;\n },\n };\n}\n\nasync function applyConfiguredRouteResolution(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): Promise<boolean> {\n if (!protectionConfig.resolveRoute) {\n return false;\n }\n\n const context = createRouteResolverContext(request, user, currentPath, protection);\n\n try {\n const result = await protectionConfig.resolveRoute(context);\n if (result) {\n throw createRedirectFromConfig(result);\n }\n\n return true;\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n\n if (!protectionConfig.handleRouteError) {\n throw error;\n }\n\n const recovery = await protectionConfig.handleRouteError({\n ...context,\n error,\n });\n\n if (recovery) {\n throw createRedirectFromConfig(recovery);\n }\n\n throw error;\n }\n}\n\n/**\n * Check user status (onboarding, subscription)\n */\nasync function checkUserStatus(\n request: Request, \n user: UserInfo, \n currentPath: string\n): Promise<Response | null> {\n if (!protectionConfig.identityChecker) {\n logger.warn('Identity checker not configured, skipping status check');\n return null;\n }\n\n try {\n const context = await protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n\n logger.debug('User status check', {\n userId: user.sub,\n currentPath,\n hasAnyMembership: context.hasAnyMembership,\n isOnboarded: context.isOnboarded,\n hasSubscription: context.hasSubscription,\n });\n\n // Check if user needs onboarding\n if (!context.hasAnyMembership) {\n if (currentPath !== '/onboarding') {\n logger.info('User has no memberships, redirecting to onboarding');\n throw createRedirectResponse('/onboarding');\n }\n return null;\n }\n\n // Check subscription status\n if (context.hasAnyMembership) {\n if (currentPath === '/onboarding') {\n logger.info('User has memberships, redirecting from onboarding to subscription');\n throw createRedirectResponse('/subscription');\n }\n\n if (currentPath === '/subscription') {\n return null;\n }\n\n // Check tenant subscription\n if (protectionConfig.tenantChecker && protectionConfig.subscriptionChecker) {\n let currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);\n\n if (!currentTenant) {\n const initResult = await protectionConfig.tenantChecker.initializeTenant(request);\n if (initResult) {\n currentTenant = initResult.tenantId;\n }\n }\n\n if (currentTenant) {\n const hasSubscription = await protectionConfig.subscriptionChecker.checkTenantSubscriptionStatus(\n request,\n currentTenant\n );\n\n if (!hasSubscription) {\n logger.info('Tenant has no subscription, redirecting to subscription');\n throw createRedirectResponse('/subscription');\n }\n } else {\n logger.info('Could not initialize tenant, redirecting to subscription');\n throw createRedirectResponse('/subscription');\n }\n }\n }\n\n return null;\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n\n logger.error('Error checking user status', error instanceof Error ? error : undefined);\n\n if (currentPath !== '/onboarding') {\n throw createRedirectResponse('/onboarding');\n }\n\n return null;\n }\n}\n\n/**\n * Main route protection function\n */\nexport async function protectRoute<T>(\n request: Request,\n protection: ProtectionLevel,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n): Promise<T> {\n const url = new URL(request.url);\n\n switch (protection) {\n case 'public':\n try {\n await autoRefreshTokens(request);\n const user = await getUser(request);\n return loaderFn(user ?? undefined);\n } catch {\n return loaderFn(undefined);\n }\n\n case 'auth':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n const handledByConfig = await applyConfiguredRouteResolution(request, user, url.pathname, 'auth');\n if (!handledByConfig) {\n await checkUserStatus(request, user, url.pathname);\n }\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'auth'));\n }\n\n case 'onboarding-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n const handledByConfig = await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'onboarding-required'\n );\n if (!handledByConfig && protectionConfig.identityChecker) {\n const context = await protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n if (context.hasAnyMembership) {\n throw createRedirectResponse('/subscription');\n }\n }\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'onboarding-required'));\n }\n\n case 'subscription-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n const handledByConfig = await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'subscription-required'\n );\n if (!handledByConfig && protectionConfig.identityChecker) {\n const context = await protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n if (!context.hasAnyMembership) {\n throw createRedirectResponse('/onboarding');\n }\n }\n\n // Initialize tenant if needed\n if (protectionConfig.tenantChecker) {\n const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);\n if (!currentTenant) {\n await protectionConfig.tenantChecker.initializeTenant(request);\n }\n }\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'subscription-required'));\n }\n\n default:\n throw new Error(`Unknown protection level: ${protection}`);\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Public route (optional auth)\n */\nexport const publicRoute = <T>(\n request: Request,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'public', loaderFn);\n\n/**\n * Auth-protected route\n */\nexport const authRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'auth', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Onboarding route\n */\nexport const onboardingRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'onboarding-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Subscription route\n */\nexport const subscriptionRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'subscription-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n","/**\n * Token Refresh Server Module\n */\n\nimport { refreshTokens } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { TokenRefreshResult } from './types';\nimport { logger } from '../logging';\n\n/**\n * Attempt to refresh the access token\n */\nexport async function attemptTokenRefresh(request: Request): Promise<TokenRefreshResult> {\n try {\n logger.info('Attempting token refresh due to 401 response');\n\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken) {\n logger.warn('No refresh token available in session');\n return {\n success: false,\n error: 'No refresh token available',\n shouldLogout: true,\n };\n }\n\n const refreshResult = await refreshTokens(request, sessionData.refreshToken);\n\n if (!refreshResult.success) {\n logger.error('Token refresh failed', undefined, {\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n });\n\n return {\n success: false,\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n };\n }\n\n const tokens = refreshResult.tokens;\n if (!tokens || !tokens.access_token) {\n logger.error('Token refresh succeeded but no access token returned');\n return {\n success: false,\n error: 'No access token in refresh response',\n shouldLogout: true,\n };\n }\n\n logger.info('Token refresh successful');\n\n return {\n success: true,\n newAccessToken: tokens.access_token,\n };\n } catch (error) {\n logger.error('Token refresh attempt failed', error instanceof Error ? error : undefined);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n shouldLogout: true,\n };\n }\n}\n\n/**\n * Check if a response indicates token expiration\n */\nexport function isTokenExpiredResponse(response: Response): boolean {\n return response.status === 401;\n}\n\n/**\n * Check if token refresh should be attempted\n */\nexport function shouldAttemptTokenRefresh(response: Response, hasAuth: boolean): boolean {\n return hasAuth && isTokenExpiredResponse(response);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-BmiuV28c.js","names":["identityAPIFetcher: IdentityAPIFetcher | null","fetcher: IdentityAPIFetcher","request: Request","error: unknown","userId: string","userId?: string","permissionFetcher: PermissionFetcher | null","fetcher: PermissionFetcher","tenantId: string","context: IdentityContextData","options: ContextToUserInfoOptions","permissions: string[]","newVersion: number"],"sources":["../src/identity/identity-context.server.ts"],"sourcesContent":["/**\n * Identity Context Server Module\n * \n * Server-side identity context management.\n */\n\nimport type { AddressData, IdentityContextData, TenantMembership } from './types';\nimport { isApiClientError } from '../api-client/types';\nimport { logger } from '../logging';\n\n/**\n * Identity API fetcher type (to avoid coupling with specific API client)\n */\nexport type IdentityAPIFetcher = (request: Request) => Promise<IdentityContextData>;\n\nlet identityAPIFetcher: IdentityAPIFetcher | null = null;\n\n/**\n * Configure identity API fetcher\n */\nexport function configureIdentityAPIFetcher(fetcher: IdentityAPIFetcher): void {\n identityAPIFetcher = fetcher;\n}\n\n// In-memory cache for identity context\nconst contextCache = new Map<\n string,\n {\n context: IdentityContextData;\n timestamp: number;\n version: number;\n }\n>();\n\nconst CONTEXT_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Fetch identity context from backend\n */\nexport async function fetchIdentityContext(request: Request): Promise<IdentityContextData> {\n if (!identityAPIFetcher) {\n throw new Error('Identity API fetcher not configured. Call configureIdentityAPIFetcher first.');\n }\n\n try {\n logger.info('Fetching identity context');\n const context = await identityAPIFetcher(request);\n \n logger.debug('Identity context fetched', {\n hasAnyMembership: context.hasAnyMembership,\n hasSubscription: context.hasSubscription,\n isOnboarded: context.isOnboarded,\n membershipCount: context.memberships?.length || 0,\n });\n\n return context;\n } catch (error: unknown) {\n logger.error('Failed to fetch identity context', error instanceof Error ? error : undefined);\n\n // Check if this is a CurrentUserMissing error\n if (isApiClientError(error) && error.response?.status === 404 && error.problemDetails?.title === 'CurrentUserMissing') {\n logger.error('CurrentUserMissing error detected - session is invalid');\n const invalidSessionError = new Error('INVALID_SESSION');\n (invalidSessionError as Error & { isInvalidSession: boolean }).isInvalidSession = true;\n throw invalidSessionError;\n }\n\n throw error;\n }\n}\n\n/**\n * Get cached identity context or fetch fresh if needed\n */\nexport async function getIdentityContext(\n request: Request,\n userId: string,\n forceRefresh = false\n): Promise<IdentityContextData> {\n const cacheKey = userId;\n const cached = contextCache.get(cacheKey);\n const now = Date.now();\n\n // Return cached if valid and not forcing refresh\n if (!forceRefresh && cached && now - cached.timestamp < CONTEXT_CACHE_TTL) {\n logger.debug('Using cached identity context for user', { userId });\n return cached.context;\n }\n\n // Fetch fresh context\n const context = await fetchIdentityContext(request);\n\n // Cache the result\n contextCache.set(cacheKey, {\n context,\n timestamp: now,\n version: context.contextVersion ?? 0,\n });\n\n logger.debug('Cached fresh identity context for user', { userId });\n return context;\n}\n\n/**\n * Clear identity context cache for user\n */\nexport function clearIdentityContextCache(userId?: string): void {\n if (userId) {\n contextCache.delete(userId);\n logger.debug('Cleared identity context cache for user', { userId });\n } else {\n contextCache.clear();\n logger.debug('Cleared all identity context cache');\n }\n}\n\n/**\n * Permission fetcher type\n */\nexport type PermissionFetcher = (request: Request, tenantId: string) => Promise<string[]>;\n\nlet permissionFetcher: PermissionFetcher | null = null;\n\n/**\n * Configure permission fetcher\n */\nexport function configurePermissionFetcher(fetcher: PermissionFetcher): void {\n permissionFetcher = fetcher;\n}\n\n/**\n * Fetch user permissions for a specific tenant\n */\nexport async function fetchUserPermissions(request: Request, tenantId: string): Promise<string[]> {\n if (!tenantId) {\n logger.warn('No tenant ID provided, skipping permission fetch');\n return [];\n }\n\n if (!permissionFetcher) {\n throw new Error('Permission fetcher not configured. Call configurePermissionFetcher first.');\n }\n\n try {\n return await permissionFetcher(request, tenantId);\n } catch (error) {\n logger.error('Failed to fetch user permissions', error instanceof Error ? error : undefined);\n return [];\n }\n}\n\nexport interface UserContextInfo {\n tenants: string[];\n memberships: TenantMembership[];\n currentTenant?: string;\n isOnboarded: boolean;\n permissions: string[];\n hasSubscription: boolean;\n userId?: string;\n email?: string;\n firstName?: string | null;\n lastName?: string | null;\n displayName?: string | null;\n profileImageUrl?: string | null;\n phoneNumber?: string | null;\n timeZone?: string;\n addresses?: AddressData[];\n hasOwnerMembership: boolean;\n}\n\nexport interface ContextToUserInfoOptions {\n currentTenant?: string | null;\n request?: Request;\n}\n\n/**\n * Convert raw identity context into a tenant-aware user info shape.\n */\nexport async function contextToUserInfo(\n context: IdentityContextData,\n options: ContextToUserInfoOptions = {}\n): Promise<UserContextInfo> {\n const memberships = context.memberships || [];\n const tenants = memberships.map((membership) => membership.tenantId);\n const resolvedCurrentTenant =\n options.currentTenant && tenants.includes(options.currentTenant)\n ? options.currentTenant\n : tenants[0] || undefined;\n\n let permissions: string[] = [];\n if (resolvedCurrentTenant && options.request) {\n permissions = await fetchUserPermissions(options.request, resolvedCurrentTenant);\n }\n\n return {\n tenants,\n memberships,\n currentTenant: resolvedCurrentTenant,\n isOnboarded: context.isOnboarded ?? false,\n permissions,\n hasSubscription: context.hasSubscription ?? false,\n userId: context.userId,\n email: context.email,\n firstName: context.firstName,\n lastName: context.lastName,\n displayName: context.displayName,\n profileImageUrl: context.profileImageUrl,\n phoneNumber: context.phoneNumber,\n timeZone: context.timeZone,\n addresses: context.addresses,\n hasOwnerMembership: context.hasOwnerMembership ?? false,\n };\n}\n\n/**\n * Check if a cached context version differs from the latest version.\n */\nexport function hasContextVersionChanged(userId: string, newVersion: number): boolean {\n const cached = contextCache.get(userId);\n return !cached || cached.version !== newVersion;\n}\n\n/**\n * Mark a cached identity context as stale after a version change.\n */\nexport function updateContextVersion(userId: string, newVersion: number): void {\n const cached = contextCache.get(userId);\n if (!cached) return;\n\n cached.version = newVersion;\n cached.timestamp = 0;\n logger.debug('Marked identity context as stale for user', {\n userId,\n version: newVersion,\n });\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-Ce9jQRdL.js","names":["DEFAULT_TENANT_COOKIE_CONFIG: Required<TenantCookieConfig>","tenantCookieConfig: Required<TenantCookieConfig>","request: Request","tenantId: string","config: TenantCookieConfig","identityContextFetcher: IdentityContextFetcher | null","fetcher: IdentityContextFetcher","request: Request","tenantId: string"],"sources":["../src/tenant/tenant-cookie.server.ts","../src/tenant/tenant.server.ts"],"sourcesContent":["/**\n * Tenant Cookie Server Module\n * \n * Cookie-based tenant storage.\n */\n\nexport type TenantCookieSameSite = 'Strict' | 'Lax' | 'None';\n\nexport interface TenantCookieConfig {\n name?: string;\n maxAge?: number;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: TenantCookieSameSite;\n}\n\nconst DEFAULT_TENANT_COOKIE_CONFIG: Required<TenantCookieConfig> = {\n name: '__active-org',\n maxAge: 60 * 60 * 24 * 365,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n httpOnly: false,\n sameSite: 'Lax',\n};\n\nlet tenantCookieConfig: Required<TenantCookieConfig> = {\n ...DEFAULT_TENANT_COOKIE_CONFIG,\n};\n\nfunction getTenantCookieConfig(): Required<TenantCookieConfig> {\n return tenantCookieConfig;\n}\n\n/**\n * Get active tenant from cookie\n */\nexport async function getActiveTenant(request: Request): Promise<string | null> {\n const config = getTenantCookieConfig();\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n\n const match = cookies.match(new RegExp(`${config.name}=([^;]+)`));\n if (!match) return null;\n\n return decodeURIComponent(match[1]);\n}\n\n/**\n * Set active tenant cookie value\n */\nexport async function setActiveTenant(tenantId: string): Promise<string> {\n const config = getTenantCookieConfig();\n const secure = config.secure ? '; Secure' : '';\n const httpOnly = config.httpOnly ? '; HttpOnly' : '';\n return `${config.name}=${encodeURIComponent(tenantId)}; Path=${config.path}${httpOnly}; SameSite=${config.sameSite}; Max-Age=${config.maxAge}${secure}`;\n}\n\n/**\n * Clear active tenant cookie value\n */\nexport async function clearActiveTenant(): Promise<string> {\n const config = getTenantCookieConfig();\n const secure = config.secure ? '; Secure' : '';\n const httpOnly = config.httpOnly ? '; HttpOnly' : '';\n return `${config.name}=; Path=${config.path}${httpOnly}; SameSite=${config.sameSite}; Max-Age=0${secure}`;\n}\n\n/**\n * Configure tenant cookie name\n */\nexport function configureTenantCookie(config: TenantCookieConfig): void {\n tenantCookieConfig = {\n ...tenantCookieConfig,\n ...config,\n };\n}\n\n/**\n * Reset tenant cookie config back to defaults.\n */\nexport function resetTenantCookieConfig(): void {\n tenantCookieConfig = {\n ...DEFAULT_TENANT_COOKIE_CONFIG,\n };\n}\n","/**\n * Tenant Server Module\n * \n * Server-side tenant management.\n */\n\nimport { getAuthSession } from '../auth/redis-session-storage.server';\nimport { getActiveTenant, setActiveTenant, clearActiveTenant } from './tenant-cookie.server';\n\n/**\n * Identity context fetcher type (to avoid circular dependency)\n */\nexport type IdentityContextFetcher = (request: Request) => Promise<{\n memberships?: Array<{ tenantId: string }>;\n} | null>;\n\nlet identityContextFetcher: IdentityContextFetcher | null = null;\n\n/**\n * Configure identity context fetcher\n */\nexport function configureIdentityContextFetcher(fetcher: IdentityContextFetcher): void {\n identityContextFetcher = fetcher;\n}\n\n/**\n * Get current active tenant from cookie\n */\nexport async function getCurrentTenant(request: Request): Promise<string | null> {\n try {\n const tenantFromCookie = await getActiveTenant(request);\n if (tenantFromCookie) {\n return tenantFromCookie;\n }\n return null;\n } catch (error) {\n console.error('Error getting current tenant:', error);\n return null;\n }\n}\n\n/**\n * Get tenant from identity context\n */\nexport async function getTenantFromIdentityContext(request: Request): Promise<string | null> {\n try {\n const sessionData = await getAuthSession(request);\n if (!sessionData?.user?.sub) {\n return null;\n }\n\n if (!identityContextFetcher) {\n console.warn('Identity context fetcher not configured');\n return null;\n }\n\n const context = await identityContextFetcher(request);\n\n if (context?.memberships && context.memberships.length > 0) {\n return context.memberships[0].tenantId;\n }\n\n return null;\n } catch (error) {\n console.error('Error getting tenant from identity context:', error);\n return null;\n }\n}\n\n/**\n * Get available tenants from identity context\n */\nexport async function getAvailableTenants(request: Request): Promise<string[]> {\n try {\n const sessionData = await getAuthSession(request);\n if (!sessionData?.user?.sub) {\n return [];\n }\n\n if (!identityContextFetcher) {\n console.warn('Identity context fetcher not configured');\n return [];\n }\n\n const context = await identityContextFetcher(request);\n\n if (context?.memberships && context.memberships.length > 0) {\n return context.memberships.map((m) => m.tenantId);\n }\n\n return [];\n } catch (error) {\n console.error('Error getting available tenants:', error);\n return [];\n }\n}\n\n/**\n * Set current active tenant\n */\nexport async function setCurrentTenant(\n request: Request,\n tenantId: string\n): Promise<{ headers: Headers; success: boolean; error?: string }> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.user) {\n return { headers: new Headers(), success: false, error: 'No active session' };\n }\n\n const cookieValue = await setActiveTenant(tenantId);\n const headers = new Headers();\n headers.append('Set-Cookie', cookieValue);\n\n return { headers, success: true };\n } catch (error) {\n console.error('Failed to set current tenant:', error);\n return {\n headers: new Headers(),\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Initialize tenant on first login\n */\nexport async function initializeTenant(\n request: Request\n): Promise<{ headers: Headers; tenantId: string } | null> {\n try {\n const sessionData = await getAuthSession(request);\n if (!sessionData?.user) {\n return null;\n }\n\n // Check if tenant is already set in cookie\n const currentTenant = await getCurrentTenant(request);\n if (currentTenant) {\n return null;\n }\n\n // Get tenant from identity context\n const tenantId = await getTenantFromIdentityContext(request);\n\n if (tenantId) {\n const cookieValue = await setActiveTenant(tenantId);\n const headers = new Headers();\n headers.append('Set-Cookie', cookieValue);\n return { headers, tenantId };\n }\n\n return null;\n } catch (error) {\n console.error('Failed to initialize tenant:', error);\n return null;\n }\n}\n\n/**\n * Clear current tenant\n */\nexport async function clearCurrentTenant(): Promise<Headers> {\n const cookieValue = await clearActiveTenant();\n const headers = new Headers();\n headers.append('Set-Cookie', cookieValue);\n return headers;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-DeBlxoaW.js","names":["DEFAULT_HEADERS: Record<string, string>","getAccessToken: GetAccessTokenFn","getCurrentTenant: GetCurrentTenantFn","logger?: APILogger","factoryOptions: APIConfigFactoryOptions","request: Request","options: CreateAPIConfigOptions","headers: Record<string, string>","accessToken: string | undefined","request: Request","sessionKey: string","refreshFn: () => Promise<TokenRefreshResult>","logger?: APILogger","init: SpineRequestInit","headersInit?: HeadersInit","init: RequestInit","timeout?: number","value: unknown","response: Response","params: {\n message: string;\n response?: APIResponse<T>;\n config?: ResponseRequestMeta;\n problemDetails?: ProblemDetails;\n shouldLogout?: boolean;\n originalError?: unknown;\n cause?: unknown;\n}","rawResponse: Response","resolvedData?: T","error: unknown","context: FetchResponseContext","retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>","context: FetchErrorContext","options: FetchSetupOptions","client: T","ClientClass: new (configuration?: unknown) => T","createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>","ConfigurationClass: new (params?: Record<string, unknown>) => unknown","options: CreateApiClientOptions"],"sources":["../src/api-client/api-config.server.ts","../src/api-client/fetch-client.server.ts"],"sourcesContent":["/**\n * API Configuration Server Module\n *\n * Centralized configuration for all API clients,\n * automatically handling authentication tokens and tenant context.\n */\n\nimport type { APIConfig, CreateAPIConfigOptions, GetAccessTokenFn, GetCurrentTenantFn, APILogger } from './types';\n\n/**\n * Base URL Configuration\n * Single unified base URL for all API operations\n */\nconst DEFAULT_API_BASE_URL = process.env.API_BASE_URL || 'https://localhost:5001';\n\n/**\n * Default Headers\n * Applied to all API requests\n */\nconst DEFAULT_HEADERS: Record<string, string> = {\n Accept: 'application/json, application/problem+json',\n 'Content-Type': 'application/json',\n};\n\n/**\n * API Config Factory Options\n */\nexport interface APIConfigFactoryOptions {\n /**\n * Base URL for API requests\n */\n baseURL?: string;\n /**\n * Default headers applied to all requests\n */\n defaultHeaders?: Record<string, string>;\n /**\n * User agent string\n */\n userAgent?: string;\n /**\n * Default timeout in milliseconds\n */\n timeout?: number;\n}\n\n/**\n * Create API Config Factory\n *\n * Creates a factory function that generates API configurations\n * with the provided auth and tenant resolvers.\n */\nexport function createAPIConfigFactory(\n getAccessToken: GetAccessTokenFn,\n getCurrentTenant: GetCurrentTenantFn,\n logger?: APILogger,\n factoryOptions: APIConfigFactoryOptions = {}\n) {\n const {\n baseURL = DEFAULT_API_BASE_URL,\n defaultHeaders = DEFAULT_HEADERS,\n userAgent = 'MimirCore/1.0',\n timeout = 30000,\n } = factoryOptions;\n\n /**\n * Resolve tenant ID from request or options\n */\n async function resolveTenantId(\n request: Request,\n options: CreateAPIConfigOptions\n ): Promise<string | null> {\n // Use explicit tenant ID if provided\n if (options.tenantId) {\n logger?.debug?.('Using explicit tenant ID', { tenantId: options.tenantId });\n return options.tenantId;\n }\n\n // Resolve from current tenant context\n try {\n const tenantId = await getCurrentTenant(request);\n if (tenantId) {\n logger?.debug?.('Resolved tenant from context', { tenantId });\n }\n return tenantId;\n } catch (error) {\n logger?.warn?.('Failed to resolve current tenant', error instanceof Error ? error : undefined);\n return null;\n }\n }\n\n /**\n * Creates unified API configuration for all services.\n *\n * This function automatically:\n * - Adds authentication token to Authorization header\n * - Injects tenant context as X-Tenant-Id header\n * - Configures base URL from environment\n * - Applies default headers\n *\n * @param request - The incoming request object\n * @param options - Configuration options\n * @returns API configuration object\n * @throws Error if required authentication or tenant context is missing\n *\n * @example\n * `\typescript\n * // Standard usage with auth and tenant\n * const config = await createAPIConfig(request);\n * const api = new IdentityApi(config);\n *\n * // Without tenant requirement\n * const config = await createAPIConfig(request, { requireTenant: false });\n *\n * // With explicit tenant override\n * const config = await createAPIConfig(request, { tenantId: 'specific-tenant-id' });\n * `\n */\n async function createAPIConfig(\n request: Request,\n options: CreateAPIConfigOptions = {}\n ): Promise<APIConfig> {\n const {\n requireTenant = true,\n includeAuth = true,\n customHeaders = {},\n } = options;\n\n // Start with default headers\n const headers: Record<string, string> = {\n ...defaultHeaders,\n 'User-Agent': userAgent,\n ...customHeaders,\n };\n\n let accessToken: string | undefined;\n\n // Add authentication if required\n if (includeAuth) {\n const token = await getAccessToken(request);\n if (!token) {\n logger?.error?.('No access token available for API request');\n throw new Error('No access token available');\n }\n\n accessToken = token;\n headers.Authorization = `Bearer ${token}`;\n logger?.debug?.('Added authorization header to API config');\n }\n\n // Resolve tenant context\n const tenantId = await resolveTenantId(request, options);\n\n // Add tenant header if required\n if (requireTenant) {\n if (!tenantId) {\n logger?.error?.('Tenant context required but not available');\n throw new Error('Tenant context is required for this operation');\n }\n\n headers['X-Tenant-Id'] = tenantId;\n logger?.debug?.('Added tenant context header', { tenantId });\n }\n\n logger?.debug?.('API config created', {\n basePath: baseURL,\n hasAuth: includeAuth,\n hasTenant: !!tenantId,\n requireTenant,\n });\n\n return {\n basePath: baseURL,\n accessToken,\n headers,\n tenantId: tenantId || '',\n baseOptions: {\n timeout,\n },\n };\n }\n\n /**\n * Get the configured API base URL\n */\n function getAPIBaseURL(): string {\n return baseURL;\n }\n\n return {\n createAPIConfig,\n getAPIBaseURL,\n };\n}\n\nexport type APIConfigFactory = ReturnType<typeof createAPIConfigFactory>;\n","/**\n * Fetch-native API client setup for generated OpenAPI clients.\n *\n * This module preserves the shared request behaviors we need around fetch:\n * - request ID enrichment\n * - serialized token refresh on 401\n * - retry with exponential backoff\n * - ProblemDetails-aware error shaping\n *\n * It gives downstream apps a single fetch-native entry point for generated clients.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type {\n APIClientError,\n APIConfig,\n APIResponse,\n APILogger,\n CreateAPIConfigOptions,\n EnhancedAPIClient,\n FetchErrorContext,\n FetchMiddleware,\n FetchResponseContext,\n ProblemDetails,\n RetryConfig,\n TokenRefreshFn,\n TokenRefreshResult,\n} from './types';\nimport { isApiClientError } from './types';\nimport { calculateBackoffDelay, fetchWithTimeout, isRetryableError } from './retry-handler';\n\ntype RuntimeApiResponse<T> = {\n raw: Response;\n value(): Promise<T>;\n};\n\ntype GeneratedFetchClient = object;\n\ntype SpineRequestInit = RequestInit & {\n __spineMeta?: {\n requestId?: string;\n retryCount?: number;\n refreshed?: boolean;\n };\n};\n\ntype ResponseRequestMeta = {\n url: string;\n method: string;\n headers: Record<string, string>;\n};\n\nconst refreshPromiseMap = new Map<string, Promise<TokenRefreshResult>>();\nconst responseRequestMeta = new WeakMap<Response, ResponseRequestMeta>();\nconst rawResponseStore = new AsyncLocalStorage<Map<string, RuntimeApiResponse<unknown>>>();\n\nfunction getSessionKeyFromRequest(request: Request): string {\n const cookie = request.headers.get('cookie') || '';\n return `session:${cookie.slice(0, 50)}`;\n}\n\nfunction getOrCreateRefreshPromise(\n sessionKey: string,\n refreshFn: () => Promise<TokenRefreshResult>,\n logger?: APILogger\n): Promise<TokenRefreshResult> {\n const existingPromise = refreshPromiseMap.get(sessionKey);\n if (existingPromise) {\n logger?.info?.('Token refresh already in progress, waiting for existing refresh');\n return existingPromise;\n }\n\n logger?.info?.('Starting new token refresh');\n\n const refreshPromise = refreshFn().finally(() => {\n refreshPromiseMap.delete(sessionKey);\n });\n\n refreshPromiseMap.set(sessionKey, refreshPromise);\n return refreshPromise;\n}\n\nfunction ensureRequestMeta(init: SpineRequestInit): NonNullable<SpineRequestInit['__spineMeta']> {\n if (!init.__spineMeta) {\n init.__spineMeta = {};\n }\n\n return init.__spineMeta;\n}\n\nfunction headersToRecord(headersInit?: HeadersInit): Record<string, string> {\n return Object.fromEntries(new Headers(headersInit).entries());\n}\n\nfunction cloneInit(init: RequestInit): SpineRequestInit {\n const source = init as SpineRequestInit;\n\n return {\n ...source,\n headers: headersToRecord(source.headers),\n __spineMeta: source.__spineMeta ? { ...source.__spineMeta } : undefined,\n };\n}\n\nfunction createTimeoutFetch(timeout?: number): typeof fetch | undefined {\n if (!timeout) {\n return undefined;\n }\n\n return (input, init) => fetchWithTimeout(input, { ...init, timeout });\n}\n\nfunction isProblemDetails(value: unknown): value is ProblemDetails {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'title' in value &&\n 'status' in value &&\n 'detail' in value\n );\n}\n\nasync function parseResponseBody(response: Response): Promise<unknown> {\n const clone = response.clone();\n const contentType = clone.headers.get('content-type') || '';\n\n if (contentType.includes('application/json') || contentType.includes('+json')) {\n try {\n return await clone.json();\n } catch {\n return undefined;\n }\n }\n\n try {\n const text = await clone.text();\n return text || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction createClientError<T = unknown>(params: {\n message: string;\n response?: APIResponse<T>;\n config?: ResponseRequestMeta;\n problemDetails?: ProblemDetails;\n shouldLogout?: boolean;\n originalError?: unknown;\n cause?: unknown;\n}): APIClientError<T> {\n const error = new Error(params.message) as APIClientError<T>;\n\n error.name = 'APIClientError';\n error.isApiClientError = true;\n error.response = params.response;\n error.config = params.config;\n error.problemDetails = params.problemDetails;\n error.shouldLogout = params.shouldLogout;\n error.originalError = params.originalError;\n error.cause = params.cause;\n\n return error;\n}\n\nasync function toAPIResponse<T>(\n rawResponse: Response,\n resolvedData?: T\n): Promise<APIResponse<T>> {\n const data = resolvedData === undefined ? ((await parseResponseBody(rawResponse)) as T) : resolvedData;\n\n return {\n data,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: headersToRecord(rawResponse.headers),\n raw: rawResponse,\n };\n}\n\nasync function normalizeClientError(error: unknown): Promise<APIClientError> {\n if (isApiClientError(error)) {\n return error;\n }\n\n if (error && typeof error === 'object' && 'response' in error && (error as { response?: unknown }).response instanceof Response) {\n const response = (error as { response: Response }).response;\n const responseData = await parseResponseBody(response);\n const problemDetails = isProblemDetails(responseData) ? responseData : undefined;\n\n return createClientError({\n message:\n problemDetails?.detail ||\n problemDetails?.title ||\n response.statusText ||\n 'API request failed',\n response: await toAPIResponse(response, responseData),\n config: responseRequestMeta.get(response),\n problemDetails,\n cause: error,\n });\n }\n\n return createClientError({\n message: error instanceof Error ? error.message : 'API request failed',\n cause: error,\n });\n}\n\nfunction isRetryableFetchError(error: unknown): boolean {\n if (isRetryableError(error)) {\n return true;\n }\n\n if (error && typeof error === 'object' && 'cause' in error) {\n return isRetryableError((error as { cause?: unknown }).cause);\n }\n\n return false;\n}\n\nasync function maybeRetryResponse(\n context: FetchResponseContext,\n retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>,\n logger?: APILogger\n): Promise<Response | undefined> {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n\n if (context.response.status === 401 || context.response.status === 409) {\n return undefined;\n }\n\n if (!isRetryableError(context.response)) {\n return undefined;\n }\n\n const retryCount = meta.retryCount ?? 0;\n if (retryCount >= retryConfig.maxRetries) {\n return undefined;\n }\n\n meta.retryCount = retryCount + 1;\n\n const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);\n logger?.info?.('Retrying request', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n attempt: meta.retryCount,\n maxRetries: retryConfig.maxRetries,\n delay,\n status: context.response.status,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return context.fetch(context.url, init);\n}\n\nasync function maybeRetryError(\n context: FetchErrorContext,\n retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>,\n logger?: APILogger\n): Promise<Response | undefined> {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n const retryCount = meta.retryCount ?? 0;\n\n if (!isRetryableFetchError(context.error) || retryCount >= retryConfig.maxRetries) {\n return undefined;\n }\n\n meta.retryCount = retryCount + 1;\n\n const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);\n logger?.info?.('Retrying request after fetch failure', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n attempt: meta.retryCount,\n maxRetries: retryConfig.maxRetries,\n delay,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return context.fetch(context.url, init);\n}\n\nexport interface FetchSetupOptions {\n logger?: APILogger;\n attemptTokenRefresh?: TokenRefreshFn;\n retryConfig?: RetryConfig;\n addRequestId?: boolean;\n}\n\nexport function createFetchMiddleware(\n request: Request,\n options: FetchSetupOptions = {}\n): FetchMiddleware[] {\n const {\n logger,\n attemptTokenRefresh,\n retryConfig = {},\n addRequestId = true,\n } = options;\n\n const normalizedRetryConfig = {\n maxRetries: retryConfig.maxRetries ?? 3,\n baseDelay: retryConfig.baseDelay ?? 1000,\n maxDelay: retryConfig.maxDelay ?? 10000,\n };\n\n return [\n {\n async pre(context) {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n const headers = new Headers(init.headers);\n\n if (addRequestId) {\n const requestId = headers.get('X-Request-Id') || crypto.randomUUID();\n headers.set('X-Request-Id', requestId);\n meta.requestId = requestId;\n }\n\n init.headers = headersToRecord(headers);\n\n logger?.debug?.('API request initiated', {\n method: (init.method ?? 'GET').toUpperCase(),\n url: context.url,\n requestId: meta.requestId,\n });\n\n return {\n url: context.url,\n init,\n };\n },\n\n async post(context) {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n\n responseRequestMeta.set(context.response, {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n headers: headersToRecord(init.headers),\n });\n\n if (context.response.status === 401 && attemptTokenRefresh && !meta.refreshed) {\n meta.refreshed = true;\n\n logger?.info?.('Received 401 from backend, attempting token refresh', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n });\n\n const refreshResult = await getOrCreateRefreshPromise(\n getSessionKeyFromRequest(request),\n () => attemptTokenRefresh(request),\n logger\n );\n\n if (refreshResult.success && refreshResult.newAccessToken) {\n const headers = new Headers(init.headers);\n headers.set('Authorization', `Bearer ${refreshResult.newAccessToken}`);\n init.headers = headersToRecord(headers);\n\n logger?.info?.('Token refresh successful, retrying original request');\n\n return context.fetch(context.url, init);\n }\n\n if (refreshResult.shouldLogout) {\n throw createClientError({\n message: 'REFRESH_TOKEN_EXPIRED',\n response: await toAPIResponse(context.response),\n config: responseRequestMeta.get(context.response),\n shouldLogout: true,\n originalError: refreshResult,\n cause: refreshResult,\n });\n }\n }\n\n const retryResponse = await maybeRetryResponse(context, normalizedRetryConfig, logger);\n if (retryResponse) {\n return retryResponse;\n }\n\n if (context.response.status >= 400) {\n const responseData = await parseResponseBody(context.response);\n const problemDetails = isProblemDetails(responseData) ? responseData : undefined;\n\n logger?.error?.('API error response received', undefined, {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n status: context.response.status,\n code: problemDetails?.title,\n detail: problemDetails?.detail,\n traceId: problemDetails?.traceId,\n });\n }\n\n return context.response;\n },\n\n async onError(context) {\n const retryResponse = await maybeRetryError(context, normalizedRetryConfig, logger);\n if (retryResponse) {\n return retryResponse;\n }\n\n return undefined;\n },\n },\n ];\n}\n\nexport interface CreateApiClientOptions extends FetchSetupOptions, CreateAPIConfigOptions {\n credentials?: RequestCredentials;\n}\n\nfunction instrumentRawMethods<T extends GeneratedFetchClient>(client: T): void {\n if ((client as { __spineRawMethodsInstrumented?: boolean }).__spineRawMethodsInstrumented) {\n return;\n }\n\n const prototype = Object.getPrototypeOf(client) as Record<string, unknown>;\n\n for (const key of Object.getOwnPropertyNames(prototype)) {\n if (!key.endsWith('Raw')) {\n continue;\n }\n\n const original = prototype[key];\n if (typeof original !== 'function') {\n continue;\n }\n\n Object.defineProperty(client, key, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: async (...args: unknown[]) => {\n const result = await (original as (...innerArgs: unknown[]) => Promise<RuntimeApiResponse<unknown>>).apply(client, args);\n rawResponseStore.getStore()?.set(key, result);\n return result;\n },\n });\n }\n\n (client as { __spineRawMethodsInstrumented?: boolean }).__spineRawMethodsInstrumented = true;\n}\n\nfunction wrapClient<T extends GeneratedFetchClient>(client: T): EnhancedAPIClient<T> {\n instrumentRawMethods(client);\n\n const methodCache = new Map<PropertyKey, unknown>();\n\n return new Proxy(client, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop !== 'string' || typeof value !== 'function' || prop.endsWith('Raw')) {\n return typeof value === 'function' ? value.bind(target) : value;\n }\n\n if (methodCache.has(prop)) {\n return methodCache.get(prop);\n }\n\n const wrapped = async (...args: unknown[]) =>\n rawResponseStore.run(new Map(), async () => {\n try {\n const resolvedData = await (value as (...innerArgs: unknown[]) => Promise<unknown>).apply(target, args);\n const rawResponse = rawResponseStore.getStore()?.get(`${prop}Raw`);\n\n if (rawResponse) {\n return toAPIResponse(rawResponse.raw, resolvedData);\n }\n\n return {\n data: resolvedData,\n status: 200,\n statusText: 'OK',\n headers: {},\n };\n } catch (error) {\n throw await normalizeClientError(error);\n }\n });\n\n methodCache.set(prop, wrapped);\n return wrapped;\n },\n }) as EnhancedAPIClient<T>;\n}\n\nexport async function createApiClient<T extends GeneratedFetchClient>(\n request: Request,\n ClientClass: new (configuration?: unknown) => T,\n createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>,\n ConfigurationClass: new (params?: Record<string, unknown>) => unknown,\n options: CreateApiClientOptions = {}\n): Promise<{ client: EnhancedAPIClient<T>; tenantId: string }> {\n const {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n credentials,\n ...fetchOptions\n } = options;\n\n const config = await createAPIConfig(request, {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n });\n\n const configuration = new ConfigurationClass({\n basePath: config.basePath,\n accessToken: config.accessToken,\n headers: config.headers,\n credentials: credentials ?? config.credentials,\n middleware: createFetchMiddleware(request, fetchOptions),\n fetchApi: createTimeoutFetch(config.baseOptions?.timeout),\n });\n\n const client = new ClientClass(configuration);\n\n return {\n client: wrapClient(client),\n tenantId: config.tenantId,\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"signalr-CSfCz1Ik.js","names":["DEFAULT_CONFIG: Required<Omit<SignalRClientConfig, 'hubPath' | 'baseUrl'>>","config: SignalRClientConfig","accessToken: string | null","event: unknown","eventName: string","handler: SignalREventHandler<T>","methodName: string","message: string","level: 'info' | 'warn' | 'error'","options: Omit<SignalRClientConfig, 'hubPath'>","handler: IdentityContextChangeHandler","identityClientInstance: IdentitySignalRClient | null"],"sources":["../src/signalr/signalr-client.ts"],"sourcesContent":["/**\n * SignalR Client for Real-time Updates\n * \n * This client connects to SignalR hub and listens for real-time events.\n * Supports identity context changes, permissions updates, etc.\n */\n\nimport { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';\nimport type {\n SignalRClientConfig,\n SignalRConnectionState,\n SignalREventHandler,\n IdentityContextChangedEvent,\n IdentityContextChangeHandler,\n} from './types';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<SignalRClientConfig, 'hubPath' | 'baseUrl'>> = {\n maxReconnectAttempts: 5,\n reconnectDelay: 1000,\n verbose: false,\n autoReconnectOnVisibility: true,\n};\n\n/**\n * SignalR Client Class\n * \n * Generic SignalR client that can be used for any hub connection.\n */\nexport class SignalRClient {\n private connection: HubConnection | null = null;\n private accessToken: string | null = null;\n private isConnecting = false;\n private reconnectAttempts = 0;\n private config: Required<Omit<SignalRClientConfig, 'baseUrl'>> & { baseUrl?: string };\n private eventHandlers: Map<string, Set<SignalREventHandler>> = new Map();\n private visibilityHandler: (() => void) | null = null;\n\n constructor(config: SignalRClientConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n // Auto-reconnect on page visibility change\n if (typeof document !== 'undefined' && this.config.autoReconnectOnVisibility) {\n this.visibilityHandler = () => {\n if (document.visibilityState === 'visible' && this.connection?.state === 'Disconnected') {\n this.log('🔄 Page became visible, attempting reconnection...');\n this.connect(this.accessToken);\n }\n };\n document.addEventListener('visibilitychange', this.visibilityHandler);\n }\n }\n\n /**\n * Connect to SignalR hub\n */\n async connect(accessToken: string | null): Promise<void> {\n if (!accessToken) {\n this.log('⚠️ No access token provided for SignalR connection', 'warn');\n return;\n }\n\n if (this.isConnecting) {\n this.log('🔄 Connection already in progress...');\n return;\n }\n\n if (this.connection?.state === 'Connected') {\n this.log('✅ Already connected');\n return;\n }\n\n this.accessToken = accessToken;\n this.isConnecting = true;\n\n try {\n const hubUrl = this.buildHubUrl();\n\n this.connection = new HubConnectionBuilder()\n .withUrl(hubUrl, {\n accessTokenFactory: () => accessToken,\n })\n .withAutomaticReconnect({\n nextRetryDelayInMilliseconds: (retryContext) => {\n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n const delay = Math.min(\n this.config.reconnectDelay * Math.pow(2, retryContext.previousRetryCount),\n 16000\n );\n this.log(`🔄 Reconnect attempt ${retryContext.previousRetryCount + 1} in ${delay}ms`);\n return delay;\n },\n })\n .configureLogging(this.config.verbose ? LogLevel.Information : LogLevel.Warning)\n .build();\n\n // Setup event handlers\n this.setupConnectionHandlers();\n\n // Connect\n this.log(`🔄 Connecting to SignalR hub: ${hubUrl}`);\n await this.connection.start();\n\n this.log(`✅ Connected successfully (ID: ${this.connection.connectionId})`);\n this.reconnectAttempts = 0;\n } catch (error) {\n this.log(`❌ Connection failed: ${error}`, 'error');\n this.reconnectAttempts++;\n\n // Retry with exponential backoff\n if (this.reconnectAttempts <= this.config.maxReconnectAttempts) {\n const delay = Math.min(\n this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),\n 30000\n );\n this.log(\n `🔄 Retrying in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`\n );\n\n setTimeout(() => {\n this.connect(accessToken);\n }, delay);\n } else {\n this.log('❌ Max reconnection attempts reached', 'error');\n }\n } finally {\n this.isConnecting = false;\n }\n }\n\n /**\n * Build hub URL\n */\n private buildHubUrl(): string {\n let baseUrl = this.config.baseUrl;\n\n // Auto-detect base URL if not provided\n if (!baseUrl && typeof window !== 'undefined') {\n baseUrl =\n window.location.hostname === 'localhost'\n ? 'https://localhost:5001' // Development\n : window.location.origin; // Production\n }\n\n return `${baseUrl}${this.config.hubPath}`;\n }\n\n /**\n * Setup connection lifecycle handlers\n */\n private setupConnectionHandlers(): void {\n if (!this.connection) return;\n\n this.connection.onreconnecting((error) => {\n this.log(`🔄 Reconnecting... ${error?.message || ''}`);\n });\n\n this.connection.onreconnected((connectionId) => {\n this.log(`✅ Reconnected (ID: ${connectionId})`);\n this.reconnectAttempts = 0;\n });\n\n this.connection.onclose((error) => {\n this.log(`🔌 Connection closed: ${error?.message || 'No error'}`);\n\n // Auto-reconnect if we have a token and it wasn't a manual disconnect\n if (this.accessToken && error) {\n setTimeout(() => {\n this.connect(this.accessToken);\n }, 5000);\n }\n });\n\n // Re-register all event handlers\n this.eventHandlers.forEach((handlers, eventName) => {\n this.connection!.on(eventName, (event: unknown) => {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n this.log(`❌ Error in ${eventName} handler: ${err}`, 'error');\n }\n });\n });\n });\n }\n\n /**\n * Disconnect from SignalR hub\n */\n async disconnect(): Promise<void> {\n if (this.connection) {\n this.log('🔌 Disconnecting...');\n await this.connection.stop();\n this.connection = null;\n this.accessToken = null;\n this.reconnectAttempts = 0;\n }\n }\n\n /**\n * Register event handler\n */\n on<T = unknown>(eventName: string, handler: SignalREventHandler<T>): () => void {\n // Add to handlers map\n if (!this.eventHandlers.has(eventName)) {\n this.eventHandlers.set(eventName, new Set());\n }\n this.eventHandlers.get(eventName)!.add(handler as SignalREventHandler);\n\n // If already connected, register immediately\n if (this.connection) {\n this.connection.on(eventName, handler);\n }\n\n // Return unsubscribe function\n return () => {\n this.eventHandlers.get(eventName)?.delete(handler as SignalREventHandler);\n this.connection?.off(eventName, handler);\n };\n }\n\n /**\n * Invoke hub method\n */\n async invoke<T = void>(methodName: string, ...args: unknown[]): Promise<T> {\n if (!this.connection || this.connection.state !== 'Connected') {\n throw new Error('SignalR not connected');\n }\n return this.connection.invoke<T>(methodName, ...args);\n }\n\n /**\n * Get current connection state\n */\n get connectionState(): SignalRConnectionState {\n return (this.connection?.state as SignalRConnectionState) || 'Disconnected';\n }\n\n /**\n * Check if connected\n */\n get isConnected(): boolean {\n return this.connection?.state === 'Connected';\n }\n\n /**\n * Get connection ID\n */\n get connectionId(): string | null {\n return this.connection?.connectionId || null;\n }\n\n /**\n * Cleanup resources\n */\n dispose(): void {\n if (this.visibilityHandler && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.visibilityHandler);\n }\n this.disconnect();\n this.eventHandlers.clear();\n }\n\n /**\n * Log message\n */\n private log(message: string, level: 'info' | 'warn' | 'error' = 'info'): void {\n if (!this.config.verbose && level === 'info') return;\n\n const prefix = '[SignalR]';\n switch (level) {\n case 'warn':\n console.warn(prefix, message);\n break;\n case 'error':\n console.error(prefix, message);\n break;\n default:\n console.log(prefix, message);\n }\n }\n}\n\n/**\n * Create Identity SignalR Client\n * \n * Factory function to create a SignalR client for identity hub.\n */\nexport function createIdentitySignalRClient(\n options: Omit<SignalRClientConfig, 'hubPath'> = {}\n): SignalRClient {\n return new SignalRClient({\n ...options,\n hubPath: '/hubs/identity',\n });\n}\n\n/**\n * Identity SignalR Client\n * \n * Specialized client for identity context changes.\n */\nexport class IdentitySignalRClient extends SignalRClient {\n private identityHandlers: IdentityContextChangeHandler[] = [];\n\n constructor(options: Omit<SignalRClientConfig, 'hubPath'> = {}) {\n super({\n ...options,\n hubPath: '/hubs/identity',\n });\n\n // Register identity context change handler\n this.on<IdentityContextChangedEvent>('IdentityContextChanged', (event) => {\n this.identityHandlers.forEach((handler) => {\n try {\n handler(event);\n } catch (error) {\n console.error('Error in identity context change handler:', error);\n }\n });\n });\n }\n\n /**\n * Add handler for identity context changes\n */\n onIdentityContextChanged(handler: IdentityContextChangeHandler): () => void {\n this.identityHandlers.push(handler);\n\n return () => {\n const index = this.identityHandlers.indexOf(handler);\n if (index > -1) {\n this.identityHandlers.splice(index, 1);\n }\n };\n }\n}\n\n/**\n * Singleton identity client instance\n */\nlet identityClientInstance: IdentitySignalRClient | null = null;\n\n/**\n * Get or create singleton identity SignalR client\n */\nexport function getIdentitySignalRClient(\n options: Omit<SignalRClientConfig, 'hubPath'> = {}\n): IdentitySignalRClient {\n if (!identityClientInstance) {\n identityClientInstance = new IdentitySignalRClient(options);\n }\n return identityClientInstance;\n}\n\n/**\n * Initialize SignalR with access token\n */\nexport async function initializeSignalR(\n accessToken: string | null,\n options: Omit<SignalRClientConfig, 'hubPath'> = {}\n): Promise<IdentitySignalRClient> {\n if (typeof window === 'undefined') {\n throw new Error('SignalR can only be initialized on the client');\n }\n\n const client = getIdentitySignalRClient(options);\n await client.connect(accessToken);\n return client;\n}\n\n/**\n * Cleanup SignalR connection\n */\nexport async function cleanupSignalR(): Promise<void> {\n if (identityClientInstance) {\n await identityClientInstance.disconnect();\n identityClientInstance.dispose();\n identityClientInstance = null;\n }\n}\n"],"mappings":";;;;;;AAmBA,MAAMA,iBAA6E;CACjF,sBAAsB;CACtB,gBAAgB;CAChB,SAAS;CACT,2BAA2B;AAC5B;;;;;;AAOD,IAAa,gBAAb,MAA2B;CACzB,AAAQ,aAAmC;CAC3C,AAAQ,cAA6B;CACrC,AAAQ,eAAe;CACvB,AAAQ,oBAAoB;CAC5B,AAAQ;CACR,AAAQ,gBAAuD,IAAI;CACnE,AAAQ,oBAAyC;CAEjD,YAAYC,QAA6B;AACvC,OAAK,SAAS;GACZ,GAAG;GACH,GAAG;EACJ;AAGD,aAAW,aAAa,eAAe,KAAK,OAAO,2BAA2B;AAC5E,QAAK,oBAAoB,MAAM;AAC7B,QAAI,SAAS,oBAAoB,aAAa,KAAK,YAAY,UAAU,gBAAgB;AACvF,UAAK,IAAI,qDAAqD;AAC9D,UAAK,QAAQ,KAAK,YAAY;IAC/B;GACF;AACD,YAAS,iBAAiB,oBAAoB,KAAK,kBAAkB;EACtE;CACF;;;;CAKD,MAAM,QAAQC,aAA2C;AACvD,OAAK,aAAa;AAChB,QAAK,IAAI,sDAAsD,OAAO;AACtE;EACD;AAED,MAAI,KAAK,cAAc;AACrB,QAAK,IAAI,uCAAuC;AAChD;EACD;AAED,MAAI,KAAK,YAAY,UAAU,aAAa;AAC1C,QAAK,IAAI,sBAAsB;AAC/B;EACD;AAED,OAAK,cAAc;AACnB,OAAK,eAAe;AAEpB,MAAI;GACF,MAAM,SAAS,KAAK,aAAa;AAEjC,QAAK,aAAa,IAAI,uBACnB,QAAQ,QAAQ,EACf,oBAAoB,MAAM,YAC3B,EAAC,CACD,uBAAuB,EACtB,8BAA8B,CAAC,iBAAiB;IAE9C,MAAM,QAAQ,KAAK,IACjB,KAAK,OAAO,iBAAiB,KAAK,IAAI,GAAG,aAAa,mBAAmB,EACzE,KACD;AACD,SAAK,KAAK,uBAAuB,aAAa,qBAAqB,EAAE,MAAM,MAAM,IAAI;AACrF,WAAO;GACR,EACF,EAAC,CACD,iBAAiB,KAAK,OAAO,UAAU,SAAS,cAAc,SAAS,QAAQ,CAC/E,OAAO;AAGV,QAAK,yBAAyB;AAG9B,QAAK,KAAK,gCAAgC,OAAO,EAAE;AACnD,SAAM,KAAK,WAAW,OAAO;AAE7B,QAAK,KAAK,gCAAgC,KAAK,WAAW,aAAa,GAAG;AAC1E,QAAK,oBAAoB;EAC1B,SAAQ,OAAO;AACd,QAAK,KAAK,uBAAuB,MAAM,GAAG,QAAQ;AAClD,QAAK;AAGL,OAAI,KAAK,qBAAqB,KAAK,OAAO,sBAAsB;IAC9D,MAAM,QAAQ,KAAK,IACjB,KAAK,OAAO,iBAAiB,KAAK,IAAI,GAAG,KAAK,oBAAoB,EAAE,EACpE,IACD;AACD,SAAK,KACF,iBAAiB,MAAM,cAAc,KAAK,kBAAkB,GAAG,KAAK,OAAO,qBAAqB,GAClG;AAED,eAAW,MAAM;AACf,UAAK,QAAQ,YAAY;IAC1B,GAAE,MAAM;GACV,MACC,MAAK,IAAI,uCAAuC,QAAQ;EAE3D,UAAS;AACR,QAAK,eAAe;EACrB;CACF;;;;CAKD,AAAQ,cAAsB;EAC5B,IAAI,UAAU,KAAK,OAAO;AAG1B,OAAK,kBAAkB,WAAW,YAChC,WACE,OAAO,SAAS,aAAa,cACzB,2BACA,OAAO,SAAS;AAGxB,UAAQ,EAAE,QAAQ,EAAE,KAAK,OAAO,QAAQ;CACzC;;;;CAKD,AAAQ,0BAAgC;AACtC,OAAK,KAAK,WAAY;AAEtB,OAAK,WAAW,eAAe,CAAC,UAAU;AACxC,QAAK,KAAK,qBAAqB,OAAO,WAAW,GAAG,EAAE;EACvD,EAAC;AAEF,OAAK,WAAW,cAAc,CAAC,iBAAiB;AAC9C,QAAK,KAAK,qBAAqB,aAAa,GAAG;AAC/C,QAAK,oBAAoB;EAC1B,EAAC;AAEF,OAAK,WAAW,QAAQ,CAAC,UAAU;AACjC,QAAK,KAAK,wBAAwB,OAAO,WAAW,WAAW,EAAE;AAGjE,OAAI,KAAK,eAAe,MACtB,YAAW,MAAM;AACf,SAAK,QAAQ,KAAK,YAAY;GAC/B,GAAE,IAAK;EAEX,EAAC;AAGF,OAAK,cAAc,QAAQ,CAAC,UAAU,cAAc;AAClD,QAAK,WAAY,GAAG,WAAW,CAACC,UAAmB;AACjD,aAAS,QAAQ,CAAC,YAAY;AAC5B,SAAI;AACF,cAAQ,MAAM;KACf,SAAQ,KAAK;AACZ,WAAK,KAAK,aAAa,UAAU,YAAY,IAAI,GAAG,QAAQ;KAC7D;IACF,EAAC;GACH,EAAC;EACH,EAAC;CACH;;;;CAKD,MAAM,aAA4B;AAChC,MAAI,KAAK,YAAY;AACnB,QAAK,IAAI,sBAAsB;AAC/B,SAAM,KAAK,WAAW,MAAM;AAC5B,QAAK,aAAa;AAClB,QAAK,cAAc;AACnB,QAAK,oBAAoB;EAC1B;CACF;;;;CAKD,GAAgBC,WAAmBC,SAA6C;AAE9E,OAAK,KAAK,cAAc,IAAI,UAAU,CACpC,MAAK,cAAc,IAAI,WAAW,IAAI,MAAM;AAE9C,OAAK,cAAc,IAAI,UAAU,CAAE,IAAI,QAA+B;AAGtE,MAAI,KAAK,WACP,MAAK,WAAW,GAAG,WAAW,QAAQ;AAIxC,SAAO,MAAM;AACX,QAAK,cAAc,IAAI,UAAU,EAAE,OAAO,QAA+B;AACzE,QAAK,YAAY,IAAI,WAAW,QAAQ;EACzC;CACF;;;;CAKD,MAAM,OAAiBC,YAAoB,GAAG,MAA6B;AACzE,OAAK,KAAK,cAAc,KAAK,WAAW,UAAU,YAChD,OAAM,IAAI,MAAM;AAElB,SAAO,KAAK,WAAW,OAAU,YAAY,GAAG,KAAK;CACtD;;;;CAKD,IAAI,kBAA0C;AAC5C,SAAQ,KAAK,YAAY,SAAoC;CAC9D;;;;CAKD,IAAI,cAAuB;AACzB,SAAO,KAAK,YAAY,UAAU;CACnC;;;;CAKD,IAAI,eAA8B;AAChC,SAAO,KAAK,YAAY,gBAAgB;CACzC;;;;CAKD,UAAgB;AACd,MAAI,KAAK,4BAA4B,aAAa,YAChD,UAAS,oBAAoB,oBAAoB,KAAK,kBAAkB;AAE1E,OAAK,YAAY;AACjB,OAAK,cAAc,OAAO;CAC3B;;;;CAKD,AAAQ,IAAIC,SAAiBC,QAAmC,QAAc;AAC5E,OAAK,KAAK,OAAO,WAAW,UAAU,OAAQ;EAE9C,MAAM,SAAS;AACf,UAAQ,OAAR;GACE,KAAK;AACH,YAAQ,KAAK,QAAQ,QAAQ;AAC7B;GACF,KAAK;AACH,YAAQ,MAAM,QAAQ,QAAQ;AAC9B;GACF,QACE,SAAQ,IAAI,QAAQ,QAAQ;EAC/B;CACF;AACF;;;;;;AAOD,SAAgB,4BACdC,UAAgD,CAAE,GACnC;AACf,QAAO,IAAI,cAAc;EACvB,GAAG;EACH,SAAS;CACV;AACF;;;;;;AAOD,IAAa,wBAAb,cAA2C,cAAc;CACvD,AAAQ,mBAAmD,CAAE;CAE7D,YAAYA,UAAgD,CAAE,GAAE;AAC9D,QAAM;GACJ,GAAG;GACH,SAAS;EACV,EAAC;AAGF,OAAK,GAAgC,0BAA0B,CAAC,UAAU;AACxE,QAAK,iBAAiB,QAAQ,CAAC,YAAY;AACzC,QAAI;AACF,aAAQ,MAAM;IACf,SAAQ,OAAO;AACd,aAAQ,MAAM,6CAA6C,MAAM;IAClE;GACF,EAAC;EACH,EAAC;CACH;;;;CAKD,yBAAyBC,SAAmD;AAC1E,OAAK,iBAAiB,KAAK,QAAQ;AAEnC,SAAO,MAAM;GACX,MAAM,QAAQ,KAAK,iBAAiB,QAAQ,QAAQ;AACpD,OAAI,QAAQ,GACV,MAAK,iBAAiB,OAAO,OAAO,EAAE;EAEzC;CACF;AACF;;;;AAKD,IAAIC,yBAAuD;;;;AAK3D,SAAgB,yBACdF,UAAgD,CAAE,GAC3B;AACvB,MAAK,uBACH,0BAAyB,IAAI,sBAAsB;AAErD,QAAO;AACR;;;;AAKD,eAAsB,kBACpBP,aACAO,UAAgD,CAAE,GAClB;AAChC,YAAW,WAAW,YACpB,OAAM,IAAI,MAAM;CAGlB,MAAM,SAAS,yBAAyB,QAAQ;AAChD,OAAM,OAAO,QAAQ,YAAY;AACjC,QAAO;AACR;;;;AAKD,eAAsB,iBAAgC;AACpD,KAAI,wBAAwB;AAC1B,QAAM,uBAAuB,YAAY;AACzC,yBAAuB,SAAS;AAChC,2BAAyB;CAC1B;AACF"}