@spidy092/auth-client 3.1.2 → 3.1.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/dist/api.cjs +117 -61
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +117 -61
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +290 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +290 -96
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +117 -61
- package/dist/preferences.cjs.map +1 -1
- package/dist/preferences.js +117 -61
- package/dist/preferences.js.map +1 -1
- package/dist/react/AuthProvider.cjs +266 -96
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +266 -96
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +117 -61
- package/dist/react/PreferencesProvider.cjs.map +1 -1
- package/dist/react/PreferencesProvider.js +117 -61
- package/dist/react/PreferencesProvider.js.map +1 -1
- package/dist/react/useAuth.cjs +7 -0
- package/dist/react/useAuth.cjs.map +1 -1
- package/dist/react/useAuth.js +7 -0
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +157 -66
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +157 -66
- package/dist/react/useSessionMonitor.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -217,6 +217,44 @@ var config = {
|
|
|
217
217
|
// ⚠️ In true production, set to false and rely on httpOnly cookies
|
|
218
218
|
persistRefreshToken: false
|
|
219
219
|
};
|
|
220
|
+
var RUNTIME_POLICY_DEFAULTS = {
|
|
221
|
+
tokenRefreshBuffer: 60,
|
|
222
|
+
sessionValidationInterval: 15 * 60 * 1e3,
|
|
223
|
+
enableSessionValidation: true,
|
|
224
|
+
enableProactiveRefresh: true,
|
|
225
|
+
validateOnVisibility: true
|
|
226
|
+
};
|
|
227
|
+
function validateRuntimePolicy(policy = {}) {
|
|
228
|
+
const numberInRange = (value, min, max) => Number.isInteger(value) && value >= min && value <= max;
|
|
229
|
+
const result = { ...RUNTIME_POLICY_DEFAULTS };
|
|
230
|
+
if (numberInRange(policy.tokenRefreshBuffer, 10, 300)) result.tokenRefreshBuffer = policy.tokenRefreshBuffer;
|
|
231
|
+
if (numberInRange(policy.sessionValidationInterval, 6e4, 864e5)) result.sessionValidationInterval = policy.sessionValidationInterval;
|
|
232
|
+
for (const key of ["enableSessionValidation", "enableProactiveRefresh", "validateOnVisibility"]) {
|
|
233
|
+
if (typeof policy[key] === "boolean") result[key] = policy[key];
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
function applyRuntimePolicy(policy = {}) {
|
|
238
|
+
config = { ...config, ...validateRuntimePolicy(policy) };
|
|
239
|
+
return getConfig();
|
|
240
|
+
}
|
|
241
|
+
async function loadRuntimePolicy() {
|
|
242
|
+
const { authBaseUrl, clientKey } = config;
|
|
243
|
+
if (!authBaseUrl || !clientKey || typeof fetch !== "function") return getConfig();
|
|
244
|
+
try {
|
|
245
|
+
const response = await fetch(`${authBaseUrl.replace(/\/+$/, "")}/clients/${encodeURIComponent(clientKey)}/config`, {
|
|
246
|
+
method: "GET",
|
|
247
|
+
credentials: "include",
|
|
248
|
+
headers: { Accept: "application/json" }
|
|
249
|
+
});
|
|
250
|
+
if (!response.ok) throw new Error(`runtime policy HTTP ${response.status}`);
|
|
251
|
+
const payload = await response.json();
|
|
252
|
+
return applyRuntimePolicy(payload.authentication_policy || {});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.warn("[auth-client] Runtime policy unavailable; using safe defaults", error.message);
|
|
255
|
+
return applyRuntimePolicy({});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
220
258
|
function setConfig(customConfig = {}) {
|
|
221
259
|
if (!customConfig.clientKey || !customConfig.authBaseUrl) {
|
|
222
260
|
throw new Error("Missing required config: clientKey and authBaseUrl are required");
|
|
@@ -302,6 +340,28 @@ function clearLoginLock() {
|
|
|
302
340
|
} catch {
|
|
303
341
|
}
|
|
304
342
|
}
|
|
343
|
+
async function reportClientDiagnostic(safe) {
|
|
344
|
+
if (typeof fetch !== "function" || typeof getToken !== "function") return;
|
|
345
|
+
if (safe.outcome === "SUCCESS" && !safe.event.startsWith("PROFILE_") && !safe.event.startsWith("SESSION_")) return;
|
|
346
|
+
const { authBaseUrl } = getConfig();
|
|
347
|
+
const token = getToken();
|
|
348
|
+
if (!authBaseUrl || !token) return;
|
|
349
|
+
try {
|
|
350
|
+
await fetch(`${authBaseUrl.replace(/\/+$/, "")}/client-telemetry`, {
|
|
351
|
+
method: "POST",
|
|
352
|
+
credentials: "include",
|
|
353
|
+
keepalive: true,
|
|
354
|
+
headers: {
|
|
355
|
+
"Content-Type": "application/json",
|
|
356
|
+
Authorization: `Bearer ${token}`,
|
|
357
|
+
"X-Correlation-ID": safe.correlationId,
|
|
358
|
+
"X-Request-ID": safe.correlationId
|
|
359
|
+
},
|
|
360
|
+
body: JSON.stringify(safe)
|
|
361
|
+
});
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
}
|
|
305
365
|
async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
306
366
|
const context = getDiagnosticContext();
|
|
307
367
|
const safe = {
|
|
@@ -318,12 +378,13 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
|
318
378
|
};
|
|
319
379
|
const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
|
|
320
380
|
console[method]("[auth-client]", safe);
|
|
381
|
+
void reportClientDiagnostic(safe);
|
|
321
382
|
return safe;
|
|
322
383
|
}
|
|
323
384
|
|
|
324
385
|
// core.js
|
|
325
386
|
var callbackProcessed = false;
|
|
326
|
-
function login(clientKeyArg, redirectUriArg) {
|
|
387
|
+
function login(clientKeyArg, redirectUriArg, options = {}) {
|
|
327
388
|
resetCallbackState();
|
|
328
389
|
const {
|
|
329
390
|
clientKey: defaultClientKey,
|
|
@@ -346,23 +407,24 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
346
407
|
sessionStorage.setItem("originalApp", clientKey);
|
|
347
408
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
348
409
|
if (isRouterMode()) {
|
|
349
|
-
return routerLogin(clientKey, redirectUri);
|
|
410
|
+
return routerLogin(clientKey, redirectUri, options);
|
|
350
411
|
} else {
|
|
351
|
-
return clientLogin(clientKey, redirectUri);
|
|
412
|
+
return clientLogin(clientKey, redirectUri, options);
|
|
352
413
|
}
|
|
353
414
|
}
|
|
354
|
-
function routerLogin(clientKey, redirectUri) {
|
|
415
|
+
function routerLogin(clientKey, redirectUri, options = {}) {
|
|
355
416
|
const { authBaseUrl } = getConfig();
|
|
356
417
|
const params = new URLSearchParams();
|
|
357
418
|
if (redirectUri) {
|
|
358
419
|
params.append("redirect_uri", redirectUri);
|
|
359
420
|
}
|
|
421
|
+
if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
|
|
360
422
|
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
361
423
|
const query = params.toString();
|
|
362
424
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
363
425
|
window.location.href = backendLoginUrl;
|
|
364
426
|
}
|
|
365
|
-
function clientLogin(clientKey, redirectUri) {
|
|
427
|
+
function clientLogin(clientKey, redirectUri, options = {}) {
|
|
366
428
|
const { accountUiUrl } = getConfig();
|
|
367
429
|
const params = new URLSearchParams({
|
|
368
430
|
client: clientKey
|
|
@@ -370,6 +432,7 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
370
432
|
if (redirectUri) {
|
|
371
433
|
params.append("redirect_uri", redirectUri);
|
|
372
434
|
}
|
|
435
|
+
if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
|
|
373
436
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
374
437
|
window.location.href = centralizedLoginUrl;
|
|
375
438
|
}
|
|
@@ -495,6 +558,28 @@ function resetCallbackState() {
|
|
|
495
558
|
}
|
|
496
559
|
var refreshInProgress = false;
|
|
497
560
|
var refreshPromise = null;
|
|
561
|
+
async function withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest) {
|
|
562
|
+
var _a;
|
|
563
|
+
const lockName = `auth-refresh-${clientKey}`;
|
|
564
|
+
const run = async () => {
|
|
565
|
+
try {
|
|
566
|
+
const persistedToken = localStorage.getItem("authToken");
|
|
567
|
+
if (tokenBeforeRefresh && persistedToken && persistedToken !== tokenBeforeRefresh) {
|
|
568
|
+
setToken(persistedToken);
|
|
569
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REUSED_CROSS_TAB", "SUCCESS", "CROSS_TAB_ROTATION", {
|
|
570
|
+
clientKey
|
|
571
|
+
});
|
|
572
|
+
return persistedToken;
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
return refreshRequest();
|
|
577
|
+
};
|
|
578
|
+
if (typeof navigator !== "undefined" && ((_a = navigator.locks) == null ? void 0 : _a.request)) {
|
|
579
|
+
return navigator.locks.request(lockName, run);
|
|
580
|
+
}
|
|
581
|
+
return run();
|
|
582
|
+
}
|
|
498
583
|
async function refreshToken() {
|
|
499
584
|
const { clientKey, authBaseUrl } = getConfig();
|
|
500
585
|
if (refreshInProgress && refreshPromise) {
|
|
@@ -503,70 +588,74 @@ async function refreshToken() {
|
|
|
503
588
|
}
|
|
504
589
|
refreshInProgress = true;
|
|
505
590
|
refreshPromise = (async () => {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
hasStoredRefreshToken: !!storedRefreshToken
|
|
513
|
-
});
|
|
514
|
-
const requestOptions = {
|
|
515
|
-
method: "POST",
|
|
516
|
-
credentials: "include",
|
|
517
|
-
// ✅ Include httpOnly cookies (for HTTPS)
|
|
518
|
-
headers: {
|
|
519
|
-
...diagnosticHeaders(),
|
|
520
|
-
"Content-Type": "application/json"
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
if (storedRefreshToken) {
|
|
524
|
-
requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
|
|
525
|
-
console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
|
|
526
|
-
}
|
|
527
|
-
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
528
|
-
if (!response.ok) {
|
|
529
|
-
const errorText = await response.text();
|
|
530
|
-
let serverCode = null;
|
|
531
|
-
try {
|
|
532
|
-
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
533
|
-
} catch {
|
|
534
|
-
}
|
|
535
|
-
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
591
|
+
const tokenBeforeRefresh = getToken();
|
|
592
|
+
const refreshRequest = async () => {
|
|
593
|
+
var _a, _b, _c, _d, _e, _f;
|
|
594
|
+
try {
|
|
595
|
+
const storedRefreshToken = getRefreshToken();
|
|
596
|
+
console.log("\u{1F504} Refreshing token:", {
|
|
536
597
|
clientKey,
|
|
537
|
-
|
|
598
|
+
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
599
|
+
hasStoredRefreshToken: !!storedRefreshToken
|
|
538
600
|
});
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
601
|
+
const requestOptions = {
|
|
602
|
+
method: "POST",
|
|
603
|
+
credentials: "include",
|
|
604
|
+
// ✅ Include httpOnly cookies (for HTTPS)
|
|
605
|
+
headers: {
|
|
606
|
+
...diagnosticHeaders(),
|
|
607
|
+
"Content-Type": "application/json"
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
if (storedRefreshToken) {
|
|
611
|
+
requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
|
|
612
|
+
console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
|
|
613
|
+
}
|
|
614
|
+
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
615
|
+
if (!response.ok) {
|
|
616
|
+
const errorText = await response.text();
|
|
617
|
+
let serverCode = null;
|
|
618
|
+
try {
|
|
619
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
620
|
+
} catch {
|
|
621
|
+
}
|
|
622
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
623
|
+
clientKey,
|
|
624
|
+
status: response.status
|
|
625
|
+
});
|
|
626
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
627
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
628
|
+
refreshError.status = response.status;
|
|
629
|
+
throw refreshError;
|
|
630
|
+
}
|
|
631
|
+
const data = await response.json();
|
|
632
|
+
const { access_token, refresh_token: new_refresh_token } = data;
|
|
633
|
+
if (!access_token) {
|
|
634
|
+
throw new Error("No access token in refresh response");
|
|
635
|
+
}
|
|
636
|
+
setToken(access_token);
|
|
637
|
+
if (new_refresh_token) {
|
|
638
|
+
setRefreshToken(new_refresh_token);
|
|
639
|
+
console.log("\u{1F504} New refresh token stored from rotation");
|
|
640
|
+
}
|
|
641
|
+
console.log("\u2705 Token refresh successful, listeners notified");
|
|
642
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
643
|
+
return access_token;
|
|
644
|
+
} catch (err) {
|
|
645
|
+
console.error("\u274C Token refresh error:", err);
|
|
646
|
+
const isAuthRejection = ((_c = err.message) == null ? void 0 : _c.includes("401")) || ((_d = err.message) == null ? void 0 : _d.includes("403")) || ((_e = err.message) == null ? void 0 : _e.includes("invalid_grant")) || ((_f = err.message) == null ? void 0 : _f.includes("Refresh failed: 4"));
|
|
647
|
+
if (isAuthRejection) {
|
|
648
|
+
clearToken();
|
|
649
|
+
clearRefreshToken();
|
|
650
|
+
}
|
|
651
|
+
throw err;
|
|
563
652
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
})
|
|
653
|
+
};
|
|
654
|
+
return withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest);
|
|
655
|
+
})().finally(() => {
|
|
656
|
+
refreshInProgress = false;
|
|
657
|
+
refreshPromise = null;
|
|
658
|
+
});
|
|
570
659
|
return refreshPromise;
|
|
571
660
|
}
|
|
572
661
|
async function validateCurrentSession() {
|
|
@@ -1119,23 +1208,72 @@ var preferences = {
|
|
|
1119
1208
|
// react/AuthProvider.jsx
|
|
1120
1209
|
import React, { createContext, useState, useEffect, useRef } from "react";
|
|
1121
1210
|
var AuthContext = createContext();
|
|
1122
|
-
function AuthProvider({ children, onSessionExpired }) {
|
|
1211
|
+
function AuthProvider({ children, onSessionExpired, manageSessionSecurity = true }) {
|
|
1123
1212
|
const [token, setTokenState] = useState(getToken());
|
|
1124
1213
|
const [user, setUser] = useState(null);
|
|
1125
1214
|
const [loading, setLoading] = useState(!!token);
|
|
1126
1215
|
const [sessionValid, setSessionValid] = useState(true);
|
|
1127
1216
|
const sessionSecurityRef = useRef(null);
|
|
1217
|
+
const onSessionExpiredRef = useRef(onSessionExpired);
|
|
1218
|
+
const recoveryInFlightRef = useRef(null);
|
|
1219
|
+
const profileRecoveryTokensRef = useRef(/* @__PURE__ */ new Set());
|
|
1220
|
+
useEffect(() => {
|
|
1221
|
+
onSessionExpiredRef.current = onSessionExpired;
|
|
1222
|
+
}, [onSessionExpired]);
|
|
1223
|
+
useEffect(() => {
|
|
1224
|
+
const unsubscribe = addTokenListener((nextToken, previousToken) => {
|
|
1225
|
+
if (nextToken === previousToken) return;
|
|
1226
|
+
setTokenState(nextToken);
|
|
1227
|
+
if (nextToken) {
|
|
1228
|
+
setSessionValid(true);
|
|
1229
|
+
setLoading(true);
|
|
1230
|
+
} else {
|
|
1231
|
+
setSessionValid(false);
|
|
1232
|
+
setUser(null);
|
|
1233
|
+
setLoading(false);
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
return unsubscribe;
|
|
1237
|
+
}, []);
|
|
1238
|
+
const invalidateLocalSession = () => {
|
|
1239
|
+
clearToken();
|
|
1240
|
+
setTokenState(null);
|
|
1241
|
+
setUser(null);
|
|
1242
|
+
setSessionValid(false);
|
|
1243
|
+
setLoading(false);
|
|
1244
|
+
};
|
|
1128
1245
|
const handleSessionInvalid = (reason) => {
|
|
1129
1246
|
console.log("\u{1F6A8} AuthProvider: Session invalidated -", reason);
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1247
|
+
if (recoveryInFlightRef.current) return recoveryInFlightRef.current;
|
|
1248
|
+
const recover = async () => {
|
|
1249
|
+
const callback = onSessionExpiredRef.current;
|
|
1250
|
+
if (typeof callback === "function") {
|
|
1251
|
+
try {
|
|
1252
|
+
await callback(reason);
|
|
1253
|
+
const recoveredToken = getToken();
|
|
1254
|
+
if (recoveredToken) {
|
|
1255
|
+
setTokenState(recoveredToken);
|
|
1256
|
+
setSessionValid(true);
|
|
1257
|
+
setLoading(true);
|
|
1258
|
+
return true;
|
|
1259
|
+
}
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
await emitAuthDiagnostic("SESSION_RECOVERY_FAILED", "FAILURE", reason, {
|
|
1262
|
+
status: (error == null ? void 0 : error.status) || null,
|
|
1263
|
+
errorCode: (error == null ? void 0 : error.code) || null
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
invalidateLocalSession();
|
|
1268
|
+
return false;
|
|
1269
|
+
};
|
|
1270
|
+
recoveryInFlightRef.current = recover().finally(() => {
|
|
1271
|
+
recoveryInFlightRef.current = null;
|
|
1272
|
+
});
|
|
1273
|
+
return recoveryInFlightRef.current;
|
|
1136
1274
|
};
|
|
1137
1275
|
useEffect(() => {
|
|
1138
|
-
if (token && !sessionSecurityRef.current) {
|
|
1276
|
+
if (manageSessionSecurity && token && !sessionSecurityRef.current) {
|
|
1139
1277
|
console.log("\u{1F510} AuthProvider: Starting session security");
|
|
1140
1278
|
const unsubscribe = onSessionInvalid(handleSessionInvalid);
|
|
1141
1279
|
sessionSecurityRef.current = startSessionSecurity(handleSessionInvalid);
|
|
@@ -1147,11 +1285,11 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
1147
1285
|
}
|
|
1148
1286
|
};
|
|
1149
1287
|
}
|
|
1150
|
-
if (!token && sessionSecurityRef.current) {
|
|
1288
|
+
if ((!manageSessionSecurity || !token) && sessionSecurityRef.current) {
|
|
1151
1289
|
sessionSecurityRef.current.stopAll();
|
|
1152
1290
|
sessionSecurityRef.current = null;
|
|
1153
1291
|
}
|
|
1154
|
-
}, [token]);
|
|
1292
|
+
}, [manageSessionSecurity, token]);
|
|
1155
1293
|
useEffect(() => {
|
|
1156
1294
|
console.log("\u{1F50D} AuthProvider useEffect triggered:", {
|
|
1157
1295
|
hasToken: !!token,
|
|
@@ -1172,26 +1310,79 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
1172
1310
|
authBaseUrl,
|
|
1173
1311
|
tokenPreview: token.slice(0, 50) + "..."
|
|
1174
1312
|
});
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1313
|
+
let cancelled = false;
|
|
1314
|
+
const fetchProfile = async (accessToken2) => {
|
|
1315
|
+
const response = await fetch(`${authBaseUrl}/account/profile`, {
|
|
1316
|
+
headers: { Authorization: `Bearer ${accessToken2}` },
|
|
1317
|
+
credentials: "include"
|
|
1318
|
+
});
|
|
1319
|
+
console.log("\u{1F4E5} Profile response status:", response.status);
|
|
1320
|
+
if (!response.ok) {
|
|
1321
|
+
const error = new Error(`Profile request failed: ${response.status}`);
|
|
1322
|
+
error.status = response.status;
|
|
1323
|
+
throw error;
|
|
1324
|
+
}
|
|
1325
|
+
return response.json();
|
|
1326
|
+
};
|
|
1327
|
+
const loadProfile = async () => {
|
|
1328
|
+
try {
|
|
1329
|
+
const responseBody = await fetchProfile(token);
|
|
1330
|
+
if (cancelled) return;
|
|
1331
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
1332
|
+
console.log("\u2705 Profile fetched successfully:", userData.email);
|
|
1333
|
+
setUser(userData);
|
|
1334
|
+
setSessionValid(true);
|
|
1335
|
+
setLoading(false);
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
if (cancelled) return;
|
|
1338
|
+
const status = Number((error == null ? void 0 : error.status) || 0);
|
|
1339
|
+
const isUnauthorized = status === 401;
|
|
1340
|
+
const callback = onSessionExpiredRef.current;
|
|
1341
|
+
await emitAuthDiagnostic(
|
|
1342
|
+
"PROFILE_REQUEST_FAILED",
|
|
1343
|
+
isUnauthorized ? "FAILURE" : "WARNING",
|
|
1344
|
+
isUnauthorized ? "PROFILE_UNAUTHORIZED" : status ? `PROFILE_HTTP_${status}` : "PROFILE_NETWORK_ERROR",
|
|
1345
|
+
{ status, errorName: error == null ? void 0 : error.name, errorCode: error == null ? void 0 : error.code }
|
|
1346
|
+
);
|
|
1347
|
+
if (!isUnauthorized) {
|
|
1348
|
+
console.warn("\u26A0\uFE0F Profile unavailable; retaining the current auth session", error);
|
|
1349
|
+
setLoading(false);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (typeof callback === "function" && !profileRecoveryTokensRef.current.has(token)) {
|
|
1353
|
+
profileRecoveryTokensRef.current.add(token);
|
|
1354
|
+
try {
|
|
1355
|
+
await callback("profile_unauthorized");
|
|
1356
|
+
const recoveredToken = getToken();
|
|
1357
|
+
if (recoveredToken && recoveredToken !== token) {
|
|
1358
|
+
setTokenState(recoveredToken);
|
|
1359
|
+
setSessionValid(true);
|
|
1360
|
+
setLoading(true);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (recoveredToken) {
|
|
1364
|
+
const responseBody = await fetchProfile(recoveredToken);
|
|
1365
|
+
if (cancelled) return;
|
|
1366
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
1367
|
+
setUser(userData);
|
|
1368
|
+
setSessionValid(true);
|
|
1369
|
+
setLoading(false);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
} catch (recoveryError) {
|
|
1373
|
+
await emitAuthDiagnostic("PROFILE_RECOVERY_FAILED", "FAILURE", "PROFILE_REFRESH_FAILED", {
|
|
1374
|
+
status: (recoveryError == null ? void 0 : recoveryError.status) || null,
|
|
1375
|
+
errorCode: (recoveryError == null ? void 0 : recoveryError.code) || null
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
invalidateLocalSession();
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
void loadProfile();
|
|
1383
|
+
return () => {
|
|
1384
|
+
cancelled = true;
|
|
1385
|
+
};
|
|
1195
1386
|
}, [token]);
|
|
1196
1387
|
const login2 = (clientKey, redirectUri, state) => {
|
|
1197
1388
|
login(clientKey, redirectUri, state);
|
|
@@ -1220,6 +1411,7 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
1220
1411
|
setToken(newToken);
|
|
1221
1412
|
setTokenState(newToken);
|
|
1222
1413
|
setSessionValid(true);
|
|
1414
|
+
if (newToken) setLoading(true);
|
|
1223
1415
|
},
|
|
1224
1416
|
clearToken: () => {
|
|
1225
1417
|
stopSessionSecurity();
|
|
@@ -1387,6 +1579,8 @@ function usePreferences() {
|
|
|
1387
1579
|
var auth = {
|
|
1388
1580
|
// 🔧 Config
|
|
1389
1581
|
setConfig,
|
|
1582
|
+
applyRuntimePolicy,
|
|
1583
|
+
loadRuntimePolicy,
|
|
1390
1584
|
getConfig,
|
|
1391
1585
|
isRouterMode,
|
|
1392
1586
|
// 🔐 Core flows
|