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