@eminuckan/spine 0.2.1 → 0.2.4
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 +29 -0
- package/README.md +45 -6
- package/dist/access/evaluator.d.ts +3 -0
- package/dist/access/evaluator.d.ts.map +1 -0
- package/dist/access/index.d.ts +3 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/index.js +4 -0
- package/dist/access/types.d.ts +30 -0
- package/dist/access/types.d.ts.map +1 -0
- package/dist/access-DlAqsVGQ.js +59 -0
- package/dist/access-DlAqsVGQ.js.map +1 -0
- package/dist/api-client/index.js +2 -2
- package/dist/api-client/server.js +3 -3
- package/dist/{api-client-DsrxK05Z.js → api-client-CZapXIF8.js} +1 -1
- package/dist/{api-client-DsrxK05Z.js.map → api-client-CZapXIF8.js.map} +1 -1
- package/dist/auth/auth.server.d.ts +21 -16
- package/dist/auth/auth.server.d.ts.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/redis-session-storage.server.d.ts +18 -1
- package/dist/auth/redis-session-storage.server.d.ts.map +1 -1
- package/dist/auth/route-protection.server.d.ts.map +1 -1
- package/dist/auth/server.d.ts +1 -1
- package/dist/auth/server.d.ts.map +1 -1
- package/dist/auth/server.js +4 -4
- package/dist/auth/types.d.ts +65 -20
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth.server-B3ODGIX4.js +1057 -0
- package/dist/auth.server-B3ODGIX4.js.map +1 -0
- package/dist/auth.server-wdKsjQsn.js +5 -0
- package/dist/identity/index.js +1 -1
- package/dist/identity/server.js +2 -2
- package/dist/{identity-CFa1txoj.js → identity-yT2ijZF0.js} +1 -1
- package/dist/{identity-CFa1txoj.js.map → identity-yT2ijZF0.js.map} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/permission-service-CvGpwI0d.js +95 -0
- package/dist/permission-service-CvGpwI0d.js.map +1 -0
- package/dist/permissions/index.js +2 -1
- package/dist/{permissions-D5ScGGnd.js → permissions-OUK8Eql2.js} +3 -95
- package/dist/permissions-OUK8Eql2.js.map +1 -0
- package/dist/query-client/index.js +1 -1
- package/dist/{query-client-Bpe08pk9.js → query-client-Bh3TyB8W.js} +2 -2
- package/dist/query-client-Bh3TyB8W.js.map +1 -0
- package/dist/react-router/index.js +11 -9
- package/dist/react-router/server.js +18 -16
- package/dist/redis-session-storage.server-D0-72FV-.js +500 -0
- package/dist/redis-session-storage.server-D0-72FV-.js.map +1 -0
- package/dist/{server-BHfKHKF_.js → server-B7JN86_0.js} +13 -9
- package/dist/server-B7JN86_0.js.map +1 -0
- package/dist/{server-C3L8J7iE.js → server-BmiuV28c.js} +2 -2
- package/dist/{server-C3L8J7iE.js.map → server-BmiuV28c.js.map} +1 -1
- package/dist/{server-BxvRxjCK.js → server-Ce9jQRdL.js} +2 -2
- package/dist/{server-BxvRxjCK.js.map → server-Ce9jQRdL.js.map} +1 -1
- package/dist/{server-DZmcd2Lr.js → server-DeBlxoaW.js} +3 -3
- package/dist/{server-DZmcd2Lr.js.map → server-DeBlxoaW.js.map} +1 -1
- package/dist/{server-CouYoZiw.js → server-hGrElJMG.js} +2 -2
- package/dist/{server-CouYoZiw.js.map → server-hGrElJMG.js.map} +1 -1
- package/dist/server.js +18 -16
- package/dist/signalr/index.js +1 -1
- package/dist/{signalr-SJ-1sIxw.js → signalr-CSfCz1Ik.js} +1 -1
- package/dist/{signalr-SJ-1sIxw.js.map → signalr-CSfCz1Ik.js.map} +1 -1
- package/dist/tenant/index.js +1 -1
- package/dist/tenant/server.js +2 -2
- package/dist/{tenant-C6KPaSCX.js → tenant-BErqEENp.js} +1 -1
- package/dist/{tenant-C6KPaSCX.js.map → tenant-BErqEENp.js.map} +1 -1
- package/dist/{types-BnKaWdfe.js → types-BaaE6wjY.js} +1 -1
- package/dist/{types-BnKaWdfe.js.map → types-BaaE6wjY.js.map} +1 -1
- package/package.json +7 -6
- package/dist/auth.server-BPiCtlJE.js +0 -5
- package/dist/auth.server-Cx2h2qu3.js +0 -724
- package/dist/auth.server-Cx2h2qu3.js.map +0 -1
- package/dist/permissions-D5ScGGnd.js.map +0 -1
- package/dist/query-client-Bpe08pk9.js.map +0 -1
- package/dist/redis-session-storage.server-BR_0y-QO.js +0 -250
- package/dist/redis-session-storage.server-BR_0y-QO.js.map +0 -1
- package/dist/server-BHfKHKF_.js.map +0 -1
- package/dist/utils.d.ts +0 -6
- package/dist/utils.d.ts.map +0 -1
- /package/dist/{src-CJ2RLi94.js → src-B9rDUgSb.js} +0 -0
|
@@ -1,724 +0,0 @@
|
|
|
1
|
-
import { logger } from "./logging-_h2mi3L9.js";
|
|
2
|
-
import { AUTH_ERROR_COOKIE_PREFIX, createAuthSession, createOAuthState, deleteOAuthState, destroyAuthSession, getAuthSession, getOAuthState, updateAuthSession } from "./redis-session-storage.server-BR_0y-QO.js";
|
|
3
|
-
import * as oauth from "oauth4webapi";
|
|
4
|
-
|
|
5
|
-
//#region src/http/response.ts
|
|
6
|
-
/**
|
|
7
|
-
* Create a redirect response without relying on a router/framework helper.
|
|
8
|
-
*/
|
|
9
|
-
function createRedirectResponse(location, init = {}) {
|
|
10
|
-
const headers = new Headers(init.headers);
|
|
11
|
-
headers.set("Location", location);
|
|
12
|
-
return new Response(null, {
|
|
13
|
-
status: init.status ?? 302,
|
|
14
|
-
headers
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/auth/auth.server.ts
|
|
20
|
-
/**
|
|
21
|
-
* Get effective SSO logout setting based on config
|
|
22
|
-
*/
|
|
23
|
-
function getEffectiveSsoLogout(config) {
|
|
24
|
-
if (config.ssoLogout !== void 0) return config.ssoLogout;
|
|
25
|
-
switch (config.applicationType) {
|
|
26
|
-
case "no-landing-page":
|
|
27
|
-
case "dashboard": return true;
|
|
28
|
-
case "landing-page":
|
|
29
|
-
case "tenant-app": return false;
|
|
30
|
-
default: return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Get effective hasLandingPage setting based on config
|
|
35
|
-
*/
|
|
36
|
-
function getEffectiveHasLandingPage(config) {
|
|
37
|
-
if (config.hasLandingPage !== void 0) return config.hasLandingPage;
|
|
38
|
-
switch (config.applicationType) {
|
|
39
|
-
case "no-landing-page":
|
|
40
|
-
case "dashboard": return false;
|
|
41
|
-
case "landing-page":
|
|
42
|
-
case "tenant-app": return true;
|
|
43
|
-
default: return true;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Create OAuth configuration from environment variables
|
|
48
|
-
*/
|
|
49
|
-
function createAuthConfig() {
|
|
50
|
-
const requiredEnvVars = {
|
|
51
|
-
authority: process.env.OIDC_AUTHORITY,
|
|
52
|
-
clientId: process.env.OIDC_CLIENT_ID,
|
|
53
|
-
redirectUri: process.env.OIDC_REDIRECT_URI
|
|
54
|
-
};
|
|
55
|
-
for (const [key, value] of Object.entries(requiredEnvVars)) if (!value) throw new Error(`Missing required environment variable: OIDC_${key.toUpperCase()}`);
|
|
56
|
-
const appTypeEnv = process.env.OIDC_APPLICATION_TYPE?.toLowerCase();
|
|
57
|
-
let applicationType = "custom";
|
|
58
|
-
if (appTypeEnv === "no-landing-page" || appTypeEnv === "no_landing_page") applicationType = "no-landing-page";
|
|
59
|
-
else if (appTypeEnv === "landing-page" || appTypeEnv === "landing_page") applicationType = "landing-page";
|
|
60
|
-
else if (appTypeEnv === "dashboard") applicationType = "dashboard";
|
|
61
|
-
else if (appTypeEnv === "tenant-app" || appTypeEnv === "tenant_app") applicationType = "tenant-app";
|
|
62
|
-
const ssoLogout = process.env.OIDC_SSO_LOGOUT !== void 0 ? process.env.OIDC_SSO_LOGOUT === "true" : void 0;
|
|
63
|
-
const hasLandingPage = process.env.OIDC_HAS_LANDING_PAGE !== void 0 ? process.env.OIDC_HAS_LANDING_PAGE === "true" : void 0;
|
|
64
|
-
return {
|
|
65
|
-
authority: requiredEnvVars.authority,
|
|
66
|
-
clientId: requiredEnvVars.clientId,
|
|
67
|
-
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
|
68
|
-
redirectUri: requiredEnvVars.redirectUri,
|
|
69
|
-
scope: process.env.OIDC_SCOPE || "openid offline_access api",
|
|
70
|
-
postLogoutRedirectUri: process.env.OIDC_POST_LOGOUT_REDIRECT_URI,
|
|
71
|
-
applicationType,
|
|
72
|
-
ssoLogout,
|
|
73
|
-
hasLandingPage
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
let OIDC_CONFIG = null;
|
|
77
|
-
function getAuthConfig() {
|
|
78
|
-
if (!OIDC_CONFIG) OIDC_CONFIG = createAuthConfig();
|
|
79
|
-
return OIDC_CONFIG;
|
|
80
|
-
}
|
|
81
|
-
let authServerCache = null;
|
|
82
|
-
const CACHE_TTL = 60 * 60 * 1e3;
|
|
83
|
-
/**
|
|
84
|
-
* Get OAuth configuration with caching
|
|
85
|
-
*/
|
|
86
|
-
async function getOAuthConfig() {
|
|
87
|
-
const config = getAuthConfig();
|
|
88
|
-
const now = Date.now();
|
|
89
|
-
if (authServerCache && now - authServerCache.cachedAt < CACHE_TTL) return {
|
|
90
|
-
authorizationServer: authServerCache.authorizationServer,
|
|
91
|
-
client: authServerCache.client
|
|
92
|
-
};
|
|
93
|
-
try {
|
|
94
|
-
const issuer = new URL(config.authority);
|
|
95
|
-
const discoveryResponse = await oauth.discoveryRequest(issuer, { algorithm: "oidc" });
|
|
96
|
-
if (!discoveryResponse.ok) {
|
|
97
|
-
const errorText = await discoveryResponse.text().catch(() => "Unable to read response");
|
|
98
|
-
logger.error("OAuth discovery error", void 0, { errorText });
|
|
99
|
-
throw new Error(`OAuth discovery failed with status ${discoveryResponse.status}`);
|
|
100
|
-
}
|
|
101
|
-
const authorizationServer = await oauth.processDiscoveryResponse(issuer, discoveryResponse);
|
|
102
|
-
const client = { client_id: config.clientId };
|
|
103
|
-
authServerCache = {
|
|
104
|
-
authorizationServer,
|
|
105
|
-
client,
|
|
106
|
-
cachedAt: now
|
|
107
|
-
};
|
|
108
|
-
logger.info("OAuth discovery completed successfully");
|
|
109
|
-
return {
|
|
110
|
-
authorizationServer,
|
|
111
|
-
client
|
|
112
|
-
};
|
|
113
|
-
} catch (error) {
|
|
114
|
-
logger.error("OAuth discovery failed", error instanceof Error ? error : void 0);
|
|
115
|
-
throw new Error(`OAuth discovery failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Get client authentication method (None for public client with PKCE)
|
|
120
|
-
*/
|
|
121
|
-
function getClientAuth() {
|
|
122
|
-
return oauth.None();
|
|
123
|
-
}
|
|
124
|
-
const DEFAULT_AUTH_CLAIM_MAPPING = {
|
|
125
|
-
subject: ["sub"],
|
|
126
|
-
name: [
|
|
127
|
-
"name",
|
|
128
|
-
"preferred_username",
|
|
129
|
-
"email"
|
|
130
|
-
],
|
|
131
|
-
email: ["email"],
|
|
132
|
-
givenName: ["given_name", "givenName"],
|
|
133
|
-
familyName: ["family_name", "familyName"],
|
|
134
|
-
picture: ["picture", "avatar_url"],
|
|
135
|
-
locale: ["locale"],
|
|
136
|
-
zoneinfo: ["zoneinfo"],
|
|
137
|
-
updatedAt: ["updated_at"],
|
|
138
|
-
tenantIds: ["tenant_ids"],
|
|
139
|
-
tenantRoles: ["tenant_roles"],
|
|
140
|
-
permissions: [
|
|
141
|
-
"app_perms",
|
|
142
|
-
"permissions",
|
|
143
|
-
"scope"
|
|
144
|
-
],
|
|
145
|
-
isOnboarded: ["is_onboarded"]
|
|
146
|
-
};
|
|
147
|
-
let authClaimMapping = { ...DEFAULT_AUTH_CLAIM_MAPPING };
|
|
148
|
-
function normalizeClaimKeys(keys) {
|
|
149
|
-
if (!Array.isArray(keys)) return [];
|
|
150
|
-
return Array.from(new Set(keys.map((key) => key.trim()).filter((key) => key.length > 0)));
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Configure claim mapping so different providers/backends can project their
|
|
154
|
-
* own claim names onto the shared auth/session primitives.
|
|
155
|
-
*/
|
|
156
|
-
function configureAuthClaimMapping(mapping) {
|
|
157
|
-
authClaimMapping = {
|
|
158
|
-
subject: mapping.subject !== void 0 ? normalizeClaimKeys(mapping.subject) : DEFAULT_AUTH_CLAIM_MAPPING.subject,
|
|
159
|
-
name: mapping.name !== void 0 ? normalizeClaimKeys(mapping.name) : DEFAULT_AUTH_CLAIM_MAPPING.name,
|
|
160
|
-
email: mapping.email !== void 0 ? normalizeClaimKeys(mapping.email) : DEFAULT_AUTH_CLAIM_MAPPING.email,
|
|
161
|
-
givenName: mapping.givenName !== void 0 ? normalizeClaimKeys(mapping.givenName) : DEFAULT_AUTH_CLAIM_MAPPING.givenName,
|
|
162
|
-
familyName: mapping.familyName !== void 0 ? normalizeClaimKeys(mapping.familyName) : DEFAULT_AUTH_CLAIM_MAPPING.familyName,
|
|
163
|
-
picture: mapping.picture !== void 0 ? normalizeClaimKeys(mapping.picture) : DEFAULT_AUTH_CLAIM_MAPPING.picture,
|
|
164
|
-
locale: mapping.locale !== void 0 ? normalizeClaimKeys(mapping.locale) : DEFAULT_AUTH_CLAIM_MAPPING.locale,
|
|
165
|
-
zoneinfo: mapping.zoneinfo !== void 0 ? normalizeClaimKeys(mapping.zoneinfo) : DEFAULT_AUTH_CLAIM_MAPPING.zoneinfo,
|
|
166
|
-
updatedAt: mapping.updatedAt !== void 0 ? normalizeClaimKeys(mapping.updatedAt) : DEFAULT_AUTH_CLAIM_MAPPING.updatedAt,
|
|
167
|
-
tenantIds: mapping.tenantIds !== void 0 ? normalizeClaimKeys(mapping.tenantIds) : DEFAULT_AUTH_CLAIM_MAPPING.tenantIds,
|
|
168
|
-
tenantRoles: mapping.tenantRoles !== void 0 ? normalizeClaimKeys(mapping.tenantRoles) : DEFAULT_AUTH_CLAIM_MAPPING.tenantRoles,
|
|
169
|
-
permissions: mapping.permissions !== void 0 ? normalizeClaimKeys(mapping.permissions) : DEFAULT_AUTH_CLAIM_MAPPING.permissions,
|
|
170
|
-
isOnboarded: mapping.isOnboarded !== void 0 ? normalizeClaimKeys(mapping.isOnboarded) : DEFAULT_AUTH_CLAIM_MAPPING.isOnboarded
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Reset claim mapping to the built-in defaults.
|
|
175
|
-
*/
|
|
176
|
-
function resetAuthClaimMapping() {
|
|
177
|
-
authClaimMapping = { ...DEFAULT_AUTH_CLAIM_MAPPING };
|
|
178
|
-
}
|
|
179
|
-
function getConfiguredAuthClaimMapping() {
|
|
180
|
-
return authClaimMapping;
|
|
181
|
-
}
|
|
182
|
-
function getFirstClaimValue(claims, claimKeys) {
|
|
183
|
-
for (const claimKey of claimKeys) if (claimKey in claims) return {
|
|
184
|
-
key: claimKey,
|
|
185
|
-
value: claims[claimKey]
|
|
186
|
-
};
|
|
187
|
-
return {};
|
|
188
|
-
}
|
|
189
|
-
function getStringClaim(claims, claimKeys) {
|
|
190
|
-
const { value } = getFirstClaimValue(claims, claimKeys);
|
|
191
|
-
if (typeof value === "string") {
|
|
192
|
-
const trimmed = value.trim();
|
|
193
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
194
|
-
}
|
|
195
|
-
if (typeof value === "number" || typeof value === "boolean") return `${value}`;
|
|
196
|
-
return void 0;
|
|
197
|
-
}
|
|
198
|
-
function getNumberClaim(claims, claimKeys) {
|
|
199
|
-
const { value } = getFirstClaimValue(claims, claimKeys);
|
|
200
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
201
|
-
if (typeof value === "string") {
|
|
202
|
-
const parsed = Number(value);
|
|
203
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
204
|
-
}
|
|
205
|
-
return void 0;
|
|
206
|
-
}
|
|
207
|
-
function getBooleanClaim(claims, claimKeys, defaultValue = false) {
|
|
208
|
-
const { value } = getFirstClaimValue(claims, claimKeys);
|
|
209
|
-
if (typeof value === "boolean") return value;
|
|
210
|
-
if (typeof value === "string") return value.toLowerCase() === "true";
|
|
211
|
-
return defaultValue;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Parse JSON safely
|
|
215
|
-
*/
|
|
216
|
-
function parseJson(value) {
|
|
217
|
-
try {
|
|
218
|
-
return JSON.parse(value);
|
|
219
|
-
} catch {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Convert value to string array
|
|
225
|
-
*/
|
|
226
|
-
function toStringArray(value) {
|
|
227
|
-
if (!value) return [];
|
|
228
|
-
if (Array.isArray(value)) return value.map((item) => typeof item === "string" ? item.trim() : `${item}`.trim()).filter((item) => item.length > 0);
|
|
229
|
-
if (typeof value === "string") {
|
|
230
|
-
const trimmed = value.trim();
|
|
231
|
-
if (!trimmed) return [];
|
|
232
|
-
if (trimmed.startsWith("[")) {
|
|
233
|
-
const parsed = parseJson(trimmed);
|
|
234
|
-
if (Array.isArray(parsed)) return parsed.map((item) => typeof item === "string" ? item.trim() : `${item}`.trim()).filter((item) => item.length > 0);
|
|
235
|
-
}
|
|
236
|
-
if (trimmed.includes(",")) return trimmed.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
237
|
-
return [trimmed];
|
|
238
|
-
}
|
|
239
|
-
return [];
|
|
240
|
-
}
|
|
241
|
-
function toScopeArray(value) {
|
|
242
|
-
if (typeof value === "string") return value.trim().split(/\s+/).filter((item) => item.length > 0);
|
|
243
|
-
return toStringArray(value);
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Extract user info from ID token claims
|
|
247
|
-
*/
|
|
248
|
-
function extractUserInfo(claims) {
|
|
249
|
-
const claimMapping = getConfiguredAuthClaimMapping();
|
|
250
|
-
let tenants = [];
|
|
251
|
-
const { value: rawTenantIds } = getFirstClaimValue(claims, claimMapping.tenantIds);
|
|
252
|
-
if (rawTenantIds !== void 0) if (typeof rawTenantIds === "string" && rawTenantIds.trim().startsWith("[")) {
|
|
253
|
-
const parsed = parseJson(rawTenantIds.trim());
|
|
254
|
-
if (Array.isArray(parsed)) tenants = parsed.map((id) => typeof id === "string" ? id.trim() : `${id}`.trim()).filter((id) => id.length > 0);
|
|
255
|
-
} else tenants = toStringArray(rawTenantIds);
|
|
256
|
-
const tenantRoles = {};
|
|
257
|
-
const { value: rawRoles } = getFirstClaimValue(claims, claimMapping.tenantRoles);
|
|
258
|
-
if (rawRoles !== void 0) {
|
|
259
|
-
let parsedRoles = rawRoles;
|
|
260
|
-
if (typeof rawRoles === "string" && rawRoles.trim().startsWith("{")) parsedRoles = parseJson(rawRoles.trim()) ?? rawRoles;
|
|
261
|
-
if (parsedRoles && typeof parsedRoles === "object" && !Array.isArray(parsedRoles)) for (const [tenantId, roleValue] of Object.entries(parsedRoles)) {
|
|
262
|
-
const roleIds = toStringArray(roleValue);
|
|
263
|
-
if (roleIds.length > 0) tenantRoles[tenantId] = roleIds;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const permissionClaim = getFirstClaimValue(claims, claimMapping.permissions);
|
|
267
|
-
const permissions = permissionClaim.key === "scope" ? toScopeArray(permissionClaim.value) : toStringArray(permissionClaim.value);
|
|
268
|
-
const isOnboarded = getBooleanClaim(claims, claimMapping.isOnboarded);
|
|
269
|
-
return {
|
|
270
|
-
tenants,
|
|
271
|
-
isOnboarded,
|
|
272
|
-
tenantRoles,
|
|
273
|
-
permissions
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Create session data from token result
|
|
278
|
-
*/
|
|
279
|
-
async function createSessionData(tokenResult, claims) {
|
|
280
|
-
const claimMapping = getConfiguredAuthClaimMapping();
|
|
281
|
-
const baseUser = {
|
|
282
|
-
sub: getStringClaim(claims, claimMapping.subject) || "unknown",
|
|
283
|
-
name: getStringClaim(claims, claimMapping.name) || getStringClaim(claims, claimMapping.email) || "User",
|
|
284
|
-
email: getStringClaim(claims, claimMapping.email),
|
|
285
|
-
givenName: getStringClaim(claims, claimMapping.givenName),
|
|
286
|
-
familyName: getStringClaim(claims, claimMapping.familyName),
|
|
287
|
-
picture: getStringClaim(claims, claimMapping.picture),
|
|
288
|
-
locale: getStringClaim(claims, claimMapping.locale),
|
|
289
|
-
zoneinfo: getStringClaim(claims, claimMapping.zoneinfo),
|
|
290
|
-
updated_at: getNumberClaim(claims, claimMapping.updatedAt)
|
|
291
|
-
};
|
|
292
|
-
const expiresAt = tokenResult.expires_in ? Date.now() + tokenResult.expires_in * 1e3 : void 0;
|
|
293
|
-
return {
|
|
294
|
-
userId: baseUser.sub,
|
|
295
|
-
accessToken: tokenResult.access_token,
|
|
296
|
-
refreshToken: tokenResult.refresh_token,
|
|
297
|
-
idToken: tokenResult.id_token,
|
|
298
|
-
expiresAt,
|
|
299
|
-
user: baseUser,
|
|
300
|
-
lastActivity: Date.now()
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
const AUTH_ERROR_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error`;
|
|
304
|
-
const AUTH_ERROR_DESC_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_auth_error_desc`;
|
|
305
|
-
const OAUTH_STATE_COOKIE = `${AUTH_ERROR_COOKIE_PREFIX}_oauth_state_id`;
|
|
306
|
-
/**
|
|
307
|
-
* Check if there's an auth error in cookies
|
|
308
|
-
*
|
|
309
|
-
* This should be called by apps (especially those without landing pages like Dashboard)
|
|
310
|
-
* BEFORE starting OAuth flow to prevent redirect loops.
|
|
311
|
-
*
|
|
312
|
-
* @example
|
|
313
|
-
* ```ts
|
|
314
|
-
* // In dashboard's index route loader
|
|
315
|
-
* const authError = getAuthError(request);
|
|
316
|
-
* if (authError) {
|
|
317
|
-
* return { authError }; // Show error to user
|
|
318
|
-
* }
|
|
319
|
-
* // No error, proceed with session check
|
|
320
|
-
* const user = await getUser(request);
|
|
321
|
-
* if (!user) {
|
|
322
|
-
* return login(request);
|
|
323
|
-
* }
|
|
324
|
-
* ```
|
|
325
|
-
*/
|
|
326
|
-
function getAuthError(request) {
|
|
327
|
-
const cookies = request.headers.get("Cookie");
|
|
328
|
-
if (!cookies) return null;
|
|
329
|
-
const errorRegex = new RegExp(`${AUTH_ERROR_COOKIE}=([^;]+)`);
|
|
330
|
-
const errorMatch = cookies.match(errorRegex);
|
|
331
|
-
if (!errorMatch) return null;
|
|
332
|
-
const error = decodeURIComponent(errorMatch[1]);
|
|
333
|
-
if (!error) return null;
|
|
334
|
-
const descRegex = new RegExp(`${AUTH_ERROR_DESC_COOKIE}=([^;]+)`);
|
|
335
|
-
const descMatch = cookies.match(descRegex);
|
|
336
|
-
const description = descMatch ? decodeURIComponent(descMatch[1]) : void 0;
|
|
337
|
-
return {
|
|
338
|
-
error,
|
|
339
|
-
description
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Clear auth error cookies
|
|
344
|
-
*
|
|
345
|
-
* Call this after displaying the error to the user
|
|
346
|
-
*/
|
|
347
|
-
function clearAuthErrorHeaders() {
|
|
348
|
-
const headers = new Headers();
|
|
349
|
-
headers.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);
|
|
350
|
-
headers.append("Set-Cookie", `${AUTH_ERROR_DESC_COOKIE}=; Path=/; Max-Age=0`);
|
|
351
|
-
return headers;
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Check if request has auth error (quick check without parsing)
|
|
355
|
-
*/
|
|
356
|
-
function hasAuthError(request) {
|
|
357
|
-
const cookies = request.headers.get("Cookie");
|
|
358
|
-
return cookies?.includes(`${AUTH_ERROR_COOKIE}=`) ?? false;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Initiate OAuth login flow
|
|
362
|
-
*
|
|
363
|
-
* @param request - The incoming request
|
|
364
|
-
* @param returnUrlOrOptions - Either a return URL string or LoginOptions object
|
|
365
|
-
*/
|
|
366
|
-
async function login(request, returnUrlOrOptions) {
|
|
367
|
-
try {
|
|
368
|
-
const config = getAuthConfig();
|
|
369
|
-
const options = typeof returnUrlOrOptions === "string" ? { returnUrl: returnUrlOrOptions } : returnUrlOrOptions || {};
|
|
370
|
-
const cookies = request.headers.get("Cookie");
|
|
371
|
-
const hasError = cookies?.includes(`${AUTH_ERROR_COOKIE}=`);
|
|
372
|
-
if (hasError) {
|
|
373
|
-
logger.warn("Auth error cookie detected, preventing redirect loop");
|
|
374
|
-
const headers$1 = new Headers();
|
|
375
|
-
headers$1.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=; Path=/; Max-Age=0`);
|
|
376
|
-
const redirectUri = new URL(config.redirectUri);
|
|
377
|
-
const baseUrl = redirectUri.origin;
|
|
378
|
-
return createRedirectResponse(baseUrl, { headers: headers$1 });
|
|
379
|
-
}
|
|
380
|
-
const { authorizationServer, client } = await getOAuthConfig();
|
|
381
|
-
const codeVerifier = oauth.generateRandomCodeVerifier();
|
|
382
|
-
const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier);
|
|
383
|
-
const state = oauth.generateRandomState();
|
|
384
|
-
const nonce = oauth.generateRandomNonce();
|
|
385
|
-
const oauthState = {
|
|
386
|
-
state,
|
|
387
|
-
codeVerifier,
|
|
388
|
-
nonce,
|
|
389
|
-
returnUrl: options.returnUrl,
|
|
390
|
-
createdAt: Date.now()
|
|
391
|
-
};
|
|
392
|
-
logger.info("Creating OAuth state", {
|
|
393
|
-
returnUrl: options.returnUrl,
|
|
394
|
-
prompt: options.prompt
|
|
395
|
-
});
|
|
396
|
-
const stateId = await createOAuthState(oauthState);
|
|
397
|
-
const authorizationUrl = new URL(authorizationServer.authorization_endpoint);
|
|
398
|
-
authorizationUrl.searchParams.set("client_id", client.client_id);
|
|
399
|
-
authorizationUrl.searchParams.set("redirect_uri", config.redirectUri);
|
|
400
|
-
authorizationUrl.searchParams.set("response_type", "code");
|
|
401
|
-
authorizationUrl.searchParams.set("scope", config.scope);
|
|
402
|
-
authorizationUrl.searchParams.set("state", state);
|
|
403
|
-
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
|
|
404
|
-
authorizationUrl.searchParams.set("code_challenge_method", "S256");
|
|
405
|
-
authorizationUrl.searchParams.set("nonce", nonce);
|
|
406
|
-
if (options.prompt) authorizationUrl.searchParams.set("prompt", options.prompt);
|
|
407
|
-
const headers = new Headers();
|
|
408
|
-
headers.append("Set-Cookie", `${OAUTH_STATE_COOKIE}=${stateId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=600`);
|
|
409
|
-
logger.info("🔍 DEBUG: OAuth login config", {
|
|
410
|
-
clientId: config.clientId,
|
|
411
|
-
redirectUri: config.redirectUri,
|
|
412
|
-
postLogoutRedirectUri: config.postLogoutRedirectUri,
|
|
413
|
-
scope: config.scope,
|
|
414
|
-
authority: config.authority,
|
|
415
|
-
authorizationEndpoint: authorizationServer.authorization_endpoint,
|
|
416
|
-
fullAuthUrl: authorizationUrl.toString()
|
|
417
|
-
});
|
|
418
|
-
logger.info("OAuth authorization flow initiated", {
|
|
419
|
-
state,
|
|
420
|
-
stateId,
|
|
421
|
-
prompt: options.prompt
|
|
422
|
-
});
|
|
423
|
-
return createRedirectResponse(authorizationUrl.toString(), { headers });
|
|
424
|
-
} catch (error) {
|
|
425
|
-
logger.error("Failed to initiate OAuth login", error instanceof Error ? error : void 0);
|
|
426
|
-
throw new Error("Login failed");
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Handle OAuth callback
|
|
431
|
-
*
|
|
432
|
-
* Handles OAuth authorization response including success and error cases.
|
|
433
|
-
*
|
|
434
|
-
* Error Handling:
|
|
435
|
-
* - `access_denied`: User doesn't have access to this application
|
|
436
|
-
* - For apps with landing page: Redirect home with error cookie
|
|
437
|
-
* - For apps without landing page: Redirect home with error cookie (must handle specially)
|
|
438
|
-
* - `login_required`: No active identity session
|
|
439
|
-
* - Redirect to home to start fresh login
|
|
440
|
-
*/
|
|
441
|
-
async function handleCallback(request) {
|
|
442
|
-
try {
|
|
443
|
-
const config = getAuthConfig();
|
|
444
|
-
const hasLandingPage = getEffectiveHasLandingPage(config);
|
|
445
|
-
const url = new URL(request.url);
|
|
446
|
-
const code = url.searchParams.get("code");
|
|
447
|
-
const state = url.searchParams.get("state");
|
|
448
|
-
const error = url.searchParams.get("error");
|
|
449
|
-
const errorDescription = url.searchParams.get("error_description");
|
|
450
|
-
logger.info("🔍 DEBUG: OAuth callback received", {
|
|
451
|
-
fullUrl: url.toString(),
|
|
452
|
-
origin: url.origin,
|
|
453
|
-
pathname: url.pathname,
|
|
454
|
-
hasCode: !!code,
|
|
455
|
-
hasState: !!state,
|
|
456
|
-
error,
|
|
457
|
-
errorDescription,
|
|
458
|
-
clientId: config.clientId,
|
|
459
|
-
redirectUri: config.redirectUri
|
|
460
|
-
});
|
|
461
|
-
if (error) {
|
|
462
|
-
logger.error("OAuth callback error", void 0, {
|
|
463
|
-
error,
|
|
464
|
-
errorDescription,
|
|
465
|
-
clientId: config.clientId,
|
|
466
|
-
applicationType: config.applicationType,
|
|
467
|
-
hasLandingPage
|
|
468
|
-
});
|
|
469
|
-
if (error === "access_denied") {
|
|
470
|
-
logger.warn("Access denied for this application", { clientId: config.clientId });
|
|
471
|
-
const headers$2 = await destroyAuthSession(request);
|
|
472
|
-
headers$2.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=access_denied; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`);
|
|
473
|
-
headers$2.append("Set-Cookie", `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription || "You do not have access to this application.")}; Path=/; SameSite=Lax; Max-Age=300`);
|
|
474
|
-
const redirectUri$1 = new URL(config.redirectUri);
|
|
475
|
-
const baseUrl$1 = redirectUri$1.origin;
|
|
476
|
-
logger.info("Redirecting to /auth/login to show access_denied error", {
|
|
477
|
-
hasLandingPage,
|
|
478
|
-
baseUrl: baseUrl$1,
|
|
479
|
-
configRedirectUri: config.redirectUri
|
|
480
|
-
});
|
|
481
|
-
return createRedirectResponse(`${baseUrl$1}/auth/login`, { headers: headers$2 });
|
|
482
|
-
}
|
|
483
|
-
if (error === "login_required") {
|
|
484
|
-
logger.info("Login required, user session may have expired");
|
|
485
|
-
const headers$2 = await destroyAuthSession(request);
|
|
486
|
-
const redirectUri$1 = new URL(config.redirectUri);
|
|
487
|
-
const baseUrl$1 = redirectUri$1.origin;
|
|
488
|
-
return createRedirectResponse(baseUrl$1, { headers: headers$2 });
|
|
489
|
-
}
|
|
490
|
-
const headers$1 = await destroyAuthSession(request);
|
|
491
|
-
headers$1.append("Set-Cookie", `${AUTH_ERROR_COOKIE}=${encodeURIComponent(error)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`);
|
|
492
|
-
if (errorDescription) headers$1.append("Set-Cookie", `${AUTH_ERROR_DESC_COOKIE}=${encodeURIComponent(errorDescription)}; Path=/; SameSite=Lax; Max-Age=300`);
|
|
493
|
-
const redirectUri = new URL(config.redirectUri);
|
|
494
|
-
const baseUrl = redirectUri.origin;
|
|
495
|
-
return createRedirectResponse(baseUrl, { headers: headers$1 });
|
|
496
|
-
}
|
|
497
|
-
if (!code || !state) throw new Error("Missing required callback parameters");
|
|
498
|
-
const cookies = request.headers.get("Cookie");
|
|
499
|
-
const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);
|
|
500
|
-
const cookieMatch = cookies?.match(oauthStateRegex);
|
|
501
|
-
const stateId = cookieMatch?.[1];
|
|
502
|
-
if (!stateId) throw new Error("OAuth state ID not found in cookie");
|
|
503
|
-
const oauthState = await getOAuthState(stateId);
|
|
504
|
-
if (!oauthState) throw new Error("No OAuth state found - state may have expired");
|
|
505
|
-
if (oauthState.state !== state) throw new Error(`OAuth state mismatch`);
|
|
506
|
-
const stateAge = Date.now() - oauthState.createdAt;
|
|
507
|
-
if (stateAge > 10 * 60 * 1e3) throw new Error("OAuth state expired");
|
|
508
|
-
const { authorizationServer, client } = await getOAuthConfig();
|
|
509
|
-
const clientAuth = getClientAuth();
|
|
510
|
-
logger.info("Initiating token exchange");
|
|
511
|
-
const currentUrl = new URL(request.url);
|
|
512
|
-
const callbackParameters = oauth.validateAuthResponse(authorizationServer, client, currentUrl, oauthState.state);
|
|
513
|
-
const tokenEndpointResponse = await oauth.authorizationCodeGrantRequest(authorizationServer, client, clientAuth, callbackParameters, config.redirectUri, oauthState.codeVerifier);
|
|
514
|
-
if (!tokenEndpointResponse.ok) {
|
|
515
|
-
const errorText = await tokenEndpointResponse.text().catch(() => "Unable to read response");
|
|
516
|
-
logger.error("Token endpoint error", void 0, { errorText });
|
|
517
|
-
throw new Error("Token exchange failed");
|
|
518
|
-
}
|
|
519
|
-
const tokenResult = await oauth.processAuthorizationCodeResponse(authorizationServer, client, tokenEndpointResponse, { expectedNonce: oauthState.nonce });
|
|
520
|
-
logger.info("Token exchange completed successfully");
|
|
521
|
-
let claims = {};
|
|
522
|
-
if (tokenResult.id_token) try {
|
|
523
|
-
const payload = tokenResult.id_token.split(".")[1];
|
|
524
|
-
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
525
|
-
const binaryString = atob(base64);
|
|
526
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
527
|
-
for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
528
|
-
claims = JSON.parse(new TextDecoder("utf-8").decode(bytes));
|
|
529
|
-
} catch (decodeError) {
|
|
530
|
-
logger.error("Failed to decode ID token", decodeError instanceof Error ? decodeError : void 0);
|
|
531
|
-
throw new Error("Invalid ID token received");
|
|
532
|
-
}
|
|
533
|
-
const newSessionData = await createSessionData(tokenResult, claims);
|
|
534
|
-
const sessionHeaders = await createAuthSession(request, newSessionData);
|
|
535
|
-
await deleteOAuthState(stateId);
|
|
536
|
-
const headers = new Headers(sessionHeaders);
|
|
537
|
-
headers.append("Set-Cookie", `${OAUTH_STATE_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
|
|
538
|
-
logger.info("OAuth callback processed successfully");
|
|
539
|
-
const redirectUrl = oauthState.returnUrl || "/";
|
|
540
|
-
return createRedirectResponse(redirectUrl, { headers });
|
|
541
|
-
} catch (error) {
|
|
542
|
-
logger.error("OAuth callback failed", error instanceof Error ? error : void 0);
|
|
543
|
-
try {
|
|
544
|
-
const cookies = request.headers.get("Cookie");
|
|
545
|
-
const oauthStateRegex = new RegExp(`${OAUTH_STATE_COOKIE}=([^;]+)`);
|
|
546
|
-
const cookieMatch = cookies?.match(oauthStateRegex);
|
|
547
|
-
const stateId = cookieMatch?.[1];
|
|
548
|
-
if (stateId) await deleteOAuthState(stateId);
|
|
549
|
-
} catch {}
|
|
550
|
-
await destroyAuthSession(request);
|
|
551
|
-
throw new Error("Authentication failed");
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Logout user
|
|
556
|
-
*
|
|
557
|
-
* Backend uses id_token_hint to identify which tokens to revoke.
|
|
558
|
-
*
|
|
559
|
-
* **Scenario 1: Full SSO Logout (Manual Sign Out)**
|
|
560
|
-
* - Query param: sso_logout=true
|
|
561
|
-
* - Result: Full logout + identity cookie cleared
|
|
562
|
-
* - User must re-authenticate everywhere
|
|
563
|
-
*
|
|
564
|
-
* **Scenario 2: Standard Logout (Automatic/Token Expired)**
|
|
565
|
-
* - No sso_logout param
|
|
566
|
-
* - Result: Tokens revoked based on id_token_hint
|
|
567
|
-
* - Backend determines which tokens to revoke
|
|
568
|
-
*
|
|
569
|
-
* Configuration:
|
|
570
|
-
* - `OIDC_SSO_LOGOUT` env var ('true' | 'false') - default behavior
|
|
571
|
-
* - Query param `sso_logout=true` - override for full logout
|
|
572
|
-
*/
|
|
573
|
-
async function logout(request) {
|
|
574
|
-
try {
|
|
575
|
-
const config = getAuthConfig();
|
|
576
|
-
const url = new URL(request.url);
|
|
577
|
-
const ssoLogout = url.searchParams.get("sso_logout") === "true" || getEffectiveSsoLogout(config);
|
|
578
|
-
const returnUrl = url.searchParams.get("returnUrl");
|
|
579
|
-
logger.info("Logout initiated", {
|
|
580
|
-
clientId: config.clientId,
|
|
581
|
-
ssoLogout
|
|
582
|
-
});
|
|
583
|
-
const sessionData = await getAuthSession(request);
|
|
584
|
-
const idToken = sessionData.idToken;
|
|
585
|
-
const sessionHeaders = await destroyAuthSession(request);
|
|
586
|
-
const headers = new Headers(sessionHeaders);
|
|
587
|
-
if (returnUrl) headers.append("Set-Cookie", `logout_return_url=${encodeURIComponent(returnUrl)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=300`);
|
|
588
|
-
if (idToken) try {
|
|
589
|
-
const { authorizationServer } = await getOAuthConfig();
|
|
590
|
-
if (authorizationServer.end_session_endpoint) {
|
|
591
|
-
const endSessionUrl = new URL(authorizationServer.end_session_endpoint);
|
|
592
|
-
endSessionUrl.searchParams.set("id_token_hint", idToken);
|
|
593
|
-
if (ssoLogout) {
|
|
594
|
-
endSessionUrl.searchParams.set("sso_logout", "true");
|
|
595
|
-
logger.info("Performing full SSO logout (identity cookie will be cleared)");
|
|
596
|
-
} else logger.info("Performing standard logout (tokens revoked based on id_token_hint)");
|
|
597
|
-
if (config.postLogoutRedirectUri) endSessionUrl.searchParams.set("post_logout_redirect_uri", config.postLogoutRedirectUri);
|
|
598
|
-
return createRedirectResponse(endSessionUrl.toString(), { headers });
|
|
599
|
-
}
|
|
600
|
-
} catch {}
|
|
601
|
-
const baseUrl = new URL(request.url).origin;
|
|
602
|
-
const registeredPath = config.postLogoutRedirectUri || "/";
|
|
603
|
-
const finalRedirectUrl = registeredPath.startsWith("http") ? registeredPath : `${baseUrl}${registeredPath}`;
|
|
604
|
-
logger.info("Logout completed", { redirectUrl: finalRedirectUrl });
|
|
605
|
-
return createRedirectResponse(finalRedirectUrl, { headers });
|
|
606
|
-
} catch (error) {
|
|
607
|
-
logger.error("Logout failed", error instanceof Error ? error : void 0);
|
|
608
|
-
const headers = await destroyAuthSession(request);
|
|
609
|
-
return createRedirectResponse("/", { headers });
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Get current user from session
|
|
614
|
-
*/
|
|
615
|
-
async function getUser(request) {
|
|
616
|
-
try {
|
|
617
|
-
const sessionData = await getAuthSession(request);
|
|
618
|
-
return sessionData.user || null;
|
|
619
|
-
} catch (error) {
|
|
620
|
-
logger.error("Failed to get user", error instanceof Error ? error : void 0);
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Require authenticated user (throws redirect to login if not authenticated)
|
|
626
|
-
*/
|
|
627
|
-
async function requireAuth(request) {
|
|
628
|
-
const user = await getUser(request);
|
|
629
|
-
if (!user) {
|
|
630
|
-
const url = new URL(request.url);
|
|
631
|
-
const returnUrl = `${url.pathname}${url.search}`;
|
|
632
|
-
logger.info("No user found, starting OAuth flow", { returnUrl });
|
|
633
|
-
throw await login(request, returnUrl);
|
|
634
|
-
}
|
|
635
|
-
return user;
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Get access token from session
|
|
639
|
-
*/
|
|
640
|
-
async function getAccessToken(request) {
|
|
641
|
-
try {
|
|
642
|
-
const sessionData = await getAuthSession(request);
|
|
643
|
-
return sessionData.accessToken || null;
|
|
644
|
-
} catch (error) {
|
|
645
|
-
logger.error("Failed to get access token", error instanceof Error ? error : void 0);
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
/**
|
|
650
|
-
* Refresh tokens
|
|
651
|
-
*/
|
|
652
|
-
async function refreshTokens(request, refreshToken) {
|
|
653
|
-
try {
|
|
654
|
-
const sessionData = await getAuthSession(request);
|
|
655
|
-
const tokenToRefresh = refreshToken || sessionData.refreshToken;
|
|
656
|
-
if (!tokenToRefresh) {
|
|
657
|
-
logger.warn("No refresh token available");
|
|
658
|
-
return {
|
|
659
|
-
success: false,
|
|
660
|
-
error: "No refresh token available",
|
|
661
|
-
shouldLogout: true
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
const { authorizationServer, client } = await getOAuthConfig();
|
|
665
|
-
const clientAuth = getClientAuth();
|
|
666
|
-
logger.info("Initiating token refresh");
|
|
667
|
-
const refreshResponse = await oauth.refreshTokenGrantRequest(authorizationServer, client, clientAuth, tokenToRefresh);
|
|
668
|
-
if (!refreshResponse.ok) {
|
|
669
|
-
const errorText = await refreshResponse.text().catch(() => "Unknown error");
|
|
670
|
-
logger.error("Token refresh failed", void 0, {
|
|
671
|
-
status: refreshResponse.status,
|
|
672
|
-
errorText
|
|
673
|
-
});
|
|
674
|
-
let errorData = {};
|
|
675
|
-
try {
|
|
676
|
-
errorData = JSON.parse(errorText);
|
|
677
|
-
} catch {}
|
|
678
|
-
const shouldLogout = refreshResponse.status === 400 || errorData.error === "invalid_grant" || errorData.error === "invalid_token" || refreshResponse.status === 401;
|
|
679
|
-
return {
|
|
680
|
-
success: false,
|
|
681
|
-
error: `Token refresh failed: ${errorData.error_description || errorData.error || "Unknown"}`,
|
|
682
|
-
shouldLogout
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
const refreshResult = await oauth.processRefreshTokenResponse(authorizationServer, client, refreshResponse);
|
|
686
|
-
logger.info("Token refresh successful");
|
|
687
|
-
const newRefreshToken = refreshResult.refresh_token || tokenToRefresh;
|
|
688
|
-
const expiresAt = refreshResult.expires_in ? Date.now() + refreshResult.expires_in * 1e3 : sessionData.expiresAt;
|
|
689
|
-
await updateAuthSession(request, {
|
|
690
|
-
accessToken: refreshResult.access_token,
|
|
691
|
-
refreshToken: newRefreshToken,
|
|
692
|
-
idToken: refreshResult.id_token,
|
|
693
|
-
expiresAt,
|
|
694
|
-
lastActivity: Date.now()
|
|
695
|
-
});
|
|
696
|
-
return {
|
|
697
|
-
success: true,
|
|
698
|
-
tokens: {
|
|
699
|
-
access_token: refreshResult.access_token,
|
|
700
|
-
refresh_token: newRefreshToken,
|
|
701
|
-
id_token: refreshResult.id_token,
|
|
702
|
-
expires_in: refreshResult.expires_in
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
} catch (error) {
|
|
706
|
-
logger.error("Token refresh failed with exception", error instanceof Error ? error : void 0);
|
|
707
|
-
const shouldLogout = error instanceof Error && (error.message.includes("invalid_grant") || error.message.includes("invalid_token") || error.message.includes("unauthorized"));
|
|
708
|
-
return {
|
|
709
|
-
success: false,
|
|
710
|
-
error: error instanceof Error ? error.message : "Token refresh failed",
|
|
711
|
-
shouldLogout
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Clear auth server cache (useful for testing)
|
|
717
|
-
*/
|
|
718
|
-
function clearAuthServerCache() {
|
|
719
|
-
authServerCache = null;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
//#endregion
|
|
723
|
-
export { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, createRedirectResponse, extractUserInfo, getAccessToken, getAuthError, getUser, handleCallback, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping };
|
|
724
|
-
//# sourceMappingURL=auth.server-Cx2h2qu3.js.map
|