tenneo-auth-plugin 1.0.2 → 1.0.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/index.js CHANGED
@@ -373,35 +373,7 @@ function createCookieStorageAdapter(namespace, options) {
373
373
  };
374
374
  }
375
375
 
376
- // src/infrastructure/storage/api.ts
377
- var KEY_SUFFIXES = {
378
- tenantKey: "tenant_key",
379
- codeVerifier: "code_verifier",
380
- state: "state",
381
- accessToken: "access_token",
382
- refreshToken: "refresh_token",
383
- expiresAt: "expires_at"
384
- };
385
- var STORAGE_KEYS = {
386
- get tenantKey() {
387
- return getStorageKey(KEY_SUFFIXES.tenantKey);
388
- },
389
- get codeVerifier() {
390
- return getStorageKey(KEY_SUFFIXES.codeVerifier);
391
- },
392
- get state() {
393
- return getStorageKey(KEY_SUFFIXES.state);
394
- },
395
- get accessToken() {
396
- return getStorageKey(KEY_SUFFIXES.accessToken);
397
- },
398
- get refreshToken() {
399
- return getStorageKey(KEY_SUFFIXES.refreshToken);
400
- },
401
- get expiresAt() {
402
- return getStorageKey(KEY_SUFFIXES.expiresAt);
403
- }
404
- };
376
+ // src/infrastructure/storage/kv.ts
405
377
  function keyToSuffix(key) {
406
378
  const prefix = getStorageKeyPrefix();
407
379
  return key.startsWith(prefix) ? key.slice(prefix.length) : key;
@@ -437,6 +409,36 @@ function removeStorage(key) {
437
409
  } catch {
438
410
  }
439
411
  }
412
+
413
+ // src/infrastructure/storage/api.ts
414
+ var KEY_SUFFIXES = {
415
+ tenantKey: "tenant_key",
416
+ codeVerifier: "code_verifier",
417
+ state: "state",
418
+ accessToken: "access_token",
419
+ refreshToken: "refresh_token",
420
+ expiresAt: "expires_at"
421
+ };
422
+ var STORAGE_KEYS = {
423
+ get tenantKey() {
424
+ return getStorageKey(KEY_SUFFIXES.tenantKey);
425
+ },
426
+ get codeVerifier() {
427
+ return getStorageKey(KEY_SUFFIXES.codeVerifier);
428
+ },
429
+ get state() {
430
+ return getStorageKey(KEY_SUFFIXES.state);
431
+ },
432
+ get accessToken() {
433
+ return getStorageKey(KEY_SUFFIXES.accessToken);
434
+ },
435
+ get refreshToken() {
436
+ return getStorageKey(KEY_SUFFIXES.refreshToken);
437
+ },
438
+ get expiresAt() {
439
+ return getStorageKey(KEY_SUFFIXES.expiresAt);
440
+ }
441
+ };
440
442
  function clearAuthStorage() {
441
443
  const keysToClear = [
442
444
  STORAGE_KEYS.accessToken,
@@ -499,28 +501,6 @@ function isAccessTokenExpired() {
499
501
  const buffer = 60 * 1e3;
500
502
  return Date.now() >= expiresAt - buffer;
501
503
  }
502
- function readHostConfig() {
503
- try {
504
- const raw = getStorage(getStorageKey("host_config"));
505
- if (!raw) return {};
506
- const parsed = JSON.parse(raw);
507
- return parsed && typeof parsed === "object" ? parsed : {};
508
- } catch {
509
- return {};
510
- }
511
- }
512
- function getClientId() {
513
- const value = readHostConfig().clientId;
514
- return typeof value === "string" && value.trim() ? value : null;
515
- }
516
- function getRedirectUri() {
517
- const value = readHostConfig().redirectUri;
518
- return typeof value === "string" && value.trim() ? value : null;
519
- }
520
- function getTenantId() {
521
- const value = readHostConfig().tenantId;
522
- return typeof value === "string" && value.trim() ? value : null;
523
- }
524
504
  function getCodeVerifier() {
525
505
  return getStorage(STORAGE_KEYS.codeVerifier);
526
506
  }
@@ -581,6 +561,252 @@ var Logger = class {
581
561
  };
582
562
  var logger = new Logger();
583
563
 
564
+ // src/config/resolver-core.ts
565
+ var inMemoryConfig = {};
566
+ var hostConfigProvider = null;
567
+ var CACHE_MARKER = "__tenneo_host_config_cache";
568
+ var CACHE_AT = "__tenneo_host_config_cache_at";
569
+ function setConfig(overrides) {
570
+ inMemoryConfig = { ...inMemoryConfig, ...overrides };
571
+ }
572
+ function clearConfigOverrides() {
573
+ inMemoryConfig = {};
574
+ }
575
+ function setHostConfigProvider(fn) {
576
+ hostConfigProvider = fn;
577
+ }
578
+ function readHostBridge() {
579
+ const out = {};
580
+ try {
581
+ if (typeof window !== "undefined" && window.__TENNEO_AUTH_HOST__) {
582
+ Object.assign(out, window.__TENNEO_AUTH_HOST__);
583
+ }
584
+ } catch {
585
+ }
586
+ try {
587
+ const fromProvider = hostConfigProvider?.();
588
+ if (fromProvider && typeof fromProvider === "object") {
589
+ Object.assign(out, fromProvider);
590
+ }
591
+ } catch (e) {
592
+ logger.warn("[config] hostConfigProvider threw", {
593
+ error: e instanceof Error ? e.message : String(e)
594
+ });
595
+ }
596
+ return out;
597
+ }
598
+ function readStorageConfigCache() {
599
+ try {
600
+ const raw = getStorage(getStorageKey("host_config"));
601
+ if (!raw) return {};
602
+ const parsed = JSON.parse(raw);
603
+ if (!parsed || typeof parsed !== "object") return {};
604
+ const { [CACHE_MARKER]: _m, [CACHE_AT]: _t, ...rest } = parsed;
605
+ if (!rest.clientId && typeof rest.client_id === "string" && rest.client_id.trim()) {
606
+ rest.clientId = rest.client_id.trim();
607
+ }
608
+ return rest;
609
+ } catch {
610
+ return {};
611
+ }
612
+ }
613
+ function resolveSDKConfig(overrides) {
614
+ const stored = readStorageConfigCache();
615
+ const bridge = readHostBridge();
616
+ return {
617
+ ...stored,
618
+ ...bridge,
619
+ ...inMemoryConfig,
620
+ ...overrides ?? {}
621
+ };
622
+ }
623
+ function nonEmptyString(v) {
624
+ if (v == null) return void 0;
625
+ const s = String(v).trim();
626
+ return s || void 0;
627
+ }
628
+ function getConfigFieldSources(overrides) {
629
+ const stored = readStorageConfigCache();
630
+ const bridge = readHostBridge();
631
+ const layers = [
632
+ { src: "storage_cache", cfg: stored },
633
+ { src: "host_bridge", cfg: bridge },
634
+ { src: "memory", cfg: inMemoryConfig }
635
+ ];
636
+ if (overrides && Object.keys(overrides).length) {
637
+ layers.push({ src: "override", cfg: overrides });
638
+ }
639
+ const pick = (key) => {
640
+ for (let i = layers.length - 1; i >= 0; i--) {
641
+ const v = nonEmptyString(layers[i].cfg[key]);
642
+ if (v !== void 0) return layers[i].src;
643
+ }
644
+ return void 0;
645
+ };
646
+ return {
647
+ tenantCode: pick("tenantCode") ?? pick("tenantKey"),
648
+ tenantKey: pick("tenantKey") ?? pick("tenantCode"),
649
+ apiBaseUrl: pick("apiBaseUrl"),
650
+ authServerUrl: pick("authServerUrl"),
651
+ callbackUrl: pick("callbackUrl"),
652
+ clientCode: pick("clientCode"),
653
+ clientId: pick("clientId"),
654
+ tenantId: pick("tenantId"),
655
+ appId: pick("appId")
656
+ };
657
+ }
658
+ function getEffectiveTenantCode(overrides) {
659
+ const cfg = resolveSDKConfig(overrides);
660
+ const t = nonEmptyString(cfg.tenantCode) ?? nonEmptyString(cfg.tenantKey) ?? nonEmptyString(cfg.clientCode) ?? "";
661
+ return t;
662
+ }
663
+ var getEffectiveTenantKey = getEffectiveTenantCode;
664
+ function trim(v) {
665
+ return v == null ? "" : String(v).trim();
666
+ }
667
+ function toResolvedConfig(cfg) {
668
+ const merged = { ...cfg };
669
+ if (!merged.tenantCode && merged.tenantKey) {
670
+ merged.tenantCode = merged.tenantKey;
671
+ }
672
+ const clientId = trim(merged.clientId) || trim(merged.clientCode);
673
+ return {
674
+ apiBaseUrl: trim(merged.apiBaseUrl),
675
+ authServerUrl: trim(merged.authServerUrl),
676
+ callbackUrl: trim(merged.callbackUrl) || trim(merged.redirectUri),
677
+ clientCode: trim(merged.clientCode),
678
+ tenantCode: trim(merged.tenantCode),
679
+ appId: trim(merged.appId),
680
+ clientId,
681
+ tenantId: trim(merged.tenantId)
682
+ };
683
+ }
684
+ var AuthConfigError = class extends Error {
685
+ constructor(missing, message) {
686
+ super(
687
+ message ?? `[tenneo-auth] Missing required auth configuration: ${missing.join(", ")}. Provide authServerUrl, callbackUrl (redirectUri), and tenant_key/tenant_code via AuthProvider/initPlugin or window.__TENNEO_AUTH_HOST__.`
688
+ );
689
+ this.code = "AUTH_CONFIG_MISSING";
690
+ this.name = "AuthConfigError";
691
+ this.missing = missing;
692
+ }
693
+ };
694
+ function resolveConfig(overrides) {
695
+ return toResolvedConfig(resolveSDKConfig(overrides));
696
+ }
697
+ function getAuthConfig(options) {
698
+ const resolved = toResolvedConfig(resolveSDKConfig(options?.overrides));
699
+ if (options?.strict) {
700
+ const missing = [];
701
+ if (!resolved.authServerUrl) missing.push("authServerUrl");
702
+ if (!resolved.callbackUrl) missing.push("callbackUrl");
703
+ if (!resolved.tenantCode) missing.push("tenantCode|tenantKey");
704
+ if (missing.length) throw new AuthConfigError(missing);
705
+ }
706
+ return resolved;
707
+ }
708
+ function syncHostConfigCacheFromMemory() {
709
+ const c = resolveSDKConfig();
710
+ const patch = {};
711
+ const keys = [
712
+ "apiBaseUrl",
713
+ "authServerUrl",
714
+ "callbackUrl",
715
+ "redirectUri",
716
+ "tenantCode",
717
+ "tenantKey",
718
+ "clientCode",
719
+ "clientId",
720
+ "tenantId",
721
+ "appId",
722
+ "basePath"
723
+ ];
724
+ for (const k of keys) {
725
+ const v = c[k];
726
+ if (v != null && String(v).trim()) {
727
+ patch[k] = String(v).trim();
728
+ }
729
+ }
730
+ if (Object.keys(patch).length) writeHostConfigCache(patch);
731
+ }
732
+ function writeHostConfigCache(partial) {
733
+ try {
734
+ const key = getStorageKey("host_config");
735
+ const raw = getStorage(key);
736
+ let existing = {};
737
+ try {
738
+ if (raw) existing = JSON.parse(raw);
739
+ } catch {
740
+ existing = {};
741
+ }
742
+ const next = {
743
+ ...existing,
744
+ ...partial,
745
+ [CACHE_MARKER]: true,
746
+ [CACHE_AT]: (/* @__PURE__ */ new Date()).toISOString()
747
+ };
748
+ setStorage(key, JSON.stringify(next));
749
+ logger.debug("[config] host_config cache updated (non-authoritative)", {
750
+ keys: Object.keys(partial)
751
+ });
752
+ } catch (e) {
753
+ logger.warn("[config] Failed to write host_config cache", {
754
+ error: e instanceof Error ? e.message : String(e)
755
+ });
756
+ }
757
+ }
758
+ function initPlugin(config, opts) {
759
+ setConfig(config);
760
+ logger.info("[config] initPlugin \u2014 runtime config updated", {
761
+ syncStorageCache: opts?.syncStorageCache !== false,
762
+ tenantCode: config.tenantCode ?? config.tenantKey ?? "(none)"
763
+ });
764
+ if (opts?.syncStorageCache !== false) {
765
+ writeHostConfigCache(config);
766
+ }
767
+ }
768
+ function logConfigResolutionDebug(context, overrides) {
769
+ const sources = getConfigFieldSources(overrides);
770
+ logger.debug(`[config] ${context}`, {
771
+ fieldSources: sources,
772
+ effectiveTenantCode: getEffectiveTenantCode(overrides) || "(empty)",
773
+ hasAuthServerUrl: !!resolveSDKConfig(overrides).authServerUrl,
774
+ hasCallbackUrl: !!resolveSDKConfig(overrides).callbackUrl
775
+ });
776
+ }
777
+
778
+ // src/config/host-fields.ts
779
+ function trim2(v) {
780
+ if (v == null) return null;
781
+ const s = String(v).trim();
782
+ return s || null;
783
+ }
784
+ function oauthClientIdForTokenRequest(raw) {
785
+ if (!raw) return null;
786
+ let s = raw.trim();
787
+ if (!s) return null;
788
+ if (s.toLowerCase().startsWith("public-")) {
789
+ s = s.slice("public-".length).trim();
790
+ }
791
+ return s || null;
792
+ }
793
+ function getClientId() {
794
+ const c = resolveSDKConfig();
795
+ const fromId = oauthClientIdForTokenRequest(trim2(c.clientId));
796
+ if (fromId) return fromId;
797
+ return oauthClientIdForTokenRequest(trim2(c.clientCode));
798
+ }
799
+ function normalizeClientIdForOAuthTokenBody(raw) {
800
+ return oauthClientIdForTokenRequest(trim2(raw));
801
+ }
802
+ function getRedirectUri() {
803
+ const c = resolveSDKConfig();
804
+ return trim2(c.callbackUrl) ?? trim2(c.redirectUri);
805
+ }
806
+ function getTenantId() {
807
+ return trim2(resolveSDKConfig().tenantId);
808
+ }
809
+
584
810
  // src/domain/errors.ts
585
811
  function isLocalhostHostname(hostname) {
586
812
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.16.");
@@ -779,71 +1005,47 @@ var DEFAULT_TOKEN_GRANT_TYPE = OAUTH_CONSTANTS.TOKEN_GRANT_TYPE;
779
1005
  var DEFAULT_REFRESH_GRANT_TYPE = OAUTH_CONSTANTS.REFRESH_GRANT_TYPE;
780
1006
 
781
1007
  // src/config/index.ts
782
- var inMemoryConfig = {};
783
- function readHostConfigFromStorage() {
784
- try {
785
- const raw = getStorage(getStorageKey("host_config"));
786
- if (!raw) return {};
787
- const parsed = JSON.parse(raw);
788
- return parsed && typeof parsed === "object" ? parsed : {};
789
- } catch {
790
- return {};
791
- }
792
- }
793
- function resolveMerged(overrides) {
794
- const stored = readHostConfigFromStorage();
795
- const cfg = { ...stored, ...inMemoryConfig, ...overrides ?? {} };
796
- if (!cfg.tenantCode && cfg.tenantKey) {
797
- cfg.tenantCode = cfg.tenantKey;
798
- }
799
- const trimmed = (v) => v == null ? "" : String(v).trim();
800
- return {
801
- apiBaseUrl: trimmed(cfg.apiBaseUrl),
802
- authServerUrl: trimmed(cfg.authServerUrl),
803
- callbackUrl: trimmed(cfg.callbackUrl),
804
- clientCode: trimmed(cfg.clientCode),
805
- tenantCode: trimmed(cfg.tenantCode),
806
- appId: trimmed(cfg.appId)
807
- };
1008
+ function getAuthConfig2(options) {
1009
+ return getAuthConfig(options);
808
1010
  }
809
- function resolveConfig(overrides) {
810
- return resolveMerged(overrides);
1011
+ function requireAuthConfig(overrides) {
1012
+ return getAuthConfig({ strict: true, overrides });
811
1013
  }
812
1014
  function getConfig() {
813
- return resolveMerged();
814
- }
815
- function getAuthConfig() {
816
- return resolveMerged();
817
- }
818
- function setConfig(overrides) {
819
- inMemoryConfig = { ...inMemoryConfig, ...overrides };
820
- }
821
- function clearConfigOverrides() {
822
- inMemoryConfig = {};
1015
+ return resolveConfig();
823
1016
  }
824
1017
  function getRedirectUri2() {
825
- const cfg = resolveMerged();
1018
+ const cfg = resolveConfig();
826
1019
  if (cfg.callbackUrl) return cfg.callbackUrl;
827
1020
  if (typeof window !== "undefined") return `${window.location.origin}/callback`;
828
1021
  return "";
829
1022
  }
830
1023
  var Config = {
831
1024
  getApiBaseUrl() {
832
- return resolveMerged().apiBaseUrl;
1025
+ return resolveConfig().apiBaseUrl;
833
1026
  },
834
1027
  getAuthServerUrl() {
835
- return resolveMerged().authServerUrl;
1028
+ return resolveConfig().authServerUrl;
836
1029
  },
837
1030
  getClientCode() {
838
- return resolveMerged().clientCode;
1031
+ return resolveConfig().clientCode;
839
1032
  },
840
1033
  getRedirectUri() {
841
1034
  return getRedirectUri2();
842
1035
  },
843
1036
  getAll() {
844
- return resolveMerged();
1037
+ return resolveConfig();
845
1038
  }
846
1039
  };
1040
+ function applyRuntimeTenantSwitch(tenantKey, opts) {
1041
+ const t = tenantKey.trim();
1042
+ if (!t) return;
1043
+ setConfig({ tenantCode: t, tenantKey: t });
1044
+ logConfigResolutionDebug("applyRuntimeTenantSwitch", { tenantCode: t, tenantKey: t });
1045
+ if (opts?.syncStorageCache !== false) {
1046
+ writeHostConfigCache({ tenantCode: t, tenantKey: t });
1047
+ }
1048
+ }
847
1049
 
848
1050
  // src/infrastructure/tenant/TenantConfigCache.ts
849
1051
  var DEFAULT_TTL_MS = 5 * 60 * 1e3;
@@ -919,16 +1121,6 @@ function consumePropsConfig() {
919
1121
  removeStorage(propsConfigKey);
920
1122
  }
921
1123
  }
922
- function readHostConfig2() {
923
- try {
924
- const raw = getStorage(getStorageKey("host_config"));
925
- if (!raw) return null;
926
- const parsed = JSON.parse(raw);
927
- return parsed && typeof parsed === "object" ? parsed : null;
928
- } catch {
929
- return null;
930
- }
931
- }
932
1124
  function validateTenantKey(value) {
933
1125
  if (typeof value !== "string") return null;
934
1126
  const trimmed = value.trim();
@@ -937,17 +1129,16 @@ function validateTenantKey(value) {
937
1129
  function resolveTenantKey() {
938
1130
  const propsConfig = consumePropsConfig();
939
1131
  const tenantFromProps = validateTenantKey(propsConfig?.tenantKey);
940
- if (tenantFromProps) return tenantFromProps;
941
- const hostConfig = readHostConfig2();
942
- if (!hostConfig) {
943
- logger.warn("Missing host_config while resolving tenant");
944
- return null;
1132
+ if (tenantFromProps) {
1133
+ logger.debug("[tenant] Resolved tenant from props_config", { tenantKey: tenantFromProps });
1134
+ return tenantFromProps;
945
1135
  }
946
- const tenantFromHost = validateTenantKey(hostConfig?.tenantKey);
947
- if (tenantFromHost) return tenantFromHost;
948
- if (hostConfig.tenantKey != null) {
949
- logger.warn("Invalid tenant key in host_config", { tenantKey: hostConfig.tenantKey });
1136
+ const fromHost = validateTenantKey(getEffectiveTenantCode());
1137
+ if (fromHost) {
1138
+ logger.debug("[tenant] Resolved tenant from host/runtime config", { tenantKey: fromHost });
1139
+ return fromHost;
950
1140
  }
1141
+ logger.warn("[tenant] Unable to resolve tenant key (props + host/runtime empty)");
951
1142
  return null;
952
1143
  }
953
1144
  async function resolveTenantConfig(tenantKey, tenantResolver) {
@@ -1080,7 +1271,7 @@ async function refreshAccessToken() {
1080
1271
  });
1081
1272
  throw new Error("Cannot refresh token: missing required data");
1082
1273
  }
1083
- const apiAuthBaseUrl = Config.getApiBaseUrl();
1274
+ const apiAuthBaseUrl = Config.getAuthServerUrl();
1084
1275
  const tokenUrl = `${apiAuthBaseUrl}/oauth/refresh`;
1085
1276
  const body = new URLSearchParams({
1086
1277
  grant_type: DEFAULT_REFRESH_GRANT_TYPE,
@@ -1139,6 +1330,56 @@ async function refreshAccessToken() {
1139
1330
  return refreshPromise;
1140
1331
  }
1141
1332
 
1333
+ // src/strategies/oauth2-pkce/pkce.ts
1334
+ function generateCodeVerifier(length = 128) {
1335
+ const array = new Uint8Array(length);
1336
+ crypto.getRandomValues(array);
1337
+ const base64 = btoa(String.fromCharCode(...array));
1338
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1339
+ }
1340
+ async function generateCodeChallenge(verifier) {
1341
+ try {
1342
+ const encoder = new TextEncoder();
1343
+ const data = encoder.encode(verifier);
1344
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
1345
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1346
+ const base64 = btoa(String.fromCharCode(...hashArray));
1347
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1348
+ } catch {
1349
+ throw new Error("Failed to generate code challenge");
1350
+ }
1351
+ }
1352
+ function generateState() {
1353
+ const array = new Uint8Array(32);
1354
+ crypto.getRandomValues(array);
1355
+ const base64 = btoa(String.fromCharCode(...array));
1356
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1357
+ }
1358
+
1359
+ // src/utils/validation.ts
1360
+ function isValidGuid(guid) {
1361
+ if (!guid || typeof guid !== "string") {
1362
+ return false;
1363
+ }
1364
+ const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1365
+ return guidRegex.test(guid.trim());
1366
+ }
1367
+ function validateTenantId(tenantId) {
1368
+ if (!tenantId || tenantId.trim() === "") {
1369
+ return {
1370
+ isValid: false,
1371
+ error: "Tenant ID is required"
1372
+ };
1373
+ }
1374
+ if (!isValidGuid(tenantId)) {
1375
+ return {
1376
+ isValid: false,
1377
+ error: "Tenant ID must be a valid GUID format (00000000-0000-0000-0000-000000000001)"
1378
+ };
1379
+ }
1380
+ return { isValid: true };
1381
+ }
1382
+
1142
1383
  // src/application/security/redirectLoopGuard.ts
1143
1384
  var DEFAULT_MAX_REDIRECTS = 5;
1144
1385
  var WINDOW_MS = 60 * 1e3;
@@ -1163,18 +1404,166 @@ function checkAndIncrementRedirect(maxAttempts = DEFAULT_MAX_REDIRECTS) {
1163
1404
  redirectCount++;
1164
1405
  return true;
1165
1406
  }
1166
- function isProcessed() {
1167
- return getStorage(getStorageKey("callback_processed")) === "true";
1168
- }
1169
- function isProcessing() {
1170
- return isCallbackProcessing();
1171
- }
1172
- function markProcessing() {
1173
- setCallbackProcessing();
1174
- }
1175
- function markProcessed() {
1176
- setStorage(getStorageKey("callback_processed"), "true");
1177
- clearCallbackProcessing();
1407
+
1408
+ // src/strategies/oauth2-pkce/oauth.ts
1409
+ function storeTenantConfig(config) {
1410
+ const validation = validateTenantId(config.tenantId);
1411
+ if (!validation.isValid) {
1412
+ logger.warn("Tenant ID validation failed", { tenantId: config.tenantId });
1413
+ }
1414
+ const authServerToStore = Config.getAuthServerUrl() || config.authServer;
1415
+ const redirectUriToStore = Config.getRedirectUri();
1416
+ writeHostConfigCache({
1417
+ authServerUrl: authServerToStore,
1418
+ clientId: config.client.clientId,
1419
+ clientCode: config.client.clientCode ?? config.client.clientId,
1420
+ redirectUri: redirectUriToStore,
1421
+ callbackUrl: redirectUriToStore,
1422
+ tenantId: config.tenantId,
1423
+ tenantKey: config.tenantKey ?? "",
1424
+ tenantCode: config.tenantKey ?? ""
1425
+ });
1426
+ if (config.tenantKey) {
1427
+ setStorage(STORAGE_KEYS.tenantKey, config.tenantKey);
1428
+ }
1429
+ }
1430
+ async function initiateAuthFlow(config) {
1431
+ try {
1432
+ logger.info("Initiating OAuth authorization flow");
1433
+ const authServerFromProps = Config.getAuthServerUrl();
1434
+ const authServerEffective = (authServerFromProps || config?.authServer || "").replace(/\/+$/, "");
1435
+ const redirectUriEffective = Config.getRedirectUri();
1436
+ const tenantIdEffective = config?.tenantId || "";
1437
+ const clientIdEffective = config?.client?.clientId || "";
1438
+ logger.debug("[initiateAuthFlow] Resolved inputs", {
1439
+ authServerFromProps: authServerFromProps ? "(provided)" : "(empty)",
1440
+ authServerEffective,
1441
+ redirectUriEffective,
1442
+ tenantIdEffective,
1443
+ clientIdEffective,
1444
+ scopes: config?.client?.scopes,
1445
+ hasRuntime: !!getAuthRuntime()
1446
+ });
1447
+ logger.info("[initiateAuthFlow] authServerUrl for OAuth", {
1448
+ authServerFromProps: authServerFromProps || null,
1449
+ authServerEffective,
1450
+ redirectUriEffective,
1451
+ tenantId: tenantIdEffective,
1452
+ tenantKey: config.tenantKey ?? null,
1453
+ clientIdEffective
1454
+ });
1455
+ const token = getAccessToken();
1456
+ if (token && !isAccessTokenExpired()) {
1457
+ logger.debug("Valid token already exists, skipping auth flow");
1458
+ clearFlowFlags();
1459
+ return;
1460
+ }
1461
+ if (isCallbackUrl()) {
1462
+ logger.debug("Callback URL detected - clearing flow flag to allow callback processing");
1463
+ clearFlowFlags();
1464
+ return;
1465
+ }
1466
+ if (isFlowInProgress()) {
1467
+ if (isFlowFlagStale(3e3)) {
1468
+ logger.info("Stale auth flow flag detected, clearing and proceeding");
1469
+ clearFlowFlags();
1470
+ } else {
1471
+ logger.debug("Auth flow already in progress (recent), skipping duplicate redirect");
1472
+ return;
1473
+ }
1474
+ }
1475
+ setFlowFlags();
1476
+ if (!(Config.getAuthServerUrl() || config?.authServer) || !config.client?.clientId || !config.client?.redirectUri) {
1477
+ logger.error("[initiateAuthFlow] Missing required tenant config", {
1478
+ authServerEffective,
1479
+ clientId: config?.client?.clientId,
1480
+ redirectUriFromTenantApi: config?.client?.redirectUri,
1481
+ redirectUriEffective
1482
+ });
1483
+ throw new Error("Invalid tenant configuration for auth flow");
1484
+ }
1485
+ storeTenantConfig(config);
1486
+ logger.debug("[initiateAuthFlow] Stored tenant config, generating PKCE");
1487
+ removeStorage(getStorageKey("callback_processed"));
1488
+ removeStorage(getStorageKey("state_mismatch"));
1489
+ clearCallbackProcessing();
1490
+ const codeVerifier = generateCodeVerifier();
1491
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
1492
+ const state = generateState();
1493
+ removeStorage(STORAGE_KEYS.codeVerifier);
1494
+ removeStorage(STORAGE_KEYS.state);
1495
+ setStorage(STORAGE_KEYS.codeVerifier, codeVerifier);
1496
+ setStorage(STORAGE_KEYS.state, state);
1497
+ const authUrl = buildAuthorizationUrl(config, codeChallenge, state);
1498
+ logger.debug("[initiateAuthFlow] Built authorization URL", {
1499
+ authServerEffective,
1500
+ urlPrefix: authUrl?.slice(0, 80)
1501
+ });
1502
+ if (!authUrl || !(authUrl.startsWith("http://") || authUrl.startsWith("https://"))) {
1503
+ throw new Error("Invalid authorization URL generated");
1504
+ }
1505
+ if (!checkAndIncrementRedirect()) {
1506
+ logger.warn("Redirect loop protection: max redirect attempts exceeded");
1507
+ clearFlowFlags();
1508
+ return;
1509
+ }
1510
+ logger.info("Redirecting to authorization server");
1511
+ emitAuthEvent(AuthEventNames.LOGIN_STARTED, { tenantKey: config.tenantKey });
1512
+ const runtime = getAuthRuntime();
1513
+ if (runtime) {
1514
+ logger.debug("[initiateAuthFlow] Redirect via runtime.urlProvider.redirect");
1515
+ runtime.urlProvider.redirect(authUrl);
1516
+ } else if (typeof window !== "undefined") {
1517
+ logger.debug("[initiateAuthFlow] Redirect via window.location.replace");
1518
+ window.location.replace(authUrl);
1519
+ } else {
1520
+ logger.error("[initiateAuthFlow] No runtime and no window; cannot redirect", { authUrl });
1521
+ }
1522
+ } catch (error) {
1523
+ clearFlowFlags();
1524
+ logger.error("Failed to initiate auth flow", {
1525
+ error: error instanceof Error ? error.message : String(error)
1526
+ });
1527
+ throw error;
1528
+ }
1529
+ }
1530
+ function buildAuthorizationUrl(config, codeChallenge, state) {
1531
+ const authServerBase = (Config.getAuthServerUrl() || config.authServer || "").replace(/\/+$/, "");
1532
+ if (isLocalhost2()) {
1533
+ logger.debug("Building authorization URL", {
1534
+ authServer: authServerBase,
1535
+ clientId: config.client.clientId,
1536
+ redirectUri: config.client.redirectUri,
1537
+ scopes: config.client.scopes
1538
+ });
1539
+ }
1540
+ const tenantIdToUse = config.tenantId;
1541
+ const clientIdToUse = config.client.clientId;
1542
+ const redirectUriToUse = Config.getRedirectUri();
1543
+ const params = new URLSearchParams({
1544
+ client_id: `public-${clientIdToUse}`,
1545
+ tenant_id: tenantIdToUse,
1546
+ redirect_uri: redirectUriToUse,
1547
+ response_type: DEFAULT_RESPONSE_TYPE,
1548
+ scope: config.client.scopes,
1549
+ state,
1550
+ code_challenge: codeChallenge,
1551
+ code_challenge_method: DEFAULT_CODE_CHALLENGE_METHOD
1552
+ });
1553
+ return `${authServerBase}/connect/authorize?${params.toString()}`;
1554
+ }
1555
+ function isProcessed() {
1556
+ return getStorage(getStorageKey("callback_processed")) === "true";
1557
+ }
1558
+ function isProcessing() {
1559
+ return isCallbackProcessing();
1560
+ }
1561
+ function markProcessing() {
1562
+ setCallbackProcessing();
1563
+ }
1564
+ function markProcessed() {
1565
+ setStorage(getStorageKey("callback_processed"), "true");
1566
+ clearCallbackProcessing();
1178
1567
  }
1179
1568
  function clearProcessing() {
1180
1569
  clearCallbackProcessing();
@@ -1193,6 +1582,38 @@ function cleanUrlSoft() {
1193
1582
  function clearAllAuthFlags() {
1194
1583
  clearFlowFlags();
1195
1584
  }
1585
+ async function tryRecoverOAuthConfigFromTenant() {
1586
+ const runtime = getAuthRuntime();
1587
+ const tenantKey = (resolveTenantKey() ?? getTenantKey() ?? "").trim();
1588
+ if (!runtime?.tenantResolver || !tenantKey) {
1589
+ logger.debug("[Callback] OAuth config recovery skipped \u2014 no tenantResolver or tenantKey", {
1590
+ hasResolver: !!runtime?.tenantResolver,
1591
+ tenantKey: tenantKey || "(empty)"
1592
+ });
1593
+ return null;
1594
+ }
1595
+ try {
1596
+ const tc = await runtime.tenantResolver.resolve(tenantKey);
1597
+ if (!tc?.client?.clientId || !tc.tenantId) {
1598
+ logger.warn("[Callback] OAuth config recovery: tenant resolve missing client or tenantId", {
1599
+ tenantKey
1600
+ });
1601
+ return null;
1602
+ }
1603
+ storeTenantConfig(tc);
1604
+ return {
1605
+ clientId: tc.client.clientId.trim() || null,
1606
+ redirectUri: tc.client.redirectUri?.trim() || getRedirectUri(),
1607
+ tenantId: tc.tenantId.trim() || null
1608
+ };
1609
+ } catch (e) {
1610
+ logger.warn("[Callback] OAuth config recovery: tenant re-resolve failed", {
1611
+ tenantKey,
1612
+ error: e instanceof Error ? e.message : String(e)
1613
+ });
1614
+ return null;
1615
+ }
1616
+ }
1196
1617
  async function handleCallback() {
1197
1618
  const runtimeSearch = getAuthRuntime()?.urlProvider.getCurrentUrl().search;
1198
1619
  const windowSearch = typeof window !== "undefined" ? window.location.search : "";
@@ -1275,9 +1696,22 @@ async function handleCallback() {
1275
1696
  cleanUrlSoft();
1276
1697
  return false;
1277
1698
  }
1278
- const clientId = getClientId();
1279
- const redirectUri = getRedirectUri();
1280
- const tenantId = getTenantId();
1699
+ let clientId = getClientId();
1700
+ let redirectUri = getRedirectUri();
1701
+ let tenantId = getTenantId();
1702
+ if (!clientId || !redirectUri || !tenantId) {
1703
+ logger.warn("[Callback] OAuth config incomplete \u2014 attempting tenant re-resolve", {
1704
+ hasClientId: !!clientId,
1705
+ hasRedirectUri: !!redirectUri,
1706
+ hasTenantId: !!tenantId
1707
+ });
1708
+ const recovered = await tryRecoverOAuthConfigFromTenant();
1709
+ if (recovered) {
1710
+ if (!redirectUri) redirectUri = recovered.redirectUri;
1711
+ if (!tenantId) tenantId = recovered.tenantId;
1712
+ clientId = getClientId() ?? normalizeClientIdForOAuthTokenBody(recovered.clientId) ?? null;
1713
+ }
1714
+ }
1281
1715
  if (!clientId || !redirectUri || !tenantId) {
1282
1716
  logger.error("[Callback] Early exit: missing OAuth config during callback", {
1283
1717
  hasClientId: !!clientId,
@@ -1335,320 +1769,120 @@ async function handleCallback() {
1335
1769
  if (status === 400 && errorCode === "invalid_grant" && (String(errorDescription).includes("already used") || String(errorDescription).includes("Code not found") || String(errorDescription).includes("expired, already used"))) {
1336
1770
  const existingToken2 = getAccessToken();
1337
1771
  const tokenValid2 = existingToken2 && !isAccessTokenExpired();
1338
- if (tokenValid2) {
1339
- removeStorage(STORAGE_KEYS.codeVerifier);
1340
- removeStorage(STORAGE_KEYS.state);
1341
- clearAllAuthFlags();
1342
- markProcessed();
1343
- cleanUrlSoft();
1344
- logger.info("OAuth callback processed successfully (code already used, but token exists)");
1345
- resetRedirectLoopGuard();
1346
- emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1347
- return true;
1348
- }
1349
- }
1350
- emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1351
- error: err.message,
1352
- message: responseData?.error_description ?? err.message
1353
- });
1354
- logger.error("OAuth callback failed (Axios error)", {
1355
- status,
1356
- statusText: err.response?.statusText,
1357
- responseData: err.response?.data,
1358
- message: err.message,
1359
- url: err.config?.url
1360
- });
1361
- } else {
1362
- logger.error("OAuth callback failed", {
1363
- error: err instanceof Error ? err.message : String(err)
1364
- });
1365
- }
1366
- clearAllAuthFlags();
1367
- const existingToken = getAccessToken();
1368
- const tokenValid = existingToken && !isAccessTokenExpired();
1369
- if (tokenValid) {
1370
- markProcessed();
1371
- cleanUrlSoft();
1372
- resetRedirectLoopGuard();
1373
- emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1374
- return true;
1375
- }
1376
- emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1377
- error: err instanceof Error ? err.message : String(err)
1378
- });
1379
- clearProcessing();
1380
- if (isCallbackUrl()) cleanUrlSoft();
1381
- return false;
1382
- }
1383
- }
1384
-
1385
- // src/application/flows/CallbackFlow.ts
1386
- var CallbackFlow = class {
1387
- async execute(context) {
1388
- const { runtime } = context;
1389
- const { urlProvider, logger: log } = runtime;
1390
- const getUrl = () => urlProvider.getCurrentUrl();
1391
- clearFlowFlags();
1392
- const handled = await handleCallback();
1393
- if (handled) {
1394
- if (isCallbackUrlFromUrl(getUrl())) {
1395
- if (isCallbackProcessing()) return { completed: true };
1396
- const u = getUrl();
1397
- urlProvider.redirect(`${u.origin}${u.pathname}`);
1398
- return { completed: true };
1399
- }
1400
- return { completed: true };
1401
- }
1402
- if (isCallbackUrlFromUrl(getUrl())) {
1403
- const u = getUrl();
1404
- urlProvider.redirect(`${u.origin}${u.pathname}`);
1405
- }
1406
- return { completed: true };
1407
- }
1408
- };
1409
-
1410
- // src/application/flows/AuthenticatedFlow.ts
1411
- var AuthenticatedFlow = class {
1412
- async execute(_context) {
1413
- return { completed: true };
1414
- }
1415
- };
1416
-
1417
- // src/application/engine/defaultEngine.ts
1418
- var defaultEngine = null;
1419
- function getDefaultAuthEngine() {
1420
- return defaultEngine;
1421
- }
1422
- function setDefaultAuthEngine(engine) {
1423
- defaultEngine = engine;
1424
- }
1425
-
1426
- // src/application/token.ts
1427
- var lastNoRefreshLog = 0;
1428
- var NO_REFRESH_LOG_INTERVAL_MS = 5e3;
1429
- async function ensureValidAccessToken() {
1430
- const engine = getDefaultAuthEngine();
1431
- if (engine) {
1432
- try {
1433
- return await engine.getAccessToken();
1434
- } catch {
1435
- }
1436
- }
1437
- const token = getAccessToken();
1438
- if (token && !isAccessTokenExpired()) return token;
1439
- const refreshToken = getRefreshToken();
1440
- if (!refreshToken) {
1441
- const now = Date.now();
1442
- if (now - lastNoRefreshLog >= NO_REFRESH_LOG_INTERVAL_MS) {
1443
- lastNoRefreshLog = now;
1444
- logger.debug("No refresh token - cannot refresh; login required");
1445
- }
1446
- throw new Error("No refresh token; login required");
1447
- }
1448
- return await refreshAccessToken();
1449
- }
1450
- function getAccessToken2() {
1451
- return getAccessToken();
1452
- }
1453
-
1454
- // src/strategies/oauth2-pkce/pkce.ts
1455
- function generateCodeVerifier(length = 128) {
1456
- const array = new Uint8Array(length);
1457
- crypto.getRandomValues(array);
1458
- const base64 = btoa(String.fromCharCode(...array));
1459
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1460
- }
1461
- async function generateCodeChallenge(verifier) {
1462
- try {
1463
- const encoder = new TextEncoder();
1464
- const data = encoder.encode(verifier);
1465
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
1466
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1467
- const base64 = btoa(String.fromCharCode(...hashArray));
1468
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1469
- } catch {
1470
- throw new Error("Failed to generate code challenge");
1471
- }
1472
- }
1473
- function generateState() {
1474
- const array = new Uint8Array(32);
1475
- crypto.getRandomValues(array);
1476
- const base64 = btoa(String.fromCharCode(...array));
1477
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1478
- }
1479
-
1480
- // src/utils/validation.ts
1481
- function isValidGuid(guid) {
1482
- if (!guid || typeof guid !== "string") {
1483
- return false;
1484
- }
1485
- const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1486
- return guidRegex.test(guid.trim());
1487
- }
1488
- function validateTenantId(tenantId) {
1489
- if (!tenantId || tenantId.trim() === "") {
1490
- return {
1491
- isValid: false,
1492
- error: "Tenant ID is required"
1493
- };
1494
- }
1495
- if (!isValidGuid(tenantId)) {
1496
- return {
1497
- isValid: false,
1498
- error: "Tenant ID must be a valid GUID format (00000000-0000-0000-0000-000000000001)"
1499
- };
1500
- }
1501
- return { isValid: true };
1502
- }
1503
-
1504
- // src/strategies/oauth2-pkce/oauth.ts
1505
- function storeTenantConfig(config) {
1506
- const validation = validateTenantId(config.tenantId);
1507
- if (!validation.isValid) {
1508
- logger.warn("Tenant ID validation failed", { tenantId: config.tenantId });
1509
- }
1510
- const authServerToStore = Config.getAuthServerUrl() || config.authServer;
1511
- const redirectUriToStore = Config.getRedirectUri();
1512
- const hostConfigKey = getStorageKey("host_config");
1513
- let existing = {};
1514
- try {
1515
- const raw = getStorage(hostConfigKey);
1516
- if (raw) existing = JSON.parse(raw);
1517
- } catch {
1518
- existing = {};
1519
- }
1520
- setStorage(
1521
- hostConfigKey,
1522
- JSON.stringify({
1523
- ...existing,
1524
- authServerUrl: authServerToStore,
1525
- clientId: config.client.clientId,
1526
- redirectUri: redirectUriToStore,
1527
- tenantId: config.tenantId,
1528
- tenantKey: config.tenantKey ?? existing.tenantKey ?? "",
1529
- tenantCode: config.tenantKey ?? existing.tenantCode ?? ""
1530
- })
1531
- );
1532
- if (config.tenantKey) {
1533
- setStorage(STORAGE_KEYS.tenantKey, config.tenantKey);
1534
- }
1535
- }
1536
- async function initiateAuthFlow(config) {
1537
- try {
1538
- logger.info("Initiating OAuth authorization flow");
1539
- const authServerFromProps = Config.getAuthServerUrl();
1540
- const authServerEffective = (authServerFromProps || config?.authServer || "").replace(/\/+$/, "");
1541
- const redirectUriEffective = Config.getRedirectUri();
1542
- const tenantIdEffective = config?.tenantId || "";
1543
- const clientIdEffective = config?.client?.clientId || "";
1544
- logger.debug("[initiateAuthFlow] Resolved inputs", {
1545
- authServerFromProps: authServerFromProps ? "(provided)" : "(empty)",
1546
- authServerEffective,
1547
- redirectUriEffective,
1548
- tenantIdEffective,
1549
- clientIdEffective,
1550
- scopes: config?.client?.scopes,
1551
- hasRuntime: !!getAuthRuntime()
1552
- });
1553
- const token = getAccessToken();
1554
- if (token && !isAccessTokenExpired()) {
1555
- logger.debug("Valid token already exists, skipping auth flow");
1556
- clearFlowFlags();
1557
- return;
1558
- }
1559
- if (isCallbackUrl()) {
1560
- logger.debug("Callback URL detected - clearing flow flag to allow callback processing");
1561
- clearFlowFlags();
1562
- return;
1563
- }
1564
- if (isFlowInProgress()) {
1565
- if (isFlowFlagStale(3e3)) {
1566
- logger.info("Stale auth flow flag detected, clearing and proceeding");
1567
- clearFlowFlags();
1568
- } else {
1569
- logger.debug("Auth flow already in progress (recent), skipping duplicate redirect");
1570
- return;
1772
+ if (tokenValid2) {
1773
+ removeStorage(STORAGE_KEYS.codeVerifier);
1774
+ removeStorage(STORAGE_KEYS.state);
1775
+ clearAllAuthFlags();
1776
+ markProcessed();
1777
+ cleanUrlSoft();
1778
+ logger.info("OAuth callback processed successfully (code already used, but token exists)");
1779
+ resetRedirectLoopGuard();
1780
+ emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1781
+ return true;
1782
+ }
1571
1783
  }
1572
- }
1573
- setFlowFlags();
1574
- if (!(Config.getAuthServerUrl() || config?.authServer) || !config.client?.clientId || !config.client?.redirectUri) {
1575
- logger.error("[initiateAuthFlow] Missing required tenant config", {
1576
- authServerEffective,
1577
- clientId: config?.client?.clientId,
1578
- redirectUriFromTenantApi: config?.client?.redirectUri,
1579
- redirectUriEffective
1784
+ emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1785
+ error: err.message,
1786
+ message: responseData?.error_description ?? err.message
1787
+ });
1788
+ logger.error("OAuth callback failed (Axios error)", {
1789
+ status,
1790
+ statusText: err.response?.statusText,
1791
+ responseData: err.response?.data,
1792
+ message: err.message,
1793
+ url: err.config?.url
1794
+ });
1795
+ } else {
1796
+ logger.error("OAuth callback failed", {
1797
+ error: err instanceof Error ? err.message : String(err)
1580
1798
  });
1581
- throw new Error("Invalid tenant configuration for auth flow");
1582
1799
  }
1583
- storeTenantConfig(config);
1584
- logger.debug("[initiateAuthFlow] Stored tenant config, generating PKCE");
1585
- removeStorage(getStorageKey("callback_processed"));
1586
- removeStorage(getStorageKey("state_mismatch"));
1587
- clearCallbackProcessing();
1588
- const codeVerifier = generateCodeVerifier();
1589
- const codeChallenge = await generateCodeChallenge(codeVerifier);
1590
- const state = generateState();
1591
- removeStorage(STORAGE_KEYS.codeVerifier);
1592
- removeStorage(STORAGE_KEYS.state);
1593
- setStorage(STORAGE_KEYS.codeVerifier, codeVerifier);
1594
- setStorage(STORAGE_KEYS.state, state);
1595
- const authUrl = buildAuthorizationUrl(config, codeChallenge, state);
1596
- logger.debug("[initiateAuthFlow] Built authorization URL", {
1597
- authServerEffective,
1598
- urlPrefix: authUrl?.slice(0, 80)
1599
- });
1600
- if (!authUrl || !(authUrl.startsWith("http://") || authUrl.startsWith("https://"))) {
1601
- throw new Error("Invalid authorization URL generated");
1800
+ clearAllAuthFlags();
1801
+ const existingToken = getAccessToken();
1802
+ const tokenValid = existingToken && !isAccessTokenExpired();
1803
+ if (tokenValid) {
1804
+ markProcessed();
1805
+ cleanUrlSoft();
1806
+ resetRedirectLoopGuard();
1807
+ emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1808
+ return true;
1602
1809
  }
1603
- if (!checkAndIncrementRedirect()) {
1604
- logger.warn("Redirect loop protection: max redirect attempts exceeded");
1605
- clearFlowFlags();
1606
- return;
1810
+ emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1811
+ error: err instanceof Error ? err.message : String(err)
1812
+ });
1813
+ clearProcessing();
1814
+ if (isCallbackUrl()) cleanUrlSoft();
1815
+ return false;
1816
+ }
1817
+ }
1818
+
1819
+ // src/application/flows/CallbackFlow.ts
1820
+ var CallbackFlow = class {
1821
+ async execute(context) {
1822
+ const { runtime } = context;
1823
+ const { urlProvider, logger: log } = runtime;
1824
+ const getUrl = () => urlProvider.getCurrentUrl();
1825
+ clearFlowFlags();
1826
+ const handled = await handleCallback();
1827
+ if (handled) {
1828
+ if (isCallbackUrlFromUrl(getUrl())) {
1829
+ if (isCallbackProcessing()) return { completed: true };
1830
+ const u = getUrl();
1831
+ urlProvider.redirect(`${u.origin}${u.pathname}`);
1832
+ return { completed: true };
1833
+ }
1834
+ return { completed: true };
1607
1835
  }
1608
- logger.info("Redirecting to authorization server");
1609
- emitAuthEvent(AuthEventNames.LOGIN_STARTED, { tenantKey: config.tenantKey });
1610
- const runtime = getAuthRuntime();
1611
- if (runtime) {
1612
- logger.debug("[initiateAuthFlow] Redirect via runtime.urlProvider.redirect");
1613
- runtime.urlProvider.redirect(authUrl);
1614
- } else if (typeof window !== "undefined") {
1615
- logger.debug("[initiateAuthFlow] Redirect via window.location.replace");
1616
- window.location.replace(authUrl);
1617
- } else {
1618
- logger.error("[initiateAuthFlow] No runtime and no window; cannot redirect", { authUrl });
1836
+ if (isCallbackUrlFromUrl(getUrl())) {
1837
+ const u = getUrl();
1838
+ urlProvider.redirect(`${u.origin}${u.pathname}`);
1619
1839
  }
1620
- } catch (error) {
1621
- clearFlowFlags();
1622
- logger.error("Failed to initiate auth flow", {
1623
- error: error instanceof Error ? error.message : String(error)
1624
- });
1625
- throw error;
1840
+ return { completed: true };
1626
1841
  }
1842
+ };
1843
+
1844
+ // src/application/flows/AuthenticatedFlow.ts
1845
+ var AuthenticatedFlow = class {
1846
+ async execute(_context) {
1847
+ return { completed: true };
1848
+ }
1849
+ };
1850
+
1851
+ // src/application/engine/defaultEngine.ts
1852
+ var defaultEngine = null;
1853
+ function getDefaultAuthEngine() {
1854
+ return defaultEngine;
1627
1855
  }
1628
- function buildAuthorizationUrl(config, codeChallenge, state) {
1629
- const authServerBase = (Config.getAuthServerUrl() || config.authServer || "").replace(/\/+$/, "");
1630
- if (isLocalhost2()) {
1631
- logger.debug("Building authorization URL", {
1632
- authServer: authServerBase,
1633
- clientId: config.client.clientId,
1634
- redirectUri: config.client.redirectUri,
1635
- scopes: config.client.scopes
1636
- });
1856
+ function setDefaultAuthEngine(engine) {
1857
+ defaultEngine = engine;
1858
+ }
1859
+
1860
+ // src/application/token.ts
1861
+ var lastNoRefreshLog = 0;
1862
+ var NO_REFRESH_LOG_INTERVAL_MS = 5e3;
1863
+ async function ensureValidAccessToken() {
1864
+ const engine = getDefaultAuthEngine();
1865
+ if (engine) {
1866
+ try {
1867
+ return await engine.getAccessToken();
1868
+ } catch {
1869
+ }
1637
1870
  }
1638
- const tenantIdToUse = config.tenantId;
1639
- const clientIdToUse = config.client.clientId;
1640
- const redirectUriToUse = Config.getRedirectUri();
1641
- const params = new URLSearchParams({
1642
- client_id: `public-${clientIdToUse}`,
1643
- tenant_id: tenantIdToUse,
1644
- redirect_uri: redirectUriToUse,
1645
- response_type: DEFAULT_RESPONSE_TYPE,
1646
- scope: config.client.scopes,
1647
- state,
1648
- code_challenge: codeChallenge,
1649
- code_challenge_method: DEFAULT_CODE_CHALLENGE_METHOD
1650
- });
1651
- return `${authServerBase}/connect/authorize?${params.toString()}`;
1871
+ const token = getAccessToken();
1872
+ if (token && !isAccessTokenExpired()) return token;
1873
+ const refreshToken = getRefreshToken();
1874
+ if (!refreshToken) {
1875
+ const now = Date.now();
1876
+ if (now - lastNoRefreshLog >= NO_REFRESH_LOG_INTERVAL_MS) {
1877
+ lastNoRefreshLog = now;
1878
+ logger.debug("No refresh token - cannot refresh; login required");
1879
+ }
1880
+ throw new Error("No refresh token; login required");
1881
+ }
1882
+ return await refreshAccessToken();
1883
+ }
1884
+ function getAccessToken2() {
1885
+ return getAccessToken();
1652
1886
  }
1653
1887
 
1654
1888
  // src/application/flows/RefreshFlow.ts
@@ -1729,13 +1963,18 @@ var TenantSwitchFlow = class {
1729
1963
  );
1730
1964
  }
1731
1965
  try {
1732
- const cfg = getAuthConfig();
1733
- setStorage(getStorageKey("host_config"), JSON.stringify({
1734
- ...cfg,
1966
+ const cfg = getAuthConfig2();
1967
+ writeHostConfigCache({
1968
+ apiBaseUrl: cfg.apiBaseUrl,
1969
+ authServerUrl: cfg.authServerUrl,
1970
+ callbackUrl: cfg.callbackUrl,
1971
+ appId: cfg.appId,
1972
+ clientCode: tenantConfig.client?.clientCode ?? cfg.clientCode ?? "",
1973
+ clientId: cfg.clientId || tenantConfig.client?.clientId,
1974
+ tenantId: tenantConfig.tenantId ?? cfg.tenantId,
1735
1975
  tenantKey,
1736
- tenantCode: tenantKey,
1737
- clientCode: tenantConfig.client?.clientCode ?? cfg.clientCode ?? ""
1738
- }));
1976
+ tenantCode: tenantKey
1977
+ });
1739
1978
  } catch {
1740
1979
  }
1741
1980
  setTenantKey(tenantKey);
@@ -1899,6 +2138,22 @@ async function bootstrap() {
1899
2138
  return bootstrapImpl();
1900
2139
  }
1901
2140
 
2141
+ // src/infrastructure/watchers/watcher-guard.ts
2142
+ var suspended = false;
2143
+ var generation = 0;
2144
+ function suspendWatchers() {
2145
+ suspended = true;
2146
+ generation += 1;
2147
+ return generation;
2148
+ }
2149
+ function resumeWatchers(expectedGen) {
2150
+ if (expectedGen !== void 0 && expectedGen !== generation) return;
2151
+ suspended = false;
2152
+ }
2153
+ function areWatchersSuspended() {
2154
+ return suspended;
2155
+ }
2156
+
1902
2157
  // src/strategies/cookie/cookie-auth.ts
1903
2158
  var COOKIE_NAMES = {
1904
2159
  accessToken: "tenneo_auth_access_token",
@@ -2025,6 +2280,10 @@ function checkCookiesDeleted() {
2025
2280
  }
2026
2281
  }
2027
2282
  function handleCookieDeletion() {
2283
+ if (areWatchersSuspended()) {
2284
+ logger.debug("Cookie deletion handler skipped \u2014 watchers suspended (logout/teardown)");
2285
+ return;
2286
+ }
2028
2287
  try {
2029
2288
  logger.info("Cookies deleted detected - clearing session and redirecting to login");
2030
2289
  clearAuthStorage();
@@ -2062,6 +2321,7 @@ function startCookieWatcher() {
2062
2321
  allCookies: initialCookies
2063
2322
  });
2064
2323
  watchInterval = setInterval(async () => {
2324
+ if (areWatchersSuspended()) return;
2065
2325
  const cookiesDeleted = checkCookiesDeleted();
2066
2326
  if (cookiesDeleted) {
2067
2327
  logger.info("Cookies deleted detected by cookie watcher");
@@ -2125,9 +2385,11 @@ function checkStorageCleared() {
2125
2385
  }
2126
2386
  }
2127
2387
  function handleStorageEvent(event) {
2388
+ if (areWatchersSuspended()) return;
2128
2389
  if (typeof window !== "undefined" && event.storageArea === window.sessionStorage) {
2129
2390
  const wasCleared = checkStorageCleared();
2130
2391
  if (wasCleared) {
2392
+ if (areWatchersSuspended()) return;
2131
2393
  logger.info("Detected manual sessionStorage clear! Redirecting to login...");
2132
2394
  const cb = getSessionClearedCallback();
2133
2395
  if (cb) {
@@ -2147,8 +2409,10 @@ function startStorageWatcher() {
2147
2409
  storageEventListener = handleStorageEvent;
2148
2410
  window.addEventListener("storage", storageEventListener);
2149
2411
  const pollInterval = setInterval(() => {
2412
+ if (areWatchersSuspended()) return;
2150
2413
  const wasCleared = checkStorageCleared();
2151
2414
  if (wasCleared) {
2415
+ if (areWatchersSuspended()) return;
2152
2416
  logger.info("Detected manual sessionStorage clear (polling)! Redirecting to login...");
2153
2417
  clearInterval(pollInterval);
2154
2418
  const cb = getSessionClearedCallback();
@@ -2219,27 +2483,6 @@ function buildUrl(baseUrl, path) {
2219
2483
  url.pathname = url.pathname.endsWith("/") ? `${url.pathname}${normalizedPath.slice(1)}` : `${url.pathname}${normalizedPath}`;
2220
2484
  return url.toString();
2221
2485
  }
2222
- function snapshotHostConfig() {
2223
- try {
2224
- const raw = getStorage(getStorageKey("host_config"));
2225
- if (!raw) return null;
2226
- const parsed = JSON.parse(raw);
2227
- return parsed && typeof parsed === "object" ? parsed : null;
2228
- } catch {
2229
- return null;
2230
- }
2231
- }
2232
- function restoreHostConfig(config) {
2233
- if (!config) return;
2234
- try {
2235
- setStorage(getStorageKey("host_config"), JSON.stringify(config));
2236
- logger.debug("Restored host_config after logout cleanup");
2237
- } catch (error) {
2238
- logger.warn("Failed to restore host_config after logout cleanup", {
2239
- error: error instanceof Error ? error.message : String(error)
2240
- });
2241
- }
2242
- }
2243
2486
  function getCsrfToken() {
2244
2487
  try {
2245
2488
  if (typeof document === "undefined") return null;
@@ -2254,62 +2497,68 @@ function getCsrfToken() {
2254
2497
  }
2255
2498
  async function callServerLogout(logoutUrl) {
2256
2499
  const csrfToken = getCsrfToken();
2257
- async function doRequest(method) {
2258
- const isPost = method === "POST";
2259
- const headers = {
2260
- "X-Requested-With": "XMLHttpRequest",
2261
- Accept: "application/json, */*"
2262
- };
2263
- if (isPost) {
2264
- headers["Content-Type"] = "application/x-www-form-urlencoded";
2265
- }
2266
- if (csrfToken) {
2267
- headers["X-CSRF-Token"] = csrfToken;
2268
- }
2500
+ const headers = {
2501
+ "X-Requested-With": "XMLHttpRequest",
2502
+ Accept: "application/json, */*",
2503
+ "Content-Type": "application/x-www-form-urlencoded"
2504
+ };
2505
+ if (csrfToken) {
2506
+ headers["X-CSRF-Token"] = csrfToken;
2507
+ }
2508
+ try {
2269
2509
  const response = await fetch(logoutUrl, {
2270
- method,
2510
+ method: "POST",
2271
2511
  mode: "cors",
2272
2512
  headers,
2273
- credentials: "include"
2513
+ credentials: "include",
2514
+ body: ""
2274
2515
  });
2275
2516
  if (!response.ok) {
2276
- logger.warn("Server logout HTTP error", {
2277
- method,
2517
+ logger.warn("[logout] Server logout HTTP error", {
2278
2518
  status: response.status,
2279
2519
  statusText: response.statusText
2280
2520
  });
2281
2521
  return { success: false };
2282
2522
  }
2283
- const data = await response.json().catch(() => ({}));
2284
- logger.info(`Server logout response (${method})`, { response: data });
2523
+ const contentType = response.headers.get("content-type") || "";
2524
+ if (!contentType.includes("application/json")) {
2525
+ logger.info("[logout] Server logout: OK without JSON body", {
2526
+ status: response.status
2527
+ });
2528
+ return { success: true, response: { success: true } };
2529
+ }
2530
+ const text = await response.text();
2531
+ let data = {};
2532
+ if (text?.trim()) {
2533
+ try {
2534
+ data = JSON.parse(text);
2535
+ } catch {
2536
+ logger.warn("[logout] Server returned non-JSON body with JSON content-type");
2537
+ return { success: false };
2538
+ }
2539
+ }
2540
+ logger.info("[logout] Server logout response", { response: data });
2285
2541
  if (data && (data.success === true || data.logout === true)) {
2286
2542
  return { success: true, response: data };
2287
2543
  }
2288
- logger.warn("Server logout response invalid payload", {
2289
- method,
2290
- response: data
2291
- });
2544
+ if (!text || text.trim() === "") {
2545
+ return { success: true, response: { success: true } };
2546
+ }
2547
+ logger.warn("[logout] Server logout: unexpected JSON payload", { response: data });
2292
2548
  return { success: false, response: data };
2293
- }
2294
- try {
2295
- const postResult = await doRequest("POST");
2296
- if (postResult.success) return postResult;
2297
- logger.debug("POST logout did not succeed, trying GET");
2298
- const getResult = await doRequest("GET");
2299
- return getResult;
2300
2549
  } catch (error) {
2301
- logger.warn("Server logout failed (both POST and GET)", {
2550
+ logger.warn("[logout] Server logout request failed", {
2302
2551
  error: error instanceof Error ? error.message : String(error)
2303
2552
  });
2304
2553
  return { success: false };
2305
2554
  }
2306
2555
  }
2307
2556
  function performLocalLogoutCleanup() {
2308
- logger.debug("Performing local logout cleanup");
2557
+ logger.debug("[logout] Local cleanup: cookies + auth storage");
2309
2558
  try {
2310
2559
  getAuthRuntime()?.clearCookies?.clearCookies();
2311
2560
  } catch (error) {
2312
- logger.debug("Runtime cookie clearer failed (ignored)", {
2561
+ logger.debug("[logout] Runtime cookie clearer failed (ignored)", {
2313
2562
  error: error instanceof Error ? error.message : String(error)
2314
2563
  });
2315
2564
  }
@@ -2317,11 +2566,11 @@ function performLocalLogoutCleanup() {
2317
2566
  try {
2318
2567
  clearAuthStorage();
2319
2568
  } catch (error) {
2320
- logger.warn("clearAuthStorage failed (ignored)", {
2569
+ logger.warn("[logout] clearAuthStorage failed", {
2321
2570
  error: error instanceof Error ? error.message : String(error)
2322
2571
  });
2323
2572
  }
2324
- logger.info("Local logout cleanup completed");
2573
+ logger.info("[logout] Local cleanup completed");
2325
2574
  }
2326
2575
  function acquireLogoutLock() {
2327
2576
  const LOCK_KEY = getStorageKey("logout_lock");
@@ -2330,7 +2579,7 @@ function acquireLogoutLock() {
2330
2579
  if (existingLock) {
2331
2580
  const lockTime = Number(existingLock);
2332
2581
  if (!Number.isNaN(lockTime) && now - lockTime < 5e3) {
2333
- logger.debug("Logout lock active \u2013 skipping duplicate logout", {
2582
+ logger.debug("[logout] Lock active \u2014 duplicate tab logout skipped", {
2334
2583
  now,
2335
2584
  lockTime
2336
2585
  });
@@ -2346,72 +2595,95 @@ function releaseLogoutLock() {
2346
2595
  } catch {
2347
2596
  }
2348
2597
  }
2349
- async function logout(tenantKey) {
2350
- const preservedHostConfig = snapshotHostConfig();
2351
- if (tenantKey?.trim()) {
2352
- const updated = {
2353
- ...preservedHostConfig ?? {},
2354
- tenantKey: tenantKey.trim(),
2355
- tenantCode: tenantKey.trim()
2356
- };
2357
- setStorage(getStorageKey("host_config"), JSON.stringify(updated));
2358
- }
2598
+ var logoutInFlight = null;
2599
+ async function runLogoutPipeline(tenantKey) {
2600
+ const gen = suspendWatchers();
2601
+ logger.info("[logout] Lifecycle: suspend watchers", { generation: gen });
2602
+ stopStorageWatcher();
2603
+ stopCookieWatcher();
2359
2604
  if (!acquireLogoutLock()) {
2360
- return { success: true, message: "Logout already in progress." };
2605
+ resumeWatchers(gen);
2606
+ logger.debug("[logout] Lifecycle: resume (skipped \u2014 lock)");
2607
+ return { success: true, message: "Logout already in progress in another tab." };
2361
2608
  }
2609
+ let serverLogoutSuccess = false;
2610
+ let serverResponse;
2362
2611
  try {
2363
- stopStorageWatcher();
2364
- stopCookieWatcher();
2365
- const authConfig = getAuthConfig();
2366
- const apiAuthBaseUrl = authConfig.authServerUrl || Config.getAuthServerUrl();
2367
- let serverLogoutSuccess = false;
2368
- let serverResponse;
2612
+ if (tenantKey?.trim()) {
2613
+ applyRuntimeTenantSwitch(tenantKey.trim(), { syncStorageCache: true });
2614
+ logger.info("[logout] Runtime tenant applied for logout context", {
2615
+ tenantKey: tenantKey.trim()
2616
+ });
2617
+ }
2618
+ const authConfig = getAuthConfig2();
2619
+ const apiAuthBaseUrl = authConfig.authServerUrl;
2369
2620
  if (apiAuthBaseUrl) {
2370
2621
  const logoutUrl = buildUrl(apiAuthBaseUrl, "account/logout");
2371
- logger.info("Calling auth server logout", { logoutUrl });
2622
+ logger.info("[logout] Calling auth server logout (POST)", { logoutUrl });
2372
2623
  const logoutResult = await callServerLogout(logoutUrl);
2373
2624
  serverLogoutSuccess = logoutResult.success;
2374
2625
  serverResponse = logoutResult.response;
2375
2626
  if (!serverLogoutSuccess) {
2376
- logger.warn("Server logout failed \u2013 session may still exist", {
2627
+ logger.warn("[logout] Server logout did not confirm success \u2014 clearing local session anyway", {
2377
2628
  logoutUrl,
2378
2629
  serverResponse
2379
2630
  });
2380
2631
  }
2381
2632
  } else {
2382
- logger.warn("API auth base URL not configured, performing local logout only");
2633
+ logger.warn("[logout] authServerUrl missing \u2014 local logout only");
2383
2634
  }
2635
+ } catch (error) {
2636
+ logger.error("[logout] Unexpected error before local cleanup", {
2637
+ error: error instanceof Error ? error.message : String(error)
2638
+ });
2639
+ }
2640
+ try {
2384
2641
  performLocalLogoutCleanup();
2385
- restoreHostConfig(preservedHostConfig);
2386
- resetCookieWatcher();
2387
- startStorageWatcher();
2388
- releaseLogoutLock();
2389
- logger.info("Logout completed \u2013 flags set, storage cleared, fresh login required", {
2390
- serverLogoutSuccess,
2391
- serverResponse
2642
+ } catch (error) {
2643
+ logger.error("[logout] Local cleanup threw", {
2644
+ error: error instanceof Error ? error.message : String(error)
2392
2645
  });
2393
- emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
2394
- serverLogoutSuccess,
2395
- tenantKey: tenantKey?.trim() || preservedHostConfig?.tenantKey || void 0,
2396
- message: serverLogoutSuccess ? "Server logout success" : "Local logout only"
2646
+ }
2647
+ try {
2648
+ syncHostConfigCacheFromMemory();
2649
+ logger.debug("[logout] host_config cache refreshed from runtime (not restored from snapshot)");
2650
+ } catch (error) {
2651
+ logger.warn("[logout] Failed to sync host config cache", {
2652
+ error: error instanceof Error ? error.message : String(error)
2397
2653
  });
2398
- return {
2399
- success: true,
2400
- message: serverLogoutSuccess ? serverResponse?.message || "Logout successful. Session cookies cleared. Fresh login required." : "Logged out locally. Fresh login required (server logout may have failed).",
2401
- serverResponse
2402
- };
2654
+ }
2655
+ try {
2656
+ resetCookieWatcher();
2657
+ startStorageWatcher();
2658
+ logger.info("[logout] Watchers restarted");
2403
2659
  } catch (error) {
2404
- logger.error("Logout failed unexpectedly", {
2660
+ logger.warn("[logout] Watcher restart failed", {
2405
2661
  error: error instanceof Error ? error.message : String(error)
2406
2662
  });
2407
- performLocalLogoutCleanup();
2408
- restoreHostConfig(preservedHostConfig);
2409
- releaseLogoutLock();
2410
- return {
2411
- success: true,
2412
- message: "Logged out locally due to an error. Fresh login required."
2413
- };
2414
2663
  }
2664
+ releaseLogoutLock();
2665
+ resumeWatchers(gen);
2666
+ logger.info("[logout] Lifecycle: complete", { serverLogoutSuccess });
2667
+ emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
2668
+ serverLogoutSuccess,
2669
+ tenantKey: tenantKey?.trim() || getAuthConfig2().tenantCode || void 0,
2670
+ message: serverLogoutSuccess ? "Server logout success" : "Local logout (server may be pending)"
2671
+ });
2672
+ return {
2673
+ success: true,
2674
+ message: serverLogoutSuccess ? serverResponse?.message || "Logout successful. Fresh login required." : "Logged out locally. Fresh login required (server logout may have failed).",
2675
+ serverResponse
2676
+ };
2677
+ }
2678
+ async function logout(tenantKey) {
2679
+ if (logoutInFlight) {
2680
+ logger.debug("[logout] Coalescing with in-flight logout");
2681
+ return logoutInFlight;
2682
+ }
2683
+ logoutInFlight = runLogoutPipeline(tenantKey).finally(() => {
2684
+ logoutInFlight = null;
2685
+ });
2686
+ return logoutInFlight;
2415
2687
  }
2416
2688
  var AuthContext = react.createContext(void 0);
2417
2689
  function useAuthContext() {
@@ -2486,7 +2758,6 @@ async function resolveTenant(tenantKey) {
2486
2758
  scopes: DEFAULT_SCOPES2
2487
2759
  }
2488
2760
  };
2489
- console.log("config====", config);
2490
2761
  logger.debug("Resolved tenant config", { tenantCode });
2491
2762
  return config;
2492
2763
  } catch (error) {
@@ -2747,33 +3018,29 @@ function AuthProvider({
2747
3018
  logger.warn("[AuthProvider] callbackUrl missing from host props");
2748
3019
  }
2749
3020
  setConfig({
2750
- ...mergedTenantCode ? { tenantCode: mergedTenantCode } : {},
3021
+ ...mergedTenantCode ? { tenantCode: mergedTenantCode, tenantKey: mergedTenantCode } : {},
2751
3022
  ...mergedApiBaseUrl ? { apiBaseUrl: mergedApiBaseUrl } : {},
2752
3023
  ...mergedAuthServerUrl ? { authServerUrl: mergedAuthServerUrl } : {},
2753
3024
  ...mergedCallbackUrl ? { callbackUrl: mergedCallbackUrl } : {}
2754
3025
  });
3026
+ logConfigResolutionDebug("AuthProvider host props applied");
2755
3027
  return () => {
2756
3028
  clearConfigOverrides();
2757
3029
  };
2758
3030
  }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
2759
3031
  react.useEffect(() => {
2760
3032
  try {
2761
- const hostConfigKey = getStorageKey("host_config");
2762
- const raw = getStorage(hostConfigKey);
2763
- const existing = raw ? JSON.parse(raw) : {};
2764
- const payload = {
2765
- ...existing,
3033
+ writeHostConfigCache({
2766
3034
  tenantCode: mergedTenantCode ?? "",
2767
3035
  tenantKey: mergedTenantCode ?? "",
2768
3036
  appId: appId ?? "",
2769
3037
  apiBaseUrl: mergedApiBaseUrl ?? "",
2770
3038
  authServerUrl: mergedAuthServerUrl ?? "",
2771
3039
  callbackUrl: mergedCallbackUrl ?? ""
2772
- };
2773
- setStorage(hostConfigKey, JSON.stringify(payload));
3040
+ });
2774
3041
  } catch {
2775
3042
  }
2776
- }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
3043
+ }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl, appId]);
2777
3044
  react.useLayoutEffect(() => {
2778
3045
  setStorageKeyPrefix(resolvedTenantKey ?? "");
2779
3046
  const adapter = storageAdapter !== void 0 && storageAdapter !== null ? storageAdapter : createSecureSessionStorageAdapter(namespace);
@@ -2839,14 +3106,14 @@ function AuthProvider({
2839
3106
  logger.debug("[AuthProvider] TenantKey unchanged", { tenantKey: resolvedTenantKey2 });
2840
3107
  }
2841
3108
  try {
2842
- const hostConfigKey = getStorageKey("host_config");
2843
- const raw = getStorage(hostConfigKey);
2844
- const existing = raw ? JSON.parse(raw) : {};
2845
- setStorage(hostConfigKey, JSON.stringify({
2846
- ...existing,
3109
+ writeHostConfigCache({
2847
3110
  tenantKey: resolvedTenantKey2,
2848
3111
  tenantCode: resolvedTenantKey2
2849
- }));
3112
+ });
3113
+ logConfigResolutionDebug("AuthProvider tenant key synced to cache", {
3114
+ tenantCode: resolvedTenantKey2,
3115
+ tenantKey: resolvedTenantKey2
3116
+ });
2850
3117
  } catch {
2851
3118
  }
2852
3119
  setStorage(getStorageKey("tenant_key"), resolvedTenantKey2);
@@ -3251,6 +3518,7 @@ function useAuthInit() {
3251
3518
  }
3252
3519
 
3253
3520
  exports.AuthCallback = AuthCallback;
3521
+ exports.AuthConfigError = AuthConfigError;
3254
3522
  exports.AuthEventNames = AuthEventNames;
3255
3523
  exports.AuthProvider = AuthProvider;
3256
3524
  exports.Config = Config;
@@ -3259,6 +3527,8 @@ exports.ProtectedRoute = ProtectedRoute;
3259
3527
  exports.STORAGE_KEYS = STORAGE_KEYS2;
3260
3528
  exports.STORAGE_KEY_PREFIX = STORAGE_KEY_PREFIX;
3261
3529
  exports.SanitizedError = SanitizedError;
3530
+ exports.applyRuntimeTenantSwitch = applyRuntimeTenantSwitch;
3531
+ exports.areWatchersSuspended = areWatchersSuspended;
3262
3532
  exports.bootstrap = bootstrap;
3263
3533
  exports.buildStorageKey = buildStorageKey;
3264
3534
  exports.createAuthEngine = createAuthEngine;
@@ -3271,28 +3541,41 @@ exports.enforceHttps = enforceHttps;
3271
3541
  exports.ensureValidAccessToken = ensureValidAccessToken;
3272
3542
  exports.extractTenantKey = extractTenantKey;
3273
3543
  exports.getAccessToken = getAccessToken2;
3274
- exports.getAuthConfig = getAuthConfig;
3544
+ exports.getAuthConfig = getAuthConfig2;
3275
3545
  exports.getAuthRuntime = getAuthRuntime;
3276
3546
  exports.getConfig = getConfig;
3547
+ exports.getConfigFieldSources = getConfigFieldSources;
3548
+ exports.getEffectiveTenantCode = getEffectiveTenantCode;
3549
+ exports.getEffectiveTenantKey = getEffectiveTenantKey;
3277
3550
  exports.getStorageAdapter = getStorageAdapter;
3278
3551
  exports.getStorageNamespace = getStorageNamespace;
3279
3552
  exports.getStoredAccessToken = getAccessToken;
3553
+ exports.initPlugin = initPlugin;
3280
3554
  exports.isAccessTokenExpired = isAccessTokenExpired;
3281
3555
  exports.isCallbackUrl = isCallbackUrl;
3556
+ exports.logConfigResolutionDebug = logConfigResolutionDebug;
3282
3557
  exports.logout = logout;
3283
3558
  exports.refreshAccessToken = refreshAccessToken;
3559
+ exports.requireAuthConfig = requireAuthConfig;
3284
3560
  exports.resetCookieWatcher = resetCookieWatcher;
3285
3561
  exports.resolveConfig = resolveConfig;
3286
3562
  exports.resolveTenant = resolveTenant;
3563
+ exports.resumeWatchers = resumeWatchers;
3287
3564
  exports.sanitizeErrorMessage = sanitizeErrorMessage;
3288
3565
  exports.setAuthRuntime = setAuthRuntime;
3289
3566
  exports.setConfig = setConfig;
3567
+ exports.setHostConfigProvider = setHostConfigProvider;
3290
3568
  exports.setStorageContext = setStorageContext;
3291
3569
  exports.startCookieWatcher = startCookieWatcher;
3570
+ exports.startStorageWatcher = startStorageWatcher;
3292
3571
  exports.stopCookieWatcher = stopCookieWatcher;
3572
+ exports.stopStorageWatcher = stopStorageWatcher;
3293
3573
  exports.subscribeAuthEvent = subscribeAuthEvent;
3574
+ exports.suspendWatchers = suspendWatchers;
3575
+ exports.syncHostConfigCacheFromMemory = syncHostConfigCacheFromMemory;
3294
3576
  exports.useAuth = useAuth;
3295
3577
  exports.useAuthContext = useAuthContext;
3296
3578
  exports.useAuthInit = useAuthInit;
3579
+ exports.writeHostConfigCache = writeHostConfigCache;
3297
3580
  //# sourceMappingURL=index.js.map
3298
3581
  //# sourceMappingURL=index.js.map