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