@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.
@@ -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");
@@ -306,6 +344,28 @@ function clearLoginLock() {
306
344
  } catch {
307
345
  }
308
346
  }
347
+ async function reportClientDiagnostic(safe) {
348
+ if (typeof fetch !== "function" || typeof getToken !== "function") return;
349
+ if (safe.outcome === "SUCCESS" && !safe.event.startsWith("PROFILE_") && !safe.event.startsWith("SESSION_")) return;
350
+ const { authBaseUrl } = getConfig();
351
+ const token = getToken();
352
+ if (!authBaseUrl || !token) return;
353
+ try {
354
+ await fetch(`${authBaseUrl.replace(/\/+$/, "")}/client-telemetry`, {
355
+ method: "POST",
356
+ credentials: "include",
357
+ keepalive: true,
358
+ headers: {
359
+ "Content-Type": "application/json",
360
+ Authorization: `Bearer ${token}`,
361
+ "X-Correlation-ID": safe.correlationId,
362
+ "X-Request-ID": safe.correlationId
363
+ },
364
+ body: JSON.stringify(safe)
365
+ });
366
+ } catch {
367
+ }
368
+ }
309
369
  async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
310
370
  const context = getDiagnosticContext();
311
371
  const safe = {
@@ -322,12 +382,13 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
322
382
  };
323
383
  const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
324
384
  console[method]("[auth-client]", safe);
385
+ void reportClientDiagnostic(safe);
325
386
  return safe;
326
387
  }
327
388
 
328
389
  // core.js
329
390
  var callbackProcessed = false;
330
- function login(clientKeyArg, redirectUriArg) {
391
+ function login(clientKeyArg, redirectUriArg, options = {}) {
331
392
  resetCallbackState();
332
393
  const {
333
394
  clientKey: defaultClientKey,
@@ -350,23 +411,24 @@ function login(clientKeyArg, redirectUriArg) {
350
411
  sessionStorage.setItem("originalApp", clientKey);
351
412
  sessionStorage.setItem("returnUrl", redirectUri);
352
413
  if (isRouterMode()) {
353
- return routerLogin(clientKey, redirectUri);
414
+ return routerLogin(clientKey, redirectUri, options);
354
415
  } else {
355
- return clientLogin(clientKey, redirectUri);
416
+ return clientLogin(clientKey, redirectUri, options);
356
417
  }
357
418
  }
358
- function routerLogin(clientKey, redirectUri) {
419
+ function routerLogin(clientKey, redirectUri, options = {}) {
359
420
  const { authBaseUrl } = getConfig();
360
421
  const params = new URLSearchParams();
361
422
  if (redirectUri) {
362
423
  params.append("redirect_uri", redirectUri);
363
424
  }
425
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
364
426
  params.append("correlation_id", getDiagnosticContext().correlationId);
365
427
  const query = params.toString();
366
428
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
367
429
  window.location.href = backendLoginUrl;
368
430
  }
369
- function clientLogin(clientKey, redirectUri) {
431
+ function clientLogin(clientKey, redirectUri, options = {}) {
370
432
  const { accountUiUrl } = getConfig();
371
433
  const params = new URLSearchParams({
372
434
  client: clientKey
@@ -374,6 +436,7 @@ function clientLogin(clientKey, redirectUri) {
374
436
  if (redirectUri) {
375
437
  params.append("redirect_uri", redirectUri);
376
438
  }
439
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
377
440
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
378
441
  window.location.href = centralizedLoginUrl;
379
442
  }
@@ -499,6 +562,28 @@ function resetCallbackState() {
499
562
  }
500
563
  var refreshInProgress = false;
501
564
  var refreshPromise = null;
565
+ async function withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest) {
566
+ var _a;
567
+ const lockName = `auth-refresh-${clientKey}`;
568
+ const run = async () => {
569
+ try {
570
+ const persistedToken = localStorage.getItem("authToken");
571
+ if (tokenBeforeRefresh && persistedToken && persistedToken !== tokenBeforeRefresh) {
572
+ setToken(persistedToken);
573
+ emitAuthDiagnostic("TOKEN_REFRESH_REUSED_CROSS_TAB", "SUCCESS", "CROSS_TAB_ROTATION", {
574
+ clientKey
575
+ });
576
+ return persistedToken;
577
+ }
578
+ } catch {
579
+ }
580
+ return refreshRequest();
581
+ };
582
+ if (typeof navigator !== "undefined" && ((_a = navigator.locks) == null ? void 0 : _a.request)) {
583
+ return navigator.locks.request(lockName, run);
584
+ }
585
+ return run();
586
+ }
502
587
  async function refreshToken() {
503
588
  const { clientKey, authBaseUrl } = getConfig();
504
589
  if (refreshInProgress && refreshPromise) {
@@ -507,70 +592,74 @@ async function refreshToken() {
507
592
  }
508
593
  refreshInProgress = true;
509
594
  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}`, {
595
+ const tokenBeforeRefresh = getToken();
596
+ const refreshRequest = async () => {
597
+ var _a, _b, _c, _d, _e, _f;
598
+ try {
599
+ const storedRefreshToken = getRefreshToken();
600
+ console.log("\u{1F504} Refreshing token:", {
540
601
  clientKey,
541
- status: response.status
602
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
603
+ hasStoredRefreshToken: !!storedRefreshToken
542
604
  });
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();
605
+ const requestOptions = {
606
+ method: "POST",
607
+ credentials: "include",
608
+ // ✅ Include httpOnly cookies (for HTTPS)
609
+ headers: {
610
+ ...diagnosticHeaders(),
611
+ "Content-Type": "application/json"
612
+ }
613
+ };
614
+ if (storedRefreshToken) {
615
+ requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
616
+ console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
617
+ }
618
+ const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
619
+ if (!response.ok) {
620
+ const errorText = await response.text();
621
+ let serverCode = null;
622
+ try {
623
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
624
+ } catch {
625
+ }
626
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
627
+ clientKey,
628
+ status: response.status
629
+ });
630
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
631
+ refreshError.code = serverCode || `HTTP_${response.status}`;
632
+ refreshError.status = response.status;
633
+ throw refreshError;
634
+ }
635
+ const data = await response.json();
636
+ const { access_token, refresh_token: new_refresh_token } = data;
637
+ if (!access_token) {
638
+ throw new Error("No access token in refresh response");
639
+ }
640
+ setToken(access_token);
641
+ if (new_refresh_token) {
642
+ setRefreshToken(new_refresh_token);
643
+ console.log("\u{1F504} New refresh token stored from rotation");
644
+ }
645
+ console.log("\u2705 Token refresh successful, listeners notified");
646
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
647
+ return access_token;
648
+ } catch (err) {
649
+ console.error("\u274C Token refresh error:", err);
650
+ 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"));
651
+ if (isAuthRejection) {
652
+ clearToken();
653
+ clearRefreshToken();
654
+ }
655
+ throw err;
567
656
  }
568
- throw err;
569
- } finally {
570
- refreshInProgress = false;
571
- refreshPromise = null;
572
- }
573
- })();
657
+ };
658
+ return withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest);
659
+ })().finally(() => {
660
+ refreshInProgress = false;
661
+ refreshPromise = null;
662
+ });
574
663
  return refreshPromise;
575
664
  }
576
665
  async function validateCurrentSession() {
@@ -1135,6 +1224,8 @@ var PreferencesContext = createContext2(null);
1135
1224
  var auth = {
1136
1225
  // 🔧 Config
1137
1226
  setConfig,
1227
+ applyRuntimePolicy,
1228
+ loadRuntimePolicy,
1138
1229
  getConfig,
1139
1230
  isRouterMode,
1140
1231
  // 🔐 Core flows