@eminuckan/spine 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/api-client/api-config.server.d.ts +40 -0
- package/dist/api-client/api-config.server.d.ts.map +1 -0
- package/dist/api-client/error-codes.d.ts +57 -0
- package/dist/api-client/error-codes.d.ts.map +1 -0
- package/dist/api-client/error-handler.d.ts +52 -0
- package/dist/api-client/error-handler.d.ts.map +1 -0
- package/dist/api-client/fetch-client.server.d.ts +29 -0
- package/dist/api-client/fetch-client.server.d.ts.map +1 -0
- package/dist/api-client/index.d.ts +11 -3
- package/dist/api-client/index.d.ts.map +1 -0
- package/dist/api-client/index.js +3 -2
- package/dist/api-client/retry-handler.d.ts +29 -0
- package/dist/api-client/retry-handler.d.ts.map +1 -0
- package/dist/api-client/server.d.ts +9 -4
- package/dist/api-client/server.d.ts.map +1 -0
- package/dist/api-client/server.js +4 -3
- package/dist/api-client/types.d.ts +121 -0
- package/dist/api-client/types.d.ts.map +1 -0
- package/dist/{api-client-Ftxqm3Ep.js → api-client-DsrxK05Z.js} +1 -1
- package/dist/{api-client-Ftxqm3Ep.js.map → api-client-DsrxK05Z.js.map} +1 -1
- package/dist/auth/auth.server.d.ts +124 -0
- package/dist/auth/auth.server.d.ts.map +1 -0
- package/dist/auth/index.d.ts +8 -2
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/redis-session-storage.server.d.ts +21 -0
- package/dist/auth/redis-session-storage.server.d.ts.map +1 -0
- package/dist/auth/route-protection.server.d.ts +107 -0
- package/dist/auth/route-protection.server.d.ts.map +1 -0
- package/dist/auth/server.d.ts +13 -3
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +3 -3
- package/dist/auth/token-refresh.server.d.ts +17 -0
- package/dist/auth/token-refresh.server.d.ts.map +1 -0
- package/dist/auth/types.d.ts +160 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth.server-BPiCtlJE.js +5 -0
- package/dist/{auth.server-C979TR6W.js → auth.server-Cx2h2qu3.js} +3 -3
- package/dist/{auth.server-C979TR6W.js.map → auth.server-Cx2h2qu3.js.map} +1 -1
- package/dist/http/response.d.ts +12 -0
- package/dist/http/response.d.ts.map +1 -0
- package/dist/identity/identity-context-provider.d.ts +19 -0
- package/dist/identity/identity-context-provider.d.ts.map +1 -0
- package/dist/identity/identity-context.server.d.ts +73 -0
- package/dist/identity/identity-context.server.d.ts.map +1 -0
- package/dist/identity/identity-store.d.ts +55 -0
- package/dist/identity/identity-store.d.ts.map +1 -0
- package/dist/identity/index.d.ts +9 -4
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/server.d.ts +6 -4
- package/dist/identity/server.d.ts.map +1 -0
- package/dist/identity/server.js +2 -1
- package/dist/identity/types.d.ts +91 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/index.d.ts +14 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -5
- package/dist/logging/config.d.ts +7 -0
- package/dist/logging/config.d.ts.map +1 -0
- package/dist/logging/index.d.ts +9 -3
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/logger.d.ts +22 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/types.d.ts +27 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/permissions/components.d.ts +48 -0
- package/dist/permissions/components.d.ts.map +1 -0
- package/dist/permissions/constants.d.ts +9 -0
- package/dist/permissions/constants.d.ts.map +1 -0
- package/dist/permissions/hooks.d.ts +47 -0
- package/dist/permissions/hooks.d.ts.map +1 -0
- package/dist/permissions/index.d.ts +28 -2
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/permission-initializer.d.ts +18 -0
- package/dist/permissions/permission-initializer.d.ts.map +1 -0
- package/dist/permissions/permission-service.d.ts +53 -0
- package/dist/permissions/permission-service.d.ts.map +1 -0
- package/dist/permissions/permission-store.d.ts +42 -0
- package/dist/permissions/permission-store.d.ts.map +1 -0
- package/dist/permissions/route-protection.server.d.ts +33 -0
- package/dist/permissions/route-protection.server.d.ts.map +1 -0
- package/dist/permissions/types.d.ts +44 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/query-client/index.d.ts +8 -2
- package/dist/query-client/index.d.ts.map +1 -0
- package/dist/query-client/index.js +1 -1
- package/dist/query-client/query-config.d.ts +55 -0
- package/dist/query-client/query-config.d.ts.map +1 -0
- package/dist/query-client/types.d.ts +115 -0
- package/dist/query-client/types.d.ts.map +1 -0
- package/dist/{query-client-DRPQIKHX.js → query-client-Bpe08pk9.js} +1 -1
- package/dist/query-client-Bpe08pk9.js.map +1 -0
- package/dist/react-router/index.d.ts +9 -12
- package/dist/react-router/index.d.ts.map +1 -0
- package/dist/react-router/index.js +6 -5
- package/dist/react-router/server.d.ts +9 -18
- package/dist/react-router/server.d.ts.map +1 -0
- package/dist/react-router/server.js +13 -12
- package/dist/{redis-session-storage.server-DhufyPjd.js → redis-session-storage.server-BR_0y-QO.js} +2 -2
- package/dist/{redis-session-storage.server-DhufyPjd.js.map → redis-session-storage.server-BR_0y-QO.js.map} +1 -1
- package/dist/{server-CiSbg-9C.js → server-BHfKHKF_.js} +5 -5
- package/dist/{server-CiSbg-9C.js.map → server-BHfKHKF_.js.map} +1 -1
- package/dist/{server-C-DiVP9e.js → server-BxvRxjCK.js} +2 -2
- package/dist/{server-C-DiVP9e.js.map → server-BxvRxjCK.js.map} +1 -1
- package/dist/{server-iWm3oeCM.js → server-C3L8J7iE.js} +3 -2
- package/dist/server-C3L8J7iE.js.map +1 -0
- package/dist/{server-CNp6gjFE.js → server-CouYoZiw.js} +1 -1
- package/dist/{server-CNp6gjFE.js.map → server-CouYoZiw.js.map} +1 -1
- package/dist/server-DZmcd2Lr.js +422 -0
- package/dist/server-DZmcd2Lr.js.map +1 -0
- package/dist/server.d.ts +14 -18
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +13 -12
- package/dist/signalr/index.d.ts +8 -4
- package/dist/signalr/index.d.ts.map +1 -0
- package/dist/signalr/index.js +1 -1
- package/dist/signalr/signalr-client.d.ts +98 -0
- package/dist/signalr/signalr-client.d.ts.map +1 -0
- package/dist/signalr/types.d.ts +42 -0
- package/dist/signalr/types.d.ts.map +1 -0
- package/dist/{signalr-BJGcXc0m.js → signalr-SJ-1sIxw.js} +1 -1
- package/dist/{signalr-BJGcXc0m.js.map → signalr-SJ-1sIxw.js.map} +1 -1
- package/dist/tenant/index.d.ts +9 -3
- package/dist/tenant/index.d.ts.map +1 -0
- package/dist/tenant/server.d.ts +7 -3
- package/dist/tenant/server.d.ts.map +1 -0
- package/dist/tenant/server.js +2 -2
- package/dist/tenant/tenant-context.d.ts +45 -0
- package/dist/tenant/tenant-context.d.ts.map +1 -0
- package/dist/tenant/tenant-cookie.server.d.ts +35 -0
- package/dist/tenant/tenant-cookie.server.d.ts.map +1 -0
- package/dist/tenant/tenant-store.d.ts +58 -0
- package/dist/tenant/tenant-store.d.ts.map +1 -0
- package/dist/tenant/tenant.server.d.ts +49 -0
- package/dist/tenant/tenant.server.d.ts.map +1 -0
- package/dist/tenant/types.d.ts +51 -0
- package/dist/tenant/types.d.ts.map +1 -0
- package/dist/types-BnKaWdfe.js +8 -0
- package/dist/types-BnKaWdfe.js.map +1 -0
- package/package.json +2 -7
- package/dist/auth.server-DU-x2LPA.js +0 -5
- package/dist/index-BIJOC8Fb.d.ts +0 -272
- package/dist/index-BIJOC8Fb.d.ts.map +0 -1
- package/dist/index-Bt9XE6gj.d.ts +0 -104
- package/dist/index-Bt9XE6gj.d.ts.map +0 -1
- package/dist/index-C2hGpzGr.d.ts +0 -74
- package/dist/index-C2hGpzGr.d.ts.map +0 -1
- package/dist/index-CfQsb3l_.d.ts +0 -170
- package/dist/index-CfQsb3l_.d.ts.map +0 -1
- package/dist/index-D6RK_5vm.d.ts +0 -199
- package/dist/index-D6RK_5vm.d.ts.map +0 -1
- package/dist/index-DrttYPAj.d.ts +0 -30
- package/dist/index-DrttYPAj.d.ts.map +0 -1
- package/dist/index-geY2ynnN.d.ts +0 -141
- package/dist/index-geY2ynnN.d.ts.map +0 -1
- package/dist/index-kr8Wk7v5.d.ts +0 -1
- package/dist/query-client-DRPQIKHX.js.map +0 -1
- package/dist/server-BoeUiXSh.d.ts +0 -40
- package/dist/server-BoeUiXSh.d.ts.map +0 -1
- package/dist/server-CLbksnvg.d.ts +0 -144
- package/dist/server-CLbksnvg.d.ts.map +0 -1
- package/dist/server-CXGaRaiS.d.ts +0 -256
- package/dist/server-CXGaRaiS.d.ts.map +0 -1
- package/dist/server-DLgH3FRR.js +0 -312
- package/dist/server-DLgH3FRR.js.map +0 -1
- package/dist/server-DazqdM63.d.ts +0 -93
- package/dist/server-DazqdM63.d.ts.map +0 -1
- package/dist/server-iWm3oeCM.js.map +0 -1
- package/dist/server-zEKMDpP7.d.ts +0 -80
- package/dist/server-zEKMDpP7.d.ts.map +0 -1
- package/dist/types-BTjFzIhv.d.ts +0 -31
- package/dist/types-BTjFzIhv.d.ts.map +0 -1
- package/dist/types-DoIfDhl6.d.ts +0 -55
- package/dist/types-DoIfDhl6.d.ts.map +0 -1
- package/dist/types-DufLRY7A.d.ts +0 -164
- package/dist/types-DufLRY7A.d.ts.map +0 -1
- package/dist/types-uDrDMkCq.d.ts +0 -93
- package/dist/types-uDrDMkCq.d.ts.map +0 -1
- /package/dist/{src-D7mh9MKA.js → src-CJ2RLi94.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.server-C979TR6W.js","names":["location: string","init: RedirectResponseInit","config: AuthConfig","applicationType: ApplicationType","OIDC_CONFIG: AuthConfig | null","authServerCache: {\n authorizationServer: oauth.AuthorizationServer;\n client: oauth.Client;\n cachedAt: number;\n} | null","client: oauth.Client","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","tokenResult: oauth.TokenEndpointResponse","baseUser: UserInfo","request: Request","returnUrlOrOptions?: string | LoginOptions","options: LoginOptions","headers","oauthState: OAuthState","redirectUri","baseUrl","refreshToken?: string","errorData: Record<string, unknown>"],"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 * OAuth2/OIDC 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 oauth from 'oauth4webapi';\n\nimport {\n createAuthSession,\n getAuthSession,\n updateAuthSession,\n destroyAuthSession,\n isSessionValid,\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 AuthError\n} from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Get effective SSO logout setting based on config\n */\nfunction getEffectiveSsoLogout(config: AuthConfig): boolean {\n // Explicit setting takes precedence\n if (config.ssoLogout !== undefined) {\n return config.ssoLogout;\n }\n \n // Determine by application type\n switch (config.applicationType) {\n case 'no-landing-page':\n case 'dashboard':\n return true; // Dashboard needs full logout (no landing page)\n case 'landing-page':\n case 'tenant-app':\n return false; // Tenant app uses app-specific logout\n default:\n return false; // Default to app-specific logout\n }\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 // Parse boolean env vars\n const ssoLogout = process.env.OIDC_SSO_LOGOUT !== undefined\n ? process.env.OIDC_SSO_LOGOUT === 'true'\n : undefined;\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 redirectUri: requiredEnvVars.redirectUri!,\n scope: process.env.OIDC_SCOPE || 'openid offline_access api',\n postLogoutRedirectUri: process.env.OIDC_POST_LOGOUT_REDIRECT_URI,\n applicationType,\n ssoLogout,\n hasLandingPage,\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 authorizationServer: oauth.AuthorizationServer;\n client: oauth.Client;\n cachedAt: number;\n} | null = null;\n\nconst CACHE_TTL = 60 * 60 * 1000; // 1 hour\n\n/**\n * Get OAuth configuration with caching\n */\nasync function getOAuthConfig(): Promise<{\n authorizationServer: oauth.AuthorizationServer;\n client: oauth.Client;\n}> {\n const config = getAuthConfig();\n const now = Date.now();\n\n if (authServerCache && now - authServerCache.cachedAt < CACHE_TTL) {\n return {\n authorizationServer: authServerCache.authorizationServer,\n client: authServerCache.client,\n };\n }\n\n try {\n const issuer = new URL(config.authority);\n const discoveryResponse = await oauth.discoveryRequest(issuer, {\n algorithm: 'oidc',\n });\n\n if (!discoveryResponse.ok) {\n const errorText = await discoveryResponse.text().catch(() => 'Unable to read response');\n logger.error('OAuth discovery error', undefined, { errorText });\n throw new Error(`OAuth discovery failed with status ${discoveryResponse.status}`);\n }\n\n const authorizationServer = await oauth.processDiscoveryResponse(issuer, discoveryResponse);\n\n const client: oauth.Client = {\n client_id: config.clientId,\n };\n\n authServerCache = {\n authorizationServer,\n client,\n cachedAt: now,\n };\n\n logger.info('OAuth discovery completed successfully');\n return { authorizationServer, client };\n } catch (error) {\n logger.error('OAuth discovery failed', error instanceof Error ? error : undefined);\n throw new Error(`OAuth discovery failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n}\n\n/**\n * Get client authentication method (None for public client with PKCE)\n */\nfunction getClientAuth(): oauth.ClientAuth {\n return oauth.None();\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: oauth.TokenEndpointResponse,\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\n return {\n userId: baseUser.sub,\n accessToken: tokenResult.access_token,\n refreshToken: tokenResult.refresh_token,\n idToken: tokenResult.id_token,\n expiresAt,\n user: baseUser,\n lastActivity: Date.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 { authorizationServer, client } = await getOAuthConfig();\n\n // Generate PKCE challenge\n const codeVerifier = oauth.generateRandomCodeVerifier();\n const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier);\n\n // Generate secure state and nonce\n const state = oauth.generateRandomState();\n const nonce = oauth.generateRandomNonce();\n\n // Create OAuth state object\n const oauthState: OAuthState = {\n state,\n codeVerifier,\n nonce,\n returnUrl: options.returnUrl,\n createdAt: Date.now(),\n };\n\n logger.info('Creating OAuth state', { returnUrl: options.returnUrl, prompt: options.prompt });\n\n // Store OAuth state in Redis\n const stateId = await createOAuthState(oauthState);\n\n // Build authorization URL\n const authorizationUrl = new URL(authorizationServer.authorization_endpoint!);\n authorizationUrl.searchParams.set('client_id', client.client_id);\n authorizationUrl.searchParams.set('redirect_uri', config.redirectUri);\n authorizationUrl.searchParams.set('response_type', 'code');\n authorizationUrl.searchParams.set('scope', config.scope);\n authorizationUrl.searchParams.set('state', state);\n authorizationUrl.searchParams.set('code_challenge', codeChallenge);\n authorizationUrl.searchParams.set('code_challenge_method', 'S256');\n authorizationUrl.searchParams.set('nonce', nonce);\n \n // Add prompt parameter if specified\n if (options.prompt) {\n authorizationUrl.searchParams.set('prompt', options.prompt);\n }\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 // DEBUG: Log full authorization URL and config\n logger.info('🔍 DEBUG: OAuth login config', {\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n postLogoutRedirectUri: config.postLogoutRedirectUri,\n scope: config.scope,\n authority: config.authority,\n authorizationEndpoint: authorizationServer.authorization_endpoint,\n fullAuthUrl: authorizationUrl.toString()\n });\n\n logger.info('OAuth authorization flow initiated', { state, stateId, prompt: options.prompt });\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\n // DEBUG: Log callback URL and params\n logger.info('🔍 DEBUG: OAuth callback received', {\n fullUrl: url.toString(),\n origin: url.origin,\n pathname: url.pathname,\n hasCode: !!code,\n hasState: !!state,\n error,\n errorDescription,\n clientId: config.clientId,\n redirectUri: config.redirectUri\n });\n\n // Handle OAuth errors\n if (error) {\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 // Validate required parameters\n if (!code || !state) {\n throw new Error('Missing required callback parameters');\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 (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 const { authorizationServer, client } = await getOAuthConfig();\n const clientAuth = getClientAuth();\n\n logger.info('Initiating token exchange');\n\n // Validate authorization response\n const currentUrl = new URL(request.url);\n const callbackParameters = oauth.validateAuthResponse(\n authorizationServer,\n client,\n currentUrl,\n oauthState.state\n );\n\n // Exchange authorization code for tokens\n const tokenEndpointResponse = await oauth.authorizationCodeGrantRequest(\n authorizationServer,\n client,\n clientAuth,\n callbackParameters,\n config.redirectUri,\n oauthState.codeVerifier\n );\n\n if (!tokenEndpointResponse.ok) {\n const errorText = await tokenEndpointResponse.text().catch(() => 'Unable to read response');\n logger.error('Token endpoint error', undefined, { errorText });\n throw new Error('Token exchange failed');\n }\n\n // Process token response\n const tokenResult = await oauth.processAuthorizationCodeResponse(\n authorizationServer,\n client,\n tokenEndpointResponse,\n { expectedNonce: oauthState.nonce }\n );\n\n logger.info('Token exchange completed successfully');\n\n // Extract claims from ID token\n let claims: Record<string, unknown> = {};\n if (tokenResult.id_token) {\n try {\n const payload = tokenResult.id_token.split('.')[1];\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n claims = JSON.parse(new TextDecoder('utf-8').decode(bytes));\n } catch (decodeError) {\n logger.error('Failed to decode ID token', decodeError instanceof Error ? decodeError : undefined);\n throw new Error('Invalid ID token received');\n }\n }\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 = oauthState.returnUrl || '/';\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 * Backend uses id_token_hint to identify which tokens to revoke.\n * \n * **Scenario 1: Full SSO Logout (Manual Sign Out)**\n * - Query param: sso_logout=true\n * - Result: Full logout + identity cookie cleared\n * - User must re-authenticate everywhere\n * \n * **Scenario 2: Standard Logout (Automatic/Token Expired)**\n * - No sso_logout param\n * - Result: Tokens revoked based on id_token_hint\n * - Backend determines which tokens to revoke\n * \n * Configuration:\n * - `OIDC_SSO_LOGOUT` env var ('true' | 'false') - default behavior\n * - Query param `sso_logout=true` - override for full logout\n */\nexport async function logout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const url = new URL(request.url);\n \n // Check for explicit sso_logout from query params\n const ssoLogout = url.searchParams.get('sso_logout') === 'true' || getEffectiveSsoLogout(config);\n const returnUrl = url.searchParams.get('returnUrl');\n \n logger.info('Logout initiated', { \n clientId: config.clientId,\n ssoLogout\n });\n \n const sessionData = await getAuthSession(request);\n const idToken = sessionData.idToken;\n\n // Destroy local session\n const sessionHeaders = await destroyAuthSession(request);\n const headers = new Headers(sessionHeaders);\n\n // Store returnUrl in cookie if provided\n if (returnUrl) {\n headers.append(\n 'Set-Cookie',\n `logout_return_url=${encodeURIComponent(returnUrl)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n }\n\n // Redirect to OAuth end_session_endpoint if available\n if (idToken) {\n try {\n const { authorizationServer } = await getOAuthConfig();\n if (authorizationServer.end_session_endpoint) {\n const endSessionUrl = new URL(authorizationServer.end_session_endpoint);\n \n // Backend uses id_token_hint to identify which tokens to revoke\n endSessionUrl.searchParams.set('id_token_hint', idToken);\n \n // Add sso_logout for full SSO logout (clears identity cookie)\n if (ssoLogout) {\n endSessionUrl.searchParams.set('sso_logout', 'true');\n logger.info('Performing full SSO logout (identity cookie will be cleared)');\n } else {\n logger.info('Performing standard logout (tokens revoked based on id_token_hint)');\n }\n \n // Add post_logout_redirect_uri if configured\n if (config.postLogoutRedirectUri) {\n endSessionUrl.searchParams.set('post_logout_redirect_uri', config.postLogoutRedirectUri);\n }\n \n return createRedirectResponse(endSessionUrl.toString(), { headers });\n }\n } catch {\n // Ignore OAuth logout errors\n }\n }\n\n // Fallback redirect\n const baseUrl = new URL(request.url).origin;\n const registeredPath = config.postLogoutRedirectUri || '/';\n const finalRedirectUrl = registeredPath.startsWith('http') \n ? registeredPath \n : `${baseUrl}${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 * 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 const { authorizationServer, client } = await getOAuthConfig();\n const clientAuth = getClientAuth();\n\n logger.info('Initiating token refresh');\n\n const refreshResponse = await oauth.refreshTokenGrantRequest(\n authorizationServer,\n client,\n clientAuth,\n tokenToRefresh\n );\n\n if (!refreshResponse.ok) {\n const errorText = await refreshResponse.text().catch(() => 'Unknown error');\n logger.error('Token refresh failed', undefined, { status: refreshResponse.status, errorText });\n\n let errorData: Record<string, unknown> = {};\n try {\n errorData = JSON.parse(errorText);\n } catch {\n // Ignore parse errors\n }\n\n const shouldLogout =\n refreshResponse.status === 400 ||\n errorData.error === 'invalid_grant' ||\n errorData.error === 'invalid_token' ||\n refreshResponse.status === 401;\n\n return {\n success: false,\n error: `Token refresh failed: ${errorData.error_description || errorData.error || 'Unknown'}`,\n shouldLogout,\n };\n }\n\n const refreshResult = await oauth.processRefreshTokenResponse(\n authorizationServer,\n client,\n refreshResponse\n );\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 const shouldLogout =\n error instanceof Error &&\n (error.message.includes('invalid_grant') ||\n error.message.includes('invalid_token') ||\n error.message.includes('unauthorized'));\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Token refresh failed',\n shouldLogout,\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}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.server-Cx2h2qu3.js","names":["location: string","init: RedirectResponseInit","config: AuthConfig","applicationType: ApplicationType","OIDC_CONFIG: AuthConfig | null","authServerCache: {\n authorizationServer: oauth.AuthorizationServer;\n client: oauth.Client;\n cachedAt: number;\n} | null","client: oauth.Client","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","tokenResult: oauth.TokenEndpointResponse","baseUser: UserInfo","request: Request","returnUrlOrOptions?: string | LoginOptions","options: LoginOptions","headers","oauthState: OAuthState","redirectUri","baseUrl","refreshToken?: string","errorData: Record<string, unknown>"],"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 * OAuth2/OIDC 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 oauth from 'oauth4webapi';\n\nimport {\n createAuthSession,\n getAuthSession,\n updateAuthSession,\n destroyAuthSession,\n isSessionValid,\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 AuthError\n} from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Get effective SSO logout setting based on config\n */\nfunction getEffectiveSsoLogout(config: AuthConfig): boolean {\n // Explicit setting takes precedence\n if (config.ssoLogout !== undefined) {\n return config.ssoLogout;\n }\n \n // Determine by application type\n switch (config.applicationType) {\n case 'no-landing-page':\n case 'dashboard':\n return true; // Dashboard needs full logout (no landing page)\n case 'landing-page':\n case 'tenant-app':\n return false; // Tenant app uses app-specific logout\n default:\n return false; // Default to app-specific logout\n }\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 // Parse boolean env vars\n const ssoLogout = process.env.OIDC_SSO_LOGOUT !== undefined\n ? process.env.OIDC_SSO_LOGOUT === 'true'\n : undefined;\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 redirectUri: requiredEnvVars.redirectUri!,\n scope: process.env.OIDC_SCOPE || 'openid offline_access api',\n postLogoutRedirectUri: process.env.OIDC_POST_LOGOUT_REDIRECT_URI,\n applicationType,\n ssoLogout,\n hasLandingPage,\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 authorizationServer: oauth.AuthorizationServer;\n client: oauth.Client;\n cachedAt: number;\n} | null = null;\n\nconst CACHE_TTL = 60 * 60 * 1000; // 1 hour\n\n/**\n * Get OAuth configuration with caching\n */\nasync function getOAuthConfig(): Promise<{\n authorizationServer: oauth.AuthorizationServer;\n client: oauth.Client;\n}> {\n const config = getAuthConfig();\n const now = Date.now();\n\n if (authServerCache && now - authServerCache.cachedAt < CACHE_TTL) {\n return {\n authorizationServer: authServerCache.authorizationServer,\n client: authServerCache.client,\n };\n }\n\n try {\n const issuer = new URL(config.authority);\n const discoveryResponse = await oauth.discoveryRequest(issuer, {\n algorithm: 'oidc',\n });\n\n if (!discoveryResponse.ok) {\n const errorText = await discoveryResponse.text().catch(() => 'Unable to read response');\n logger.error('OAuth discovery error', undefined, { errorText });\n throw new Error(`OAuth discovery failed with status ${discoveryResponse.status}`);\n }\n\n const authorizationServer = await oauth.processDiscoveryResponse(issuer, discoveryResponse);\n\n const client: oauth.Client = {\n client_id: config.clientId,\n };\n\n authServerCache = {\n authorizationServer,\n client,\n cachedAt: now,\n };\n\n logger.info('OAuth discovery completed successfully');\n return { authorizationServer, client };\n } catch (error) {\n logger.error('OAuth discovery failed', error instanceof Error ? error : undefined);\n throw new Error(`OAuth discovery failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n}\n\n/**\n * Get client authentication method (None for public client with PKCE)\n */\nfunction getClientAuth(): oauth.ClientAuth {\n return oauth.None();\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: oauth.TokenEndpointResponse,\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\n return {\n userId: baseUser.sub,\n accessToken: tokenResult.access_token,\n refreshToken: tokenResult.refresh_token,\n idToken: tokenResult.id_token,\n expiresAt,\n user: baseUser,\n lastActivity: Date.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 { authorizationServer, client } = await getOAuthConfig();\n\n // Generate PKCE challenge\n const codeVerifier = oauth.generateRandomCodeVerifier();\n const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier);\n\n // Generate secure state and nonce\n const state = oauth.generateRandomState();\n const nonce = oauth.generateRandomNonce();\n\n // Create OAuth state object\n const oauthState: OAuthState = {\n state,\n codeVerifier,\n nonce,\n returnUrl: options.returnUrl,\n createdAt: Date.now(),\n };\n\n logger.info('Creating OAuth state', { returnUrl: options.returnUrl, prompt: options.prompt });\n\n // Store OAuth state in Redis\n const stateId = await createOAuthState(oauthState);\n\n // Build authorization URL\n const authorizationUrl = new URL(authorizationServer.authorization_endpoint!);\n authorizationUrl.searchParams.set('client_id', client.client_id);\n authorizationUrl.searchParams.set('redirect_uri', config.redirectUri);\n authorizationUrl.searchParams.set('response_type', 'code');\n authorizationUrl.searchParams.set('scope', config.scope);\n authorizationUrl.searchParams.set('state', state);\n authorizationUrl.searchParams.set('code_challenge', codeChallenge);\n authorizationUrl.searchParams.set('code_challenge_method', 'S256');\n authorizationUrl.searchParams.set('nonce', nonce);\n \n // Add prompt parameter if specified\n if (options.prompt) {\n authorizationUrl.searchParams.set('prompt', options.prompt);\n }\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 // DEBUG: Log full authorization URL and config\n logger.info('🔍 DEBUG: OAuth login config', {\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n postLogoutRedirectUri: config.postLogoutRedirectUri,\n scope: config.scope,\n authority: config.authority,\n authorizationEndpoint: authorizationServer.authorization_endpoint,\n fullAuthUrl: authorizationUrl.toString()\n });\n\n logger.info('OAuth authorization flow initiated', { state, stateId, prompt: options.prompt });\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\n // DEBUG: Log callback URL and params\n logger.info('🔍 DEBUG: OAuth callback received', {\n fullUrl: url.toString(),\n origin: url.origin,\n pathname: url.pathname,\n hasCode: !!code,\n hasState: !!state,\n error,\n errorDescription,\n clientId: config.clientId,\n redirectUri: config.redirectUri\n });\n\n // Handle OAuth errors\n if (error) {\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 // Validate required parameters\n if (!code || !state) {\n throw new Error('Missing required callback parameters');\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 (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 const { authorizationServer, client } = await getOAuthConfig();\n const clientAuth = getClientAuth();\n\n logger.info('Initiating token exchange');\n\n // Validate authorization response\n const currentUrl = new URL(request.url);\n const callbackParameters = oauth.validateAuthResponse(\n authorizationServer,\n client,\n currentUrl,\n oauthState.state\n );\n\n // Exchange authorization code for tokens\n const tokenEndpointResponse = await oauth.authorizationCodeGrantRequest(\n authorizationServer,\n client,\n clientAuth,\n callbackParameters,\n config.redirectUri,\n oauthState.codeVerifier\n );\n\n if (!tokenEndpointResponse.ok) {\n const errorText = await tokenEndpointResponse.text().catch(() => 'Unable to read response');\n logger.error('Token endpoint error', undefined, { errorText });\n throw new Error('Token exchange failed');\n }\n\n // Process token response\n const tokenResult = await oauth.processAuthorizationCodeResponse(\n authorizationServer,\n client,\n tokenEndpointResponse,\n { expectedNonce: oauthState.nonce }\n );\n\n logger.info('Token exchange completed successfully');\n\n // Extract claims from ID token\n let claims: Record<string, unknown> = {};\n if (tokenResult.id_token) {\n try {\n const payload = tokenResult.id_token.split('.')[1];\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n claims = JSON.parse(new TextDecoder('utf-8').decode(bytes));\n } catch (decodeError) {\n logger.error('Failed to decode ID token', decodeError instanceof Error ? decodeError : undefined);\n throw new Error('Invalid ID token received');\n }\n }\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 = oauthState.returnUrl || '/';\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 * Backend uses id_token_hint to identify which tokens to revoke.\n * \n * **Scenario 1: Full SSO Logout (Manual Sign Out)**\n * - Query param: sso_logout=true\n * - Result: Full logout + identity cookie cleared\n * - User must re-authenticate everywhere\n * \n * **Scenario 2: Standard Logout (Automatic/Token Expired)**\n * - No sso_logout param\n * - Result: Tokens revoked based on id_token_hint\n * - Backend determines which tokens to revoke\n * \n * Configuration:\n * - `OIDC_SSO_LOGOUT` env var ('true' | 'false') - default behavior\n * - Query param `sso_logout=true` - override for full logout\n */\nexport async function logout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const url = new URL(request.url);\n \n // Check for explicit sso_logout from query params\n const ssoLogout = url.searchParams.get('sso_logout') === 'true' || getEffectiveSsoLogout(config);\n const returnUrl = url.searchParams.get('returnUrl');\n \n logger.info('Logout initiated', { \n clientId: config.clientId,\n ssoLogout\n });\n \n const sessionData = await getAuthSession(request);\n const idToken = sessionData.idToken;\n\n // Destroy local session\n const sessionHeaders = await destroyAuthSession(request);\n const headers = new Headers(sessionHeaders);\n\n // Store returnUrl in cookie if provided\n if (returnUrl) {\n headers.append(\n 'Set-Cookie',\n `logout_return_url=${encodeURIComponent(returnUrl)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n }\n\n // Redirect to OAuth end_session_endpoint if available\n if (idToken) {\n try {\n const { authorizationServer } = await getOAuthConfig();\n if (authorizationServer.end_session_endpoint) {\n const endSessionUrl = new URL(authorizationServer.end_session_endpoint);\n \n // Backend uses id_token_hint to identify which tokens to revoke\n endSessionUrl.searchParams.set('id_token_hint', idToken);\n \n // Add sso_logout for full SSO logout (clears identity cookie)\n if (ssoLogout) {\n endSessionUrl.searchParams.set('sso_logout', 'true');\n logger.info('Performing full SSO logout (identity cookie will be cleared)');\n } else {\n logger.info('Performing standard logout (tokens revoked based on id_token_hint)');\n }\n \n // Add post_logout_redirect_uri if configured\n if (config.postLogoutRedirectUri) {\n endSessionUrl.searchParams.set('post_logout_redirect_uri', config.postLogoutRedirectUri);\n }\n \n return createRedirectResponse(endSessionUrl.toString(), { headers });\n }\n } catch {\n // Ignore OAuth logout errors\n }\n }\n\n // Fallback redirect\n const baseUrl = new URL(request.url).origin;\n const registeredPath = config.postLogoutRedirectUri || '/';\n const finalRedirectUrl = registeredPath.startsWith('http') \n ? registeredPath \n : `${baseUrl}${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 * 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 const { authorizationServer, client } = await getOAuthConfig();\n const clientAuth = getClientAuth();\n\n logger.info('Initiating token refresh');\n\n const refreshResponse = await oauth.refreshTokenGrantRequest(\n authorizationServer,\n client,\n clientAuth,\n tokenToRefresh\n );\n\n if (!refreshResponse.ok) {\n const errorText = await refreshResponse.text().catch(() => 'Unknown error');\n logger.error('Token refresh failed', undefined, { status: refreshResponse.status, errorText });\n\n let errorData: Record<string, unknown> = {};\n try {\n errorData = JSON.parse(errorText);\n } catch {\n // Ignore parse errors\n }\n\n const shouldLogout =\n refreshResponse.status === 400 ||\n errorData.error === 'invalid_grant' ||\n errorData.error === 'invalid_token' ||\n refreshResponse.status === 401;\n\n return {\n success: false,\n error: `Token refresh failed: ${errorData.error_description || errorData.error || 'Unknown'}`,\n shouldLogout,\n };\n }\n\n const refreshResult = await oauth.processRefreshTokenResponse(\n authorizationServer,\n client,\n refreshResponse\n );\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 const shouldLogout =\n error instanceof Error &&\n (error.message.includes('invalid_grant') ||\n error.message.includes('invalid_token') ||\n error.message.includes('unauthorized'));\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Token refresh failed',\n shouldLogout,\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}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP response helpers that stay framework-agnostic.
|
|
3
|
+
*/
|
|
4
|
+
export interface RedirectResponseInit {
|
|
5
|
+
headers?: HeadersInit;
|
|
6
|
+
status?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a redirect response without relying on a router/framework helper.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createRedirectResponse(location: string, init?: RedirectResponseInit): Response;
|
|
12
|
+
//# sourceMappingURL=response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/http/response.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,oBAAyB,GAC9B,QAAQ,CAQV"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Context Provider
|
|
3
|
+
*
|
|
4
|
+
* Initializes the identity store and listens to SignalR events.
|
|
5
|
+
*/
|
|
6
|
+
import { type ReactNode } from 'react';
|
|
7
|
+
import type { IdentityContextData, IdentityContextChangedEvent } from './types';
|
|
8
|
+
interface IdentityContextProviderProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
initialContext?: Partial<IdentityContextData>;
|
|
11
|
+
accessToken?: string | null;
|
|
12
|
+
signalRClient?: {
|
|
13
|
+
onIdentityContextChanged: (handler: (event: IdentityContextChangedEvent) => void) => () => void;
|
|
14
|
+
disconnect: () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare function IdentityContextProvider({ children, initialContext, accessToken, signalRClient, }: IdentityContextProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=identity-context-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-context-provider.d.ts","sourceRoot":"","sources":["../../src/identity/identity-context-provider.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElD,OAAO,KAAK,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAEhF,UAAU,4BAA4B;IACpC,QAAQ,EAAE,SAAS,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE;QACd,wBAAwB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,2BAA2B,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;QAChG,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACjC,CAAC;CACH;AAED,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,GACd,EAAE,4BAA4B,2CAwD9B"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Context Server Module
|
|
3
|
+
*
|
|
4
|
+
* Server-side identity context management.
|
|
5
|
+
*/
|
|
6
|
+
import type { AddressData, IdentityContextData, TenantMembership } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Identity API fetcher type (to avoid coupling with specific API client)
|
|
9
|
+
*/
|
|
10
|
+
export type IdentityAPIFetcher = (request: Request) => Promise<IdentityContextData>;
|
|
11
|
+
/**
|
|
12
|
+
* Configure identity API fetcher
|
|
13
|
+
*/
|
|
14
|
+
export declare function configureIdentityAPIFetcher(fetcher: IdentityAPIFetcher): void;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch identity context from backend
|
|
17
|
+
*/
|
|
18
|
+
export declare function fetchIdentityContext(request: Request): Promise<IdentityContextData>;
|
|
19
|
+
/**
|
|
20
|
+
* Get cached identity context or fetch fresh if needed
|
|
21
|
+
*/
|
|
22
|
+
export declare function getIdentityContext(request: Request, userId: string, forceRefresh?: boolean): Promise<IdentityContextData>;
|
|
23
|
+
/**
|
|
24
|
+
* Clear identity context cache for user
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearIdentityContextCache(userId?: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Permission fetcher type
|
|
29
|
+
*/
|
|
30
|
+
export type PermissionFetcher = (request: Request, tenantId: string) => Promise<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Configure permission fetcher
|
|
33
|
+
*/
|
|
34
|
+
export declare function configurePermissionFetcher(fetcher: PermissionFetcher): void;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch user permissions for a specific tenant
|
|
37
|
+
*/
|
|
38
|
+
export declare function fetchUserPermissions(request: Request, tenantId: string): Promise<string[]>;
|
|
39
|
+
export interface UserContextInfo {
|
|
40
|
+
tenants: string[];
|
|
41
|
+
memberships: TenantMembership[];
|
|
42
|
+
currentTenant?: string;
|
|
43
|
+
isOnboarded: boolean;
|
|
44
|
+
permissions: string[];
|
|
45
|
+
hasSubscription: boolean;
|
|
46
|
+
userId?: string;
|
|
47
|
+
email?: string;
|
|
48
|
+
firstName?: string | null;
|
|
49
|
+
lastName?: string | null;
|
|
50
|
+
displayName?: string | null;
|
|
51
|
+
profileImageUrl?: string | null;
|
|
52
|
+
phoneNumber?: string | null;
|
|
53
|
+
timeZone?: string;
|
|
54
|
+
addresses?: AddressData[];
|
|
55
|
+
hasOwnerMembership: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface ContextToUserInfoOptions {
|
|
58
|
+
currentTenant?: string | null;
|
|
59
|
+
request?: Request;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Convert raw identity context into a tenant-aware user info shape.
|
|
63
|
+
*/
|
|
64
|
+
export declare function contextToUserInfo(context: IdentityContextData, options?: ContextToUserInfoOptions): Promise<UserContextInfo>;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a cached context version differs from the latest version.
|
|
67
|
+
*/
|
|
68
|
+
export declare function hasContextVersionChanged(userId: string, newVersion: number): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Mark a cached identity context as stale after a version change.
|
|
71
|
+
*/
|
|
72
|
+
export declare function updateContextVersion(userId: string, newVersion: number): void;
|
|
73
|
+
//# sourceMappingURL=identity-context.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-context.server.d.ts","sourceRoot":"","sources":["../../src/identity/identity-context.server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIlF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAIpF;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAE7E;AAcD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8BzF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,UAAQ,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAuB9B;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ/D;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAI1F;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAE3E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgBhG;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,eAAe,CAAC,CA+B1B;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAGpF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAU7E"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Store (Zustand)
|
|
3
|
+
*
|
|
4
|
+
* Centralized state management for identity context.
|
|
5
|
+
*/
|
|
6
|
+
import type { IdentityContextData } from './types';
|
|
7
|
+
interface IdentityState {
|
|
8
|
+
context: IdentityContextData;
|
|
9
|
+
setContext: (context: Partial<IdentityContextData>) => void;
|
|
10
|
+
refreshContext: () => Promise<void>;
|
|
11
|
+
refreshPermissions: () => Promise<void>;
|
|
12
|
+
reset: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface IdentityUnauthorizedDetails {
|
|
15
|
+
reason: 'current-user-missing' | 'unauthorized';
|
|
16
|
+
logoutPath: string;
|
|
17
|
+
response?: Response;
|
|
18
|
+
}
|
|
19
|
+
export interface IdentityStoreConfig {
|
|
20
|
+
contextEndpoint?: string;
|
|
21
|
+
permissionsEndpoint?: string;
|
|
22
|
+
logoutPath?: string;
|
|
23
|
+
fetcher?: typeof fetch;
|
|
24
|
+
onUnauthorized?: (details: IdentityUnauthorizedDetails) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Configure the identity store for different app/back-end conventions.
|
|
28
|
+
*/
|
|
29
|
+
export declare function configureIdentityStore(config: IdentityStoreConfig): void;
|
|
30
|
+
/**
|
|
31
|
+
* Reset identity store configuration to the built-in defaults.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resetIdentityStoreConfig(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Backward-compatible endpoint-only configuration helper.
|
|
36
|
+
*/
|
|
37
|
+
export declare function configureIdentityEndpoints(config: {
|
|
38
|
+
contextEndpoint?: string;
|
|
39
|
+
permissionsEndpoint?: string;
|
|
40
|
+
}): void;
|
|
41
|
+
export declare const useIdentityStore: import("zustand").UseBoundStore<import("zustand").StoreApi<IdentityState>>;
|
|
42
|
+
export declare const useIsOnboarded: () => boolean | undefined;
|
|
43
|
+
export declare const useHasSubscription: () => boolean | undefined;
|
|
44
|
+
export declare const useUserTenants: () => {
|
|
45
|
+
tenants: string[] | undefined;
|
|
46
|
+
currentTenant: string | undefined;
|
|
47
|
+
};
|
|
48
|
+
export declare const useUserPermissions: () => string[] | undefined;
|
|
49
|
+
export declare const useIdentityContext: () => {
|
|
50
|
+
context: IdentityContextData;
|
|
51
|
+
refreshContext: () => Promise<void>;
|
|
52
|
+
refreshPermissions: () => Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=identity-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-store.d.ts","sourceRoot":"","sources":["../../src/identity/identity-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAwBnD,UAAU,aAAa;IACrB,OAAO,EAAE,mBAAmB,CAAC;IAG7B,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,mBAAmB,CAAC,KAAK,IAAI,CAAC;IAC5D,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,sBAAsB,GAAG,cAAc,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,IAAI,CAAC;CACjE;AAuCD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAKxE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,IAAI,CAEP;AAED,eAAO,MAAM,gBAAgB,4EAuH1B,CAAC;AAGJ,eAAO,MAAM,cAAc,2BAA+D,CAAC;AAC3F,eAAO,MAAM,kBAAkB,2BAAmE,CAAC;AACnG,eAAO,MAAM,cAAc;;;CAItB,CAAC;AACN,eAAO,MAAM,kBAAkB,4BAA+D,CAAC;AAC/F,eAAO,MAAM,kBAAkB;;0BAvNP,OAAO,CAAC,IAAI,CAAC;8BACT,OAAO,CAAC,IAAI,CAAC;CA4NxC,CAAC"}
|
package/dist/identity/index.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Identity Module - Client-side exports
|
|
3
|
+
*
|
|
4
|
+
* User identity context and real-time updates.
|
|
5
|
+
*/
|
|
6
|
+
export * from './types';
|
|
7
|
+
export * from './identity-store';
|
|
8
|
+
export * from './identity-context-provider';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/identity/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
1
|
+
/**
|
|
2
|
+
* Identity Module - Server-side exports
|
|
3
|
+
*/
|
|
4
|
+
export * from './identity-context.server';
|
|
5
|
+
export * from './types';
|
|
6
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/identity/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,2BAA2B,CAAC;AAC1C,cAAc,SAAS,CAAC"}
|
package/dist/identity/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../logging-_h2mi3L9.js";
|
|
2
|
-
import
|
|
2
|
+
import "../types-BnKaWdfe.js";
|
|
3
|
+
import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../server-C3L8J7iE.js";
|
|
3
4
|
|
|
4
5
|
export { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Types
|
|
3
|
+
*
|
|
4
|
+
* Generic identity context types that can be used with any backend.
|
|
5
|
+
* All fields are optional to support different backend implementations.
|
|
6
|
+
*/
|
|
7
|
+
import type { TenantMembership } from '../tenant/types';
|
|
8
|
+
export type { TenantMembership } from '../tenant/types';
|
|
9
|
+
/**
|
|
10
|
+
* Address data for user profile
|
|
11
|
+
*/
|
|
12
|
+
export interface AddressData {
|
|
13
|
+
addressId?: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
line1: string;
|
|
16
|
+
line2?: string | null;
|
|
17
|
+
city: string;
|
|
18
|
+
state: string;
|
|
19
|
+
postalCode: string;
|
|
20
|
+
country: string;
|
|
21
|
+
isPrimary?: boolean;
|
|
22
|
+
attention?: string | null;
|
|
23
|
+
region?: string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Core identity context data
|
|
27
|
+
*
|
|
28
|
+
* This interface is designed to be flexible and work with various backend implementations.
|
|
29
|
+
* Only the fields your backend returns need to be populated.
|
|
30
|
+
*/
|
|
31
|
+
export interface IdentityContextData {
|
|
32
|
+
/** Unique user identifier */
|
|
33
|
+
userId?: string;
|
|
34
|
+
/** User's email address */
|
|
35
|
+
email?: string;
|
|
36
|
+
/** User's first name */
|
|
37
|
+
firstName?: string | null;
|
|
38
|
+
/** User's last name */
|
|
39
|
+
lastName?: string | null;
|
|
40
|
+
/** User's display name (computed or custom) */
|
|
41
|
+
displayName?: string | null;
|
|
42
|
+
/** URL to user's profile image/avatar */
|
|
43
|
+
profileImageUrl?: string | null;
|
|
44
|
+
/** User's phone number */
|
|
45
|
+
phoneNumber?: string | null;
|
|
46
|
+
/** User's timezone (e.g., "America/New_York") */
|
|
47
|
+
timeZone?: string;
|
|
48
|
+
/** User's preferred locale (e.g., "en-US") */
|
|
49
|
+
locale?: string;
|
|
50
|
+
/** User's addresses */
|
|
51
|
+
addresses?: AddressData[];
|
|
52
|
+
/** Whether user has any tenant membership */
|
|
53
|
+
hasAnyMembership?: boolean;
|
|
54
|
+
/** Whether user owns any tenant */
|
|
55
|
+
hasOwnerMembership?: boolean;
|
|
56
|
+
/** List of tenant IDs user belongs to */
|
|
57
|
+
tenants?: string[];
|
|
58
|
+
/** Currently active tenant ID */
|
|
59
|
+
currentTenant?: string;
|
|
60
|
+
/** Detailed membership information */
|
|
61
|
+
memberships?: TenantMembership[];
|
|
62
|
+
/** Whether user has completed onboarding */
|
|
63
|
+
isOnboarded?: boolean;
|
|
64
|
+
/** Whether user has an active subscription */
|
|
65
|
+
hasSubscription?: boolean;
|
|
66
|
+
/** Whether user's email is verified */
|
|
67
|
+
emailVerified?: boolean;
|
|
68
|
+
/** Whether 2FA is enabled */
|
|
69
|
+
twoFactorEnabled?: boolean;
|
|
70
|
+
/** User's permissions (for current tenant or global) */
|
|
71
|
+
permissions?: string[];
|
|
72
|
+
/** User's global roles */
|
|
73
|
+
globalRoles?: string[];
|
|
74
|
+
/** Version number for cache invalidation */
|
|
75
|
+
contextVersion?: number;
|
|
76
|
+
/** Loading state indicator */
|
|
77
|
+
isLoading?: boolean;
|
|
78
|
+
/** Last time context was updated */
|
|
79
|
+
lastUpdated?: Date;
|
|
80
|
+
/** Additional custom data from backend */
|
|
81
|
+
metadata?: Record<string, unknown>;
|
|
82
|
+
}
|
|
83
|
+
export interface IdentityContextChangedEvent {
|
|
84
|
+
reason: string;
|
|
85
|
+
tenantId?: string;
|
|
86
|
+
triggeredByUserId?: string;
|
|
87
|
+
contextVersion: number;
|
|
88
|
+
timestamp: string;
|
|
89
|
+
}
|
|
90
|
+
export type IdentityContextChangeHandler = (event: IdentityContextChangedEvent) => void;
|
|
91
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/identity/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAElC,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,yCAAyC;IACzC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGhC,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAG1B,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mCAAmC;IACnC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAGjC,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8CAA8C;IAC9C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAG3B,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAGvB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oCAAoC;IACpC,WAAW,CAAC,EAAE,IAAI,CAAC;IAGnB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,4BAA4B,GAAG,CAAC,KAAK,EAAE,2BAA2B,KAAK,IAAI,CAAC"}
|