@spidy092/auth-client 3.1.2 → 3.1.3

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/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");
@@ -323,7 +361,7 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
323
361
 
324
362
  // core.js
325
363
  var callbackProcessed = false;
326
- function login(clientKeyArg, redirectUriArg) {
364
+ function login(clientKeyArg, redirectUriArg, options = {}) {
327
365
  resetCallbackState();
328
366
  const {
329
367
  clientKey: defaultClientKey,
@@ -346,23 +384,24 @@ function login(clientKeyArg, redirectUriArg) {
346
384
  sessionStorage.setItem("originalApp", clientKey);
347
385
  sessionStorage.setItem("returnUrl", redirectUri);
348
386
  if (isRouterMode()) {
349
- return routerLogin(clientKey, redirectUri);
387
+ return routerLogin(clientKey, redirectUri, options);
350
388
  } else {
351
- return clientLogin(clientKey, redirectUri);
389
+ return clientLogin(clientKey, redirectUri, options);
352
390
  }
353
391
  }
354
- function routerLogin(clientKey, redirectUri) {
392
+ function routerLogin(clientKey, redirectUri, options = {}) {
355
393
  const { authBaseUrl } = getConfig();
356
394
  const params = new URLSearchParams();
357
395
  if (redirectUri) {
358
396
  params.append("redirect_uri", redirectUri);
359
397
  }
398
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
360
399
  params.append("correlation_id", getDiagnosticContext().correlationId);
361
400
  const query = params.toString();
362
401
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
363
402
  window.location.href = backendLoginUrl;
364
403
  }
365
- function clientLogin(clientKey, redirectUri) {
404
+ function clientLogin(clientKey, redirectUri, options = {}) {
366
405
  const { accountUiUrl } = getConfig();
367
406
  const params = new URLSearchParams({
368
407
  client: clientKey
@@ -370,6 +409,7 @@ function clientLogin(clientKey, redirectUri) {
370
409
  if (redirectUri) {
371
410
  params.append("redirect_uri", redirectUri);
372
411
  }
412
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
373
413
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
374
414
  window.location.href = centralizedLoginUrl;
375
415
  }
@@ -495,6 +535,28 @@ function resetCallbackState() {
495
535
  }
496
536
  var refreshInProgress = false;
497
537
  var refreshPromise = null;
538
+ async function withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest) {
539
+ var _a;
540
+ const lockName = `auth-refresh-${clientKey}`;
541
+ const run = async () => {
542
+ try {
543
+ const persistedToken = localStorage.getItem("authToken");
544
+ if (tokenBeforeRefresh && persistedToken && persistedToken !== tokenBeforeRefresh) {
545
+ setToken(persistedToken);
546
+ emitAuthDiagnostic("TOKEN_REFRESH_REUSED_CROSS_TAB", "SUCCESS", "CROSS_TAB_ROTATION", {
547
+ clientKey
548
+ });
549
+ return persistedToken;
550
+ }
551
+ } catch {
552
+ }
553
+ return refreshRequest();
554
+ };
555
+ if (typeof navigator !== "undefined" && ((_a = navigator.locks) == null ? void 0 : _a.request)) {
556
+ return navigator.locks.request(lockName, run);
557
+ }
558
+ return run();
559
+ }
498
560
  async function refreshToken() {
499
561
  const { clientKey, authBaseUrl } = getConfig();
500
562
  if (refreshInProgress && refreshPromise) {
@@ -503,70 +565,74 @@ async function refreshToken() {
503
565
  }
504
566
  refreshInProgress = true;
505
567
  refreshPromise = (async () => {
506
- var _a, _b, _c, _d, _e, _f;
507
- try {
508
- const storedRefreshToken = getRefreshToken();
509
- console.log("\u{1F504} Refreshing token:", {
510
- clientKey,
511
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
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}`, {
568
+ const tokenBeforeRefresh = getToken();
569
+ const refreshRequest = async () => {
570
+ var _a, _b, _c, _d, _e, _f;
571
+ try {
572
+ const storedRefreshToken = getRefreshToken();
573
+ console.log("\u{1F504} Refreshing token:", {
536
574
  clientKey,
537
- status: response.status
575
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
576
+ hasStoredRefreshToken: !!storedRefreshToken
538
577
  });
539
- const refreshError = new Error(`Refresh failed: ${response.status}`);
540
- refreshError.code = serverCode || `HTTP_${response.status}`;
541
- refreshError.status = response.status;
542
- throw refreshError;
543
- }
544
- const data = await response.json();
545
- const { access_token, refresh_token: new_refresh_token } = data;
546
- if (!access_token) {
547
- throw new Error("No access token in refresh response");
548
- }
549
- setToken(access_token);
550
- if (new_refresh_token) {
551
- setRefreshToken(new_refresh_token);
552
- console.log("\u{1F504} New refresh token stored from rotation");
553
- }
554
- console.log("\u2705 Token refresh successful, listeners notified");
555
- emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
556
- return access_token;
557
- } catch (err) {
558
- console.error("\u274C Token refresh error:", err);
559
- 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"));
560
- if (isAuthRejection) {
561
- clearToken();
562
- clearRefreshToken();
578
+ const requestOptions = {
579
+ method: "POST",
580
+ credentials: "include",
581
+ // ✅ Include httpOnly cookies (for HTTPS)
582
+ headers: {
583
+ ...diagnosticHeaders(),
584
+ "Content-Type": "application/json"
585
+ }
586
+ };
587
+ if (storedRefreshToken) {
588
+ requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
589
+ console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
590
+ }
591
+ const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
592
+ if (!response.ok) {
593
+ const errorText = await response.text();
594
+ let serverCode = null;
595
+ try {
596
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
597
+ } catch {
598
+ }
599
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
600
+ clientKey,
601
+ status: response.status
602
+ });
603
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
604
+ refreshError.code = serverCode || `HTTP_${response.status}`;
605
+ refreshError.status = response.status;
606
+ throw refreshError;
607
+ }
608
+ const data = await response.json();
609
+ const { access_token, refresh_token: new_refresh_token } = data;
610
+ if (!access_token) {
611
+ throw new Error("No access token in refresh response");
612
+ }
613
+ setToken(access_token);
614
+ if (new_refresh_token) {
615
+ setRefreshToken(new_refresh_token);
616
+ console.log("\u{1F504} New refresh token stored from rotation");
617
+ }
618
+ console.log("\u2705 Token refresh successful, listeners notified");
619
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
620
+ return access_token;
621
+ } catch (err) {
622
+ console.error("\u274C Token refresh error:", err);
623
+ 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"));
624
+ if (isAuthRejection) {
625
+ clearToken();
626
+ clearRefreshToken();
627
+ }
628
+ throw err;
563
629
  }
564
- throw err;
565
- } finally {
566
- refreshInProgress = false;
567
- refreshPromise = null;
568
- }
569
- })();
630
+ };
631
+ return withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest);
632
+ })().finally(() => {
633
+ refreshInProgress = false;
634
+ refreshPromise = null;
635
+ });
570
636
  return refreshPromise;
571
637
  }
572
638
  async function validateCurrentSession() {
@@ -1387,6 +1453,8 @@ function usePreferences() {
1387
1453
  var auth = {
1388
1454
  // 🔧 Config
1389
1455
  setConfig,
1456
+ applyRuntimePolicy,
1457
+ loadRuntimePolicy,
1390
1458
  getConfig,
1391
1459
  isRouterMode,
1392
1460
  // 🔐 Core flows