@eminuckan/spine 0.3.3 → 0.3.6

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/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  No unreleased changes.
8
8
 
9
+ ## [0.3.6] - 2026-07-14
10
+
11
+ ### Fixed
12
+
13
+ - Carry forward the back-channel logout telemetry classification prepared in 0.3.5, which was not published after npm rejected provenance from a self-hosted release runner.
14
+ - Run npm trusted publishing on a GitHub-hosted Ubuntu runner and upgrade the release checkout action to v7 so provenance satisfies npm's supported-provider requirements.
15
+
16
+ ## [0.3.5] - 2026-07-14
17
+
18
+ ### Fixed
19
+
20
+ - Classify malformed or unverifiable back-channel logout tokens as expected client warnings while preserving error-level reporting for provider, discovery, JWKS, and configuration failures.
21
+
22
+ ## [0.3.4] - 2026-07-13
23
+
24
+ ### Security
25
+
26
+ - Restrict OAuth return targets to safe same-origin application paths and revalidate stored state during callbacks.
27
+ - Redact invitation tokens and OAuth callback details from authentication logs.
28
+ - Mark temporary production OAuth cookies as Secure, HttpOnly, and SameSite=Lax.
29
+
9
30
  ## [0.3.3] - 2026-07-11
10
31
 
11
32
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Spine
2
2
 
3
- [![CI](https://github.com/eminuckan/spine/actions/workflows/ci.yml/badge.svg)](https://github.com/eminuckan/spine/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/Unitfield/spine/actions/workflows/ci.yml/badge.svg)](https://github.com/Unitfield/spine/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/%40eminuckan%2Fspine.svg)](https://www.npmjs.com/package/@eminuckan/spine)
5
5
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
 
package/SECURITY.md CHANGED
@@ -8,7 +8,7 @@ Security fixes are applied to the latest published release line and the current
8
8
 
9
9
  Do not open a public issue or discussion for a suspected vulnerability.
10
10
 
11
- Use [GitHub private vulnerability reporting](https://github.com/eminuckan/spine/security/advisories/new) from the repository's **Security** tab. Maintainers should coordinate investigation and remediation in a private draft security advisory.
11
+ Use [GitHub private vulnerability reporting](https://github.com/Unitfield/spine/security/advisories/new) from the repository's **Security** tab. Maintainers should coordinate investigation and remediation in a private draft security advisory.
12
12
 
13
13
  Include the affected package entry point or adapter, impact, reproduction steps, affected version or revision, and any suggested mitigation. Redact credentials, tokens, session material, and application data from the report.
14
14
 
@@ -1 +1 @@
1
- {"version":3,"file":"auth.server.d.ts","sourceRoot":"","sources":["../../src/auth/auth.server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAKL,cAAc,EASf,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAEV,gBAAgB,EAChB,QAAQ,EAGR,kBAAkB,EAClB,YAAY,EAKZ,SAAS,EACV,MAAM,SAAS,CAAC;AAgOjB,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,GAAG,CAM3F;AAmVD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAgBzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAkID;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAChE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAkDA;AAyDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAkB/D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAK/C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAGtD;AAMD;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,OAAO,EAAE,OAAO,EAChB,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,GACzC,OAAO,CAAC,QAAQ,CAAC,CAgGnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2OxE;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkGhE;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8CjF;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmClF;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAQxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAWrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ7E;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,kBAAkB,CAAC,CAiD7B;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
1
+ {"version":3,"file":"auth.server.d.ts","sourceRoot":"","sources":["../../src/auth/auth.server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAKL,cAAc,EASf,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAEV,gBAAgB,EAChB,QAAQ,EAGR,kBAAkB,EAClB,YAAY,EAKZ,SAAS,EACV,MAAM,SAAS,CAAC;AAsOjB,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,GAAG,CAM3F;AAsVD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAgBzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAkID;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAChE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAkDA;AA0DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAkB/D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAW/C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAGtD;AAMD;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,OAAO,EAAE,OAAO,EAChB,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,GACzC,OAAO,CAAC,QAAQ,CAAC,CAyGnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0PxE;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2GhE;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsDjF;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmClF;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAQxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAWrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ7E;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,kBAAkB,CAAC,CAiD7B;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Normalize a post-authentication destination to a path on the current origin.
3
+ *
4
+ * Local paths and same-origin absolute URLs are supported for compatibility.
5
+ * Network-path references, backslash authority variants, encoded authority
6
+ * variants, non-HTTP schemes, and authentication loop targets are rejected.
7
+ */
8
+ export declare function sanitizeOAuthReturnUrl(returnUrl: string | null | undefined, request: Request): string | null;
9
+ type TemporaryAuthCookieOptions = {
10
+ maxAge: number;
11
+ };
12
+ /** Serialize a short-lived, server-only authentication cookie. */
13
+ export declare function serializeTemporaryAuthCookie(name: string, value: string, options: TemporaryAuthCookieOptions): string;
14
+ export {};
15
+ //# sourceMappingURL=oauth-security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-security.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-security.ts"],"names":[],"mappings":"AAyCA;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,OAAO,EAAE,OAAO,GACf,MAAM,GAAG,IAAI,CAqBf;AAED,KAAK,0BAA0B,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,0BAA0B,GAClC,MAAM,CAcR"}
@@ -1,3 +1,3 @@
1
1
  import { S as updateAuthSession, _ as isSessionValid, a as createOAuthState, b as listCurrentUserAuthSessions, c as destroyAuthSessionById, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, h as getCurrentAuthSessionId, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, m as getAuthSessionById, n as cleanupExpiredOAuthStates, o as deleteOAuthState, p as getAuthSession, r as closeRedisConnection, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, u as destroyAuthSessionsBySessionState, v as listAuthSessionDataForUser, x as requireAuthSession, y as listAuthSessionsForUser } from "../redis-session-storage.server-DGgQcJ9B.mjs";
2
- import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "../server-CqX2yCa4.mjs";
2
+ import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "../server-DPCu40ud.mjs";
3
3
  export { AUTH_ERROR_COOKIE_PREFIX, attemptTokenRefresh, authRoute, cleanupExpiredOAuthStates, clearAuthErrorHeaders, clearAuthServerCache, closeRedisConnection, configureAuthClaimMapping, configureRouteProtection, createAuthSession, createAuthorizationResponseUrl, createOAuthState, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, extractUserInfo, getAccessToken, getAuthError, getAuthSession, getAuthSessionById, getCurrentAuthSessionId, getOAuthState, getUser, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, isSessionValid, isTokenExpiredResponse, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, login, logout, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, refreshTokens, requireAuth, requireAuthSession, resetAuthClaimMapping, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute, updateAuthSession };
@@ -176,7 +176,12 @@ export type PublicAuthorizationStateContext = Record<string, string | number | b
176
176
  * Login options
177
177
  */
178
178
  export interface LoginOptions {
179
- /** Return URL after successful login */
179
+ /**
180
+ * Return URL after successful login.
181
+ *
182
+ * Spine accepts local paths and same-origin absolute URLs, which are stored
183
+ * as local paths. Unsafe or malformed destinations fall back to `/`.
184
+ */
180
185
  returnUrl?: string;
181
186
  /** OAuth prompt parameter (none, login, consent, select_account, create) */
182
187
  prompt?: 'none' | 'login' | 'consent' | 'select_account' | 'create';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ import { a as withRetry, c as handleApiError, i as isRetryableError, l as ErrorC
9
9
  import { a as getIdentitySignalRClient, i as createIdentitySignalRClient, n as SignalRClient, o as initializeSignalR, r as cleanupSignalR, t as IdentitySignalRClient } from "../signalr-C-TA8nEE.mjs";
10
10
  import { i as invalidationHelpers, n as createQueryClient, r as createQueryKeyFactory, t as cachePresets } from "../query-client-_xB300gc.mjs";
11
11
  import { S as updateAuthSession, _ as isSessionValid, a as createOAuthState, b as listCurrentUserAuthSessions, c as destroyAuthSessionById, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, h as getCurrentAuthSessionId, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, m as getAuthSessionById, n as cleanupExpiredOAuthStates, o as deleteOAuthState, p as getAuthSession, r as closeRedisConnection, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, u as destroyAuthSessionsBySessionState, v as listAuthSessionDataForUser, x as requireAuthSession, y as listAuthSessionsForUser } from "../redis-session-storage.server-DGgQcJ9B.mjs";
12
- import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "../server-CqX2yCa4.mjs";
12
+ import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "../server-DPCu40ud.mjs";
13
13
  import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "../tenant/server.mjs";
14
14
  import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "../identity/server.mjs";
15
15
  import { n as createFetchMiddleware, r as createAPIConfigFactory, t as createApiClient } from "../server-ByScGRXA.mjs";
@@ -1,7 +1,7 @@
1
1
  import { logger } from "./logging/index.mjs";
2
2
  import { S as updateAuthSession, a as createOAuthState, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, o as deleteOAuthState, p as getAuthSession, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, v as listAuthSessionDataForUser } from "./redis-session-storage.server-DGgQcJ9B.mjs";
3
3
  import * as oidc from "openid-client";
4
- import { createRemoteJWKSet, jwtVerify } from "jose";
4
+ import { createRemoteJWKSet, errors, jwtVerify } from "jose";
5
5
  //#region src/http/response.ts
6
6
  /**
7
7
  * Create a redirect response without relying on a router/framework helper.
@@ -71,6 +71,67 @@ function buildAuthorizationState(baseState, publicStateContext) {
71
71
  return `${baseState}.${Buffer.from(JSON.stringify(normalizedContext), "utf8").toString("base64url")}`;
72
72
  }
73
73
  //#endregion
74
+ //#region src/auth/oauth-security.ts
75
+ const AUTH_FLOW_PATHS = /* @__PURE__ */ new Set([
76
+ "/auth/login",
77
+ "/auth/callback",
78
+ "/auth/logout"
79
+ ]);
80
+ function isAuthFlowPath(path) {
81
+ const normalized = path.split("#")[0].split("?")[0].replace(/\/+$/, "") || "/";
82
+ return AUTH_FLOW_PATHS.has(normalized);
83
+ }
84
+ function hasUnsafeAuthorityPrefix(value) {
85
+ const normalized = value.trimStart();
86
+ return normalized.startsWith("//") || normalized.startsWith("/\\") || normalized.startsWith("\\");
87
+ }
88
+ function hasUnsafeEncodedAuthorityPrefix(value) {
89
+ const queryOrFragmentIndex = value.search(/[?#]/);
90
+ let decoded = queryOrFragmentIndex === -1 ? value : value.slice(0, queryOrFragmentIndex);
91
+ for (let attempt = 0; attempt < 3; attempt += 1) {
92
+ if (hasUnsafeAuthorityPrefix(decoded)) return true;
93
+ try {
94
+ const next = decodeURIComponent(decoded);
95
+ if (next === decoded) return false;
96
+ decoded = next;
97
+ } catch {
98
+ return true;
99
+ }
100
+ }
101
+ return hasUnsafeAuthorityPrefix(decoded);
102
+ }
103
+ /**
104
+ * Normalize a post-authentication destination to a path on the current origin.
105
+ *
106
+ * Local paths and same-origin absolute URLs are supported for compatibility.
107
+ * Network-path references, backslash authority variants, encoded authority
108
+ * variants, non-HTTP schemes, and authentication loop targets are rejected.
109
+ */
110
+ function sanitizeOAuthReturnUrl(returnUrl, request) {
111
+ if (!returnUrl || hasUnsafeEncodedAuthorityPrefix(returnUrl)) return null;
112
+ try {
113
+ const requestOrigin = new URL(request.url).origin;
114
+ const candidateUrl = returnUrl.startsWith("/") && !returnUrl.startsWith("//") ? new URL(returnUrl, requestOrigin) : new URL(returnUrl);
115
+ if (candidateUrl.origin !== requestOrigin) return null;
116
+ const safePath = `${candidateUrl.pathname}${candidateUrl.search}${candidateUrl.hash}`;
117
+ return isAuthFlowPath(safePath) ? null : safePath;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ /** Serialize a short-lived, server-only authentication cookie. */
123
+ function serializeTemporaryAuthCookie(name, value, options) {
124
+ const segments = [
125
+ `${name}=${encodeURIComponent(value)}`,
126
+ "Path=/",
127
+ "HttpOnly",
128
+ "SameSite=Lax",
129
+ `Max-Age=${options.maxAge}`
130
+ ];
131
+ if (process.env.NODE_ENV === "production") segments.push("Secure");
132
+ return segments.join("; ");
133
+ }
134
+ //#endregion
74
135
  //#region src/auth/auth.server.ts
75
136
  /**
76
137
  * OpenID Connect Authentication Server Module
@@ -145,6 +206,26 @@ function getAuthConfig() {
145
206
  let authServerCache = null;
146
207
  const CACHE_TTL = 3600 * 1e3;
147
208
  const jwksCache = /* @__PURE__ */ new Map();
209
+ const INVALID_LOGOUT_TOKEN_JOSE_ERROR_CODES = /* @__PURE__ */ new Set([
210
+ errors.JOSEAlgNotAllowed.code,
211
+ errors.JOSENotSupported.code,
212
+ errors.JWKSNoMatchingKey.code,
213
+ errors.JWSInvalid.code,
214
+ errors.JWSSignatureVerificationFailed.code,
215
+ errors.JWTClaimValidationFailed.code,
216
+ errors.JWTExpired.code,
217
+ errors.JWTInvalid.code
218
+ ]);
219
+ var InvalidBackChannelLogoutTokenError = class extends Error {
220
+ constructor(message) {
221
+ super(message);
222
+ this.name = "InvalidBackChannelLogoutTokenError";
223
+ }
224
+ };
225
+ function isInvalidBackChannelLogoutTokenError(error) {
226
+ if (error instanceof InvalidBackChannelLogoutTokenError) return true;
227
+ return error instanceof errors.JOSEError && INVALID_LOGOUT_TOKEN_JOSE_ERROR_CODES.has(error.code);
228
+ }
148
229
  function normalizeIssuer(value) {
149
230
  const normalized = value?.trim().replace(/\/+$/, "");
150
231
  return normalized && normalized.length > 0 ? normalized : null;
@@ -168,20 +249,6 @@ function canUseIdTokenHint(idToken, expectedIssuer) {
168
249
  const tokenIssuer = getJwtIssuer(idToken);
169
250
  return tokenIssuer !== null && tokenIssuer === normalizeIssuer(expectedIssuer);
170
251
  }
171
- function sanitizeLogoutReturnUrl(returnUrl, request) {
172
- if (!returnUrl) return null;
173
- try {
174
- const requestUrl = new URL(request.url);
175
- const decodedReturnUrl = decodeURIComponent(returnUrl);
176
- if (decodedReturnUrl.startsWith("/") && !decodedReturnUrl.startsWith("//")) return isAuthLoopPath(decodedReturnUrl) ? null : decodedReturnUrl;
177
- const candidateUrl = new URL(decodedReturnUrl);
178
- if (candidateUrl.origin !== requestUrl.origin) return null;
179
- const sameOriginPath = `${candidateUrl.pathname}${candidateUrl.search}${candidateUrl.hash}`;
180
- return isAuthLoopPath(sameOriginPath) ? null : sameOriginPath;
181
- } catch {
182
- return null;
183
- }
184
- }
185
252
  function appendApplicationInitiatedActionResult(returnUrl, action, status) {
186
253
  if (!action && !status) return returnUrl;
187
254
  try {
@@ -204,10 +271,6 @@ function createAuthorizationResponseUrl(requestUrl, redirectUri) {
204
271
  authorizationResponseUrl.hash = "";
205
272
  return authorizationResponseUrl;
206
273
  }
207
- function isAuthLoopPath(path) {
208
- const normalized = path.split("#")[0].split("?")[0].replace(/\/+$/, "") || "/";
209
- return normalized === "/auth/logout" || normalized === "/auth/callback";
210
- }
211
274
  function isLocalInsecureIssuer(authority) {
212
275
  try {
213
276
  const issuer = new URL(authority);
@@ -350,13 +413,13 @@ async function getLogoutTokenFromRequest(request) {
350
413
  }
351
414
  function validateBackChannelLogoutClaims(payload, clientId) {
352
415
  const events = payload.events;
353
- if (!(events && typeof events === "object" && !Array.isArray(events) && "http://schemas.openid.net/event/backchannel-logout" in events)) throw new Error("Invalid back-channel logout token: missing back-channel logout event");
354
- if ("nonce" in payload) throw new Error("Invalid back-channel logout token: nonce is not allowed");
416
+ if (!(events && typeof events === "object" && !Array.isArray(events) && "http://schemas.openid.net/event/backchannel-logout" in events)) throw new InvalidBackChannelLogoutTokenError("Invalid back-channel logout token: missing back-channel logout event");
417
+ if ("nonce" in payload) throw new InvalidBackChannelLogoutTokenError("Invalid back-channel logout token: nonce is not allowed");
355
418
  const audience = payload.aud;
356
- if (!(Array.isArray(audience) ? audience.includes(clientId) : audience === clientId)) throw new Error("Invalid back-channel logout token: audience mismatch");
419
+ if (!(Array.isArray(audience) ? audience.includes(clientId) : audience === clientId)) throw new InvalidBackChannelLogoutTokenError("Invalid back-channel logout token: audience mismatch");
357
420
  const subject = typeof payload.sub === "string" ? payload.sub : void 0;
358
421
  const sid = typeof payload.sid === "string" ? payload.sid : void 0;
359
- if (!subject && !sid) throw new Error("Invalid back-channel logout token: subject or sid is required");
422
+ if (!subject && !sid) throw new InvalidBackChannelLogoutTokenError("Invalid back-channel logout token: subject or sid is required");
360
423
  return {
361
424
  issuer: typeof payload.iss === "string" ? payload.iss : void 0,
362
425
  subject,
@@ -550,6 +613,7 @@ async function createSessionData(tokenResult, claims) {
550
613
  const AUTH_ERROR_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error`;
551
614
  const AUTH_ERROR_DESC_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error_desc`;
552
615
  const OAUTH_STATE_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_oauth_state_id`;
616
+ const LOGOUT_RETURN_URL_COOKIE = "logout_return_url";
553
617
  /**
554
618
  * Check if there's an auth error in cookies
555
619
  *
@@ -592,8 +656,8 @@ function getAuthError(request) {
592
656
  */
593
657
  function clearAuthErrorHeaders() {
594
658
  const headers = new Headers();
595
- headers.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);
596
- headers.append("Set-Cookie", `${AUTH_ERROR_DESC_COOKIE}=; Path=/; Max-Age=0`);
659
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, "", { maxAge: 0 }));
660
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, "", { maxAge: 0 }));
597
661
  return headers;
598
662
  }
599
663
  /**
@@ -612,10 +676,11 @@ async function login(request, returnUrlOrOptions) {
612
676
  try {
613
677
  const config = getAuthConfig();
614
678
  const options = typeof returnUrlOrOptions === "string" ? { returnUrl: returnUrlOrOptions } : returnUrlOrOptions || {};
679
+ const returnUrl = sanitizeOAuthReturnUrl(options.returnUrl, request) ?? "/";
615
680
  if (request.headers.get("Cookie")?.includes(`${AUTH_ERROR_COOKIE}=`)) {
616
681
  logger.warn("Auth error cookie detected, preventing redirect loop");
617
682
  const headers = new Headers();
618
- headers.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);
683
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, "", { maxAge: 0 }));
619
684
  const baseUrl = new URL(config.redirectUri).origin;
620
685
  return createRedirectResponse(baseUrl, { headers });
621
686
  }
@@ -629,14 +694,14 @@ async function login(request, returnUrlOrOptions) {
629
694
  state,
630
695
  codeVerifier,
631
696
  nonce,
632
- returnUrl: options.returnUrl,
697
+ returnUrl,
633
698
  kcAction: options.kcAction,
634
699
  createdAt: Date.now()
635
700
  };
636
701
  logger.info("Creating OAuth state", {
637
- returnUrl: options.returnUrl,
702
+ hasReturnUrl: Boolean(options.returnUrl),
638
703
  prompt: options.prompt,
639
- kcAction: options.kcAction,
704
+ hasKcAction: Boolean(options.kcAction),
640
705
  extraAuthParamKeys: Object.keys(extraAuthParams)
641
706
  });
642
707
  const stateId = await createOAuthState(oauthState);
@@ -653,19 +718,19 @@ async function login(request, returnUrlOrOptions) {
653
718
  if (options.kcAction) authorizationParameters.kc_action = options.kcAction;
654
719
  const authorizationUrl = oidc.buildAuthorizationUrl(oidcConfig, authorizationParameters);
655
720
  const headers = new Headers();
656
- headers.append("Set-Cookie", `${OAUTH_STATE_COOKIE}=${stateId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=600`);
721
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, stateId, { maxAge: 600 }));
657
722
  logger.info("OIDC authorization flow initiated", {
658
723
  clientId: config.clientId,
659
724
  redirectUri: config.redirectUri,
660
725
  scope: config.scope,
661
726
  authorizationEndpoint: oidcConfig.serverMetadata().authorization_endpoint,
662
- stateId,
727
+ hasStateId: true,
663
728
  prompt: options.prompt,
664
- kcAction: options.kcAction
729
+ hasKcAction: Boolean(options.kcAction)
665
730
  });
666
731
  return createRedirectResponse(authorizationUrl.toString(), { headers });
667
732
  } catch (error) {
668
- logger.error("Failed to initiate OAuth login", error instanceof Error ? error : void 0);
733
+ logger.error("Failed to initiate OAuth login", void 0, { errorType: error instanceof Error ? error.name : "UnknownError" });
669
734
  throw new Error("Login failed");
670
735
  }
671
736
  }
@@ -695,10 +760,10 @@ async function handleCallback(request) {
695
760
  logger.info("OIDC callback received", {
696
761
  hasCode: !!code,
697
762
  hasState: !!state,
698
- error,
699
- errorDescription,
700
- kcAction,
701
- kcActionStatus,
763
+ hasError: Boolean(error),
764
+ hasErrorDescription: Boolean(errorDescription),
765
+ hasKcAction: Boolean(kcAction),
766
+ hasKcActionStatus: Boolean(kcActionStatus),
702
767
  clientId: config.clientId,
703
768
  redirectUri: config.redirectUri
704
769
  });
@@ -713,21 +778,21 @@ async function handleCallback(request) {
713
778
  if (oauthState && oauthState.state === state && stateAge <= 600 * 1e3) {
714
779
  await deleteOAuthState(stateId);
715
780
  const headers = new Headers();
716
- headers.append("Set-Cookie", `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
717
- const redirectUrl = appendApplicationInitiatedActionResult(oauthState.returnUrl || "/", kcAction || oauthState.kcAction, kcActionStatus);
781
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, "", { maxAge: 0 }));
782
+ const redirectUrl = appendApplicationInitiatedActionResult(sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? "/", kcAction || oauthState.kcAction, kcActionStatus);
718
783
  logger.info("Application initiated action returned with OAuth error", {
719
- error,
720
- errorDescription,
721
- kcAction: kcAction || oauthState.kcAction,
722
- kcActionStatus
784
+ hasError: Boolean(error),
785
+ hasErrorDescription: Boolean(errorDescription),
786
+ hasKcAction: Boolean(kcAction || oauthState.kcAction),
787
+ hasKcActionStatus: Boolean(kcActionStatus)
723
788
  });
724
789
  return createRedirectResponse(redirectUrl, { headers });
725
790
  }
726
791
  }
727
792
  }
728
793
  logger.error("OAuth callback error", void 0, {
729
- error,
730
- errorDescription,
794
+ hasError: Boolean(error),
795
+ hasErrorDescription: Boolean(errorDescription),
731
796
  clientId: config.clientId,
732
797
  applicationType: config.applicationType,
733
798
  hasLandingPage
@@ -735,8 +800,8 @@ async function handleCallback(request) {
735
800
  if (error === "access_denied") {
736
801
  logger.warn("Access denied for this application", { clientId: config.clientId });
737
802
  const headers = await destroyAuthSession(request);
738
- headers.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=access_denied; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`);
739
- headers.append("Set-Cookie", `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription || "You do not have access to this application.")}; Path=/; SameSite=Lax; Max-Age=300`);
803
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, "access_denied", { maxAge: 300 }));
804
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, errorDescription || "You do not have access to this application.", { maxAge: 300 }));
740
805
  const baseUrl = new URL(config.redirectUri).origin;
741
806
  logger.info("Redirecting to /auth/login to show access_denied error", {
742
807
  hasLandingPage,
@@ -752,8 +817,8 @@ async function handleCallback(request) {
752
817
  return createRedirectResponse(baseUrl, { headers });
753
818
  }
754
819
  const headers = await destroyAuthSession(request);
755
- headers.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=${encodeURIComponent(error)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`);
756
- if (errorDescription) headers.append("Set-Cookie", `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription)}; Path=/; SameSite=Lax; Max-Age=300`);
820
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, error, { maxAge: 300 }));
821
+ if (errorDescription) headers.append("Set-Cookie", serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, errorDescription, { maxAge: 300 }));
757
822
  const baseUrl = new URL(config.redirectUri).origin;
758
823
  return createRedirectResponse(baseUrl, { headers });
759
824
  }
@@ -770,11 +835,11 @@ async function handleCallback(request) {
770
835
  if (kcActionStatus) {
771
836
  await deleteOAuthState(stateId);
772
837
  const headers = new Headers();
773
- headers.append("Set-Cookie", `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
774
- const redirectUrl = appendApplicationInitiatedActionResult(oauthState.returnUrl || "/", kcAction || oauthState.kcAction, kcActionStatus);
838
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, "", { maxAge: 0 }));
839
+ const redirectUrl = appendApplicationInitiatedActionResult(sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? "/", kcAction || oauthState.kcAction, kcActionStatus);
775
840
  logger.info("Application initiated action returned without authorization code", {
776
- kcAction: kcAction || oauthState.kcAction,
777
- kcActionStatus
841
+ hasKcAction: Boolean(kcAction || oauthState.kcAction),
842
+ hasKcActionStatus: Boolean(kcActionStatus)
778
843
  });
779
844
  return createRedirectResponse(redirectUrl, { headers });
780
845
  }
@@ -793,11 +858,11 @@ async function handleCallback(request) {
793
858
  const sessionHeaders = await createAuthSession(request, await createSessionData(tokenResult, getClaimsFromTokenResponse(tokenResult)));
794
859
  await deleteOAuthState(stateId);
795
860
  const headers = new Headers(sessionHeaders);
796
- headers.append("Set-Cookie", `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
861
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, "", { maxAge: 0 }));
797
862
  logger.info("OAuth callback processed successfully");
798
- return createRedirectResponse(appendApplicationInitiatedActionResult(oauthState.returnUrl || "/", kcAction || oauthState.kcAction, kcActionStatus), { headers });
863
+ return createRedirectResponse(appendApplicationInitiatedActionResult(sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? "/", kcAction || oauthState.kcAction, kcActionStatus), { headers });
799
864
  } catch (error) {
800
- logger.error("OAuth callback failed", error instanceof Error ? error : void 0);
865
+ logger.error("OAuth callback failed", void 0, { errorType: error instanceof Error ? error.name : "UnknownError" });
801
866
  try {
802
867
  const cookies = request.headers.get("Cookie");
803
868
  const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);
@@ -821,7 +886,7 @@ async function logout(request) {
821
886
  const config = getAuthConfig();
822
887
  const url = new URL(request.url);
823
888
  const logoutScope = getLogoutScope(url);
824
- const returnUrl = sanitizeLogoutReturnUrl(url.searchParams.get("returnUrl"), request);
889
+ const returnUrl = sanitizeOAuthReturnUrl(url.searchParams.get("returnUrl"), request);
825
890
  logger.info("Logout initiated", {
826
891
  clientId: config.clientId,
827
892
  logoutScope
@@ -831,22 +896,22 @@ async function logout(request) {
831
896
  if (logoutScope === "local") {
832
897
  const headers = await destroyAuthSession(request);
833
898
  const absoluteRedirectUrl = getAbsoluteRedirectUrl(request, returnUrl || "/");
834
- headers.append("Set-Cookie", "logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0");
835
- logger.info("Local application logout completed", { redirectUrl: absoluteRedirectUrl });
899
+ headers.append("Set-Cookie", serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, "", { maxAge: 0 }));
900
+ logger.info("Local application logout completed", { hasReturnUrl: Boolean(returnUrl) });
836
901
  return createRedirectResponse(absoluteRedirectUrl, { headers });
837
902
  }
838
903
  const sessionHeaders = new Headers();
839
904
  if (logoutScope === "all" && sessionData.userId) {
840
905
  await revokeAndDestroyAllUserSessionsBestEffort(sessionData.userId);
841
- sessionHeaders.append("Set-Cookie", "logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0");
906
+ sessionHeaders.append("Set-Cookie", serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, "", { maxAge: 0 }));
842
907
  (await destroyAuthSession(request)).forEach((value, key) => sessionHeaders.append(key, value));
843
908
  } else {
844
909
  await revokeSessionTokensBestEffort(sessionData);
845
910
  (await destroyAuthSession(request)).forEach((value, key) => sessionHeaders.append(key, value));
846
911
  }
847
912
  const headers = new Headers(sessionHeaders);
848
- if (returnUrl) headers.append("Set-Cookie", `logout_return_url=${encodeURIComponent(returnUrl)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`);
849
- else headers.append("Set-Cookie", "logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0");
913
+ if (returnUrl) headers.append("Set-Cookie", serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, returnUrl, { maxAge: 300 }));
914
+ else headers.append("Set-Cookie", serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, "", { maxAge: 0 }));
850
915
  try {
851
916
  const oidcConfig = await getOAuthConfig();
852
917
  const serverMetadata = oidcConfig.serverMetadata();
@@ -869,7 +934,7 @@ async function logout(request) {
869
934
  logger.warn("OIDC end_session endpoint unavailable, falling back to local redirect", { error: getOAuthErrorMessage(error) });
870
935
  }
871
936
  const finalRedirectUrl = getAbsoluteRedirectUrl(request, config.postLogoutRedirectUri || "/");
872
- logger.info("Logout completed", { redirectUrl: finalRedirectUrl });
937
+ logger.info("Logout completed", { hasConfiguredRedirect: Boolean(config.postLogoutRedirectUri) });
873
938
  return createRedirectResponse(finalRedirectUrl, { headers });
874
939
  } catch (error) {
875
940
  logger.error("Logout failed", error instanceof Error ? error : void 0);
@@ -913,7 +978,11 @@ async function handleBackChannelLogout(request) {
913
978
  ...result
914
979
  });
915
980
  } catch (error) {
916
- logger.error("Back-channel logout failed", error instanceof Error ? error : void 0);
981
+ if (isInvalidBackChannelLogoutTokenError(error)) logger.warn("Back-channel logout token rejected", {
982
+ error: getOAuthErrorMessage(error),
983
+ errorCode: error instanceof errors.JOSEError ? error.code : void 0
984
+ });
985
+ else logger.error("Back-channel logout failed", error instanceof Error ? error : void 0);
917
986
  return Response.json({
918
987
  error: "invalid_logout_token",
919
988
  error_description: getOAuthErrorMessage(error)
@@ -969,7 +1038,7 @@ async function requireAuth(request) {
969
1038
  if (!user) {
970
1039
  const url = new URL(request.url);
971
1040
  const returnUrl = `${url.pathname}${url.search}`;
972
- logger.info("No user found, starting OAuth flow", { returnUrl });
1041
+ logger.info("No user found, starting OAuth flow", { hasReturnUrl: true });
973
1042
  throw await login(request, returnUrl);
974
1043
  }
975
1044
  return user;
@@ -1332,4 +1401,4 @@ function shouldAttemptTokenRefresh(response, hasAuth) {
1332
1401
  //#endregion
1333
1402
  export { resetAuthClaimMapping as A, handleCallback as C, logout as D, login as E, refreshTokens as O, handleBackChannelLogout as S, hasAuthError as T, createAuthorizationResponseUrl as _, configureRouteProtection as a, getAuthError as b, protectRoute as c, resetRouteProtectionConfig as d, shouldRefreshToken as f, configureAuthClaimMapping as g, clearAuthServerCache as h, authRoute as i, requireAuth as k, protectedRoute as l, clearAuthErrorHeaders as m, isTokenExpiredResponse as n, onboardingRoute as o, subscriptionRoute as p, shouldAttemptTokenRefresh as r, policyRoute as s, attemptTokenRefresh as t, publicRoute as u, extractUserInfo as v, handleFrontChannelLogout as w, getUser as x, getAccessToken as y };
1334
1403
 
1335
- //# sourceMappingURL=server-CqX2yCa4.mjs.map
1404
+ //# sourceMappingURL=server-DPCu40ud.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-DPCu40ud.mjs","names":["joseErrors"],"sources":["../src/http/response.ts","../src/auth/authorization-parameters.ts","../src/auth/oauth-security.ts","../src/auth/auth.server.ts","../src/auth/route-protection.server.ts","../src/auth/token-refresh.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","import type { LoginOptions } from './types';\n\nconst RESERVED_AUTHORIZATION_PARAMETER_NAMES = new Set([\n 'client_id',\n 'code_challenge',\n 'code_challenge_method',\n 'kc_action',\n 'nonce',\n 'prompt',\n 'redirect_uri',\n 'response_mode',\n 'response_type',\n 'scope',\n 'state',\n]);\n\nconst DEFAULT_EXTRA_AUTHORIZATION_PARAMETER_NAMES = new Set([\n 'acr_values',\n 'kc_idp_hint',\n 'login_hint',\n 'ui_locales',\n]);\n\nfunction isAllowedExtraAuthorizationParameter(\n key: string,\n allowedNames: Set<string>,\n allowedPrefixes: string[]\n): boolean {\n const normalized = key.toLowerCase();\n return allowedNames.has(normalized) || allowedPrefixes.some((prefix) => normalized.startsWith(prefix));\n}\n\nexport function sanitizeExtraAuthorizationParameters(\n params: LoginOptions['extraAuthParams'] | undefined,\n options: Pick<LoginOptions, 'extraAuthParamNames' | 'extraAuthParamPrefixes'> = {}\n): Record<string, string> {\n if (!params) {\n return {};\n }\n\n const allowedNames = new Set([\n ...DEFAULT_EXTRA_AUTHORIZATION_PARAMETER_NAMES,\n ...(options.extraAuthParamNames ?? []).map((name) => name.trim().toLowerCase()).filter(Boolean),\n ]);\n const allowedPrefixes = (options.extraAuthParamPrefixes ?? [])\n .map((prefix) => prefix.trim().toLowerCase())\n .filter(Boolean);\n const sanitized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(params)) {\n const normalizedKey = key.trim();\n if (\n normalizedKey.length === 0 ||\n RESERVED_AUTHORIZATION_PARAMETER_NAMES.has(normalizedKey.toLowerCase()) ||\n !isAllowedExtraAuthorizationParameter(normalizedKey, allowedNames, allowedPrefixes) ||\n value === null ||\n value === undefined\n ) {\n continue;\n }\n\n const normalizedValue = String(value).trim();\n if (normalizedValue.length === 0) {\n continue;\n }\n\n sanitized[normalizedKey] = normalizedValue;\n }\n\n return sanitized;\n}\n\nexport function normalizePublicAuthorizationStateContext(\n context: LoginOptions['publicStateContext']\n): Record<string, string> | null {\n if (!context) {\n return null;\n }\n\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(context)) {\n const normalizedKey = key.trim();\n if (\n normalizedKey.length === 0 ||\n RESERVED_AUTHORIZATION_PARAMETER_NAMES.has(normalizedKey.toLowerCase()) ||\n value === null ||\n value === undefined\n ) {\n continue;\n }\n\n const normalizedValue = String(value).trim();\n if (normalizedValue.length === 0) {\n continue;\n }\n\n normalized[normalizedKey] = normalizedValue;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : null;\n}\n\nexport function buildAuthorizationState(\n baseState: string,\n publicStateContext: LoginOptions['publicStateContext']\n): string {\n const normalizedContext = normalizePublicAuthorizationStateContext(publicStateContext);\n if (!normalizedContext) {\n return baseState;\n }\n\n return `${baseState}.${Buffer.from(JSON.stringify(normalizedContext), 'utf8').toString('base64url')}`;\n}\n","const AUTH_FLOW_PATHS = new Set(['/auth/login', '/auth/callback', '/auth/logout']);\n\nfunction isAuthFlowPath(path: string): boolean {\n const normalized = path.split('#')[0].split('?')[0].replace(/\\/+$/, '') || '/';\n return AUTH_FLOW_PATHS.has(normalized);\n}\n\nfunction hasUnsafeAuthorityPrefix(value: string): boolean {\n const normalized = value.trimStart();\n return (\n normalized.startsWith('//') ||\n normalized.startsWith('/\\\\') ||\n normalized.startsWith('\\\\')\n );\n}\n\nfunction hasUnsafeEncodedAuthorityPrefix(value: string): boolean {\n const queryOrFragmentIndex = value.search(/[?#]/);\n let decoded = queryOrFragmentIndex === -1\n ? value\n : value.slice(0, queryOrFragmentIndex);\n\n for (let attempt = 0; attempt < 3; attempt += 1) {\n if (hasUnsafeAuthorityPrefix(decoded)) {\n return true;\n }\n\n try {\n const next = decodeURIComponent(decoded);\n if (next === decoded) {\n return false;\n }\n decoded = next;\n } catch {\n return true;\n }\n }\n\n return hasUnsafeAuthorityPrefix(decoded);\n}\n\n/**\n * Normalize a post-authentication destination to a path on the current origin.\n *\n * Local paths and same-origin absolute URLs are supported for compatibility.\n * Network-path references, backslash authority variants, encoded authority\n * variants, non-HTTP schemes, and authentication loop targets are rejected.\n */\nexport function sanitizeOAuthReturnUrl(\n returnUrl: string | null | undefined,\n request: Request,\n): string | null {\n if (!returnUrl || hasUnsafeEncodedAuthorityPrefix(returnUrl)) {\n return null;\n }\n\n try {\n const requestOrigin = new URL(request.url).origin;\n const isLocalPath = returnUrl.startsWith('/') && !returnUrl.startsWith('//');\n const candidateUrl = isLocalPath\n ? new URL(returnUrl, requestOrigin)\n : new URL(returnUrl);\n\n if (candidateUrl.origin !== requestOrigin) {\n return null;\n }\n\n const safePath = `${candidateUrl.pathname}${candidateUrl.search}${candidateUrl.hash}`;\n return isAuthFlowPath(safePath) ? null : safePath;\n } catch {\n return null;\n }\n}\n\ntype TemporaryAuthCookieOptions = {\n maxAge: number;\n};\n\n/** Serialize a short-lived, server-only authentication cookie. */\nexport function serializeTemporaryAuthCookie(\n name: string,\n value: string,\n options: TemporaryAuthCookieOptions,\n): string {\n const segments = [\n `${name}=${encodeURIComponent(value)}`,\n 'Path=/',\n 'HttpOnly',\n 'SameSite=Lax',\n `Max-Age=${options.maxAge}`,\n ];\n\n if (process.env.NODE_ENV === 'production') {\n segments.push('Secure');\n }\n\n return segments.join('; ');\n}\n","/**\n * OpenID Connect Authentication Server Module\n *\n * Handles:\n * - Login flow (PKCE)\n * - Token exchange\n * - Token refresh\n * - Logout\n * - Session management\n */\n\nimport * as oidc from 'openid-client';\nimport { createRemoteJWKSet, errors as joseErrors, jwtVerify } from 'jose';\n\nimport {\n createAuthSession,\n getAuthSession,\n updateAuthSession,\n destroyAuthSession,\n isSessionValid,\n listAuthSessionDataForUser,\n destroyAuthSessionsByIdentitySession,\n destroyAuthSessionsBySid,\n destroyAuthSessionsForUser,\n createOAuthState,\n getOAuthState,\n deleteOAuthState,\n AUTH_ERROR_COOKIE_PREFIX,\n} from './redis-session-storage.server';\nimport type { \n AuthConfig, \n AuthClaimMapping,\n UserInfo, \n SessionData, \n OAuthState,\n TokenRefreshResult,\n LoginOptions,\n ApplicationType,\n OidcClientAuthMethod,\n BackChannelLogoutResult,\n FrontChannelLogoutResult,\n AuthError\n} from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\nimport {\n buildAuthorizationState,\n sanitizeExtraAuthorizationParameters,\n} from './authorization-parameters';\nimport {\n sanitizeOAuthReturnUrl,\n serializeTemporaryAuthCookie,\n} from './oauth-security';\n\ntype OidcTokenResponse = oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\ntype LogoutScope = 'identity' | 'local' | 'all';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Get effective hasLandingPage setting based on config\n */\nfunction getEffectiveHasLandingPage(config: AuthConfig): boolean {\n // Explicit setting takes precedence\n if (config.hasLandingPage !== undefined) {\n return config.hasLandingPage;\n }\n \n // Determine by application type\n switch (config.applicationType) {\n case 'no-landing-page':\n case 'dashboard':\n return false; // Dashboard has no landing page\n case 'landing-page':\n case 'tenant-app':\n return true; // Tenant app has landing page\n default:\n return true; // Default to having a landing page\n }\n}\n\n/**\n * Create OAuth configuration from environment variables\n */\nfunction createAuthConfig(): AuthConfig {\n const requiredEnvVars = {\n authority: process.env.OIDC_AUTHORITY,\n clientId: process.env.OIDC_CLIENT_ID,\n redirectUri: process.env.OIDC_REDIRECT_URI,\n };\n\n for (const [key, value] of Object.entries(requiredEnvVars)) {\n if (!value) {\n throw new Error(`Missing required environment variable: OIDC_${key.toUpperCase()}`);\n }\n }\n\n // Parse application type from env\n const appTypeEnv = process.env.OIDC_APPLICATION_TYPE?.toLowerCase();\n let applicationType: ApplicationType = 'custom';\n if (appTypeEnv === 'no-landing-page' || appTypeEnv === 'no_landing_page') {\n applicationType = 'no-landing-page';\n } else if (appTypeEnv === 'landing-page' || appTypeEnv === 'landing_page') {\n applicationType = 'landing-page';\n } else if (appTypeEnv === 'dashboard') {\n applicationType = 'dashboard';\n } else if (appTypeEnv === 'tenant-app' || appTypeEnv === 'tenant_app') {\n applicationType = 'tenant-app';\n }\n\n const clientAuthMethod = parseOidcClientAuthMethod(\n process.env.OIDC_CLIENT_AUTH_METHOD,\n process.env.OIDC_CLIENT_SECRET ? 'client_secret_post' : 'none'\n );\n\n const hasLandingPage = process.env.OIDC_HAS_LANDING_PAGE !== undefined\n ? process.env.OIDC_HAS_LANDING_PAGE === 'true'\n : undefined;\n\n return {\n authority: requiredEnvVars.authority!,\n clientId: requiredEnvVars.clientId!,\n clientSecret: process.env.OIDC_CLIENT_SECRET,\n clientAuthMethod,\n redirectUri: requiredEnvVars.redirectUri!,\n scope: process.env.OIDC_SCOPE || 'openid profile email api',\n postLogoutRedirectUri: process.env.OIDC_POST_LOGOUT_REDIRECT_URI,\n applicationType,\n hasLandingPage,\n };\n}\n\nfunction parseOidcClientAuthMethod(\n value: string | undefined,\n fallback: OidcClientAuthMethod\n): OidcClientAuthMethod {\n const normalized = value?.trim().toLowerCase();\n\n switch (normalized) {\n case undefined:\n case '':\n return fallback;\n case 'none':\n return 'none';\n case 'client_secret_post':\n case 'post':\n return 'client_secret_post';\n case 'client_secret_basic':\n case 'basic':\n return 'client_secret_basic';\n default:\n throw new Error(`Unsupported OIDC_CLIENT_AUTH_METHOD: ${value}`);\n }\n}\n\nlet OIDC_CONFIG: AuthConfig | null = null;\n\nfunction getAuthConfig(): AuthConfig {\n if (!OIDC_CONFIG) {\n OIDC_CONFIG = createAuthConfig();\n }\n return OIDC_CONFIG;\n}\n\n// ============================================================================\n// OAuth Discovery Cache\n// ============================================================================\n\nlet authServerCache: {\n configuration: oidc.Configuration;\n cachedAt: number;\n} | null = null;\n\nconst CACHE_TTL = 60 * 60 * 1000; // 1 hour\nconst jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nconst INVALID_LOGOUT_TOKEN_JOSE_ERROR_CODES = new Set([\n joseErrors.JOSEAlgNotAllowed.code,\n joseErrors.JOSENotSupported.code,\n joseErrors.JWKSNoMatchingKey.code,\n joseErrors.JWSInvalid.code,\n joseErrors.JWSSignatureVerificationFailed.code,\n joseErrors.JWTClaimValidationFailed.code,\n joseErrors.JWTExpired.code,\n joseErrors.JWTInvalid.code,\n]);\n\nclass InvalidBackChannelLogoutTokenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidBackChannelLogoutTokenError';\n }\n}\n\nfunction isInvalidBackChannelLogoutTokenError(error: unknown): boolean {\n if (error instanceof InvalidBackChannelLogoutTokenError) {\n return true;\n }\n\n return error instanceof joseErrors.JOSEError &&\n INVALID_LOGOUT_TOKEN_JOSE_ERROR_CODES.has(error.code);\n}\n\nfunction normalizeIssuer(value: string | undefined): string | null {\n const normalized = value?.trim().replace(/\\/+$/, '');\n return normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction getJwtClaims(token: string | undefined): Record<string, unknown> | null {\n if (!token) {\n return null;\n }\n\n try {\n const [, payload] = token.split('.');\n if (!payload) {\n return null;\n }\n\n return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction getJwtIssuer(token: string | undefined): string | null {\n const issuer = getJwtClaims(token)?.iss;\n return typeof issuer === 'string' ? normalizeIssuer(issuer) : null;\n}\n\nfunction canUseIdTokenHint(idToken: string | undefined, expectedIssuer: string): boolean {\n if (!idToken) {\n return false;\n }\n\n const tokenIssuer = getJwtIssuer(idToken);\n return tokenIssuer !== null && tokenIssuer === normalizeIssuer(expectedIssuer);\n}\n\nfunction appendApplicationInitiatedActionResult(\n returnUrl: string,\n action: string | null | undefined,\n status: string | null | undefined\n): string {\n if (!action && !status) {\n return returnUrl;\n }\n\n try {\n const isRelative = returnUrl.startsWith('/') && !returnUrl.startsWith('//');\n const url = new URL(returnUrl, isRelative ? 'http://spine.local' : undefined);\n\n if (action) {\n url.searchParams.set('kc_action', action);\n }\n\n if (status) {\n url.searchParams.set('kc_action_status', status);\n }\n\n return isRelative ? `${url.pathname}${url.search}${url.hash}` : url.toString();\n } catch {\n const params = new URLSearchParams();\n if (action) params.set('kc_action', action);\n if (status) params.set('kc_action_status', status);\n const separator = returnUrl.includes('?') ? '&' : '?';\n return `${returnUrl}${separator}${params.toString()}`;\n }\n}\n\nexport function createAuthorizationResponseUrl(requestUrl: string, redirectUri: string): URL {\n const incomingUrl = new URL(requestUrl);\n const authorizationResponseUrl = new URL(redirectUri);\n authorizationResponseUrl.search = incomingUrl.search;\n authorizationResponseUrl.hash = \"\";\n return authorizationResponseUrl;\n}\n\nfunction isLocalInsecureIssuer(authority: string): boolean {\n try {\n const issuer = new URL(authority);\n return (\n issuer.protocol === 'http:' &&\n ['localhost', '127.0.0.1', '::1', '[::1]'].includes(issuer.hostname)\n );\n } catch {\n return false;\n }\n}\n\nfunction shouldAllowInsecureOidcRequests(authority: string): boolean {\n if (process.env.NODE_ENV === 'production') {\n return false;\n }\n\n if (process.env.OIDC_ALLOW_INSECURE_REQUESTS === 'true') {\n return true;\n }\n\n return isLocalInsecureIssuer(authority);\n}\n\nfunction getDiscoveryOptions(): oidc.DiscoveryRequestOptions {\n const config = getAuthConfig();\n\n return shouldAllowInsecureOidcRequests(config.authority)\n ? { algorithm: 'oidc', execute: [oidc.allowInsecureRequests] }\n : { algorithm: 'oidc' };\n}\n\nfunction getClientMetadata(config: AuthConfig): string | undefined {\n return config.clientAuthMethod === 'none' ? undefined : config.clientSecret;\n}\n\nfunction getClientAuthentication(config: AuthConfig): oidc.ClientAuth {\n switch (config.clientAuthMethod) {\n case 'client_secret_basic':\n if (!config.clientSecret) {\n throw new Error('OIDC_CLIENT_SECRET is required for client_secret_basic authentication');\n }\n\n return oidc.ClientSecretBasic(config.clientSecret);\n case 'client_secret_post':\n if (!config.clientSecret) {\n throw new Error('OIDC_CLIENT_SECRET is required for client_secret_post authentication');\n }\n\n return oidc.ClientSecretPost(config.clientSecret);\n case 'none':\n default:\n return oidc.None();\n }\n}\n\nfunction getOAuthErrorMessage(error: unknown): string {\n if (error instanceof oidc.ResponseBodyError) {\n return error.error_description || error.error || error.message;\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'Unknown error';\n}\n\nfunction shouldLogoutAfterOAuthError(error: unknown): boolean {\n if (error instanceof oidc.ResponseBodyError) {\n return (\n error.status === 400 ||\n error.status === 401 ||\n error.error === 'invalid_grant' ||\n error.error === 'invalid_token'\n );\n }\n\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return message.includes('invalid_grant') || message.includes('invalid_token') || message.includes('unauthorized');\n}\n\nfunction getLogoutScope(url: URL): LogoutScope {\n const requestedScope =\n url.searchParams.get('logout') ??\n url.searchParams.get('logout_scope') ??\n url.searchParams.get('scope');\n const normalized = requestedScope?.trim().toLowerCase();\n\n if (\n normalized === 'local' ||\n normalized === 'application' ||\n normalized === 'app' ||\n normalized === 'current-device'\n ) {\n return 'local';\n }\n\n if (\n normalized === 'all' ||\n normalized === 'all-local' ||\n normalized === 'all-devices' ||\n normalized === 'everywhere'\n ) {\n return 'all';\n }\n\n if (url.searchParams.get('local_only') === 'true' || url.searchParams.get('client_id_only') === 'true') {\n return 'local';\n }\n\n return 'identity';\n}\n\nfunction getAbsoluteRedirectUrl(request: Request, redirectUrl: string): string {\n if (/^https?:\\/\\//i.test(redirectUrl)) {\n return redirectUrl;\n }\n\n return new URL(redirectUrl, new URL(request.url).origin).toString();\n}\n\nasync function revokeTokenBestEffort(\n configuration: oidc.Configuration,\n token: string | undefined,\n tokenTypeHint: 'access_token' | 'refresh_token'\n): Promise<void> {\n if (!token) {\n return;\n }\n\n try {\n await oidc.tokenRevocation(configuration, token, {\n token_type_hint: tokenTypeHint,\n });\n } catch (error) {\n logger.warn('OIDC token revocation failed', {\n tokenTypeHint,\n error: getOAuthErrorMessage(error),\n });\n }\n}\n\nasync function revokeSessionTokensBestEffort(sessionData: SessionData): Promise<void> {\n if (!sessionData.refreshToken && !sessionData.accessToken) {\n return;\n }\n\n try {\n const configuration = await getOAuthConfig();\n await revokeTokenBestEffort(configuration, sessionData.refreshToken, 'refresh_token');\n await revokeTokenBestEffort(configuration, sessionData.accessToken, 'access_token');\n } catch (error) {\n logger.warn('Skipping OIDC token revocation because provider configuration is unavailable', {\n error: getOAuthErrorMessage(error),\n });\n }\n}\n\nasync function revokeAndDestroyAllUserSessionsBestEffort(userId: string): Promise<number> {\n const sessions = await listAuthSessionDataForUser(userId);\n\n try {\n const configuration = await getOAuthConfig();\n for (const { data } of sessions) {\n await revokeTokenBestEffort(configuration, data.refreshToken, 'refresh_token');\n await revokeTokenBestEffort(configuration, data.accessToken, 'access_token');\n }\n } catch (error) {\n logger.warn('Skipping all-session token revocation because provider configuration is unavailable', {\n error: getOAuthErrorMessage(error),\n });\n }\n\n return await destroyAuthSessionsForUser(userId);\n}\n\nfunction getClaimsFromTokenResponse(tokenResult: OidcTokenResponse): Record<string, unknown> {\n const claims = tokenResult.claims();\n\n if (claims) {\n return claims as Record<string, unknown>;\n }\n\n return getJwtClaims(tokenResult.id_token) ?? {};\n}\n\n/**\n * Get OIDC configuration with caching.\n */\nasync function getOAuthConfig(): Promise<oidc.Configuration> {\n const config = getAuthConfig();\n const now = Date.now();\n\n if (authServerCache && now - authServerCache.cachedAt < CACHE_TTL) {\n return authServerCache.configuration;\n }\n\n try {\n const configuration = await oidc.discovery(\n new URL(config.authority),\n config.clientId,\n getClientMetadata(config),\n getClientAuthentication(config),\n getDiscoveryOptions()\n );\n\n authServerCache = {\n configuration,\n cachedAt: now,\n };\n\n logger.info('OIDC discovery completed successfully', {\n issuer: configuration.serverMetadata().issuer,\n clientId: config.clientId,\n clientAuthMethod: config.clientAuthMethod,\n });\n return configuration;\n } catch (error) {\n logger.error('OIDC discovery failed', error instanceof Error ? error : undefined, {\n authority: config.authority,\n clientId: config.clientId,\n });\n throw new Error(`OIDC discovery failed: ${getOAuthErrorMessage(error)}`);\n }\n}\n\nfunction getJwks(jwksUri: string): ReturnType<typeof createRemoteJWKSet> {\n const cachedJwks = jwksCache.get(jwksUri);\n if (cachedJwks) {\n return cachedJwks;\n }\n\n const jwks = createRemoteJWKSet(new URL(jwksUri));\n jwksCache.set(jwksUri, jwks);\n return jwks;\n}\n\nasync function getLogoutTokenFromRequest(request: Request): Promise<string | null> {\n const contentType = request.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json')) {\n const body = await request.json().catch(() => null) as { logout_token?: unknown } | null;\n return typeof body?.logout_token === 'string' ? body.logout_token : null;\n }\n\n const formData = await request.formData().catch(() => null);\n const logoutToken = formData?.get('logout_token');\n return typeof logoutToken === 'string' ? logoutToken : null;\n}\n\nfunction validateBackChannelLogoutClaims(payload: Record<string, unknown>, clientId: string): {\n issuer?: string;\n subject?: string;\n sid?: string;\n} {\n const events = payload.events;\n const hasBackChannelEvent =\n events &&\n typeof events === 'object' &&\n !Array.isArray(events) &&\n 'http://schemas.openid.net/event/backchannel-logout' in events;\n\n if (!hasBackChannelEvent) {\n throw new InvalidBackChannelLogoutTokenError(\n 'Invalid back-channel logout token: missing back-channel logout event'\n );\n }\n\n if ('nonce' in payload) {\n throw new InvalidBackChannelLogoutTokenError(\n 'Invalid back-channel logout token: nonce is not allowed'\n );\n }\n\n const audience = payload.aud;\n const hasClientAudience = Array.isArray(audience)\n ? audience.includes(clientId)\n : audience === clientId;\n\n if (!hasClientAudience) {\n throw new InvalidBackChannelLogoutTokenError(\n 'Invalid back-channel logout token: audience mismatch'\n );\n }\n\n const subject = typeof payload.sub === 'string' ? payload.sub : undefined;\n const sid = typeof payload.sid === 'string' ? payload.sid : undefined;\n\n if (!subject && !sid) {\n throw new InvalidBackChannelLogoutTokenError(\n 'Invalid back-channel logout token: subject or sid is required'\n );\n }\n\n return {\n issuer: typeof payload.iss === 'string' ? payload.iss : undefined,\n subject,\n sid,\n };\n}\n\n// ============================================================================\n// Claim Extraction\n// ============================================================================\n\nconst DEFAULT_AUTH_CLAIM_MAPPING: Required<AuthClaimMapping> = {\n subject: ['sub'],\n name: ['name', 'preferred_username', 'email'],\n email: ['email'],\n givenName: ['given_name', 'givenName'],\n familyName: ['family_name', 'familyName'],\n picture: ['picture', 'avatar_url'],\n locale: ['locale'],\n zoneinfo: ['zoneinfo'],\n updatedAt: ['updated_at'],\n tenantIds: ['tenant_ids'],\n tenantRoles: ['tenant_roles'],\n permissions: ['app_perms', 'permissions', 'scope'],\n isOnboarded: ['is_onboarded'],\n};\n\nlet authClaimMapping: Required<AuthClaimMapping> = {\n ...DEFAULT_AUTH_CLAIM_MAPPING,\n};\n\nfunction normalizeClaimKeys(keys?: string[]): string[] {\n if (!Array.isArray(keys)) {\n return [];\n }\n\n return Array.from(\n new Set(\n keys\n .map((key) => key.trim())\n .filter((key) => key.length > 0)\n )\n );\n}\n\n/**\n * Configure claim mapping so different providers/backends can project their\n * own claim names onto the shared auth/session primitives.\n */\nexport function configureAuthClaimMapping(mapping: AuthClaimMapping): void {\n authClaimMapping = {\n subject: mapping.subject !== undefined ? normalizeClaimKeys(mapping.subject) : DEFAULT_AUTH_CLAIM_MAPPING.subject,\n name: mapping.name !== undefined ? normalizeClaimKeys(mapping.name) : DEFAULT_AUTH_CLAIM_MAPPING.name,\n email: mapping.email !== undefined ? normalizeClaimKeys(mapping.email) : DEFAULT_AUTH_CLAIM_MAPPING.email,\n givenName: mapping.givenName !== undefined ? normalizeClaimKeys(mapping.givenName) : DEFAULT_AUTH_CLAIM_MAPPING.givenName,\n familyName: mapping.familyName !== undefined ? normalizeClaimKeys(mapping.familyName) : DEFAULT_AUTH_CLAIM_MAPPING.familyName,\n picture: mapping.picture !== undefined ? normalizeClaimKeys(mapping.picture) : DEFAULT_AUTH_CLAIM_MAPPING.picture,\n locale: mapping.locale !== undefined ? normalizeClaimKeys(mapping.locale) : DEFAULT_AUTH_CLAIM_MAPPING.locale,\n zoneinfo: mapping.zoneinfo !== undefined ? normalizeClaimKeys(mapping.zoneinfo) : DEFAULT_AUTH_CLAIM_MAPPING.zoneinfo,\n updatedAt: mapping.updatedAt !== undefined ? normalizeClaimKeys(mapping.updatedAt) : DEFAULT_AUTH_CLAIM_MAPPING.updatedAt,\n tenantIds: mapping.tenantIds !== undefined ? normalizeClaimKeys(mapping.tenantIds) : DEFAULT_AUTH_CLAIM_MAPPING.tenantIds,\n tenantRoles: mapping.tenantRoles !== undefined ? normalizeClaimKeys(mapping.tenantRoles) : DEFAULT_AUTH_CLAIM_MAPPING.tenantRoles,\n permissions: mapping.permissions !== undefined ? normalizeClaimKeys(mapping.permissions) : DEFAULT_AUTH_CLAIM_MAPPING.permissions,\n isOnboarded: mapping.isOnboarded !== undefined ? normalizeClaimKeys(mapping.isOnboarded) : DEFAULT_AUTH_CLAIM_MAPPING.isOnboarded,\n };\n}\n\n/**\n * Reset claim mapping to the built-in defaults.\n */\nexport function resetAuthClaimMapping(): void {\n authClaimMapping = {\n ...DEFAULT_AUTH_CLAIM_MAPPING,\n };\n}\n\nfunction getConfiguredAuthClaimMapping(): Required<AuthClaimMapping> {\n return authClaimMapping;\n}\n\nfunction getFirstClaimValue(\n claims: Record<string, unknown>,\n claimKeys: string[]\n): { key?: string; value?: unknown } {\n for (const claimKey of claimKeys) {\n if (claimKey in claims) {\n return {\n key: claimKey,\n value: claims[claimKey],\n };\n }\n }\n\n return {};\n}\n\nfunction getStringClaim(claims: Record<string, unknown>, claimKeys: string[]): string | undefined {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return `${value}`;\n }\n\n return undefined;\n}\n\nfunction getNumberClaim(claims: Record<string, unknown>, claimKeys: string[]): number | undefined {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === 'string') {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n return undefined;\n}\n\nfunction getBooleanClaim(\n claims: Record<string, unknown>,\n claimKeys: string[],\n defaultValue = false\n): boolean {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'boolean') {\n return value;\n }\n\n if (typeof value === 'string') {\n return value.toLowerCase() === 'true';\n }\n\n return defaultValue;\n}\n\n/**\n * Parse JSON safely\n */\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\n/**\n * Convert value to string array\n */\nfunction toStringArray(value: unknown): string[] {\n if (!value) return [];\n\n if (Array.isArray(value)) {\n return value\n .map((item) => (typeof item === 'string' ? item.trim() : `${item}`.trim()))\n .filter((item) => item.length > 0);\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (!trimmed) return [];\n\n if (trimmed.startsWith('[')) {\n const parsed = parseJson(trimmed);\n if (Array.isArray(parsed)) {\n return parsed\n .map((item) => (typeof item === 'string' ? item.trim() : `${item}`.trim()))\n .filter((item) => item.length > 0);\n }\n }\n\n if (trimmed.includes(',')) {\n return trimmed\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item.length > 0);\n }\n\n return [trimmed];\n }\n\n return [];\n}\n\nfunction toScopeArray(value: unknown): string[] {\n if (typeof value === 'string') {\n return value\n .trim()\n .split(/\\s+/)\n .filter((item) => item.length > 0);\n }\n\n return toStringArray(value);\n}\n\n/**\n * Extract user info from ID token claims\n */\nexport function extractUserInfo(claims: Record<string, unknown>): {\n tenants: string[];\n currentTenant?: string;\n isOnboarded: boolean;\n tenantRoles: Record<string, string[]>;\n permissions: string[];\n} {\n const claimMapping = getConfiguredAuthClaimMapping();\n\n // Extract tenant IDs\n let tenants: string[] = [];\n const { value: rawTenantIds } = getFirstClaimValue(claims, claimMapping.tenantIds);\n if (rawTenantIds !== undefined) {\n if (typeof rawTenantIds === 'string' && rawTenantIds.trim().startsWith('[')) {\n const parsed = parseJson(rawTenantIds.trim());\n if (Array.isArray(parsed)) {\n tenants = parsed\n .map((id) => (typeof id === 'string' ? id.trim() : `${id}`.trim()))\n .filter((id) => id.length > 0);\n }\n } else {\n tenants = toStringArray(rawTenantIds);\n }\n }\n\n // Extract tenant roles\n const tenantRoles: Record<string, string[]> = {};\n const { value: rawRoles } = getFirstClaimValue(claims, claimMapping.tenantRoles);\n if (rawRoles !== undefined) {\n let parsedRoles: unknown = rawRoles;\n\n if (typeof rawRoles === 'string' && rawRoles.trim().startsWith('{')) {\n parsedRoles = parseJson(rawRoles.trim()) ?? rawRoles;\n }\n\n if (parsedRoles && typeof parsedRoles === 'object' && !Array.isArray(parsedRoles)) {\n for (const [tenantId, roleValue] of Object.entries(parsedRoles as Record<string, unknown>)) {\n const roleIds = toStringArray(roleValue);\n if (roleIds.length > 0) {\n tenantRoles[tenantId] = roleIds;\n }\n }\n }\n }\n\n // Extract permissions\n const permissionClaim = getFirstClaimValue(claims, claimMapping.permissions);\n const permissions =\n permissionClaim.key === 'scope'\n ? toScopeArray(permissionClaim.value)\n : toStringArray(permissionClaim.value);\n\n // Extract onboarding status\n const isOnboarded = getBooleanClaim(claims, claimMapping.isOnboarded);\n\n return { tenants, isOnboarded, tenantRoles, permissions };\n}\n\n// ============================================================================\n// Session Data Creation\n// ============================================================================\n\n/**\n * Create session data from token result\n */\nasync function createSessionData(\n tokenResult: OidcTokenResponse,\n claims: Record<string, unknown>\n): Promise<Partial<SessionData>> {\n const claimMapping = getConfiguredAuthClaimMapping();\n const baseUser: UserInfo = {\n sub: getStringClaim(claims, claimMapping.subject) || 'unknown',\n name: getStringClaim(claims, claimMapping.name) || getStringClaim(claims, claimMapping.email) || 'User',\n email: getStringClaim(claims, claimMapping.email),\n givenName: getStringClaim(claims, claimMapping.givenName),\n familyName: getStringClaim(claims, claimMapping.familyName),\n picture: getStringClaim(claims, claimMapping.picture),\n locale: getStringClaim(claims, claimMapping.locale),\n zoneinfo: getStringClaim(claims, claimMapping.zoneinfo),\n updated_at: getNumberClaim(claims, claimMapping.updatedAt),\n };\n\n const expiresAt = tokenResult.expires_in\n ? Date.now() + tokenResult.expires_in * 1000\n : undefined;\n const now = Date.now();\n const sessionState = (tokenResult as Record<string, unknown>).session_state;\n\n return {\n userId: baseUser.sub,\n accessToken: tokenResult.access_token,\n refreshToken: tokenResult.refresh_token,\n idToken: tokenResult.id_token,\n expiresAt,\n issuer: getStringClaim(claims, ['iss']),\n sid: getStringClaim(claims, ['sid']),\n sessionState: typeof sessionState === 'string' ? sessionState : undefined,\n clientId: getAuthConfig().clientId,\n user: baseUser,\n createdAt: now,\n lastActivity: now,\n };\n}\n\n// ============================================================================\n// Auth Error Handling\n// ============================================================================\n\n// Auth error cookie names - unique per application to prevent conflicts\nconst AUTH_ERROR_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error`;\nconst AUTH_ERROR_DESC_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error_desc`;\nconst OAUTH_STATE_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_oauth_state_id`;\nconst LOGOUT_RETURN_URL_COOKIE = 'logout_return_url';\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(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, '', { maxAge: 0 }),\n );\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, '', { maxAge: 0 }),\n );\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 const returnUrl = sanitizeOAuthReturnUrl(options.returnUrl, request) ?? '/';\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(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, '', { maxAge: 0 }),\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 const oidcConfig = await getOAuthConfig();\n\n // Generate PKCE challenge\n const codeVerifier = oidc.randomPKCECodeVerifier();\n const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);\n\n const extraAuthParams = sanitizeExtraAuthorizationParameters(options.extraAuthParams, options);\n\n // Generate secure state and nonce. The state may carry a non-secret UI\n // hint for Keycloak themes, but callback validation still compares the\n // exact value stored server-side.\n const state = buildAuthorizationState(oidc.randomState(), options.publicStateContext);\n const nonce = oidc.randomNonce();\n\n // Create OAuth state object\n const oauthState: OAuthState = {\n state,\n codeVerifier,\n nonce,\n returnUrl,\n kcAction: options.kcAction,\n createdAt: Date.now(),\n };\n\n logger.info('Creating OAuth state', {\n hasReturnUrl: Boolean(options.returnUrl),\n prompt: options.prompt,\n hasKcAction: Boolean(options.kcAction),\n extraAuthParamKeys: Object.keys(extraAuthParams),\n });\n\n // Store OAuth state in Redis\n const stateId = await createOAuthState(oauthState);\n\n const authorizationParameters: Record<string, string> = {\n redirect_uri: config.redirectUri,\n scope: config.scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...extraAuthParams,\n };\n\n if (options.prompt) {\n authorizationParameters.prompt = options.prompt;\n }\n\n if (options.kcAction) {\n authorizationParameters.kc_action = options.kcAction;\n }\n\n const authorizationUrl = oidc.buildAuthorizationUrl(oidcConfig, authorizationParameters);\n\n // Store stateId in cookie for callback (app-specific to prevent conflicts)\n const headers = new Headers();\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, stateId, { maxAge: 600 }),\n );\n\n logger.info('OIDC authorization flow initiated', {\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scope: config.scope,\n authorizationEndpoint: oidcConfig.serverMetadata().authorization_endpoint,\n hasStateId: true,\n prompt: options.prompt,\n hasKcAction: Boolean(options.kcAction),\n });\n\n return createRedirectResponse(authorizationUrl.toString(), { headers });\n } catch (error) {\n logger.error('Failed to initiate OAuth login', undefined, {\n errorType: error instanceof Error ? error.name : 'UnknownError',\n });\n throw new Error('Login failed');\n }\n}\n\n/**\n * Handle OAuth callback\n * \n * Handles OAuth authorization response including success and error cases.\n * \n * Error Handling:\n * - `access_denied`: User doesn't have access to this application\n * - For apps with landing page: Redirect home with error cookie\n * - For apps without landing page: Redirect home with error cookie (must handle specially)\n * - `login_required`: No active identity session\n * - Redirect to home to start fresh login\n */\nexport async function handleCallback(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const hasLandingPage = getEffectiveHasLandingPage(config);\n const url = new URL(request.url);\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n const kcAction = url.searchParams.get('kc_action');\n const kcActionStatus = url.searchParams.get('kc_action_status');\n\n logger.info('OIDC callback received', {\n hasCode: !!code,\n hasState: !!state,\n hasError: Boolean(error),\n hasErrorDescription: Boolean(errorDescription),\n hasKcAction: Boolean(kcAction),\n hasKcActionStatus: Boolean(kcActionStatus),\n clientId: config.clientId,\n redirectUri: config.redirectUri\n });\n\n // Handle OAuth errors\n if (error) {\n if (kcActionStatus && state) {\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n\n if (stateId) {\n const oauthState = await getOAuthState(stateId);\n const stateAge = oauthState ? Date.now() - oauthState.createdAt : Number.POSITIVE_INFINITY;\n\n if (oauthState && oauthState.state === state && stateAge <= 10 * 60 * 1000) {\n await deleteOAuthState(stateId);\n const headers = new Headers();\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, '', { maxAge: 0 }),\n );\n\n const redirectUrl = appendApplicationInitiatedActionResult(\n sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n\n logger.info('Application initiated action returned with OAuth error', {\n hasError: Boolean(error),\n hasErrorDescription: Boolean(errorDescription),\n hasKcAction: Boolean(kcAction || oauthState.kcAction),\n hasKcActionStatus: Boolean(kcActionStatus),\n });\n\n return createRedirectResponse(redirectUrl, { headers });\n }\n }\n }\n\n logger.error('OAuth callback error', undefined, { \n hasError: Boolean(error),\n hasErrorDescription: Boolean(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 serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, 'access_denied', { maxAge: 300 }),\n );\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(\n AUTH_ERROR_DESC_COOKIE,\n errorDescription || 'You do not have access to this application.',\n { maxAge: 300 },\n ),\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 serializeTemporaryAuthCookie(AUTH_ERROR_COOKIE, error, { maxAge: 300 }),\n );\n if (errorDescription) {\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(AUTH_ERROR_DESC_COOKIE, errorDescription, { maxAge: 300 }),\n );\n }\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n\n // Get stateId from cookie (app-specific)\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n\n if (!stateId) {\n throw new Error('OAuth state ID not found in cookie');\n }\n\n // Retrieve OAuth state from Redis\n const oauthState = await getOAuthState(stateId);\n\n if (!oauthState) {\n throw new Error('No OAuth state found - state may have expired');\n }\n\n if (!state) {\n throw new Error('Missing required callback state parameter');\n }\n\n if (oauthState.state !== state) {\n throw new Error(`OAuth state mismatch`);\n }\n\n // Check state age (10 minutes max)\n const stateAge = Date.now() - oauthState.createdAt;\n if (stateAge > 10 * 60 * 1000) {\n throw new Error('OAuth state expired');\n }\n\n if (!code) {\n if (kcActionStatus) {\n await deleteOAuthState(stateId);\n const headers = new Headers();\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, '', { maxAge: 0 }),\n );\n\n const redirectUrl = appendApplicationInitiatedActionResult(\n sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n\n logger.info('Application initiated action returned without authorization code', {\n hasKcAction: Boolean(kcAction || oauthState.kcAction),\n hasKcActionStatus: Boolean(kcActionStatus),\n });\n\n return createRedirectResponse(redirectUrl, { headers });\n }\n\n throw new Error('Missing required callback code parameter');\n }\n\n logger.info('Initiating token exchange');\n\n const oidcConfig = await getOAuthConfig();\n const authorizationResponseUrl = createAuthorizationResponseUrl(request.url, config.redirectUri);\n const tokenResult = await oidc.authorizationCodeGrant(oidcConfig, authorizationResponseUrl, {\n pkceCodeVerifier: oauthState.codeVerifier,\n expectedState: oauthState.state,\n expectedNonce: oauthState.nonce,\n idTokenExpected: config.scope.split(/\\s+/).includes('openid'),\n });\n\n logger.info('Token exchange completed successfully');\n\n const claims = getClaimsFromTokenResponse(tokenResult);\n\n // Create session data\n const newSessionData = await createSessionData(tokenResult, claims);\n\n // Create new session\n const sessionHeaders = await createAuthSession(request, newSessionData);\n\n // Clean up OAuth state from Redis\n await deleteOAuthState(stateId);\n\n // Combine headers and clear OAuth state cookie (app-specific)\n const headers = new Headers(sessionHeaders);\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(OAUTH_STATE_COOKIE, '', { maxAge: 0 }),\n );\n\n logger.info('OAuth callback processed successfully');\n\n // Redirect to return URL or default location\n const redirectUrl = appendApplicationInitiatedActionResult(\n sanitizeOAuthReturnUrl(oauthState.returnUrl, request) ?? '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n return createRedirectResponse(redirectUrl, { headers });\n } catch (error) {\n logger.error('OAuth callback failed', undefined, {\n errorType: error instanceof Error ? error.name : 'UnknownError',\n });\n\n // Try to clean up OAuth state on error\n try {\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n if (stateId) {\n await deleteOAuthState(stateId);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n await destroyAuthSession(request);\n throw new Error('Authentication failed');\n }\n}\n\n/**\n * Logout user.\n *\n * Default logout follows OIDC RP-Initiated Logout and ends the identity\n * provider session. Automatic/session-expiry cleanup can request\n * `/auth/logout?logout=local` to clear only this app's Redis session without\n * bouncing the browser through the identity provider or revoking SSO tokens.\n */\nexport async function logout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const url = new URL(request.url);\n \n const logoutScope = getLogoutScope(url);\n const returnUrl = sanitizeOAuthReturnUrl(url.searchParams.get('returnUrl'), request);\n \n logger.info('Logout initiated', { \n clientId: config.clientId,\n logoutScope,\n });\n \n const sessionData = await getAuthSession(request);\n const idToken = sessionData.idToken;\n\n if (logoutScope === 'local') {\n // Local logout is RP-only cleanup. Tokens are server-side and become\n // unreachable once the Redis session is destroyed; revoking the refresh\n // token can also terminate the Keycloak SSO session for this user.\n const headers = await destroyAuthSession(request);\n const redirectUrl = returnUrl || '/';\n const absoluteRedirectUrl = getAbsoluteRedirectUrl(request, redirectUrl);\n\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, '', { maxAge: 0 }),\n );\n logger.info('Local application logout completed', { hasReturnUrl: Boolean(returnUrl) });\n return createRedirectResponse(absoluteRedirectUrl, { headers });\n }\n\n const sessionHeaders = new Headers();\n\n if (logoutScope === 'all' && sessionData.userId) {\n await revokeAndDestroyAllUserSessionsBestEffort(sessionData.userId);\n sessionHeaders.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, '', { maxAge: 0 }),\n );\n const expiredHeaders = await destroyAuthSession(request);\n expiredHeaders.forEach((value, key) => sessionHeaders.append(key, value));\n } else {\n await revokeSessionTokensBestEffort(sessionData);\n const expiredHeaders = await destroyAuthSession(request);\n expiredHeaders.forEach((value, key) => sessionHeaders.append(key, value));\n }\n\n const headers = new Headers(sessionHeaders);\n\n // Store returnUrl in cookie if provided for the post-logout landing redirect.\n if (returnUrl) {\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, returnUrl, { maxAge: 300 }),\n );\n } else {\n headers.append(\n 'Set-Cookie',\n serializeTemporaryAuthCookie(LOGOUT_RETURN_URL_COOKIE, '', { maxAge: 0 }),\n );\n }\n\n try {\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const logoutParameters: Record<string, string> = {};\n const expectedIssuer = serverMetadata.issuer ?? config.authority;\n\n if (idToken && canUseIdTokenHint(idToken, expectedIssuer)) {\n logoutParameters.id_token_hint = idToken;\n } else if (idToken) {\n logger.warn('Skipping id_token_hint because the session token issuer does not match the active OIDC provider', {\n tokenIssuer: getJwtIssuer(idToken),\n expectedIssuer: normalizeIssuer(expectedIssuer),\n clientId: config.clientId,\n });\n }\n\n if (config.postLogoutRedirectUri) {\n logoutParameters.post_logout_redirect_uri = config.postLogoutRedirectUri;\n }\n\n const endSessionUrl = oidc.buildEndSessionUrl(oidcConfig, logoutParameters);\n logger.info('RP-Initiated Logout redirect created', {\n endSessionEndpoint: serverMetadata.end_session_endpoint,\n hasIdTokenHint: Boolean(logoutParameters.id_token_hint),\n });\n\n return createRedirectResponse(endSessionUrl.toString(), { headers });\n } catch (error) {\n logger.warn('OIDC end_session endpoint unavailable, falling back to local redirect', {\n error: getOAuthErrorMessage(error),\n });\n }\n\n // Fallback redirect\n const registeredPath = config.postLogoutRedirectUri || '/';\n const finalRedirectUrl = getAbsoluteRedirectUrl(request, registeredPath);\n\n logger.info('Logout completed', { hasConfiguredRedirect: Boolean(config.postLogoutRedirectUri) });\n return createRedirectResponse(finalRedirectUrl, { headers });\n } catch (error) {\n logger.error('Logout failed', error instanceof Error ? error : undefined);\n const headers = await destroyAuthSession(request);\n return createRedirectResponse('/', { headers });\n }\n}\n\n/**\n * Handle an OIDC Back-Channel Logout request from Keycloak or another OP.\n *\n * Configure the client Backchannel logout URL to this endpoint. The logout\n * token is signature-verified against the provider JWKS, issuer/audience are\n * checked, and all matching local Redis sessions are destroyed by `sid` first,\n * falling back to `sub` when the OP omits a session id.\n */\nexport async function handleBackChannelLogout(request: Request): Promise<Response> {\n if (request.method !== 'POST') {\n return Response.json({ error: 'method_not_allowed' }, { status: 405 });\n }\n\n try {\n const config = getAuthConfig();\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const logoutToken = await getLogoutTokenFromRequest(request);\n\n if (!logoutToken) {\n return Response.json({ error: 'missing_logout_token' }, { status: 400 });\n }\n\n if (!serverMetadata.jwks_uri) {\n throw new Error('OIDC provider does not expose jwks_uri');\n }\n\n const { payload } = await jwtVerify(logoutToken, getJwks(serverMetadata.jwks_uri), {\n issuer: serverMetadata.issuer ?? config.authority,\n audience: config.clientId,\n clockTolerance: 60,\n });\n\n const claims = validateBackChannelLogoutClaims(payload as Record<string, unknown>, config.clientId);\n const destroyedSessions = await destroyAuthSessionsByIdentitySession({\n sid: claims.sid,\n userId: claims.subject,\n });\n const result: BackChannelLogoutResult = {\n destroyedSessions,\n issuer: claims.issuer,\n subject: claims.subject,\n sid: claims.sid,\n };\n\n logger.info('Back-channel logout processed', { ...result });\n return Response.json({ success: true, ...result });\n } catch (error) {\n if (isInvalidBackChannelLogoutTokenError(error)) {\n logger.warn('Back-channel logout token rejected', {\n error: getOAuthErrorMessage(error),\n errorCode: error instanceof joseErrors.JOSEError ? error.code : undefined,\n });\n } else {\n logger.error('Back-channel logout failed', error instanceof Error ? error : undefined);\n }\n\n return Response.json(\n { error: 'invalid_logout_token', error_description: getOAuthErrorMessage(error) },\n { status: 400 }\n );\n }\n}\n\n/**\n * Handle OIDC Front-Channel Logout iframe/browser requests.\n *\n * Front-channel logout cannot carry a signed logout token, so this validates the\n * issuer and applies local cleanup only when a `sid` is present.\n */\nexport async function handleFrontChannelLogout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const url = new URL(request.url);\n const issuer = url.searchParams.get('iss');\n const sid = url.searchParams.get('sid');\n const expectedIssuer = normalizeIssuer(serverMetadata.issuer ?? config.authority);\n\n if (!issuer || normalizeIssuer(issuer) !== expectedIssuer) {\n return new Response(null, { status: 204 });\n }\n\n const destroyedSessions = sid ? await destroyAuthSessionsBySid(sid) : 0;\n const result: FrontChannelLogoutResult = {\n destroyedSessions,\n issuer,\n sid: sid ?? undefined,\n };\n\n logger.info('Front-channel logout processed', { ...result });\n return new Response(null, {\n status: 204,\n headers: {\n 'Cache-Control': 'no-store',\n },\n });\n } catch (error) {\n logger.warn('Front-channel logout ignored', {\n error: getOAuthErrorMessage(error),\n });\n\n return new Response(null, { status: 204 });\n }\n}\n\n/**\n * Get current user from session\n */\nexport async function getUser(request: Request): Promise<UserInfo | null> {\n try {\n const sessionData = await getAuthSession(request);\n return sessionData.user || null;\n } catch (error) {\n logger.error('Failed to get user', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\n/**\n * Require authenticated user (throws redirect to login if not authenticated)\n */\nexport async function requireAuth(request: Request): Promise<UserInfo> {\n const user = await getUser(request);\n\n if (!user) {\n const url = new URL(request.url);\n const returnUrl = `${url.pathname}${url.search}`;\n logger.info('No user found, starting OAuth flow', { hasReturnUrl: true });\n throw await login(request, returnUrl);\n }\n\n return user;\n}\n\n/**\n * Get access token from session\n */\nexport async function getAccessToken(request: Request): Promise<string | null> {\n try {\n const sessionData = await getAuthSession(request);\n return sessionData.accessToken || null;\n } catch (error) {\n logger.error('Failed to get access token', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\n/**\n * Refresh tokens\n */\nexport async function refreshTokens(\n request: Request,\n refreshToken?: string\n): Promise<TokenRefreshResult> {\n try {\n const sessionData = await getAuthSession(request);\n const tokenToRefresh = refreshToken || sessionData.refreshToken;\n\n if (!tokenToRefresh) {\n logger.warn('No refresh token available');\n return { success: false, error: 'No refresh token available', shouldLogout: true };\n }\n\n logger.info('Initiating token refresh');\n\n const oidcConfig = await getOAuthConfig();\n const refreshResult = await oidc.refreshTokenGrant(oidcConfig, tokenToRefresh);\n\n logger.info('Token refresh successful');\n\n const newRefreshToken = refreshResult.refresh_token || tokenToRefresh;\n const expiresAt = refreshResult.expires_in\n ? Date.now() + refreshResult.expires_in * 1000\n : sessionData.expiresAt;\n\n // Update session\n await updateAuthSession(request, {\n accessToken: refreshResult.access_token,\n refreshToken: newRefreshToken,\n idToken: refreshResult.id_token,\n expiresAt,\n lastActivity: Date.now(),\n });\n\n return {\n success: true,\n tokens: {\n access_token: refreshResult.access_token,\n refresh_token: newRefreshToken,\n id_token: refreshResult.id_token,\n expires_in: refreshResult.expires_in,\n },\n };\n } catch (error) {\n logger.error('Token refresh failed with exception', error instanceof Error ? error : undefined);\n\n return {\n success: false,\n error: getOAuthErrorMessage(error),\n shouldLogout: shouldLogoutAfterOAuthError(error),\n };\n }\n}\n\n// Re-export session validation\nexport { isSessionValid };\n\n/**\n * Clear auth server cache (useful for testing)\n */\nexport function clearAuthServerCache(): void {\n authServerCache = null;\n jwksCache.clear();\n}\n","/**\n * Route Protection Server Module\n * \n * Provides route protection utilities for different authentication levels.\n */\n\nimport { getUser, requireAuth, refreshTokens, login, logout } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { ProtectionLevel, UserInfo } from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// Refresh shortly before expiry. Keep this below common 5-minute access-token\n// lifetimes so a freshly issued token does not refresh again immediately.\nconst REFRESH_THRESHOLD = resolveRefreshThreshold();\n\nfunction resolveRefreshThreshold(): number {\n const configured = Number(process.env.OIDC_REFRESH_THRESHOLD_MS);\n return Number.isFinite(configured) && configured > 0 ? configured : 60_000;\n}\n\n/**\n * Check if token needs refresh\n */\nexport async function shouldRefreshToken(request: Request): Promise<boolean> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {\n if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {\n logger.info('Token expired and no refresh token, forcing local application logout');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'expired-no-refresh-token');\n throw await logout(logoutRequest);\n }\n return false;\n }\n\n if (!sessionData.expiresAt || !sessionData.accessToken) {\n return false;\n }\n\n const now = Date.now();\n\n // Check JWT expiry\n let jwtExpiry: number | null = null;\n try {\n const tokenParts = sessionData.accessToken.split('.');\n if (tokenParts.length === 3) {\n const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());\n if (payload.exp) {\n jwtExpiry = payload.exp * 1000;\n }\n }\n } catch {\n // Ignore JWT decode errors\n }\n\n const earliestExpiry = jwtExpiry && jwtExpiry < sessionData.expiresAt \n ? jwtExpiry \n : sessionData.expiresAt;\n const timeUntilEarliestExpiry = earliestExpiry - now;\n\n return timeUntilEarliestExpiry <= REFRESH_THRESHOLD;\n } catch (error) {\n logger.error('Error checking token refresh', error instanceof Error ? error : undefined);\n return false;\n }\n}\n\n/**\n * Auto-refresh tokens if needed\n */\nasync function autoRefreshTokens(request: Request): Promise<void> {\n try {\n const needsRefresh = await shouldRefreshToken(request);\n\n if (needsRefresh) {\n logger.info('Auto-refreshing tokens due to expiry');\n const result = await refreshTokens(request);\n\n if (result.success) {\n logger.info('Auto-refresh completed successfully');\n } else {\n logger.error('Auto-refresh failed', undefined, { error: result.error });\n\n if (result.shouldLogout) {\n logger.error('Refresh token invalid, forcing local application logout');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'refresh-failed');\n throw await logout(logoutRequest);\n }\n }\n }\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw error;\n }\n}\n\n/**\n * Identity context check configuration\n */\nexport interface DefaultRouteIdentityContext {\n hasAnyMembership: boolean;\n hasEntitlement?: boolean;\n /** @deprecated Use hasEntitlement or app-specific fields instead. */\n hasSubscription: boolean;\n isOnboarded: boolean;\n}\n\nexport interface IdentityContextChecker<TIdentityContext = DefaultRouteIdentityContext> {\n getIdentityContext: (\n request: Request,\n userId: string,\n forceRefresh?: boolean\n ) => Promise<DefaultRouteIdentityContext & TIdentityContext>;\n}\n\n/**\n * Tenant checker configuration\n */\nexport interface TenantChecker {\n getCurrentTenant: (request: Request) => Promise<string | null>;\n initializeTenant: (request: Request) => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\n/**\n * Legacy subscription checker configuration\n *\n * @deprecated Put entitlement policy in resolveRoute or app-level route loaders.\n */\nexport interface SubscriptionChecker {\n checkTenantSubscriptionStatus: (request: Request, tenantId: string) => Promise<boolean>;\n}\n\nexport interface RouteProtectionRedirect {\n location: string;\n headers?: HeadersInit;\n status?: number;\n}\n\nexport interface AutomaticLogoutRequestContext {\n request: Request;\n reason: 'expired-no-refresh-token' | 'refresh-failed';\n}\n\nexport interface LoginReturnUrlContext {\n request: Request;\n currentPath: string;\n protection: ProtectionLevel;\n}\n\nexport interface RouteProtectionResolverContext<TIdentityContext = DefaultRouteIdentityContext> {\n request: Request;\n user: UserInfo;\n currentPath: string;\n protection: ProtectionLevel;\n getIdentityContext: (forceRefresh?: boolean) => Promise<TIdentityContext | null>;\n getCurrentTenant: () => Promise<string | null>;\n initializeTenant: () => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\nexport interface RouteProtectionErrorHandlerContext<TIdentityContext = DefaultRouteIdentityContext>\n extends RouteProtectionResolverContext<TIdentityContext> {\n error: unknown;\n}\n\n/**\n * Route protection configuration\n */\nexport interface RouteProtectionConfig<TIdentityContext = DefaultRouteIdentityContext> {\n identityChecker?: IdentityContextChecker<TIdentityContext>;\n tenantChecker?: TenantChecker;\n subscriptionChecker?: SubscriptionChecker;\n resolveRoute?: (\n context: RouteProtectionResolverContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n handleRouteError?: (\n context: RouteProtectionErrorHandlerContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n getLoginReturnUrl?: (context: LoginReturnUrlContext) => string;\n createAutomaticLogoutRequest?: (\n context: AutomaticLogoutRequestContext\n ) => Promise<Request> | Request;\n}\n\nlet protectionConfig: RouteProtectionConfig<any> = {};\n\n/**\n * Configure route protection with identity, tenant, and app policy checkers\n */\nexport function configureRouteProtection<TIdentityContext = DefaultRouteIdentityContext>(\n config: RouteProtectionConfig<TIdentityContext>\n): void {\n protectionConfig = config;\n}\n\n/**\n * Reset route protection to its built-in defaults.\n */\nexport function resetRouteProtectionConfig(): void {\n protectionConfig = {};\n}\n\nfunction createRedirectFromConfig(redirect: RouteProtectionRedirect): Response {\n return createRedirectResponse(redirect.location, {\n headers: redirect.headers,\n status: redirect.status,\n });\n}\n\nfunction getLoginReturnUrl(\n request: Request,\n currentPath: string,\n protection: ProtectionLevel\n): string {\n if (protectionConfig.getLoginReturnUrl) {\n return protectionConfig.getLoginReturnUrl({\n request,\n currentPath,\n protection,\n });\n }\n\n switch (protection) {\n case 'onboarding-required':\n case 'subscription-required':\n case 'public':\n case 'auth':\n default: {\n const url = new URL(request.url);\n return url.pathname === '/' ? '/' : `${url.pathname}${url.search}`;\n }\n }\n}\n\nasync function createAutomaticLogoutRequest(\n request: Request,\n reason: AutomaticLogoutRequestContext['reason']\n): Promise<Request> {\n if (protectionConfig.createAutomaticLogoutRequest) {\n return protectionConfig.createAutomaticLogoutRequest({ request, reason });\n }\n\n const logoutUrl = new URL(request.url);\n logoutUrl.pathname = '/auth/logout';\n logoutUrl.searchParams.set('logout', 'local');\n\n return new Request(logoutUrl.toString(), {\n headers: request.headers,\n });\n}\n\nfunction createRouteResolverContext(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): RouteProtectionResolverContext<any> {\n let identityContextPromise: Promise<any | null> | null = null;\n let currentTenantPromise: Promise<string | null> | null = null;\n let initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null = null;\n\n return {\n request,\n user,\n currentPath,\n protection,\n getIdentityContext: async (forceRefresh = false) => {\n if (!protectionConfig.identityChecker) {\n return null;\n }\n\n if (forceRefresh) {\n return protectionConfig.identityChecker.getIdentityContext(request, user.sub, true);\n }\n\n if (!identityContextPromise) {\n identityContextPromise = protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n }\n\n return identityContextPromise;\n },\n getCurrentTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!currentTenantPromise) {\n currentTenantPromise = protectionConfig.tenantChecker.getCurrentTenant(request);\n }\n\n return currentTenantPromise;\n },\n initializeTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!initializeTenantPromise) {\n initializeTenantPromise = protectionConfig.tenantChecker.initializeTenant(request);\n }\n\n return initializeTenantPromise;\n },\n };\n}\n\nasync function applyConfiguredRouteResolution(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): Promise<boolean> {\n if (!protectionConfig.resolveRoute) {\n return false;\n }\n\n const context = createRouteResolverContext(request, user, currentPath, protection);\n\n try {\n const result = await protectionConfig.resolveRoute(context);\n if (result) {\n throw createRedirectFromConfig(result);\n }\n\n return true;\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n\n if (!protectionConfig.handleRouteError) {\n throw error;\n }\n\n const recovery = await protectionConfig.handleRouteError({\n ...context,\n error,\n });\n\n if (recovery) {\n throw createRedirectFromConfig(recovery);\n }\n\n throw error;\n }\n}\n\n/**\n * Main route protection function\n */\nexport async function protectRoute<T>(\n request: Request,\n protection: ProtectionLevel,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n): Promise<T> {\n const url = new URL(request.url);\n\n switch (protection) {\n case 'public':\n try {\n await autoRefreshTokens(request);\n const user = await getUser(request);\n return loaderFn(user ?? undefined);\n } catch {\n return loaderFn(undefined);\n }\n\n case 'auth':\n case 'policy':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n await applyConfiguredRouteResolution(request, user, url.pathname, protection);\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, protection));\n }\n\n case 'onboarding-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'onboarding-required'\n );\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'onboarding-required'));\n }\n\n case 'subscription-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'subscription-required'\n );\n\n // Initialize tenant if needed\n if (protectionConfig.tenantChecker) {\n const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);\n if (!currentTenant) {\n await protectionConfig.tenantChecker.initializeTenant(request);\n }\n }\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'subscription-required'));\n }\n\n default:\n throw new Error(`Unknown protection level: ${protection}`);\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Public route (optional auth)\n */\nexport const publicRoute = <T>(\n request: Request,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'public', loaderFn);\n\n/**\n * Auth-protected route\n */\nexport const authRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'auth', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Generic policy-protected route. Apps define policy in configureRouteProtection.\n */\nexport const policyRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'policy', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Alias for auth-protected routes.\n */\nexport const protectedRoute = authRoute;\n\n/**\n * Setup policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const onboardingRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'onboarding-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Entitlement policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const subscriptionRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'subscription-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n","/**\n * Token Refresh Server Module\n */\n\nimport { refreshTokens } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { TokenRefreshResult } from './types';\nimport { logger } from '../logging';\n\n/**\n * Attempt to refresh the access token\n */\nexport async function attemptTokenRefresh(request: Request): Promise<TokenRefreshResult> {\n try {\n logger.info('Attempting token refresh due to 401 response');\n\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken) {\n logger.warn('No refresh token available in session');\n return {\n success: false,\n error: 'No refresh token available',\n shouldLogout: true,\n };\n }\n\n const refreshResult = await refreshTokens(request, sessionData.refreshToken);\n\n if (!refreshResult.success) {\n logger.error('Token refresh failed', undefined, {\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n });\n\n return {\n success: false,\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n };\n }\n\n const tokens = refreshResult.tokens;\n if (!tokens || !tokens.access_token) {\n logger.error('Token refresh succeeded but no access token returned');\n return {\n success: false,\n error: 'No access token in refresh response',\n shouldLogout: true,\n };\n }\n\n logger.info('Token refresh successful');\n\n return {\n success: true,\n newAccessToken: tokens.access_token,\n };\n } catch (error) {\n logger.error('Token refresh attempt failed', error instanceof Error ? error : undefined);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n shouldLogout: true,\n };\n }\n}\n\n/**\n * Check if a response indicates token expiration\n */\nexport function isTokenExpiredResponse(response: Response): boolean {\n return response.status === 401;\n}\n\n/**\n * Check if token refresh should be attempted\n */\nexport function shouldAttemptTokenRefresh(response: Response, hasAuth: boolean): boolean {\n return hasAuth && isTokenExpiredResponse(response);\n}\n"],"mappings":"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"}
package/dist/server.mjs CHANGED
@@ -9,7 +9,7 @@ import { a as withRetry, c as handleApiError, i as isRetryableError, l as ErrorC
9
9
  import { a as getIdentitySignalRClient, i as createIdentitySignalRClient, n as SignalRClient, o as initializeSignalR, r as cleanupSignalR, t as IdentitySignalRClient } from "./signalr-C-TA8nEE.mjs";
10
10
  import { i as invalidationHelpers, n as createQueryClient, r as createQueryKeyFactory, t as cachePresets } from "./query-client-_xB300gc.mjs";
11
11
  import { S as updateAuthSession, _ as isSessionValid, a as createOAuthState, b as listCurrentUserAuthSessions, c as destroyAuthSessionById, d as destroyAuthSessionsBySid, f as destroyAuthSessionsForUser, g as getOAuthState, h as getCurrentAuthSessionId, i as createAuthSession, l as destroyAuthSessionsByIdentitySession, m as getAuthSessionById, n as cleanupExpiredOAuthStates, o as deleteOAuthState, p as getAuthSession, r as closeRedisConnection, s as destroyAuthSession, t as AUTH_ERROR_COOKIE_PREFIX, u as destroyAuthSessionsBySessionState, v as listAuthSessionDataForUser, x as requireAuthSession, y as listAuthSessionsForUser } from "./redis-session-storage.server-DGgQcJ9B.mjs";
12
- import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "./server-CqX2yCa4.mjs";
12
+ import { A as resetAuthClaimMapping, C as handleCallback, D as logout, E as login, O as refreshTokens, S as handleBackChannelLogout, T as hasAuthError, _ as createAuthorizationResponseUrl, a as configureRouteProtection, b as getAuthError, c as protectRoute, d as resetRouteProtectionConfig, f as shouldRefreshToken, g as configureAuthClaimMapping, h as clearAuthServerCache, i as authRoute, k as requireAuth, l as protectedRoute, m as clearAuthErrorHeaders, n as isTokenExpiredResponse, o as onboardingRoute, p as subscriptionRoute, r as shouldAttemptTokenRefresh, s as policyRoute, t as attemptTokenRefresh, u as publicRoute, v as extractUserInfo, w as handleFrontChannelLogout, x as getUser, y as getAccessToken } from "./server-DPCu40ud.mjs";
13
13
  import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "./tenant/server.mjs";
14
14
  import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "./identity/server.mjs";
15
15
  import { n as createFetchMiddleware, r as createAPIConfigFactory, t as createApiClient } from "./server-ByScGRXA.mjs";
@@ -4,7 +4,7 @@ This is the maintainer-facing application packet for the Codex for Open Source p
4
4
 
5
5
  ## Repository
6
6
 
7
- - GitHub: `https://github.com/eminuckan/spine`
7
+ - GitHub: `https://github.com/Unitfield/spine`
8
8
  - npm: `https://www.npmjs.com/package/@eminuckan/spine`
9
9
  - Package: `@eminuckan/spine`
10
10
  - License: MIT
package/docs/releasing.md CHANGED
@@ -29,14 +29,14 @@ Update the version in `package.json` and add a changelog entry.
29
29
  If the repository does not exist yet:
30
30
 
31
31
  ```bash
32
- gh repo create eminuckan/spine --public --source=. --remote=origin --push
32
+ gh repo create Unitfield/spine --public --source=. --remote=origin --push
33
33
  ```
34
34
 
35
35
  Useful follow-up commands:
36
36
 
37
37
  ```bash
38
- gh repo edit eminuckan/spine --description "Framework-agnostic SaaS primitives for auth, identity, multi-tenancy, permissions, and API access."
39
- gh repo edit eminuckan/spine --homepage "https://github.com/eminuckan/spine#readme"
38
+ gh repo edit Unitfield/spine --description "Framework-agnostic SaaS primitives for auth, identity, multi-tenancy, permissions, and API access."
39
+ gh repo edit Unitfield/spine --homepage "https://github.com/Unitfield/spine#readme"
40
40
  ```
41
41
 
42
42
  ## npm Publishing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eminuckan/spine",
3
- "version": "0.3.3",
3
+ "version": "0.3.6",
4
4
  "description": "Framework-agnostic SaaS primitives for auth, identity, multi-tenancy, permissions, and API access.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -163,13 +163,13 @@
163
163
  "typescript",
164
164
  "zustand"
165
165
  ],
166
- "homepage": "https://github.com/eminuckan/spine#readme",
166
+ "homepage": "https://github.com/Unitfield/spine#readme",
167
167
  "bugs": {
168
- "url": "https://github.com/eminuckan/spine/issues"
168
+ "url": "https://github.com/Unitfield/spine/issues"
169
169
  },
170
170
  "repository": {
171
171
  "type": "git",
172
- "url": "git+https://github.com/eminuckan/spine.git"
172
+ "url": "git+https://github.com/Unitfield/spine.git"
173
173
  },
174
174
  "author": "Muhammet Emin Uckan <eminuckan@gmail.com>",
175
175
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-CqX2yCa4.mjs","names":[],"sources":["../src/http/response.ts","../src/auth/authorization-parameters.ts","../src/auth/auth.server.ts","../src/auth/route-protection.server.ts","../src/auth/token-refresh.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","import type { LoginOptions } from './types';\n\nconst RESERVED_AUTHORIZATION_PARAMETER_NAMES = new Set([\n 'client_id',\n 'code_challenge',\n 'code_challenge_method',\n 'kc_action',\n 'nonce',\n 'prompt',\n 'redirect_uri',\n 'response_mode',\n 'response_type',\n 'scope',\n 'state',\n]);\n\nconst DEFAULT_EXTRA_AUTHORIZATION_PARAMETER_NAMES = new Set([\n 'acr_values',\n 'kc_idp_hint',\n 'login_hint',\n 'ui_locales',\n]);\n\nfunction isAllowedExtraAuthorizationParameter(\n key: string,\n allowedNames: Set<string>,\n allowedPrefixes: string[]\n): boolean {\n const normalized = key.toLowerCase();\n return allowedNames.has(normalized) || allowedPrefixes.some((prefix) => normalized.startsWith(prefix));\n}\n\nexport function sanitizeExtraAuthorizationParameters(\n params: LoginOptions['extraAuthParams'] | undefined,\n options: Pick<LoginOptions, 'extraAuthParamNames' | 'extraAuthParamPrefixes'> = {}\n): Record<string, string> {\n if (!params) {\n return {};\n }\n\n const allowedNames = new Set([\n ...DEFAULT_EXTRA_AUTHORIZATION_PARAMETER_NAMES,\n ...(options.extraAuthParamNames ?? []).map((name) => name.trim().toLowerCase()).filter(Boolean),\n ]);\n const allowedPrefixes = (options.extraAuthParamPrefixes ?? [])\n .map((prefix) => prefix.trim().toLowerCase())\n .filter(Boolean);\n const sanitized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(params)) {\n const normalizedKey = key.trim();\n if (\n normalizedKey.length === 0 ||\n RESERVED_AUTHORIZATION_PARAMETER_NAMES.has(normalizedKey.toLowerCase()) ||\n !isAllowedExtraAuthorizationParameter(normalizedKey, allowedNames, allowedPrefixes) ||\n value === null ||\n value === undefined\n ) {\n continue;\n }\n\n const normalizedValue = String(value).trim();\n if (normalizedValue.length === 0) {\n continue;\n }\n\n sanitized[normalizedKey] = normalizedValue;\n }\n\n return sanitized;\n}\n\nexport function normalizePublicAuthorizationStateContext(\n context: LoginOptions['publicStateContext']\n): Record<string, string> | null {\n if (!context) {\n return null;\n }\n\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(context)) {\n const normalizedKey = key.trim();\n if (\n normalizedKey.length === 0 ||\n RESERVED_AUTHORIZATION_PARAMETER_NAMES.has(normalizedKey.toLowerCase()) ||\n value === null ||\n value === undefined\n ) {\n continue;\n }\n\n const normalizedValue = String(value).trim();\n if (normalizedValue.length === 0) {\n continue;\n }\n\n normalized[normalizedKey] = normalizedValue;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : null;\n}\n\nexport function buildAuthorizationState(\n baseState: string,\n publicStateContext: LoginOptions['publicStateContext']\n): string {\n const normalizedContext = normalizePublicAuthorizationStateContext(publicStateContext);\n if (!normalizedContext) {\n return baseState;\n }\n\n return `${baseState}.${Buffer.from(JSON.stringify(normalizedContext), 'utf8').toString('base64url')}`;\n}\n","/**\n * OpenID Connect Authentication Server Module\n *\n * Handles:\n * - Login flow (PKCE)\n * - Token exchange\n * - Token refresh\n * - Logout\n * - Session management\n */\n\nimport * as oidc from 'openid-client';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\n\nimport {\n createAuthSession,\n getAuthSession,\n updateAuthSession,\n destroyAuthSession,\n isSessionValid,\n listAuthSessionDataForUser,\n destroyAuthSessionsByIdentitySession,\n destroyAuthSessionsBySid,\n destroyAuthSessionsForUser,\n createOAuthState,\n getOAuthState,\n deleteOAuthState,\n AUTH_ERROR_COOKIE_PREFIX,\n} from './redis-session-storage.server';\nimport type { \n AuthConfig, \n AuthClaimMapping,\n UserInfo, \n SessionData, \n OAuthState,\n TokenRefreshResult,\n LoginOptions,\n ApplicationType,\n OidcClientAuthMethod,\n BackChannelLogoutResult,\n FrontChannelLogoutResult,\n AuthError\n} from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\nimport {\n buildAuthorizationState,\n sanitizeExtraAuthorizationParameters,\n} from './authorization-parameters';\n\ntype OidcTokenResponse = oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\ntype LogoutScope = 'identity' | 'local' | 'all';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Get effective hasLandingPage setting based on config\n */\nfunction getEffectiveHasLandingPage(config: AuthConfig): boolean {\n // Explicit setting takes precedence\n if (config.hasLandingPage !== undefined) {\n return config.hasLandingPage;\n }\n \n // Determine by application type\n switch (config.applicationType) {\n case 'no-landing-page':\n case 'dashboard':\n return false; // Dashboard has no landing page\n case 'landing-page':\n case 'tenant-app':\n return true; // Tenant app has landing page\n default:\n return true; // Default to having a landing page\n }\n}\n\n/**\n * Create OAuth configuration from environment variables\n */\nfunction createAuthConfig(): AuthConfig {\n const requiredEnvVars = {\n authority: process.env.OIDC_AUTHORITY,\n clientId: process.env.OIDC_CLIENT_ID,\n redirectUri: process.env.OIDC_REDIRECT_URI,\n };\n\n for (const [key, value] of Object.entries(requiredEnvVars)) {\n if (!value) {\n throw new Error(`Missing required environment variable: OIDC_${key.toUpperCase()}`);\n }\n }\n\n // Parse application type from env\n const appTypeEnv = process.env.OIDC_APPLICATION_TYPE?.toLowerCase();\n let applicationType: ApplicationType = 'custom';\n if (appTypeEnv === 'no-landing-page' || appTypeEnv === 'no_landing_page') {\n applicationType = 'no-landing-page';\n } else if (appTypeEnv === 'landing-page' || appTypeEnv === 'landing_page') {\n applicationType = 'landing-page';\n } else if (appTypeEnv === 'dashboard') {\n applicationType = 'dashboard';\n } else if (appTypeEnv === 'tenant-app' || appTypeEnv === 'tenant_app') {\n applicationType = 'tenant-app';\n }\n\n const clientAuthMethod = parseOidcClientAuthMethod(\n process.env.OIDC_CLIENT_AUTH_METHOD,\n process.env.OIDC_CLIENT_SECRET ? 'client_secret_post' : 'none'\n );\n\n const hasLandingPage = process.env.OIDC_HAS_LANDING_PAGE !== undefined\n ? process.env.OIDC_HAS_LANDING_PAGE === 'true'\n : undefined;\n\n return {\n authority: requiredEnvVars.authority!,\n clientId: requiredEnvVars.clientId!,\n clientSecret: process.env.OIDC_CLIENT_SECRET,\n clientAuthMethod,\n redirectUri: requiredEnvVars.redirectUri!,\n scope: process.env.OIDC_SCOPE || 'openid profile email api',\n postLogoutRedirectUri: process.env.OIDC_POST_LOGOUT_REDIRECT_URI,\n applicationType,\n hasLandingPage,\n };\n}\n\nfunction parseOidcClientAuthMethod(\n value: string | undefined,\n fallback: OidcClientAuthMethod\n): OidcClientAuthMethod {\n const normalized = value?.trim().toLowerCase();\n\n switch (normalized) {\n case undefined:\n case '':\n return fallback;\n case 'none':\n return 'none';\n case 'client_secret_post':\n case 'post':\n return 'client_secret_post';\n case 'client_secret_basic':\n case 'basic':\n return 'client_secret_basic';\n default:\n throw new Error(`Unsupported OIDC_CLIENT_AUTH_METHOD: ${value}`);\n }\n}\n\nlet OIDC_CONFIG: AuthConfig | null = null;\n\nfunction getAuthConfig(): AuthConfig {\n if (!OIDC_CONFIG) {\n OIDC_CONFIG = createAuthConfig();\n }\n return OIDC_CONFIG;\n}\n\n// ============================================================================\n// OAuth Discovery Cache\n// ============================================================================\n\nlet authServerCache: {\n configuration: oidc.Configuration;\n cachedAt: number;\n} | null = null;\n\nconst CACHE_TTL = 60 * 60 * 1000; // 1 hour\nconst jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction normalizeIssuer(value: string | undefined): string | null {\n const normalized = value?.trim().replace(/\\/+$/, '');\n return normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction getJwtClaims(token: string | undefined): Record<string, unknown> | null {\n if (!token) {\n return null;\n }\n\n try {\n const [, payload] = token.split('.');\n if (!payload) {\n return null;\n }\n\n return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction getJwtIssuer(token: string | undefined): string | null {\n const issuer = getJwtClaims(token)?.iss;\n return typeof issuer === 'string' ? normalizeIssuer(issuer) : null;\n}\n\nfunction canUseIdTokenHint(idToken: string | undefined, expectedIssuer: string): boolean {\n if (!idToken) {\n return false;\n }\n\n const tokenIssuer = getJwtIssuer(idToken);\n return tokenIssuer !== null && tokenIssuer === normalizeIssuer(expectedIssuer);\n}\n\nfunction sanitizeLogoutReturnUrl(returnUrl: string | null, request: Request): string | null {\n if (!returnUrl) {\n return null;\n }\n\n try {\n const requestUrl = new URL(request.url);\n const decodedReturnUrl = decodeURIComponent(returnUrl);\n\n if (decodedReturnUrl.startsWith('/') && !decodedReturnUrl.startsWith('//')) {\n return isAuthLoopPath(decodedReturnUrl) ? null : decodedReturnUrl;\n }\n\n const candidateUrl = new URL(decodedReturnUrl);\n if (candidateUrl.origin !== requestUrl.origin) {\n return null;\n }\n\n const sameOriginPath = `${candidateUrl.pathname}${candidateUrl.search}${candidateUrl.hash}`;\n return isAuthLoopPath(sameOriginPath) ? null : sameOriginPath;\n } catch {\n return null;\n }\n}\n\nfunction appendApplicationInitiatedActionResult(\n returnUrl: string,\n action: string | null | undefined,\n status: string | null | undefined\n): string {\n if (!action && !status) {\n return returnUrl;\n }\n\n try {\n const isRelative = returnUrl.startsWith('/') && !returnUrl.startsWith('//');\n const url = new URL(returnUrl, isRelative ? 'http://spine.local' : undefined);\n\n if (action) {\n url.searchParams.set('kc_action', action);\n }\n\n if (status) {\n url.searchParams.set('kc_action_status', status);\n }\n\n return isRelative ? `${url.pathname}${url.search}${url.hash}` : url.toString();\n } catch {\n const params = new URLSearchParams();\n if (action) params.set('kc_action', action);\n if (status) params.set('kc_action_status', status);\n const separator = returnUrl.includes('?') ? '&' : '?';\n return `${returnUrl}${separator}${params.toString()}`;\n }\n}\n\nexport function createAuthorizationResponseUrl(requestUrl: string, redirectUri: string): URL {\n const incomingUrl = new URL(requestUrl);\n const authorizationResponseUrl = new URL(redirectUri);\n authorizationResponseUrl.search = incomingUrl.search;\n authorizationResponseUrl.hash = \"\";\n return authorizationResponseUrl;\n}\n\nfunction isAuthLoopPath(path: string): boolean {\n const normalized = path.split('#')[0].split('?')[0].replace(/\\/+$/, '') || '/';\n return normalized === '/auth/logout' || normalized === '/auth/callback';\n}\n\nfunction isLocalInsecureIssuer(authority: string): boolean {\n try {\n const issuer = new URL(authority);\n return (\n issuer.protocol === 'http:' &&\n ['localhost', '127.0.0.1', '::1', '[::1]'].includes(issuer.hostname)\n );\n } catch {\n return false;\n }\n}\n\nfunction shouldAllowInsecureOidcRequests(authority: string): boolean {\n if (process.env.NODE_ENV === 'production') {\n return false;\n }\n\n if (process.env.OIDC_ALLOW_INSECURE_REQUESTS === 'true') {\n return true;\n }\n\n return isLocalInsecureIssuer(authority);\n}\n\nfunction getDiscoveryOptions(): oidc.DiscoveryRequestOptions {\n const config = getAuthConfig();\n\n return shouldAllowInsecureOidcRequests(config.authority)\n ? { algorithm: 'oidc', execute: [oidc.allowInsecureRequests] }\n : { algorithm: 'oidc' };\n}\n\nfunction getClientMetadata(config: AuthConfig): string | undefined {\n return config.clientAuthMethod === 'none' ? undefined : config.clientSecret;\n}\n\nfunction getClientAuthentication(config: AuthConfig): oidc.ClientAuth {\n switch (config.clientAuthMethod) {\n case 'client_secret_basic':\n if (!config.clientSecret) {\n throw new Error('OIDC_CLIENT_SECRET is required for client_secret_basic authentication');\n }\n\n return oidc.ClientSecretBasic(config.clientSecret);\n case 'client_secret_post':\n if (!config.clientSecret) {\n throw new Error('OIDC_CLIENT_SECRET is required for client_secret_post authentication');\n }\n\n return oidc.ClientSecretPost(config.clientSecret);\n case 'none':\n default:\n return oidc.None();\n }\n}\n\nfunction getOAuthErrorMessage(error: unknown): string {\n if (error instanceof oidc.ResponseBodyError) {\n return error.error_description || error.error || error.message;\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'Unknown error';\n}\n\nfunction shouldLogoutAfterOAuthError(error: unknown): boolean {\n if (error instanceof oidc.ResponseBodyError) {\n return (\n error.status === 400 ||\n error.status === 401 ||\n error.error === 'invalid_grant' ||\n error.error === 'invalid_token'\n );\n }\n\n const message = error instanceof Error ? error.message.toLowerCase() : '';\n return message.includes('invalid_grant') || message.includes('invalid_token') || message.includes('unauthorized');\n}\n\nfunction getLogoutScope(url: URL): LogoutScope {\n const requestedScope =\n url.searchParams.get('logout') ??\n url.searchParams.get('logout_scope') ??\n url.searchParams.get('scope');\n const normalized = requestedScope?.trim().toLowerCase();\n\n if (\n normalized === 'local' ||\n normalized === 'application' ||\n normalized === 'app' ||\n normalized === 'current-device'\n ) {\n return 'local';\n }\n\n if (\n normalized === 'all' ||\n normalized === 'all-local' ||\n normalized === 'all-devices' ||\n normalized === 'everywhere'\n ) {\n return 'all';\n }\n\n if (url.searchParams.get('local_only') === 'true' || url.searchParams.get('client_id_only') === 'true') {\n return 'local';\n }\n\n return 'identity';\n}\n\nfunction getAbsoluteRedirectUrl(request: Request, redirectUrl: string): string {\n if (/^https?:\\/\\//i.test(redirectUrl)) {\n return redirectUrl;\n }\n\n return new URL(redirectUrl, new URL(request.url).origin).toString();\n}\n\nasync function revokeTokenBestEffort(\n configuration: oidc.Configuration,\n token: string | undefined,\n tokenTypeHint: 'access_token' | 'refresh_token'\n): Promise<void> {\n if (!token) {\n return;\n }\n\n try {\n await oidc.tokenRevocation(configuration, token, {\n token_type_hint: tokenTypeHint,\n });\n } catch (error) {\n logger.warn('OIDC token revocation failed', {\n tokenTypeHint,\n error: getOAuthErrorMessage(error),\n });\n }\n}\n\nasync function revokeSessionTokensBestEffort(sessionData: SessionData): Promise<void> {\n if (!sessionData.refreshToken && !sessionData.accessToken) {\n return;\n }\n\n try {\n const configuration = await getOAuthConfig();\n await revokeTokenBestEffort(configuration, sessionData.refreshToken, 'refresh_token');\n await revokeTokenBestEffort(configuration, sessionData.accessToken, 'access_token');\n } catch (error) {\n logger.warn('Skipping OIDC token revocation because provider configuration is unavailable', {\n error: getOAuthErrorMessage(error),\n });\n }\n}\n\nasync function revokeAndDestroyAllUserSessionsBestEffort(userId: string): Promise<number> {\n const sessions = await listAuthSessionDataForUser(userId);\n\n try {\n const configuration = await getOAuthConfig();\n for (const { data } of sessions) {\n await revokeTokenBestEffort(configuration, data.refreshToken, 'refresh_token');\n await revokeTokenBestEffort(configuration, data.accessToken, 'access_token');\n }\n } catch (error) {\n logger.warn('Skipping all-session token revocation because provider configuration is unavailable', {\n error: getOAuthErrorMessage(error),\n });\n }\n\n return await destroyAuthSessionsForUser(userId);\n}\n\nfunction getClaimsFromTokenResponse(tokenResult: OidcTokenResponse): Record<string, unknown> {\n const claims = tokenResult.claims();\n\n if (claims) {\n return claims as Record<string, unknown>;\n }\n\n return getJwtClaims(tokenResult.id_token) ?? {};\n}\n\n/**\n * Get OIDC configuration with caching.\n */\nasync function getOAuthConfig(): Promise<oidc.Configuration> {\n const config = getAuthConfig();\n const now = Date.now();\n\n if (authServerCache && now - authServerCache.cachedAt < CACHE_TTL) {\n return authServerCache.configuration;\n }\n\n try {\n const configuration = await oidc.discovery(\n new URL(config.authority),\n config.clientId,\n getClientMetadata(config),\n getClientAuthentication(config),\n getDiscoveryOptions()\n );\n\n authServerCache = {\n configuration,\n cachedAt: now,\n };\n\n logger.info('OIDC discovery completed successfully', {\n issuer: configuration.serverMetadata().issuer,\n clientId: config.clientId,\n clientAuthMethod: config.clientAuthMethod,\n });\n return configuration;\n } catch (error) {\n logger.error('OIDC discovery failed', error instanceof Error ? error : undefined, {\n authority: config.authority,\n clientId: config.clientId,\n });\n throw new Error(`OIDC discovery failed: ${getOAuthErrorMessage(error)}`);\n }\n}\n\nfunction getJwks(jwksUri: string): ReturnType<typeof createRemoteJWKSet> {\n const cachedJwks = jwksCache.get(jwksUri);\n if (cachedJwks) {\n return cachedJwks;\n }\n\n const jwks = createRemoteJWKSet(new URL(jwksUri));\n jwksCache.set(jwksUri, jwks);\n return jwks;\n}\n\nasync function getLogoutTokenFromRequest(request: Request): Promise<string | null> {\n const contentType = request.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json')) {\n const body = await request.json().catch(() => null) as { logout_token?: unknown } | null;\n return typeof body?.logout_token === 'string' ? body.logout_token : null;\n }\n\n const formData = await request.formData().catch(() => null);\n const logoutToken = formData?.get('logout_token');\n return typeof logoutToken === 'string' ? logoutToken : null;\n}\n\nfunction validateBackChannelLogoutClaims(payload: Record<string, unknown>, clientId: string): {\n issuer?: string;\n subject?: string;\n sid?: string;\n} {\n const events = payload.events;\n const hasBackChannelEvent =\n events &&\n typeof events === 'object' &&\n !Array.isArray(events) &&\n 'http://schemas.openid.net/event/backchannel-logout' in events;\n\n if (!hasBackChannelEvent) {\n throw new Error('Invalid back-channel logout token: missing back-channel logout event');\n }\n\n if ('nonce' in payload) {\n throw new Error('Invalid back-channel logout token: nonce is not allowed');\n }\n\n const audience = payload.aud;\n const hasClientAudience = Array.isArray(audience)\n ? audience.includes(clientId)\n : audience === clientId;\n\n if (!hasClientAudience) {\n throw new Error('Invalid back-channel logout token: audience mismatch');\n }\n\n const subject = typeof payload.sub === 'string' ? payload.sub : undefined;\n const sid = typeof payload.sid === 'string' ? payload.sid : undefined;\n\n if (!subject && !sid) {\n throw new Error('Invalid back-channel logout token: subject or sid is required');\n }\n\n return {\n issuer: typeof payload.iss === 'string' ? payload.iss : undefined,\n subject,\n sid,\n };\n}\n\n// ============================================================================\n// Claim Extraction\n// ============================================================================\n\nconst DEFAULT_AUTH_CLAIM_MAPPING: Required<AuthClaimMapping> = {\n subject: ['sub'],\n name: ['name', 'preferred_username', 'email'],\n email: ['email'],\n givenName: ['given_name', 'givenName'],\n familyName: ['family_name', 'familyName'],\n picture: ['picture', 'avatar_url'],\n locale: ['locale'],\n zoneinfo: ['zoneinfo'],\n updatedAt: ['updated_at'],\n tenantIds: ['tenant_ids'],\n tenantRoles: ['tenant_roles'],\n permissions: ['app_perms', 'permissions', 'scope'],\n isOnboarded: ['is_onboarded'],\n};\n\nlet authClaimMapping: Required<AuthClaimMapping> = {\n ...DEFAULT_AUTH_CLAIM_MAPPING,\n};\n\nfunction normalizeClaimKeys(keys?: string[]): string[] {\n if (!Array.isArray(keys)) {\n return [];\n }\n\n return Array.from(\n new Set(\n keys\n .map((key) => key.trim())\n .filter((key) => key.length > 0)\n )\n );\n}\n\n/**\n * Configure claim mapping so different providers/backends can project their\n * own claim names onto the shared auth/session primitives.\n */\nexport function configureAuthClaimMapping(mapping: AuthClaimMapping): void {\n authClaimMapping = {\n subject: mapping.subject !== undefined ? normalizeClaimKeys(mapping.subject) : DEFAULT_AUTH_CLAIM_MAPPING.subject,\n name: mapping.name !== undefined ? normalizeClaimKeys(mapping.name) : DEFAULT_AUTH_CLAIM_MAPPING.name,\n email: mapping.email !== undefined ? normalizeClaimKeys(mapping.email) : DEFAULT_AUTH_CLAIM_MAPPING.email,\n givenName: mapping.givenName !== undefined ? normalizeClaimKeys(mapping.givenName) : DEFAULT_AUTH_CLAIM_MAPPING.givenName,\n familyName: mapping.familyName !== undefined ? normalizeClaimKeys(mapping.familyName) : DEFAULT_AUTH_CLAIM_MAPPING.familyName,\n picture: mapping.picture !== undefined ? normalizeClaimKeys(mapping.picture) : DEFAULT_AUTH_CLAIM_MAPPING.picture,\n locale: mapping.locale !== undefined ? normalizeClaimKeys(mapping.locale) : DEFAULT_AUTH_CLAIM_MAPPING.locale,\n zoneinfo: mapping.zoneinfo !== undefined ? normalizeClaimKeys(mapping.zoneinfo) : DEFAULT_AUTH_CLAIM_MAPPING.zoneinfo,\n updatedAt: mapping.updatedAt !== undefined ? normalizeClaimKeys(mapping.updatedAt) : DEFAULT_AUTH_CLAIM_MAPPING.updatedAt,\n tenantIds: mapping.tenantIds !== undefined ? normalizeClaimKeys(mapping.tenantIds) : DEFAULT_AUTH_CLAIM_MAPPING.tenantIds,\n tenantRoles: mapping.tenantRoles !== undefined ? normalizeClaimKeys(mapping.tenantRoles) : DEFAULT_AUTH_CLAIM_MAPPING.tenantRoles,\n permissions: mapping.permissions !== undefined ? normalizeClaimKeys(mapping.permissions) : DEFAULT_AUTH_CLAIM_MAPPING.permissions,\n isOnboarded: mapping.isOnboarded !== undefined ? normalizeClaimKeys(mapping.isOnboarded) : DEFAULT_AUTH_CLAIM_MAPPING.isOnboarded,\n };\n}\n\n/**\n * Reset claim mapping to the built-in defaults.\n */\nexport function resetAuthClaimMapping(): void {\n authClaimMapping = {\n ...DEFAULT_AUTH_CLAIM_MAPPING,\n };\n}\n\nfunction getConfiguredAuthClaimMapping(): Required<AuthClaimMapping> {\n return authClaimMapping;\n}\n\nfunction getFirstClaimValue(\n claims: Record<string, unknown>,\n claimKeys: string[]\n): { key?: string; value?: unknown } {\n for (const claimKey of claimKeys) {\n if (claimKey in claims) {\n return {\n key: claimKey,\n value: claims[claimKey],\n };\n }\n }\n\n return {};\n}\n\nfunction getStringClaim(claims: Record<string, unknown>, claimKeys: string[]): string | undefined {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return `${value}`;\n }\n\n return undefined;\n}\n\nfunction getNumberClaim(claims: Record<string, unknown>, claimKeys: string[]): number | undefined {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === 'string') {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n return undefined;\n}\n\nfunction getBooleanClaim(\n claims: Record<string, unknown>,\n claimKeys: string[],\n defaultValue = false\n): boolean {\n const { value } = getFirstClaimValue(claims, claimKeys);\n\n if (typeof value === 'boolean') {\n return value;\n }\n\n if (typeof value === 'string') {\n return value.toLowerCase() === 'true';\n }\n\n return defaultValue;\n}\n\n/**\n * Parse JSON safely\n */\nfunction parseJson(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\n/**\n * Convert value to string array\n */\nfunction toStringArray(value: unknown): string[] {\n if (!value) return [];\n\n if (Array.isArray(value)) {\n return value\n .map((item) => (typeof item === 'string' ? item.trim() : `${item}`.trim()))\n .filter((item) => item.length > 0);\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (!trimmed) return [];\n\n if (trimmed.startsWith('[')) {\n const parsed = parseJson(trimmed);\n if (Array.isArray(parsed)) {\n return parsed\n .map((item) => (typeof item === 'string' ? item.trim() : `${item}`.trim()))\n .filter((item) => item.length > 0);\n }\n }\n\n if (trimmed.includes(',')) {\n return trimmed\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item.length > 0);\n }\n\n return [trimmed];\n }\n\n return [];\n}\n\nfunction toScopeArray(value: unknown): string[] {\n if (typeof value === 'string') {\n return value\n .trim()\n .split(/\\s+/)\n .filter((item) => item.length > 0);\n }\n\n return toStringArray(value);\n}\n\n/**\n * Extract user info from ID token claims\n */\nexport function extractUserInfo(claims: Record<string, unknown>): {\n tenants: string[];\n currentTenant?: string;\n isOnboarded: boolean;\n tenantRoles: Record<string, string[]>;\n permissions: string[];\n} {\n const claimMapping = getConfiguredAuthClaimMapping();\n\n // Extract tenant IDs\n let tenants: string[] = [];\n const { value: rawTenantIds } = getFirstClaimValue(claims, claimMapping.tenantIds);\n if (rawTenantIds !== undefined) {\n if (typeof rawTenantIds === 'string' && rawTenantIds.trim().startsWith('[')) {\n const parsed = parseJson(rawTenantIds.trim());\n if (Array.isArray(parsed)) {\n tenants = parsed\n .map((id) => (typeof id === 'string' ? id.trim() : `${id}`.trim()))\n .filter((id) => id.length > 0);\n }\n } else {\n tenants = toStringArray(rawTenantIds);\n }\n }\n\n // Extract tenant roles\n const tenantRoles: Record<string, string[]> = {};\n const { value: rawRoles } = getFirstClaimValue(claims, claimMapping.tenantRoles);\n if (rawRoles !== undefined) {\n let parsedRoles: unknown = rawRoles;\n\n if (typeof rawRoles === 'string' && rawRoles.trim().startsWith('{')) {\n parsedRoles = parseJson(rawRoles.trim()) ?? rawRoles;\n }\n\n if (parsedRoles && typeof parsedRoles === 'object' && !Array.isArray(parsedRoles)) {\n for (const [tenantId, roleValue] of Object.entries(parsedRoles as Record<string, unknown>)) {\n const roleIds = toStringArray(roleValue);\n if (roleIds.length > 0) {\n tenantRoles[tenantId] = roleIds;\n }\n }\n }\n }\n\n // Extract permissions\n const permissionClaim = getFirstClaimValue(claims, claimMapping.permissions);\n const permissions =\n permissionClaim.key === 'scope'\n ? toScopeArray(permissionClaim.value)\n : toStringArray(permissionClaim.value);\n\n // Extract onboarding status\n const isOnboarded = getBooleanClaim(claims, claimMapping.isOnboarded);\n\n return { tenants, isOnboarded, tenantRoles, permissions };\n}\n\n// ============================================================================\n// Session Data Creation\n// ============================================================================\n\n/**\n * Create session data from token result\n */\nasync function createSessionData(\n tokenResult: OidcTokenResponse,\n claims: Record<string, unknown>\n): Promise<Partial<SessionData>> {\n const claimMapping = getConfiguredAuthClaimMapping();\n const baseUser: UserInfo = {\n sub: getStringClaim(claims, claimMapping.subject) || 'unknown',\n name: getStringClaim(claims, claimMapping.name) || getStringClaim(claims, claimMapping.email) || 'User',\n email: getStringClaim(claims, claimMapping.email),\n givenName: getStringClaim(claims, claimMapping.givenName),\n familyName: getStringClaim(claims, claimMapping.familyName),\n picture: getStringClaim(claims, claimMapping.picture),\n locale: getStringClaim(claims, claimMapping.locale),\n zoneinfo: getStringClaim(claims, claimMapping.zoneinfo),\n updated_at: getNumberClaim(claims, claimMapping.updatedAt),\n };\n\n const expiresAt = tokenResult.expires_in\n ? Date.now() + tokenResult.expires_in * 1000\n : undefined;\n const now = Date.now();\n const sessionState = (tokenResult as Record<string, unknown>).session_state;\n\n return {\n userId: baseUser.sub,\n accessToken: tokenResult.access_token,\n refreshToken: tokenResult.refresh_token,\n idToken: tokenResult.id_token,\n expiresAt,\n issuer: getStringClaim(claims, ['iss']),\n sid: getStringClaim(claims, ['sid']),\n sessionState: typeof sessionState === 'string' ? sessionState : undefined,\n clientId: getAuthConfig().clientId,\n user: baseUser,\n createdAt: now,\n lastActivity: now,\n };\n}\n\n// ============================================================================\n// Auth Error Handling\n// ============================================================================\n\n// Auth error cookie names - unique per application to prevent conflicts\nconst AUTH_ERROR_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error`;\nconst AUTH_ERROR_DESC_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error_desc`;\nconst OAUTH_STATE_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_oauth_state_id`;\n\n/**\n * Check if there's an auth error in cookies\n * \n * This should be called by apps (especially those without landing pages like Dashboard)\n * BEFORE starting OAuth flow to prevent redirect loops.\n * \n * @example\n * ```ts\n * // In dashboard's index route loader\n * const authError = getAuthError(request);\n * if (authError) {\n * return { authError }; // Show error to user\n * }\n * // No error, proceed with session check\n * const user = await getUser(request);\n * if (!user) {\n * return login(request);\n * }\n * ```\n */\nexport function getAuthError(request: Request): AuthError | null {\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n \n // Parse auth_error cookie (app-specific)\n const errorRegex = new RegExp(`${AUTH_ERROR_COOKIE}=([^;]+)`);\n const errorMatch = cookies.match(errorRegex);\n if (!errorMatch) return null;\n \n const error = decodeURIComponent(errorMatch[1]);\n if (!error) return null;\n \n // Parse auth_error_description cookie (app-specific)\n const descRegex = new RegExp(`${AUTH_ERROR_DESC_COOKIE}=([^;]+)`);\n const descMatch = cookies.match(descRegex);\n const description = descMatch ? decodeURIComponent(descMatch[1]) : undefined;\n \n return { error, description };\n}\n\n/**\n * Clear auth error cookies\n * \n * Call this after displaying the error to the user\n */\nexport function clearAuthErrorHeaders(): Headers {\n const headers = new Headers();\n headers.append('Set-Cookie', `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);\n headers.append('Set-Cookie', `${AUTH_ERROR_DESC_COOKIE}=; Path=/; Max-Age=0`);\n return headers;\n}\n\n/**\n * Check if request has auth error (quick check without parsing)\n */\nexport function hasAuthError(request: Request): boolean {\n const cookies = request.headers.get('Cookie');\n return cookies?.includes(`${AUTH_ERROR_COOKIE}=`) ?? false;\n}\n\n// ============================================================================\n// Auth Functions\n// ============================================================================\n\n/**\n * Initiate OAuth login flow\n * \n * @param request - The incoming request\n * @param returnUrlOrOptions - Either a return URL string or LoginOptions object\n */\nexport async function login(\n request: Request, \n returnUrlOrOptions?: string | LoginOptions\n): Promise<Response> {\n try {\n const config = getAuthConfig();\n \n // Parse options\n const options: LoginOptions = typeof returnUrlOrOptions === 'string' \n ? { returnUrl: returnUrlOrOptions }\n : returnUrlOrOptions || {};\n\n // Check for auth_error cookie to prevent redirect loops\n const cookies = request.headers.get('Cookie');\n const hasError = cookies?.includes(`${AUTH_ERROR_COOKIE}=`);\n if (hasError) {\n logger.warn('Auth error cookie detected, preventing redirect loop');\n // Clear the error cookie and redirect to home\n const headers = new Headers();\n headers.append('Set-Cookie', `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n\n const oidcConfig = await getOAuthConfig();\n\n // Generate PKCE challenge\n const codeVerifier = oidc.randomPKCECodeVerifier();\n const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);\n\n const extraAuthParams = sanitizeExtraAuthorizationParameters(options.extraAuthParams, options);\n\n // Generate secure state and nonce. The state may carry a non-secret UI\n // hint for Keycloak themes, but callback validation still compares the\n // exact value stored server-side.\n const state = buildAuthorizationState(oidc.randomState(), options.publicStateContext);\n const nonce = oidc.randomNonce();\n\n // Create OAuth state object\n const oauthState: OAuthState = {\n state,\n codeVerifier,\n nonce,\n returnUrl: options.returnUrl,\n kcAction: options.kcAction,\n createdAt: Date.now(),\n };\n\n logger.info('Creating OAuth state', {\n returnUrl: options.returnUrl,\n prompt: options.prompt,\n kcAction: options.kcAction,\n extraAuthParamKeys: Object.keys(extraAuthParams),\n });\n\n // Store OAuth state in Redis\n const stateId = await createOAuthState(oauthState);\n\n const authorizationParameters: Record<string, string> = {\n redirect_uri: config.redirectUri,\n scope: config.scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...extraAuthParams,\n };\n\n if (options.prompt) {\n authorizationParameters.prompt = options.prompt;\n }\n\n if (options.kcAction) {\n authorizationParameters.kc_action = options.kcAction;\n }\n\n const authorizationUrl = oidc.buildAuthorizationUrl(oidcConfig, authorizationParameters);\n\n // Store stateId in cookie for callback (app-specific to prevent conflicts)\n const headers = new Headers();\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=${stateId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=600`);\n\n logger.info('OIDC authorization flow initiated', {\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scope: config.scope,\n authorizationEndpoint: oidcConfig.serverMetadata().authorization_endpoint,\n stateId,\n prompt: options.prompt,\n kcAction: options.kcAction,\n });\n\n return createRedirectResponse(authorizationUrl.toString(), { headers });\n } catch (error) {\n logger.error('Failed to initiate OAuth login', error instanceof Error ? error : undefined);\n throw new Error('Login failed');\n }\n}\n\n/**\n * Handle OAuth callback\n * \n * Handles OAuth authorization response including success and error cases.\n * \n * Error Handling:\n * - `access_denied`: User doesn't have access to this application\n * - For apps with landing page: Redirect home with error cookie\n * - For apps without landing page: Redirect home with error cookie (must handle specially)\n * - `login_required`: No active identity session\n * - Redirect to home to start fresh login\n */\nexport async function handleCallback(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const hasLandingPage = getEffectiveHasLandingPage(config);\n const url = new URL(request.url);\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n const kcAction = url.searchParams.get('kc_action');\n const kcActionStatus = url.searchParams.get('kc_action_status');\n\n logger.info('OIDC callback received', {\n hasCode: !!code,\n hasState: !!state,\n error,\n errorDescription,\n kcAction,\n kcActionStatus,\n clientId: config.clientId,\n redirectUri: config.redirectUri\n });\n\n // Handle OAuth errors\n if (error) {\n if (kcActionStatus && state) {\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n\n if (stateId) {\n const oauthState = await getOAuthState(stateId);\n const stateAge = oauthState ? Date.now() - oauthState.createdAt : Number.POSITIVE_INFINITY;\n\n if (oauthState && oauthState.state === state && stateAge <= 10 * 60 * 1000) {\n await deleteOAuthState(stateId);\n const headers = new Headers();\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);\n\n const redirectUrl = appendApplicationInitiatedActionResult(\n oauthState.returnUrl || '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n\n logger.info('Application initiated action returned with OAuth error', {\n error,\n errorDescription,\n kcAction: kcAction || oauthState.kcAction,\n kcActionStatus,\n });\n\n return createRedirectResponse(redirectUrl, { headers });\n }\n }\n }\n\n logger.error('OAuth callback error', undefined, { \n error, \n errorDescription,\n clientId: config.clientId,\n applicationType: config.applicationType,\n hasLandingPage\n });\n \n // Handle access_denied specially - user doesn't have access to this app\n // This can happen when identity cookie is valid but user has no access to this client\n if (error === 'access_denied') {\n logger.warn('Access denied for this application', { clientId: config.clientId });\n // Clear any existing session and redirect to home without triggering new login\n const headers = await destroyAuthSession(request);\n // Set error cookies to:\n // 1. Prevent redirect loop (auth_error)\n // 2. Allow UI to show appropriate message (auth_error_type, auth_error_description)\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_COOKIE}=access_denied; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription || 'You do not have access to this application.')}; Path=/; SameSite=Lax; Max-Age=300`\n );\n // Use redirectUri to get the correct base URL with port\n // request.url.origin can lose the port in some cases\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n logger.info('Redirecting to /auth/login to show access_denied error', { \n hasLandingPage,\n baseUrl,\n configRedirectUri: config.redirectUri\n });\n // Redirect to /auth/login so user immediately sees the access denied page\n // instead of landing page (which doesn't show the error)\n return createRedirectResponse(`${baseUrl}/auth/login`, { headers });\n }\n \n // Handle login_required - session expired or user not logged in\n if (error === 'login_required') {\n logger.info('Login required, user session may have expired');\n const headers = await destroyAuthSession(request);\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n \n // Handle other errors\n const headers = await destroyAuthSession(request);\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_COOKIE}=${encodeURIComponent(error)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n if (errorDescription) {\n headers.append(\n 'Set-Cookie',\n `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription)}; Path=/; SameSite=Lax; Max-Age=300`\n );\n }\n // Use redirectUri to get correct base URL with port\n const redirectUri = new URL(config.redirectUri);\n const baseUrl = redirectUri.origin;\n return createRedirectResponse(baseUrl, { headers });\n }\n\n // Get stateId from cookie (app-specific)\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n\n if (!stateId) {\n throw new Error('OAuth state ID not found in cookie');\n }\n\n // Retrieve OAuth state from Redis\n const oauthState = await getOAuthState(stateId);\n\n if (!oauthState) {\n throw new Error('No OAuth state found - state may have expired');\n }\n\n if (!state) {\n throw new Error('Missing required callback state parameter');\n }\n\n if (oauthState.state !== state) {\n throw new Error(`OAuth state mismatch`);\n }\n\n // Check state age (10 minutes max)\n const stateAge = Date.now() - oauthState.createdAt;\n if (stateAge > 10 * 60 * 1000) {\n throw new Error('OAuth state expired');\n }\n\n if (!code) {\n if (kcActionStatus) {\n await deleteOAuthState(stateId);\n const headers = new Headers();\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);\n\n const redirectUrl = appendApplicationInitiatedActionResult(\n oauthState.returnUrl || '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n\n logger.info('Application initiated action returned without authorization code', {\n kcAction: kcAction || oauthState.kcAction,\n kcActionStatus,\n });\n\n return createRedirectResponse(redirectUrl, { headers });\n }\n\n throw new Error('Missing required callback code parameter');\n }\n\n logger.info('Initiating token exchange');\n\n const oidcConfig = await getOAuthConfig();\n const authorizationResponseUrl = createAuthorizationResponseUrl(request.url, config.redirectUri);\n const tokenResult = await oidc.authorizationCodeGrant(oidcConfig, authorizationResponseUrl, {\n pkceCodeVerifier: oauthState.codeVerifier,\n expectedState: oauthState.state,\n expectedNonce: oauthState.nonce,\n idTokenExpected: config.scope.split(/\\s+/).includes('openid'),\n });\n\n logger.info('Token exchange completed successfully');\n\n const claims = getClaimsFromTokenResponse(tokenResult);\n\n // Create session data\n const newSessionData = await createSessionData(tokenResult, claims);\n\n // Create new session\n const sessionHeaders = await createAuthSession(request, newSessionData);\n\n // Clean up OAuth state from Redis\n await deleteOAuthState(stateId);\n\n // Combine headers and clear OAuth state cookie (app-specific)\n const headers = new Headers(sessionHeaders);\n headers.append('Set-Cookie', `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);\n\n logger.info('OAuth callback processed successfully');\n\n // Redirect to return URL or default location\n const redirectUrl = appendApplicationInitiatedActionResult(\n oauthState.returnUrl || '/',\n kcAction || oauthState.kcAction,\n kcActionStatus\n );\n return createRedirectResponse(redirectUrl, { headers });\n } catch (error) {\n logger.error('OAuth callback failed', error instanceof Error ? error : undefined);\n\n // Try to clean up OAuth state on error\n try {\n const cookies = request.headers.get('Cookie');\n const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);\n const cookieMatch = cookies?.match(oauthStateRegex);\n const stateId = cookieMatch?.[1];\n if (stateId) {\n await deleteOAuthState(stateId);\n }\n } catch {\n // Ignore cleanup errors\n }\n\n await destroyAuthSession(request);\n throw new Error('Authentication failed');\n }\n}\n\n/**\n * Logout user.\n *\n * Default logout follows OIDC RP-Initiated Logout and ends the identity\n * provider session. Automatic/session-expiry cleanup can request\n * `/auth/logout?logout=local` to clear only this app's Redis session without\n * bouncing the browser through the identity provider or revoking SSO tokens.\n */\nexport async function logout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const url = new URL(request.url);\n \n const logoutScope = getLogoutScope(url);\n const returnUrl = sanitizeLogoutReturnUrl(url.searchParams.get('returnUrl'), request);\n \n logger.info('Logout initiated', { \n clientId: config.clientId,\n logoutScope,\n });\n \n const sessionData = await getAuthSession(request);\n const idToken = sessionData.idToken;\n\n if (logoutScope === 'local') {\n // Local logout is RP-only cleanup. Tokens are server-side and become\n // unreachable once the Redis session is destroyed; revoking the refresh\n // token can also terminate the Keycloak SSO session for this user.\n const headers = await destroyAuthSession(request);\n const redirectUrl = returnUrl || '/';\n const absoluteRedirectUrl = getAbsoluteRedirectUrl(request, redirectUrl);\n\n headers.append('Set-Cookie', 'logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');\n logger.info('Local application logout completed', { redirectUrl: absoluteRedirectUrl });\n return createRedirectResponse(absoluteRedirectUrl, { headers });\n }\n\n const sessionHeaders = new Headers();\n\n if (logoutScope === 'all' && sessionData.userId) {\n await revokeAndDestroyAllUserSessionsBestEffort(sessionData.userId);\n sessionHeaders.append('Set-Cookie', 'logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');\n const expiredHeaders = await destroyAuthSession(request);\n expiredHeaders.forEach((value, key) => sessionHeaders.append(key, value));\n } else {\n await revokeSessionTokensBestEffort(sessionData);\n const expiredHeaders = await destroyAuthSession(request);\n expiredHeaders.forEach((value, key) => sessionHeaders.append(key, value));\n }\n\n const headers = new Headers(sessionHeaders);\n\n // Store returnUrl in cookie if provided for the post-logout landing redirect.\n if (returnUrl) {\n headers.append(\n 'Set-Cookie',\n `logout_return_url=${encodeURIComponent(returnUrl)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`\n );\n } else {\n headers.append('Set-Cookie', 'logout_return_url=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');\n }\n\n try {\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const logoutParameters: Record<string, string> = {};\n const expectedIssuer = serverMetadata.issuer ?? config.authority;\n\n if (idToken && canUseIdTokenHint(idToken, expectedIssuer)) {\n logoutParameters.id_token_hint = idToken;\n } else if (idToken) {\n logger.warn('Skipping id_token_hint because the session token issuer does not match the active OIDC provider', {\n tokenIssuer: getJwtIssuer(idToken),\n expectedIssuer: normalizeIssuer(expectedIssuer),\n clientId: config.clientId,\n });\n }\n\n if (config.postLogoutRedirectUri) {\n logoutParameters.post_logout_redirect_uri = config.postLogoutRedirectUri;\n }\n\n const endSessionUrl = oidc.buildEndSessionUrl(oidcConfig, logoutParameters);\n logger.info('RP-Initiated Logout redirect created', {\n endSessionEndpoint: serverMetadata.end_session_endpoint,\n hasIdTokenHint: Boolean(logoutParameters.id_token_hint),\n });\n\n return createRedirectResponse(endSessionUrl.toString(), { headers });\n } catch (error) {\n logger.warn('OIDC end_session endpoint unavailable, falling back to local redirect', {\n error: getOAuthErrorMessage(error),\n });\n }\n\n // Fallback redirect\n const registeredPath = config.postLogoutRedirectUri || '/';\n const finalRedirectUrl = getAbsoluteRedirectUrl(request, registeredPath);\n\n logger.info('Logout completed', { redirectUrl: finalRedirectUrl });\n return createRedirectResponse(finalRedirectUrl, { headers });\n } catch (error) {\n logger.error('Logout failed', error instanceof Error ? error : undefined);\n const headers = await destroyAuthSession(request);\n return createRedirectResponse('/', { headers });\n }\n}\n\n/**\n * Handle an OIDC Back-Channel Logout request from Keycloak or another OP.\n *\n * Configure the client Backchannel logout URL to this endpoint. The logout\n * token is signature-verified against the provider JWKS, issuer/audience are\n * checked, and all matching local Redis sessions are destroyed by `sid` first,\n * falling back to `sub` when the OP omits a session id.\n */\nexport async function handleBackChannelLogout(request: Request): Promise<Response> {\n if (request.method !== 'POST') {\n return Response.json({ error: 'method_not_allowed' }, { status: 405 });\n }\n\n try {\n const config = getAuthConfig();\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const logoutToken = await getLogoutTokenFromRequest(request);\n\n if (!logoutToken) {\n return Response.json({ error: 'missing_logout_token' }, { status: 400 });\n }\n\n if (!serverMetadata.jwks_uri) {\n throw new Error('OIDC provider does not expose jwks_uri');\n }\n\n const { payload } = await jwtVerify(logoutToken, getJwks(serverMetadata.jwks_uri), {\n issuer: serverMetadata.issuer ?? config.authority,\n audience: config.clientId,\n clockTolerance: 60,\n });\n\n const claims = validateBackChannelLogoutClaims(payload as Record<string, unknown>, config.clientId);\n const destroyedSessions = await destroyAuthSessionsByIdentitySession({\n sid: claims.sid,\n userId: claims.subject,\n });\n const result: BackChannelLogoutResult = {\n destroyedSessions,\n issuer: claims.issuer,\n subject: claims.subject,\n sid: claims.sid,\n };\n\n logger.info('Back-channel logout processed', { ...result });\n return Response.json({ success: true, ...result });\n } catch (error) {\n logger.error('Back-channel logout failed', error instanceof Error ? error : undefined);\n return Response.json(\n { error: 'invalid_logout_token', error_description: getOAuthErrorMessage(error) },\n { status: 400 }\n );\n }\n}\n\n/**\n * Handle OIDC Front-Channel Logout iframe/browser requests.\n *\n * Front-channel logout cannot carry a signed logout token, so this validates the\n * issuer and applies local cleanup only when a `sid` is present.\n */\nexport async function handleFrontChannelLogout(request: Request): Promise<Response> {\n try {\n const config = getAuthConfig();\n const oidcConfig = await getOAuthConfig();\n const serverMetadata = oidcConfig.serverMetadata();\n const url = new URL(request.url);\n const issuer = url.searchParams.get('iss');\n const sid = url.searchParams.get('sid');\n const expectedIssuer = normalizeIssuer(serverMetadata.issuer ?? config.authority);\n\n if (!issuer || normalizeIssuer(issuer) !== expectedIssuer) {\n return new Response(null, { status: 204 });\n }\n\n const destroyedSessions = sid ? await destroyAuthSessionsBySid(sid) : 0;\n const result: FrontChannelLogoutResult = {\n destroyedSessions,\n issuer,\n sid: sid ?? undefined,\n };\n\n logger.info('Front-channel logout processed', { ...result });\n return new Response(null, {\n status: 204,\n headers: {\n 'Cache-Control': 'no-store',\n },\n });\n } catch (error) {\n logger.warn('Front-channel logout ignored', {\n error: getOAuthErrorMessage(error),\n });\n\n return new Response(null, { status: 204 });\n }\n}\n\n/**\n * Get current user from session\n */\nexport async function getUser(request: Request): Promise<UserInfo | null> {\n try {\n const sessionData = await getAuthSession(request);\n return sessionData.user || null;\n } catch (error) {\n logger.error('Failed to get user', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\n/**\n * Require authenticated user (throws redirect to login if not authenticated)\n */\nexport async function requireAuth(request: Request): Promise<UserInfo> {\n const user = await getUser(request);\n\n if (!user) {\n const url = new URL(request.url);\n const returnUrl = `${url.pathname}${url.search}`;\n logger.info('No user found, starting OAuth flow', { returnUrl });\n throw await login(request, returnUrl);\n }\n\n return user;\n}\n\n/**\n * Get access token from session\n */\nexport async function getAccessToken(request: Request): Promise<string | null> {\n try {\n const sessionData = await getAuthSession(request);\n return sessionData.accessToken || null;\n } catch (error) {\n logger.error('Failed to get access token', error instanceof Error ? error : undefined);\n return null;\n }\n}\n\n/**\n * Refresh tokens\n */\nexport async function refreshTokens(\n request: Request,\n refreshToken?: string\n): Promise<TokenRefreshResult> {\n try {\n const sessionData = await getAuthSession(request);\n const tokenToRefresh = refreshToken || sessionData.refreshToken;\n\n if (!tokenToRefresh) {\n logger.warn('No refresh token available');\n return { success: false, error: 'No refresh token available', shouldLogout: true };\n }\n\n logger.info('Initiating token refresh');\n\n const oidcConfig = await getOAuthConfig();\n const refreshResult = await oidc.refreshTokenGrant(oidcConfig, tokenToRefresh);\n\n logger.info('Token refresh successful');\n\n const newRefreshToken = refreshResult.refresh_token || tokenToRefresh;\n const expiresAt = refreshResult.expires_in\n ? Date.now() + refreshResult.expires_in * 1000\n : sessionData.expiresAt;\n\n // Update session\n await updateAuthSession(request, {\n accessToken: refreshResult.access_token,\n refreshToken: newRefreshToken,\n idToken: refreshResult.id_token,\n expiresAt,\n lastActivity: Date.now(),\n });\n\n return {\n success: true,\n tokens: {\n access_token: refreshResult.access_token,\n refresh_token: newRefreshToken,\n id_token: refreshResult.id_token,\n expires_in: refreshResult.expires_in,\n },\n };\n } catch (error) {\n logger.error('Token refresh failed with exception', error instanceof Error ? error : undefined);\n\n return {\n success: false,\n error: getOAuthErrorMessage(error),\n shouldLogout: shouldLogoutAfterOAuthError(error),\n };\n }\n}\n\n// Re-export session validation\nexport { isSessionValid };\n\n/**\n * Clear auth server cache (useful for testing)\n */\nexport function clearAuthServerCache(): void {\n authServerCache = null;\n jwksCache.clear();\n}\n","/**\n * Route Protection Server Module\n * \n * Provides route protection utilities for different authentication levels.\n */\n\nimport { getUser, requireAuth, refreshTokens, login, logout } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { ProtectionLevel, UserInfo } from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// Refresh shortly before expiry. Keep this below common 5-minute access-token\n// lifetimes so a freshly issued token does not refresh again immediately.\nconst REFRESH_THRESHOLD = resolveRefreshThreshold();\n\nfunction resolveRefreshThreshold(): number {\n const configured = Number(process.env.OIDC_REFRESH_THRESHOLD_MS);\n return Number.isFinite(configured) && configured > 0 ? configured : 60_000;\n}\n\n/**\n * Check if token needs refresh\n */\nexport async function shouldRefreshToken(request: Request): Promise<boolean> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {\n if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {\n logger.info('Token expired and no refresh token, forcing local application logout');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'expired-no-refresh-token');\n throw await logout(logoutRequest);\n }\n return false;\n }\n\n if (!sessionData.expiresAt || !sessionData.accessToken) {\n return false;\n }\n\n const now = Date.now();\n\n // Check JWT expiry\n let jwtExpiry: number | null = null;\n try {\n const tokenParts = sessionData.accessToken.split('.');\n if (tokenParts.length === 3) {\n const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());\n if (payload.exp) {\n jwtExpiry = payload.exp * 1000;\n }\n }\n } catch {\n // Ignore JWT decode errors\n }\n\n const earliestExpiry = jwtExpiry && jwtExpiry < sessionData.expiresAt \n ? jwtExpiry \n : sessionData.expiresAt;\n const timeUntilEarliestExpiry = earliestExpiry - now;\n\n return timeUntilEarliestExpiry <= REFRESH_THRESHOLD;\n } catch (error) {\n logger.error('Error checking token refresh', error instanceof Error ? error : undefined);\n return false;\n }\n}\n\n/**\n * Auto-refresh tokens if needed\n */\nasync function autoRefreshTokens(request: Request): Promise<void> {\n try {\n const needsRefresh = await shouldRefreshToken(request);\n\n if (needsRefresh) {\n logger.info('Auto-refreshing tokens due to expiry');\n const result = await refreshTokens(request);\n\n if (result.success) {\n logger.info('Auto-refresh completed successfully');\n } else {\n logger.error('Auto-refresh failed', undefined, { error: result.error });\n\n if (result.shouldLogout) {\n logger.error('Refresh token invalid, forcing local application logout');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'refresh-failed');\n throw await logout(logoutRequest);\n }\n }\n }\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw error;\n }\n}\n\n/**\n * Identity context check configuration\n */\nexport interface DefaultRouteIdentityContext {\n hasAnyMembership: boolean;\n hasEntitlement?: boolean;\n /** @deprecated Use hasEntitlement or app-specific fields instead. */\n hasSubscription: boolean;\n isOnboarded: boolean;\n}\n\nexport interface IdentityContextChecker<TIdentityContext = DefaultRouteIdentityContext> {\n getIdentityContext: (\n request: Request,\n userId: string,\n forceRefresh?: boolean\n ) => Promise<DefaultRouteIdentityContext & TIdentityContext>;\n}\n\n/**\n * Tenant checker configuration\n */\nexport interface TenantChecker {\n getCurrentTenant: (request: Request) => Promise<string | null>;\n initializeTenant: (request: Request) => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\n/**\n * Legacy subscription checker configuration\n *\n * @deprecated Put entitlement policy in resolveRoute or app-level route loaders.\n */\nexport interface SubscriptionChecker {\n checkTenantSubscriptionStatus: (request: Request, tenantId: string) => Promise<boolean>;\n}\n\nexport interface RouteProtectionRedirect {\n location: string;\n headers?: HeadersInit;\n status?: number;\n}\n\nexport interface AutomaticLogoutRequestContext {\n request: Request;\n reason: 'expired-no-refresh-token' | 'refresh-failed';\n}\n\nexport interface LoginReturnUrlContext {\n request: Request;\n currentPath: string;\n protection: ProtectionLevel;\n}\n\nexport interface RouteProtectionResolverContext<TIdentityContext = DefaultRouteIdentityContext> {\n request: Request;\n user: UserInfo;\n currentPath: string;\n protection: ProtectionLevel;\n getIdentityContext: (forceRefresh?: boolean) => Promise<TIdentityContext | null>;\n getCurrentTenant: () => Promise<string | null>;\n initializeTenant: () => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\nexport interface RouteProtectionErrorHandlerContext<TIdentityContext = DefaultRouteIdentityContext>\n extends RouteProtectionResolverContext<TIdentityContext> {\n error: unknown;\n}\n\n/**\n * Route protection configuration\n */\nexport interface RouteProtectionConfig<TIdentityContext = DefaultRouteIdentityContext> {\n identityChecker?: IdentityContextChecker<TIdentityContext>;\n tenantChecker?: TenantChecker;\n subscriptionChecker?: SubscriptionChecker;\n resolveRoute?: (\n context: RouteProtectionResolverContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n handleRouteError?: (\n context: RouteProtectionErrorHandlerContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n getLoginReturnUrl?: (context: LoginReturnUrlContext) => string;\n createAutomaticLogoutRequest?: (\n context: AutomaticLogoutRequestContext\n ) => Promise<Request> | Request;\n}\n\nlet protectionConfig: RouteProtectionConfig<any> = {};\n\n/**\n * Configure route protection with identity, tenant, and app policy checkers\n */\nexport function configureRouteProtection<TIdentityContext = DefaultRouteIdentityContext>(\n config: RouteProtectionConfig<TIdentityContext>\n): void {\n protectionConfig = config;\n}\n\n/**\n * Reset route protection to its built-in defaults.\n */\nexport function resetRouteProtectionConfig(): void {\n protectionConfig = {};\n}\n\nfunction createRedirectFromConfig(redirect: RouteProtectionRedirect): Response {\n return createRedirectResponse(redirect.location, {\n headers: redirect.headers,\n status: redirect.status,\n });\n}\n\nfunction getLoginReturnUrl(\n request: Request,\n currentPath: string,\n protection: ProtectionLevel\n): string {\n if (protectionConfig.getLoginReturnUrl) {\n return protectionConfig.getLoginReturnUrl({\n request,\n currentPath,\n protection,\n });\n }\n\n switch (protection) {\n case 'onboarding-required':\n case 'subscription-required':\n case 'public':\n case 'auth':\n default: {\n const url = new URL(request.url);\n return url.pathname === '/' ? '/' : `${url.pathname}${url.search}`;\n }\n }\n}\n\nasync function createAutomaticLogoutRequest(\n request: Request,\n reason: AutomaticLogoutRequestContext['reason']\n): Promise<Request> {\n if (protectionConfig.createAutomaticLogoutRequest) {\n return protectionConfig.createAutomaticLogoutRequest({ request, reason });\n }\n\n const logoutUrl = new URL(request.url);\n logoutUrl.pathname = '/auth/logout';\n logoutUrl.searchParams.set('logout', 'local');\n\n return new Request(logoutUrl.toString(), {\n headers: request.headers,\n });\n}\n\nfunction createRouteResolverContext(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): RouteProtectionResolverContext<any> {\n let identityContextPromise: Promise<any | null> | null = null;\n let currentTenantPromise: Promise<string | null> | null = null;\n let initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null = null;\n\n return {\n request,\n user,\n currentPath,\n protection,\n getIdentityContext: async (forceRefresh = false) => {\n if (!protectionConfig.identityChecker) {\n return null;\n }\n\n if (forceRefresh) {\n return protectionConfig.identityChecker.getIdentityContext(request, user.sub, true);\n }\n\n if (!identityContextPromise) {\n identityContextPromise = protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n }\n\n return identityContextPromise;\n },\n getCurrentTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!currentTenantPromise) {\n currentTenantPromise = protectionConfig.tenantChecker.getCurrentTenant(request);\n }\n\n return currentTenantPromise;\n },\n initializeTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!initializeTenantPromise) {\n initializeTenantPromise = protectionConfig.tenantChecker.initializeTenant(request);\n }\n\n return initializeTenantPromise;\n },\n };\n}\n\nasync function applyConfiguredRouteResolution(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): Promise<boolean> {\n if (!protectionConfig.resolveRoute) {\n return false;\n }\n\n const context = createRouteResolverContext(request, user, currentPath, protection);\n\n try {\n const result = await protectionConfig.resolveRoute(context);\n if (result) {\n throw createRedirectFromConfig(result);\n }\n\n return true;\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n\n if (!protectionConfig.handleRouteError) {\n throw error;\n }\n\n const recovery = await protectionConfig.handleRouteError({\n ...context,\n error,\n });\n\n if (recovery) {\n throw createRedirectFromConfig(recovery);\n }\n\n throw error;\n }\n}\n\n/**\n * Main route protection function\n */\nexport async function protectRoute<T>(\n request: Request,\n protection: ProtectionLevel,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n): Promise<T> {\n const url = new URL(request.url);\n\n switch (protection) {\n case 'public':\n try {\n await autoRefreshTokens(request);\n const user = await getUser(request);\n return loaderFn(user ?? undefined);\n } catch {\n return loaderFn(undefined);\n }\n\n case 'auth':\n case 'policy':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n await applyConfiguredRouteResolution(request, user, url.pathname, protection);\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, protection));\n }\n\n case 'onboarding-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'onboarding-required'\n );\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'onboarding-required'));\n }\n\n case 'subscription-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'subscription-required'\n );\n\n // Initialize tenant if needed\n if (protectionConfig.tenantChecker) {\n const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);\n if (!currentTenant) {\n await protectionConfig.tenantChecker.initializeTenant(request);\n }\n }\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'subscription-required'));\n }\n\n default:\n throw new Error(`Unknown protection level: ${protection}`);\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Public route (optional auth)\n */\nexport const publicRoute = <T>(\n request: Request,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'public', loaderFn);\n\n/**\n * Auth-protected route\n */\nexport const authRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'auth', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Generic policy-protected route. Apps define policy in configureRouteProtection.\n */\nexport const policyRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'policy', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Alias for auth-protected routes.\n */\nexport const protectedRoute = authRoute;\n\n/**\n * Setup policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const onboardingRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'onboarding-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Entitlement policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const subscriptionRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'subscription-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n","/**\n * Token Refresh Server Module\n */\n\nimport { refreshTokens } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { TokenRefreshResult } from './types';\nimport { logger } from '../logging';\n\n/**\n * Attempt to refresh the access token\n */\nexport async function attemptTokenRefresh(request: Request): Promise<TokenRefreshResult> {\n try {\n logger.info('Attempting token refresh due to 401 response');\n\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken) {\n logger.warn('No refresh token available in session');\n return {\n success: false,\n error: 'No refresh token available',\n shouldLogout: true,\n };\n }\n\n const refreshResult = await refreshTokens(request, sessionData.refreshToken);\n\n if (!refreshResult.success) {\n logger.error('Token refresh failed', undefined, {\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n });\n\n return {\n success: false,\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n };\n }\n\n const tokens = refreshResult.tokens;\n if (!tokens || !tokens.access_token) {\n logger.error('Token refresh succeeded but no access token returned');\n return {\n success: false,\n error: 'No access token in refresh response',\n shouldLogout: true,\n };\n }\n\n logger.info('Token refresh successful');\n\n return {\n success: true,\n newAccessToken: tokens.access_token,\n };\n } catch (error) {\n logger.error('Token refresh attempt failed', error instanceof Error ? error : undefined);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n shouldLogout: true,\n };\n }\n}\n\n/**\n * Check if a response indicates token expiration\n */\nexport function isTokenExpiredResponse(response: Response): boolean {\n return response.status === 401;\n}\n\n/**\n * Check if token refresh should be attempted\n */\nexport function shouldAttemptTokenRefresh(response: Response, hasAuth: boolean): boolean {\n return hasAuth && isTokenExpiredResponse(response);\n}\n"],"mappings":"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"}