@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.
@@ -221,6 +221,44 @@ var config = {
221
221
  // ⚠️ In true production, set to false and rely on httpOnly cookies
222
222
  persistRefreshToken: false
223
223
  };
224
+ var RUNTIME_POLICY_DEFAULTS = {
225
+ tokenRefreshBuffer: 60,
226
+ sessionValidationInterval: 15 * 60 * 1e3,
227
+ enableSessionValidation: true,
228
+ enableProactiveRefresh: true,
229
+ validateOnVisibility: true
230
+ };
231
+ function validateRuntimePolicy(policy = {}) {
232
+ const numberInRange = (value, min, max) => Number.isInteger(value) && value >= min && value <= max;
233
+ const result = { ...RUNTIME_POLICY_DEFAULTS };
234
+ if (numberInRange(policy.tokenRefreshBuffer, 10, 300)) result.tokenRefreshBuffer = policy.tokenRefreshBuffer;
235
+ if (numberInRange(policy.sessionValidationInterval, 6e4, 864e5)) result.sessionValidationInterval = policy.sessionValidationInterval;
236
+ for (const key of ["enableSessionValidation", "enableProactiveRefresh", "validateOnVisibility"]) {
237
+ if (typeof policy[key] === "boolean") result[key] = policy[key];
238
+ }
239
+ return result;
240
+ }
241
+ function applyRuntimePolicy(policy = {}) {
242
+ config = { ...config, ...validateRuntimePolicy(policy) };
243
+ return getConfig();
244
+ }
245
+ async function loadRuntimePolicy() {
246
+ const { authBaseUrl, clientKey } = config;
247
+ if (!authBaseUrl || !clientKey || typeof fetch !== "function") return getConfig();
248
+ try {
249
+ const response = await fetch(`${authBaseUrl.replace(/\/+$/, "")}/clients/${encodeURIComponent(clientKey)}/config`, {
250
+ method: "GET",
251
+ credentials: "include",
252
+ headers: { Accept: "application/json" }
253
+ });
254
+ if (!response.ok) throw new Error(`runtime policy HTTP ${response.status}`);
255
+ const payload = await response.json();
256
+ return applyRuntimePolicy(payload.authentication_policy || {});
257
+ } catch (error) {
258
+ console.warn("[auth-client] Runtime policy unavailable; using safe defaults", error.message);
259
+ return applyRuntimePolicy({});
260
+ }
261
+ }
224
262
  function setConfig(customConfig = {}) {
225
263
  if (!customConfig.clientKey || !customConfig.authBaseUrl) {
226
264
  throw new Error("Missing required config: clientKey and authBaseUrl are required");
@@ -327,7 +365,7 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
327
365
 
328
366
  // core.js
329
367
  var callbackProcessed = false;
330
- function login(clientKeyArg, redirectUriArg) {
368
+ function login(clientKeyArg, redirectUriArg, options = {}) {
331
369
  resetCallbackState();
332
370
  const {
333
371
  clientKey: defaultClientKey,
@@ -350,23 +388,24 @@ function login(clientKeyArg, redirectUriArg) {
350
388
  sessionStorage.setItem("originalApp", clientKey);
351
389
  sessionStorage.setItem("returnUrl", redirectUri);
352
390
  if (isRouterMode()) {
353
- return routerLogin(clientKey, redirectUri);
391
+ return routerLogin(clientKey, redirectUri, options);
354
392
  } else {
355
- return clientLogin(clientKey, redirectUri);
393
+ return clientLogin(clientKey, redirectUri, options);
356
394
  }
357
395
  }
358
- function routerLogin(clientKey, redirectUri) {
396
+ function routerLogin(clientKey, redirectUri, options = {}) {
359
397
  const { authBaseUrl } = getConfig();
360
398
  const params = new URLSearchParams();
361
399
  if (redirectUri) {
362
400
  params.append("redirect_uri", redirectUri);
363
401
  }
402
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
364
403
  params.append("correlation_id", getDiagnosticContext().correlationId);
365
404
  const query = params.toString();
366
405
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
367
406
  window.location.href = backendLoginUrl;
368
407
  }
369
- function clientLogin(clientKey, redirectUri) {
408
+ function clientLogin(clientKey, redirectUri, options = {}) {
370
409
  const { accountUiUrl } = getConfig();
371
410
  const params = new URLSearchParams({
372
411
  client: clientKey
@@ -374,6 +413,7 @@ function clientLogin(clientKey, redirectUri) {
374
413
  if (redirectUri) {
375
414
  params.append("redirect_uri", redirectUri);
376
415
  }
416
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
377
417
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
378
418
  window.location.href = centralizedLoginUrl;
379
419
  }
@@ -499,6 +539,28 @@ function resetCallbackState() {
499
539
  }
500
540
  var refreshInProgress = false;
501
541
  var refreshPromise = null;
542
+ async function withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest) {
543
+ var _a;
544
+ const lockName = `auth-refresh-${clientKey}`;
545
+ const run = async () => {
546
+ try {
547
+ const persistedToken = localStorage.getItem("authToken");
548
+ if (tokenBeforeRefresh && persistedToken && persistedToken !== tokenBeforeRefresh) {
549
+ setToken(persistedToken);
550
+ emitAuthDiagnostic("TOKEN_REFRESH_REUSED_CROSS_TAB", "SUCCESS", "CROSS_TAB_ROTATION", {
551
+ clientKey
552
+ });
553
+ return persistedToken;
554
+ }
555
+ } catch {
556
+ }
557
+ return refreshRequest();
558
+ };
559
+ if (typeof navigator !== "undefined" && ((_a = navigator.locks) == null ? void 0 : _a.request)) {
560
+ return navigator.locks.request(lockName, run);
561
+ }
562
+ return run();
563
+ }
502
564
  async function refreshToken() {
503
565
  const { clientKey, authBaseUrl } = getConfig();
504
566
  if (refreshInProgress && refreshPromise) {
@@ -507,70 +569,74 @@ async function refreshToken() {
507
569
  }
508
570
  refreshInProgress = true;
509
571
  refreshPromise = (async () => {
510
- var _a, _b, _c, _d, _e, _f;
511
- try {
512
- const storedRefreshToken = getRefreshToken();
513
- console.log("\u{1F504} Refreshing token:", {
514
- clientKey,
515
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
516
- hasStoredRefreshToken: !!storedRefreshToken
517
- });
518
- const requestOptions = {
519
- method: "POST",
520
- credentials: "include",
521
- // ✅ Include httpOnly cookies (for HTTPS)
522
- headers: {
523
- ...diagnosticHeaders(),
524
- "Content-Type": "application/json"
525
- }
526
- };
527
- if (storedRefreshToken) {
528
- requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
529
- console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
530
- }
531
- const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
532
- if (!response.ok) {
533
- const errorText = await response.text();
534
- let serverCode = null;
535
- try {
536
- serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
537
- } catch {
538
- }
539
- emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
572
+ const tokenBeforeRefresh = getToken();
573
+ const refreshRequest = async () => {
574
+ var _a, _b, _c, _d, _e, _f;
575
+ try {
576
+ const storedRefreshToken = getRefreshToken();
577
+ console.log("\u{1F504} Refreshing token:", {
540
578
  clientKey,
541
- status: response.status
579
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
580
+ hasStoredRefreshToken: !!storedRefreshToken
542
581
  });
543
- const refreshError = new Error(`Refresh failed: ${response.status}`);
544
- refreshError.code = serverCode || `HTTP_${response.status}`;
545
- refreshError.status = response.status;
546
- throw refreshError;
547
- }
548
- const data = await response.json();
549
- const { access_token, refresh_token: new_refresh_token } = data;
550
- if (!access_token) {
551
- throw new Error("No access token in refresh response");
552
- }
553
- setToken(access_token);
554
- if (new_refresh_token) {
555
- setRefreshToken(new_refresh_token);
556
- console.log("\u{1F504} New refresh token stored from rotation");
557
- }
558
- console.log("\u2705 Token refresh successful, listeners notified");
559
- emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
560
- return access_token;
561
- } catch (err) {
562
- console.error("\u274C Token refresh error:", err);
563
- 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"));
564
- if (isAuthRejection) {
565
- clearToken();
566
- clearRefreshToken();
582
+ const requestOptions = {
583
+ method: "POST",
584
+ credentials: "include",
585
+ // ✅ Include httpOnly cookies (for HTTPS)
586
+ headers: {
587
+ ...diagnosticHeaders(),
588
+ "Content-Type": "application/json"
589
+ }
590
+ };
591
+ if (storedRefreshToken) {
592
+ requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
593
+ console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
594
+ }
595
+ const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
596
+ if (!response.ok) {
597
+ const errorText = await response.text();
598
+ let serverCode = null;
599
+ try {
600
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
601
+ } catch {
602
+ }
603
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
604
+ clientKey,
605
+ status: response.status
606
+ });
607
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
608
+ refreshError.code = serverCode || `HTTP_${response.status}`;
609
+ refreshError.status = response.status;
610
+ throw refreshError;
611
+ }
612
+ const data = await response.json();
613
+ const { access_token, refresh_token: new_refresh_token } = data;
614
+ if (!access_token) {
615
+ throw new Error("No access token in refresh response");
616
+ }
617
+ setToken(access_token);
618
+ if (new_refresh_token) {
619
+ setRefreshToken(new_refresh_token);
620
+ console.log("\u{1F504} New refresh token stored from rotation");
621
+ }
622
+ console.log("\u2705 Token refresh successful, listeners notified");
623
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
624
+ return access_token;
625
+ } catch (err) {
626
+ console.error("\u274C Token refresh error:", err);
627
+ 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"));
628
+ if (isAuthRejection) {
629
+ clearToken();
630
+ clearRefreshToken();
631
+ }
632
+ throw err;
567
633
  }
568
- throw err;
569
- } finally {
570
- refreshInProgress = false;
571
- refreshPromise = null;
572
- }
573
- })();
634
+ };
635
+ return withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest);
636
+ })().finally(() => {
637
+ refreshInProgress = false;
638
+ refreshPromise = null;
639
+ });
574
640
  return refreshPromise;
575
641
  }
576
642
  async function validateCurrentSession() {
@@ -1135,6 +1201,8 @@ var PreferencesContext = createContext2(null);
1135
1201
  var auth = {
1136
1202
  // 🔧 Config
1137
1203
  setConfig,
1204
+ applyRuntimePolicy,
1205
+ loadRuntimePolicy,
1138
1206
  getConfig,
1139
1207
  isRouterMode,
1140
1208
  // 🔐 Core flows