tenneo-auth-plugin 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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) {
@@ -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,158 @@ 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
+ const token = getAccessToken();
1442
+ if (token && !isAccessTokenExpired()) {
1443
+ logger.debug("Valid token already exists, skipping auth flow");
1444
+ clearFlowFlags();
1445
+ return;
1446
+ }
1447
+ if (isCallbackUrl()) {
1448
+ logger.debug("Callback URL detected - clearing flow flag to allow callback processing");
1449
+ clearFlowFlags();
1450
+ return;
1451
+ }
1452
+ if (isFlowInProgress()) {
1453
+ if (isFlowFlagStale(3e3)) {
1454
+ logger.info("Stale auth flow flag detected, clearing and proceeding");
1455
+ clearFlowFlags();
1456
+ } else {
1457
+ logger.debug("Auth flow already in progress (recent), skipping duplicate redirect");
1458
+ return;
1459
+ }
1460
+ }
1461
+ setFlowFlags();
1462
+ if (!(Config.getAuthServerUrl() || config?.authServer) || !config.client?.clientId || !config.client?.redirectUri) {
1463
+ logger.error("[initiateAuthFlow] Missing required tenant config", {
1464
+ authServerEffective,
1465
+ clientId: config?.client?.clientId,
1466
+ redirectUriFromTenantApi: config?.client?.redirectUri,
1467
+ redirectUriEffective
1468
+ });
1469
+ throw new Error("Invalid tenant configuration for auth flow");
1470
+ }
1471
+ storeTenantConfig(config);
1472
+ logger.debug("[initiateAuthFlow] Stored tenant config, generating PKCE");
1473
+ removeStorage(getStorageKey("callback_processed"));
1474
+ removeStorage(getStorageKey("state_mismatch"));
1475
+ clearCallbackProcessing();
1476
+ const codeVerifier = generateCodeVerifier();
1477
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
1478
+ const state = generateState();
1479
+ removeStorage(STORAGE_KEYS.codeVerifier);
1480
+ removeStorage(STORAGE_KEYS.state);
1481
+ setStorage(STORAGE_KEYS.codeVerifier, codeVerifier);
1482
+ setStorage(STORAGE_KEYS.state, state);
1483
+ const authUrl = buildAuthorizationUrl(config, codeChallenge, state);
1484
+ logger.debug("[initiateAuthFlow] Built authorization URL", {
1485
+ authServerEffective,
1486
+ urlPrefix: authUrl?.slice(0, 80)
1487
+ });
1488
+ if (!authUrl || !(authUrl.startsWith("http://") || authUrl.startsWith("https://"))) {
1489
+ throw new Error("Invalid authorization URL generated");
1490
+ }
1491
+ if (!checkAndIncrementRedirect()) {
1492
+ logger.warn("Redirect loop protection: max redirect attempts exceeded");
1493
+ clearFlowFlags();
1494
+ return;
1495
+ }
1496
+ logger.info("Redirecting to authorization server");
1497
+ emitAuthEvent(AuthEventNames.LOGIN_STARTED, { tenantKey: config.tenantKey });
1498
+ const runtime = getAuthRuntime();
1499
+ if (runtime) {
1500
+ logger.debug("[initiateAuthFlow] Redirect via runtime.urlProvider.redirect");
1501
+ runtime.urlProvider.redirect(authUrl);
1502
+ } else if (typeof window !== "undefined") {
1503
+ logger.debug("[initiateAuthFlow] Redirect via window.location.replace");
1504
+ window.location.replace(authUrl);
1505
+ } else {
1506
+ logger.error("[initiateAuthFlow] No runtime and no window; cannot redirect", { authUrl });
1507
+ }
1508
+ } catch (error) {
1509
+ clearFlowFlags();
1510
+ logger.error("Failed to initiate auth flow", {
1511
+ error: error instanceof Error ? error.message : String(error)
1512
+ });
1513
+ throw error;
1514
+ }
1515
+ }
1516
+ function buildAuthorizationUrl(config, codeChallenge, state) {
1517
+ const authServerBase = (Config.getAuthServerUrl() || config.authServer || "").replace(/\/+$/, "");
1518
+ if (isLocalhost2()) {
1519
+ logger.debug("Building authorization URL", {
1520
+ authServer: authServerBase,
1521
+ clientId: config.client.clientId,
1522
+ redirectUri: config.client.redirectUri,
1523
+ scopes: config.client.scopes
1524
+ });
1525
+ }
1526
+ const tenantIdToUse = config.tenantId;
1527
+ const clientIdToUse = config.client.clientId;
1528
+ const redirectUriToUse = Config.getRedirectUri();
1529
+ const params = new URLSearchParams({
1530
+ client_id: `public-${clientIdToUse}`,
1531
+ tenant_id: tenantIdToUse,
1532
+ redirect_uri: redirectUriToUse,
1533
+ response_type: DEFAULT_RESPONSE_TYPE,
1534
+ scope: config.client.scopes,
1535
+ state,
1536
+ code_challenge: codeChallenge,
1537
+ code_challenge_method: DEFAULT_CODE_CHALLENGE_METHOD
1538
+ });
1539
+ return `${authServerBase}/connect/authorize?${params.toString()}`;
1540
+ }
1541
+ function isProcessed() {
1542
+ return getStorage(getStorageKey("callback_processed")) === "true";
1543
+ }
1544
+ function isProcessing() {
1545
+ return isCallbackProcessing();
1546
+ }
1547
+ function markProcessing() {
1548
+ setCallbackProcessing();
1549
+ }
1550
+ function markProcessed() {
1551
+ setStorage(getStorageKey("callback_processed"), "true");
1552
+ clearCallbackProcessing();
1172
1553
  }
1173
1554
  function clearProcessing() {
1174
1555
  clearCallbackProcessing();
@@ -1187,6 +1568,38 @@ function cleanUrlSoft() {
1187
1568
  function clearAllAuthFlags() {
1188
1569
  clearFlowFlags();
1189
1570
  }
1571
+ async function tryRecoverOAuthConfigFromTenant() {
1572
+ const runtime = getAuthRuntime();
1573
+ const tenantKey = (resolveTenantKey() ?? getTenantKey() ?? "").trim();
1574
+ if (!runtime?.tenantResolver || !tenantKey) {
1575
+ logger.debug("[Callback] OAuth config recovery skipped \u2014 no tenantResolver or tenantKey", {
1576
+ hasResolver: !!runtime?.tenantResolver,
1577
+ tenantKey: tenantKey || "(empty)"
1578
+ });
1579
+ return null;
1580
+ }
1581
+ try {
1582
+ const tc = await runtime.tenantResolver.resolve(tenantKey);
1583
+ if (!tc?.client?.clientId || !tc.tenantId) {
1584
+ logger.warn("[Callback] OAuth config recovery: tenant resolve missing client or tenantId", {
1585
+ tenantKey
1586
+ });
1587
+ return null;
1588
+ }
1589
+ storeTenantConfig(tc);
1590
+ return {
1591
+ clientId: tc.client.clientId.trim() || null,
1592
+ redirectUri: tc.client.redirectUri?.trim() || getRedirectUri(),
1593
+ tenantId: tc.tenantId.trim() || null
1594
+ };
1595
+ } catch (e) {
1596
+ logger.warn("[Callback] OAuth config recovery: tenant re-resolve failed", {
1597
+ tenantKey,
1598
+ error: e instanceof Error ? e.message : String(e)
1599
+ });
1600
+ return null;
1601
+ }
1602
+ }
1190
1603
  async function handleCallback() {
1191
1604
  const runtimeSearch = getAuthRuntime()?.urlProvider.getCurrentUrl().search;
1192
1605
  const windowSearch = typeof window !== "undefined" ? window.location.search : "";
@@ -1269,9 +1682,22 @@ async function handleCallback() {
1269
1682
  cleanUrlSoft();
1270
1683
  return false;
1271
1684
  }
1272
- const clientId = getClientId();
1273
- const redirectUri = getRedirectUri();
1274
- const tenantId = getTenantId();
1685
+ let clientId = getClientId();
1686
+ let redirectUri = getRedirectUri();
1687
+ let tenantId = getTenantId();
1688
+ if (!clientId || !redirectUri || !tenantId) {
1689
+ logger.warn("[Callback] OAuth config incomplete \u2014 attempting tenant re-resolve", {
1690
+ hasClientId: !!clientId,
1691
+ hasRedirectUri: !!redirectUri,
1692
+ hasTenantId: !!tenantId
1693
+ });
1694
+ const recovered = await tryRecoverOAuthConfigFromTenant();
1695
+ if (recovered) {
1696
+ if (!redirectUri) redirectUri = recovered.redirectUri;
1697
+ if (!tenantId) tenantId = recovered.tenantId;
1698
+ clientId = getClientId() ?? normalizeClientIdForOAuthTokenBody(recovered.clientId) ?? null;
1699
+ }
1700
+ }
1275
1701
  if (!clientId || !redirectUri || !tenantId) {
1276
1702
  logger.error("[Callback] Early exit: missing OAuth config during callback", {
1277
1703
  hasClientId: !!clientId,
@@ -1329,320 +1755,120 @@ async function handleCallback() {
1329
1755
  if (status === 400 && errorCode === "invalid_grant" && (String(errorDescription).includes("already used") || String(errorDescription).includes("Code not found") || String(errorDescription).includes("expired, already used"))) {
1330
1756
  const existingToken2 = getAccessToken();
1331
1757
  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;
1758
+ if (tokenValid2) {
1759
+ removeStorage(STORAGE_KEYS.codeVerifier);
1760
+ removeStorage(STORAGE_KEYS.state);
1761
+ clearAllAuthFlags();
1762
+ markProcessed();
1763
+ cleanUrlSoft();
1764
+ logger.info("OAuth callback processed successfully (code already used, but token exists)");
1765
+ resetRedirectLoopGuard();
1766
+ emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1767
+ return true;
1768
+ }
1565
1769
  }
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
1770
+ emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1771
+ error: err.message,
1772
+ message: responseData?.error_description ?? err.message
1773
+ });
1774
+ logger.error("OAuth callback failed (Axios error)", {
1775
+ status,
1776
+ statusText: err.response?.statusText,
1777
+ responseData: err.response?.data,
1778
+ message: err.message,
1779
+ url: err.config?.url
1780
+ });
1781
+ } else {
1782
+ logger.error("OAuth callback failed", {
1783
+ error: err instanceof Error ? err.message : String(err)
1574
1784
  });
1575
- throw new Error("Invalid tenant configuration for auth flow");
1576
1785
  }
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");
1786
+ clearAllAuthFlags();
1787
+ const existingToken = getAccessToken();
1788
+ const tokenValid = existingToken && !isAccessTokenExpired();
1789
+ if (tokenValid) {
1790
+ markProcessed();
1791
+ cleanUrlSoft();
1792
+ resetRedirectLoopGuard();
1793
+ emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1794
+ return true;
1596
1795
  }
1597
- if (!checkAndIncrementRedirect()) {
1598
- logger.warn("Redirect loop protection: max redirect attempts exceeded");
1599
- clearFlowFlags();
1600
- return;
1796
+ emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1797
+ error: err instanceof Error ? err.message : String(err)
1798
+ });
1799
+ clearProcessing();
1800
+ if (isCallbackUrl()) cleanUrlSoft();
1801
+ return false;
1802
+ }
1803
+ }
1804
+
1805
+ // src/application/flows/CallbackFlow.ts
1806
+ var CallbackFlow = class {
1807
+ async execute(context) {
1808
+ const { runtime } = context;
1809
+ const { urlProvider, logger: log } = runtime;
1810
+ const getUrl = () => urlProvider.getCurrentUrl();
1811
+ clearFlowFlags();
1812
+ const handled = await handleCallback();
1813
+ if (handled) {
1814
+ if (isCallbackUrlFromUrl(getUrl())) {
1815
+ if (isCallbackProcessing()) return { completed: true };
1816
+ const u = getUrl();
1817
+ urlProvider.redirect(`${u.origin}${u.pathname}`);
1818
+ return { completed: true };
1819
+ }
1820
+ return { completed: true };
1601
1821
  }
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 });
1822
+ if (isCallbackUrlFromUrl(getUrl())) {
1823
+ const u = getUrl();
1824
+ urlProvider.redirect(`${u.origin}${u.pathname}`);
1613
1825
  }
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;
1826
+ return { completed: true };
1620
1827
  }
1828
+ };
1829
+
1830
+ // src/application/flows/AuthenticatedFlow.ts
1831
+ var AuthenticatedFlow = class {
1832
+ async execute(_context) {
1833
+ return { completed: true };
1834
+ }
1835
+ };
1836
+
1837
+ // src/application/engine/defaultEngine.ts
1838
+ var defaultEngine = null;
1839
+ function getDefaultAuthEngine() {
1840
+ return defaultEngine;
1621
1841
  }
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
- });
1842
+ function setDefaultAuthEngine(engine) {
1843
+ defaultEngine = engine;
1844
+ }
1845
+
1846
+ // src/application/token.ts
1847
+ var lastNoRefreshLog = 0;
1848
+ var NO_REFRESH_LOG_INTERVAL_MS = 5e3;
1849
+ async function ensureValidAccessToken() {
1850
+ const engine = getDefaultAuthEngine();
1851
+ if (engine) {
1852
+ try {
1853
+ return await engine.getAccessToken();
1854
+ } catch {
1855
+ }
1631
1856
  }
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()}`;
1857
+ const token = getAccessToken();
1858
+ if (token && !isAccessTokenExpired()) return token;
1859
+ const refreshToken = getRefreshToken();
1860
+ if (!refreshToken) {
1861
+ const now = Date.now();
1862
+ if (now - lastNoRefreshLog >= NO_REFRESH_LOG_INTERVAL_MS) {
1863
+ lastNoRefreshLog = now;
1864
+ logger.debug("No refresh token - cannot refresh; login required");
1865
+ }
1866
+ throw new Error("No refresh token; login required");
1867
+ }
1868
+ return await refreshAccessToken();
1869
+ }
1870
+ function getAccessToken2() {
1871
+ return getAccessToken();
1646
1872
  }
1647
1873
 
1648
1874
  // src/application/flows/RefreshFlow.ts
@@ -1723,13 +1949,18 @@ var TenantSwitchFlow = class {
1723
1949
  );
1724
1950
  }
1725
1951
  try {
1726
- const cfg = getAuthConfig();
1727
- setStorage(getStorageKey("host_config"), JSON.stringify({
1728
- ...cfg,
1952
+ const cfg = getAuthConfig2();
1953
+ writeHostConfigCache({
1954
+ apiBaseUrl: cfg.apiBaseUrl,
1955
+ authServerUrl: cfg.authServerUrl,
1956
+ callbackUrl: cfg.callbackUrl,
1957
+ appId: cfg.appId,
1958
+ clientCode: tenantConfig.client?.clientCode ?? cfg.clientCode ?? "",
1959
+ clientId: cfg.clientId || tenantConfig.client?.clientId,
1960
+ tenantId: tenantConfig.tenantId ?? cfg.tenantId,
1729
1961
  tenantKey,
1730
- tenantCode: tenantKey,
1731
- clientCode: tenantConfig.client?.clientCode ?? cfg.clientCode ?? ""
1732
- }));
1962
+ tenantCode: tenantKey
1963
+ });
1733
1964
  } catch {
1734
1965
  }
1735
1966
  setTenantKey(tenantKey);
@@ -1893,6 +2124,22 @@ async function bootstrap() {
1893
2124
  return bootstrapImpl();
1894
2125
  }
1895
2126
 
2127
+ // src/infrastructure/watchers/watcher-guard.ts
2128
+ var suspended = false;
2129
+ var generation = 0;
2130
+ function suspendWatchers() {
2131
+ suspended = true;
2132
+ generation += 1;
2133
+ return generation;
2134
+ }
2135
+ function resumeWatchers(expectedGen) {
2136
+ if (expectedGen !== void 0 && expectedGen !== generation) return;
2137
+ suspended = false;
2138
+ }
2139
+ function areWatchersSuspended() {
2140
+ return suspended;
2141
+ }
2142
+
1896
2143
  // src/strategies/cookie/cookie-auth.ts
1897
2144
  var COOKIE_NAMES = {
1898
2145
  accessToken: "tenneo_auth_access_token",
@@ -2019,6 +2266,10 @@ function checkCookiesDeleted() {
2019
2266
  }
2020
2267
  }
2021
2268
  function handleCookieDeletion() {
2269
+ if (areWatchersSuspended()) {
2270
+ logger.debug("Cookie deletion handler skipped \u2014 watchers suspended (logout/teardown)");
2271
+ return;
2272
+ }
2022
2273
  try {
2023
2274
  logger.info("Cookies deleted detected - clearing session and redirecting to login");
2024
2275
  clearAuthStorage();
@@ -2056,6 +2307,7 @@ function startCookieWatcher() {
2056
2307
  allCookies: initialCookies
2057
2308
  });
2058
2309
  watchInterval = setInterval(async () => {
2310
+ if (areWatchersSuspended()) return;
2059
2311
  const cookiesDeleted = checkCookiesDeleted();
2060
2312
  if (cookiesDeleted) {
2061
2313
  logger.info("Cookies deleted detected by cookie watcher");
@@ -2119,9 +2371,11 @@ function checkStorageCleared() {
2119
2371
  }
2120
2372
  }
2121
2373
  function handleStorageEvent(event) {
2374
+ if (areWatchersSuspended()) return;
2122
2375
  if (typeof window !== "undefined" && event.storageArea === window.sessionStorage) {
2123
2376
  const wasCleared = checkStorageCleared();
2124
2377
  if (wasCleared) {
2378
+ if (areWatchersSuspended()) return;
2125
2379
  logger.info("Detected manual sessionStorage clear! Redirecting to login...");
2126
2380
  const cb = getSessionClearedCallback();
2127
2381
  if (cb) {
@@ -2141,8 +2395,10 @@ function startStorageWatcher() {
2141
2395
  storageEventListener = handleStorageEvent;
2142
2396
  window.addEventListener("storage", storageEventListener);
2143
2397
  const pollInterval = setInterval(() => {
2398
+ if (areWatchersSuspended()) return;
2144
2399
  const wasCleared = checkStorageCleared();
2145
2400
  if (wasCleared) {
2401
+ if (areWatchersSuspended()) return;
2146
2402
  logger.info("Detected manual sessionStorage clear (polling)! Redirecting to login...");
2147
2403
  clearInterval(pollInterval);
2148
2404
  const cb = getSessionClearedCallback();
@@ -2213,27 +2469,6 @@ function buildUrl(baseUrl, path) {
2213
2469
  url.pathname = url.pathname.endsWith("/") ? `${url.pathname}${normalizedPath.slice(1)}` : `${url.pathname}${normalizedPath}`;
2214
2470
  return url.toString();
2215
2471
  }
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
2472
  function getCsrfToken() {
2238
2473
  try {
2239
2474
  if (typeof document === "undefined") return null;
@@ -2248,62 +2483,68 @@ function getCsrfToken() {
2248
2483
  }
2249
2484
  async function callServerLogout(logoutUrl) {
2250
2485
  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
- }
2486
+ const headers = {
2487
+ "X-Requested-With": "XMLHttpRequest",
2488
+ Accept: "application/json, */*",
2489
+ "Content-Type": "application/x-www-form-urlencoded"
2490
+ };
2491
+ if (csrfToken) {
2492
+ headers["X-CSRF-Token"] = csrfToken;
2493
+ }
2494
+ try {
2263
2495
  const response = await fetch(logoutUrl, {
2264
- method,
2496
+ method: "POST",
2265
2497
  mode: "cors",
2266
2498
  headers,
2267
- credentials: "include"
2499
+ credentials: "include",
2500
+ body: ""
2268
2501
  });
2269
2502
  if (!response.ok) {
2270
- logger.warn("Server logout HTTP error", {
2271
- method,
2503
+ logger.warn("[logout] Server logout HTTP error", {
2272
2504
  status: response.status,
2273
2505
  statusText: response.statusText
2274
2506
  });
2275
2507
  return { success: false };
2276
2508
  }
2277
- const data = await response.json().catch(() => ({}));
2278
- logger.info(`Server logout response (${method})`, { response: data });
2509
+ const contentType = response.headers.get("content-type") || "";
2510
+ if (!contentType.includes("application/json")) {
2511
+ logger.info("[logout] Server logout: OK without JSON body", {
2512
+ status: response.status
2513
+ });
2514
+ return { success: true, response: { success: true } };
2515
+ }
2516
+ const text = await response.text();
2517
+ let data = {};
2518
+ if (text?.trim()) {
2519
+ try {
2520
+ data = JSON.parse(text);
2521
+ } catch {
2522
+ logger.warn("[logout] Server returned non-JSON body with JSON content-type");
2523
+ return { success: false };
2524
+ }
2525
+ }
2526
+ logger.info("[logout] Server logout response", { response: data });
2279
2527
  if (data && (data.success === true || data.logout === true)) {
2280
2528
  return { success: true, response: data };
2281
2529
  }
2282
- logger.warn("Server logout response invalid payload", {
2283
- method,
2284
- response: data
2285
- });
2530
+ if (!text || text.trim() === "") {
2531
+ return { success: true, response: { success: true } };
2532
+ }
2533
+ logger.warn("[logout] Server logout: unexpected JSON payload", { response: data });
2286
2534
  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
2535
  } catch (error) {
2295
- logger.warn("Server logout failed (both POST and GET)", {
2536
+ logger.warn("[logout] Server logout request failed", {
2296
2537
  error: error instanceof Error ? error.message : String(error)
2297
2538
  });
2298
2539
  return { success: false };
2299
2540
  }
2300
2541
  }
2301
2542
  function performLocalLogoutCleanup() {
2302
- logger.debug("Performing local logout cleanup");
2543
+ logger.debug("[logout] Local cleanup: cookies + auth storage");
2303
2544
  try {
2304
2545
  getAuthRuntime()?.clearCookies?.clearCookies();
2305
2546
  } catch (error) {
2306
- logger.debug("Runtime cookie clearer failed (ignored)", {
2547
+ logger.debug("[logout] Runtime cookie clearer failed (ignored)", {
2307
2548
  error: error instanceof Error ? error.message : String(error)
2308
2549
  });
2309
2550
  }
@@ -2311,11 +2552,11 @@ function performLocalLogoutCleanup() {
2311
2552
  try {
2312
2553
  clearAuthStorage();
2313
2554
  } catch (error) {
2314
- logger.warn("clearAuthStorage failed (ignored)", {
2555
+ logger.warn("[logout] clearAuthStorage failed", {
2315
2556
  error: error instanceof Error ? error.message : String(error)
2316
2557
  });
2317
2558
  }
2318
- logger.info("Local logout cleanup completed");
2559
+ logger.info("[logout] Local cleanup completed");
2319
2560
  }
2320
2561
  function acquireLogoutLock() {
2321
2562
  const LOCK_KEY = getStorageKey("logout_lock");
@@ -2324,7 +2565,7 @@ function acquireLogoutLock() {
2324
2565
  if (existingLock) {
2325
2566
  const lockTime = Number(existingLock);
2326
2567
  if (!Number.isNaN(lockTime) && now - lockTime < 5e3) {
2327
- logger.debug("Logout lock active \u2013 skipping duplicate logout", {
2568
+ logger.debug("[logout] Lock active \u2014 duplicate tab logout skipped", {
2328
2569
  now,
2329
2570
  lockTime
2330
2571
  });
@@ -2340,72 +2581,95 @@ function releaseLogoutLock() {
2340
2581
  } catch {
2341
2582
  }
2342
2583
  }
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
- }
2584
+ var logoutInFlight = null;
2585
+ async function runLogoutPipeline(tenantKey) {
2586
+ const gen = suspendWatchers();
2587
+ logger.info("[logout] Lifecycle: suspend watchers", { generation: gen });
2588
+ stopStorageWatcher();
2589
+ stopCookieWatcher();
2353
2590
  if (!acquireLogoutLock()) {
2354
- return { success: true, message: "Logout already in progress." };
2591
+ resumeWatchers(gen);
2592
+ logger.debug("[logout] Lifecycle: resume (skipped \u2014 lock)");
2593
+ return { success: true, message: "Logout already in progress in another tab." };
2355
2594
  }
2595
+ let serverLogoutSuccess = false;
2596
+ let serverResponse;
2356
2597
  try {
2357
- stopStorageWatcher();
2358
- stopCookieWatcher();
2359
- const authConfig = getAuthConfig();
2360
- const apiAuthBaseUrl = authConfig.authServerUrl || Config.getAuthServerUrl();
2361
- let serverLogoutSuccess = false;
2362
- let serverResponse;
2598
+ if (tenantKey?.trim()) {
2599
+ applyRuntimeTenantSwitch(tenantKey.trim(), { syncStorageCache: true });
2600
+ logger.info("[logout] Runtime tenant applied for logout context", {
2601
+ tenantKey: tenantKey.trim()
2602
+ });
2603
+ }
2604
+ const authConfig = getAuthConfig2();
2605
+ const apiAuthBaseUrl = authConfig.authServerUrl;
2363
2606
  if (apiAuthBaseUrl) {
2364
2607
  const logoutUrl = buildUrl(apiAuthBaseUrl, "account/logout");
2365
- logger.info("Calling auth server logout", { logoutUrl });
2608
+ logger.info("[logout] Calling auth server logout (POST)", { logoutUrl });
2366
2609
  const logoutResult = await callServerLogout(logoutUrl);
2367
2610
  serverLogoutSuccess = logoutResult.success;
2368
2611
  serverResponse = logoutResult.response;
2369
2612
  if (!serverLogoutSuccess) {
2370
- logger.warn("Server logout failed \u2013 session may still exist", {
2613
+ logger.warn("[logout] Server logout did not confirm success \u2014 clearing local session anyway", {
2371
2614
  logoutUrl,
2372
2615
  serverResponse
2373
2616
  });
2374
2617
  }
2375
2618
  } else {
2376
- logger.warn("API auth base URL not configured, performing local logout only");
2619
+ logger.warn("[logout] authServerUrl missing \u2014 local logout only");
2377
2620
  }
2621
+ } catch (error) {
2622
+ logger.error("[logout] Unexpected error before local cleanup", {
2623
+ error: error instanceof Error ? error.message : String(error)
2624
+ });
2625
+ }
2626
+ try {
2378
2627
  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
2628
+ } catch (error) {
2629
+ logger.error("[logout] Local cleanup threw", {
2630
+ error: error instanceof Error ? error.message : String(error)
2386
2631
  });
2387
- emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
2388
- serverLogoutSuccess,
2389
- tenantKey: tenantKey?.trim() || preservedHostConfig?.tenantKey || void 0,
2390
- message: serverLogoutSuccess ? "Server logout success" : "Local logout only"
2632
+ }
2633
+ try {
2634
+ syncHostConfigCacheFromMemory();
2635
+ logger.debug("[logout] host_config cache refreshed from runtime (not restored from snapshot)");
2636
+ } catch (error) {
2637
+ logger.warn("[logout] Failed to sync host config cache", {
2638
+ error: error instanceof Error ? error.message : String(error)
2391
2639
  });
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
- };
2640
+ }
2641
+ try {
2642
+ resetCookieWatcher();
2643
+ startStorageWatcher();
2644
+ logger.info("[logout] Watchers restarted");
2397
2645
  } catch (error) {
2398
- logger.error("Logout failed unexpectedly", {
2646
+ logger.warn("[logout] Watcher restart failed", {
2399
2647
  error: error instanceof Error ? error.message : String(error)
2400
2648
  });
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
2649
  }
2650
+ releaseLogoutLock();
2651
+ resumeWatchers(gen);
2652
+ logger.info("[logout] Lifecycle: complete", { serverLogoutSuccess });
2653
+ emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
2654
+ serverLogoutSuccess,
2655
+ tenantKey: tenantKey?.trim() || getAuthConfig2().tenantCode || void 0,
2656
+ message: serverLogoutSuccess ? "Server logout success" : "Local logout (server may be pending)"
2657
+ });
2658
+ return {
2659
+ success: true,
2660
+ message: serverLogoutSuccess ? serverResponse?.message || "Logout successful. Fresh login required." : "Logged out locally. Fresh login required (server logout may have failed).",
2661
+ serverResponse
2662
+ };
2663
+ }
2664
+ async function logout(tenantKey) {
2665
+ if (logoutInFlight) {
2666
+ logger.debug("[logout] Coalescing with in-flight logout");
2667
+ return logoutInFlight;
2668
+ }
2669
+ logoutInFlight = runLogoutPipeline(tenantKey).finally(() => {
2670
+ logoutInFlight = null;
2671
+ });
2672
+ return logoutInFlight;
2409
2673
  }
2410
2674
  var AuthContext = createContext(void 0);
2411
2675
  function useAuthContext() {
@@ -2480,7 +2744,6 @@ async function resolveTenant(tenantKey) {
2480
2744
  scopes: DEFAULT_SCOPES2
2481
2745
  }
2482
2746
  };
2483
- console.log("config====", config);
2484
2747
  logger.debug("Resolved tenant config", { tenantCode });
2485
2748
  return config;
2486
2749
  } catch (error) {
@@ -2741,33 +3004,29 @@ function AuthProvider({
2741
3004
  logger.warn("[AuthProvider] callbackUrl missing from host props");
2742
3005
  }
2743
3006
  setConfig({
2744
- ...mergedTenantCode ? { tenantCode: mergedTenantCode } : {},
3007
+ ...mergedTenantCode ? { tenantCode: mergedTenantCode, tenantKey: mergedTenantCode } : {},
2745
3008
  ...mergedApiBaseUrl ? { apiBaseUrl: mergedApiBaseUrl } : {},
2746
3009
  ...mergedAuthServerUrl ? { authServerUrl: mergedAuthServerUrl } : {},
2747
3010
  ...mergedCallbackUrl ? { callbackUrl: mergedCallbackUrl } : {}
2748
3011
  });
3012
+ logConfigResolutionDebug("AuthProvider host props applied");
2749
3013
  return () => {
2750
3014
  clearConfigOverrides();
2751
3015
  };
2752
3016
  }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
2753
3017
  useEffect(() => {
2754
3018
  try {
2755
- const hostConfigKey = getStorageKey("host_config");
2756
- const raw = getStorage(hostConfigKey);
2757
- const existing = raw ? JSON.parse(raw) : {};
2758
- const payload = {
2759
- ...existing,
3019
+ writeHostConfigCache({
2760
3020
  tenantCode: mergedTenantCode ?? "",
2761
3021
  tenantKey: mergedTenantCode ?? "",
2762
3022
  appId: appId ?? "",
2763
3023
  apiBaseUrl: mergedApiBaseUrl ?? "",
2764
3024
  authServerUrl: mergedAuthServerUrl ?? "",
2765
3025
  callbackUrl: mergedCallbackUrl ?? ""
2766
- };
2767
- setStorage(hostConfigKey, JSON.stringify(payload));
3026
+ });
2768
3027
  } catch {
2769
3028
  }
2770
- }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
3029
+ }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl, appId]);
2771
3030
  useLayoutEffect(() => {
2772
3031
  setStorageKeyPrefix(resolvedTenantKey ?? "");
2773
3032
  const adapter = storageAdapter !== void 0 && storageAdapter !== null ? storageAdapter : createSecureSessionStorageAdapter(namespace);
@@ -2833,14 +3092,14 @@ function AuthProvider({
2833
3092
  logger.debug("[AuthProvider] TenantKey unchanged", { tenantKey: resolvedTenantKey2 });
2834
3093
  }
2835
3094
  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,
3095
+ writeHostConfigCache({
2841
3096
  tenantKey: resolvedTenantKey2,
2842
3097
  tenantCode: resolvedTenantKey2
2843
- }));
3098
+ });
3099
+ logConfigResolutionDebug("AuthProvider tenant key synced to cache", {
3100
+ tenantCode: resolvedTenantKey2,
3101
+ tenantKey: resolvedTenantKey2
3102
+ });
2844
3103
  } catch {
2845
3104
  }
2846
3105
  setStorage(getStorageKey("tenant_key"), resolvedTenantKey2);
@@ -3244,6 +3503,6 @@ function useAuthInit() {
3244
3503
  );
3245
3504
  }
3246
3505
 
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 };
3506
+ 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
3507
  //# sourceMappingURL=index.mjs.map
3249
3508
  //# sourceMappingURL=index.mjs.map