tenneo-auth-plugin 0.1.11 → 1.0.2

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
@@ -15,64 +15,6 @@ function getAuthRuntime() {
15
15
  function isLocalhost(hostname) {
16
16
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.endsWith(".local");
17
17
  }
18
- function extractTenantKeyFromDomain(urlOrHostname, config) {
19
- try {
20
- let hostname;
21
- if (urlOrHostname.startsWith("http://") || urlOrHostname.startsWith("https://")) {
22
- try {
23
- const url = new URL(urlOrHostname);
24
- hostname = url.hostname;
25
- } catch {
26
- hostname = urlOrHostname.replace(/^https?:\/\//, "").split("/")[0];
27
- }
28
- } else {
29
- hostname = urlOrHostname;
30
- }
31
- const parts = hostname.split(".");
32
- if (parts.length < 2) return config.getClientCode();
33
- const tenantKey = parts[0];
34
- if (!tenantKey || tenantKey.trim() === "") return config.getClientCode();
35
- if (tenantKey.trim().toLowerCase() === "productlms") return config.getClientCode();
36
- return tenantKey.trim();
37
- } catch {
38
- return config.getClientCode();
39
- }
40
- }
41
- function extractTenantKeyFromUrl(url, config, getStorage3, getKey) {
42
- try {
43
- const propsConfigKey = getKey ? getKey("props_config") : "tenneo_auth_props_config";
44
- const propsConfigStr = getStorage3(propsConfigKey);
45
- if (propsConfigStr) {
46
- try {
47
- const propsConfig = JSON.parse(propsConfigStr);
48
- if (propsConfig.tenantKey?.trim()) return propsConfig.tenantKey.trim();
49
- } catch {
50
- }
51
- }
52
- const tenantKeyStorageKey = getKey ? getKey("tenant_key") : "tenneo_auth_tenant_key";
53
- const storedTenantKey = getStorage3(tenantKeyStorageKey);
54
- if (storedTenantKey?.trim()) return storedTenantKey.trim();
55
- const urlParams = new URLSearchParams(url.search);
56
- const tenantKeyFromQuery = urlParams.get("t");
57
- if (tenantKeyFromQuery?.trim()) {
58
- const hostnameParts = url.hostname.split(".");
59
- const baseDomain = hostnameParts.length >= 2 ? hostnameParts.slice(-2).join(".") : url.hostname;
60
- const simulatedHostname = `${tenantKeyFromQuery.trim()}.${baseDomain}`;
61
- const key = extractTenantKeyFromDomain(simulatedHostname, config);
62
- if (key) return key;
63
- return tenantKeyFromQuery.trim();
64
- }
65
- const keyFromHost = extractTenantKeyFromDomain(url.hostname, config);
66
- if (keyFromHost) return keyFromHost;
67
- const clientCode = config.getClientCode();
68
- if (clientCode?.trim()) return clientCode.trim();
69
- return config.getClientCode();
70
- } catch {
71
- const clientCode = config.getClientCode();
72
- if (clientCode?.trim()) return clientCode.trim();
73
- return config.getClientCode();
74
- }
75
- }
76
18
  function isCallbackUrlFromUrl(url) {
77
19
  const params = new URLSearchParams(url.search);
78
20
  return params.has("code") && params.has("state");
@@ -133,8 +75,7 @@ var CONFIG_KEY_SUFFIXES = {
133
75
  stateMismatch: "state_mismatch",
134
76
  bootstrapInProgress: "bootstrap_in_progress",
135
77
  bootstrapTimestamp: "bootstrap_timestamp",
136
- callbackHandled: "callback_handled",
137
- idToken: "id_token"
78
+ callbackHandled: "callback_handled"
138
79
  };
139
80
 
140
81
  // src/infrastructure/storage/context.ts
@@ -428,10 +369,6 @@ function createCookieStorageAdapter(namespace, options) {
428
369
 
429
370
  // src/infrastructure/storage/api.ts
430
371
  var KEY_SUFFIXES = {
431
- authServerUrl: "auth_server_url",
432
- clientId: "client_id",
433
- redirectUri: "redirect_uri",
434
- tenantId: "tenant_id",
435
372
  tenantKey: "tenant_key",
436
373
  codeVerifier: "code_verifier",
437
374
  state: "state",
@@ -440,18 +377,6 @@ var KEY_SUFFIXES = {
440
377
  expiresAt: "expires_at"
441
378
  };
442
379
  var STORAGE_KEYS = {
443
- get authServerUrl() {
444
- return getStorageKey(KEY_SUFFIXES.authServerUrl);
445
- },
446
- get clientId() {
447
- return getStorageKey(KEY_SUFFIXES.clientId);
448
- },
449
- get redirectUri() {
450
- return getStorageKey(KEY_SUFFIXES.redirectUri);
451
- },
452
- get tenantId() {
453
- return getStorageKey(KEY_SUFFIXES.tenantId);
454
- },
455
380
  get tenantKey() {
456
381
  return getStorageKey(KEY_SUFFIXES.tenantKey);
457
382
  },
@@ -507,22 +432,22 @@ function removeStorage(key) {
507
432
  }
508
433
  }
509
434
  function clearAuthStorage() {
510
- const adapter = getStorageAdapter();
511
- if (!adapter?.clearSync) return;
512
- try {
513
- adapter.clearTokensSync?.();
514
- adapter.clearSync();
515
- } catch {
516
- }
435
+ const keysToClear = [
436
+ STORAGE_KEYS.accessToken,
437
+ STORAGE_KEYS.refreshToken,
438
+ STORAGE_KEYS.expiresAt,
439
+ STORAGE_KEYS.codeVerifier,
440
+ STORAGE_KEYS.state,
441
+ getStorageKey("callback_processed"),
442
+ getStorageKey("props_config"),
443
+ getStorageKey("tenant_switched"),
444
+ getStorageKey("new_tenant_key"),
445
+ getStorageKey("preserved_tenant_key")
446
+ ];
447
+ for (const key of keysToClear) removeStorage(key);
517
448
  }
518
449
  function clearAuthSessionStorage() {
519
- const adapter = getStorageAdapter();
520
- if (!adapter?.clearSync) return;
521
- try {
522
- adapter.clearTokensSync?.();
523
- adapter.clearSync();
524
- } catch {
525
- }
450
+ clearAuthStorage();
526
451
  }
527
452
  function clearAllCookies() {
528
453
  const runtime = getAuthRuntime();
@@ -531,27 +456,8 @@ function clearAllCookies() {
531
456
  } catch {
532
457
  }
533
458
  }
534
- function clearAllBrowserStorage() {
535
- if (typeof window === "undefined") return;
536
- try {
537
- window.localStorage?.clear();
538
- } catch {
539
- }
540
- try {
541
- window.sessionStorage?.clear();
542
- } catch {
543
- }
544
- }
545
459
  function clearAllStorage() {
546
- const forceLogin = getStorage(getStorageKey("force_login"));
547
- const logoutInProgress = getStorage(getStorageKey("logout_in_progress"));
548
- const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
549
- clearAuthSessionStorage();
550
460
  clearAuthStorage();
551
- clearAllBrowserStorage();
552
- if (forceLogin === "true") setStorage(getStorageKey("force_login"), "true");
553
- if (logoutInProgress === "true") setStorage(getStorageKey("logout_in_progress"), "true");
554
- if (preservedTenantKey) setStorage(getStorageKey("preserved_tenant_key"), preservedTenantKey);
555
461
  clearAllCookies();
556
462
  }
557
463
  function getAccessToken() {
@@ -587,14 +493,27 @@ function isAccessTokenExpired() {
587
493
  const buffer = 60 * 1e3;
588
494
  return Date.now() >= expiresAt - buffer;
589
495
  }
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
+ }
590
506
  function getClientId() {
591
- return getStorage(STORAGE_KEYS.clientId);
507
+ const value = readHostConfig().clientId;
508
+ return typeof value === "string" && value.trim() ? value : null;
592
509
  }
593
510
  function getRedirectUri() {
594
- return getStorage(STORAGE_KEYS.redirectUri);
511
+ const value = readHostConfig().redirectUri;
512
+ return typeof value === "string" && value.trim() ? value : null;
595
513
  }
596
514
  function getTenantId() {
597
- return getStorage(STORAGE_KEYS.tenantId);
515
+ const value = readHostConfig().tenantId;
516
+ return typeof value === "string" && value.trim() ? value : null;
598
517
  }
599
518
  function getCodeVerifier() {
600
519
  return getStorage(STORAGE_KEYS.codeVerifier);
@@ -711,10 +630,8 @@ function resolveAuthLifecycleState(input) {
711
630
  hasRefreshToken,
712
631
  isTokenExpired
713
632
  } = input;
714
- const forceLogin = getStorage3("force_login") === "true";
715
633
  const callbackProcessed = getStorage3("callback_processed") === "true";
716
634
  const tenantSwitched = getStorage3("tenant_switched") === "true";
717
- if (forceLogin) return "FORCE_LOGIN" /* FORCE_LOGIN */;
718
635
  if (isCallbackUrl2(url)) return "PROCESSING_CALLBACK" /* PROCESSING_CALLBACK */;
719
636
  if (hasValidToken) return "AUTHENTICATED" /* AUTHENTICATED */;
720
637
  if (callbackProcessed && !hasToken) return "LOGIN_REQUIRED" /* LOGIN_REQUIRED */;
@@ -775,30 +692,58 @@ function setFlowFlags() {
775
692
  setFlowInProgress();
776
693
  }
777
694
 
778
- // src/strategies/oauth2-pkce/pkce.ts
779
- function generateCodeVerifier(length = 128) {
780
- const array = new Uint8Array(length);
781
- crypto.getRandomValues(array);
782
- const base64 = btoa(String.fromCharCode(...array));
783
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
695
+ // src/application/events/AuthEvents.ts
696
+ var AuthEventNames = {
697
+ LOGIN_STARTED: "LOGIN_STARTED",
698
+ LOGIN_SUCCESS: "LOGIN_SUCCESS",
699
+ LOGIN_FAILED: "LOGIN_FAILED",
700
+ CALLBACK_SUCCESS: "CALLBACK_SUCCESS",
701
+ CALLBACK_FAILED: "CALLBACK_FAILED",
702
+ TOKEN_REFRESH_SUCCESS: "TOKEN_REFRESH_SUCCESS",
703
+ TOKEN_REFRESH_FAILED: "TOKEN_REFRESH_FAILED",
704
+ TENANT_SWITCHED: "TENANT_SWITCHED",
705
+ LOGOUT_COMPLETED: "LOGOUT_COMPLETED",
706
+ BOOTSTRAP_STATE_DETECTED: "BOOTSTRAP_STATE_DETECTED",
707
+ REDIRECT_LOOP_RISK: "REDIRECT_LOOP_RISK"
708
+ };
709
+ var listeners = /* @__PURE__ */ new Map();
710
+ function getHandlers(event) {
711
+ let set = listeners.get(event);
712
+ if (!set) {
713
+ set = /* @__PURE__ */ new Set();
714
+ listeners.set(event, set);
715
+ }
716
+ return set;
784
717
  }
785
- async function generateCodeChallenge(verifier) {
786
- try {
787
- const encoder = new TextEncoder();
788
- const data = encoder.encode(verifier);
789
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
790
- const hashArray = Array.from(new Uint8Array(hashBuffer));
791
- const base64 = btoa(String.fromCharCode(...hashArray));
792
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
793
- } catch {
794
- throw new Error("Failed to generate code challenge");
718
+ function getWildcardHandlers() {
719
+ let set = listeners.get("*");
720
+ if (!set) {
721
+ set = /* @__PURE__ */ new Set();
722
+ listeners.set("*", set);
795
723
  }
724
+ return set;
796
725
  }
797
- function generateState() {
798
- const array = new Uint8Array(32);
799
- crypto.getRandomValues(array);
800
- const base64 = btoa(String.fromCharCode(...array));
801
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
726
+ function emitAuthEvent(event, payload) {
727
+ const handlers = getHandlers(event);
728
+ const wildcard = getWildcardHandlers();
729
+ const p = payload ?? {};
730
+ handlers.forEach((fn) => {
731
+ try {
732
+ fn(p);
733
+ } catch {
734
+ }
735
+ });
736
+ wildcard.forEach((fn) => {
737
+ try {
738
+ fn({ ...p, event });
739
+ } catch {
740
+ }
741
+ });
742
+ }
743
+ function subscribeAuthEvent(event, handler) {
744
+ const set = event === "*" ? getWildcardHandlers() : getHandlers(event);
745
+ set.add(handler);
746
+ return () => set.delete(handler);
802
747
  }
803
748
 
804
749
  // src/config/constants.ts
@@ -842,12 +787,17 @@ function readHostConfigFromStorage() {
842
787
  function resolveMerged(overrides) {
843
788
  const stored = readHostConfigFromStorage();
844
789
  const cfg = { ...stored, ...inMemoryConfig, ...overrides ?? {} };
790
+ if (!cfg.tenantCode && cfg.tenantKey) {
791
+ cfg.tenantCode = cfg.tenantKey;
792
+ }
845
793
  const trimmed = (v) => v == null ? "" : String(v).trim();
846
794
  return {
847
795
  apiBaseUrl: trimmed(cfg.apiBaseUrl),
848
796
  authServerUrl: trimmed(cfg.authServerUrl),
849
797
  callbackUrl: trimmed(cfg.callbackUrl),
850
- clientCode: trimmed(cfg.clientCode)
798
+ clientCode: trimmed(cfg.clientCode),
799
+ tenantCode: trimmed(cfg.tenantCode),
800
+ appId: trimmed(cfg.appId)
851
801
  };
852
802
  }
853
803
  function resolveConfig(overrides) {
@@ -856,6 +806,9 @@ function resolveConfig(overrides) {
856
806
  function getConfig() {
857
807
  return resolveMerged();
858
808
  }
809
+ function getAuthConfig() {
810
+ return resolveMerged();
811
+ }
859
812
  function setConfig(overrides) {
860
813
  inMemoryConfig = { ...inMemoryConfig, ...overrides };
861
814
  }
@@ -886,28 +839,121 @@ var Config = {
886
839
  }
887
840
  };
888
841
 
889
- // src/utils/validation.ts
890
- function isValidGuid(guid) {
891
- if (!guid || typeof guid !== "string") {
892
- return false;
842
+ // src/infrastructure/tenant/TenantConfigCache.ts
843
+ var DEFAULT_TTL_MS = 5 * 60 * 1e3;
844
+ var TenantConfigCache = class {
845
+ constructor(ttlMs = DEFAULT_TTL_MS) {
846
+ this.cache = /* @__PURE__ */ new Map();
847
+ this.inFlight = /* @__PURE__ */ new Map();
848
+ this.ttlMs = ttlMs;
849
+ }
850
+ get(tenantKey) {
851
+ const entry = this.cache.get(tenantKey);
852
+ if (!entry) return null;
853
+ if (Date.now() >= entry.expiresAt) {
854
+ this.cache.delete(tenantKey);
855
+ return null;
856
+ }
857
+ return entry.config;
858
+ }
859
+ set(tenantKey, config) {
860
+ this.cache.set(tenantKey, {
861
+ config,
862
+ expiresAt: Date.now() + this.ttlMs
863
+ });
864
+ }
865
+ invalidate(tenantKey) {
866
+ if (tenantKey) {
867
+ this.cache.delete(tenantKey);
868
+ this.inFlight.delete(tenantKey);
869
+ } else {
870
+ this.cache.clear();
871
+ this.inFlight.clear();
872
+ }
873
+ }
874
+ async getOrResolve(tenantKey, resolver) {
875
+ if (!tenantKey || !tenantKey.trim()) return null;
876
+ const key = tenantKey.trim();
877
+ const cached = this.get(key);
878
+ if (cached) {
879
+ logger.debug("Tenant config from cache", { tenantKey: key });
880
+ return cached;
881
+ }
882
+ const existing = this.inFlight.get(key);
883
+ if (existing) {
884
+ logger.debug("Tenant resolution reused (in-flight)", { tenantKey: key });
885
+ return existing;
886
+ }
887
+ const promise = (async () => {
888
+ try {
889
+ const config = await resolver(key);
890
+ if (config) this.set(key, config);
891
+ return config;
892
+ } finally {
893
+ this.inFlight.delete(key);
894
+ }
895
+ })();
896
+ this.inFlight.set(key, promise);
897
+ return promise;
898
+ }
899
+ };
900
+
901
+ // src/infrastructure/tenant/tenantResolution.ts
902
+ var defaultTenantConfigCache = new TenantConfigCache();
903
+ function consumePropsConfig() {
904
+ const propsConfigKey = getStorageKey("props_config");
905
+ try {
906
+ const propsConfigStr = getStorage(propsConfigKey);
907
+ if (!propsConfigStr) return null;
908
+ const parsed = JSON.parse(propsConfigStr);
909
+ return parsed && typeof parsed === "object" ? parsed : null;
910
+ } catch {
911
+ return null;
912
+ } finally {
913
+ removeStorage(propsConfigKey);
893
914
  }
894
- const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
895
- return guidRegex.test(guid.trim());
896
915
  }
897
- function validateTenantId(tenantId) {
898
- if (!tenantId || tenantId.trim() === "") {
899
- return {
900
- isValid: false,
901
- error: "Tenant ID is required"
902
- };
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;
903
924
  }
904
- if (!isValidGuid(tenantId)) {
905
- return {
906
- isValid: false,
907
- error: "Tenant ID must be a valid GUID format (00000000-0000-0000-0000-000000000001)"
908
- };
925
+ }
926
+ function validateTenantKey(value) {
927
+ if (typeof value !== "string") return null;
928
+ const trimmed = value.trim();
929
+ return trimmed ? trimmed : null;
930
+ }
931
+ function resolveTenantKey() {
932
+ const propsConfig = consumePropsConfig();
933
+ 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;
909
939
  }
910
- return { isValid: true };
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 });
944
+ }
945
+ return null;
946
+ }
947
+ async function resolveTenantConfig(tenantKey, tenantResolver) {
948
+ const resolved = await tenantResolver.resolve(tenantKey);
949
+ if (!resolved) return null;
950
+ return { ...resolved, tenantKey };
951
+ }
952
+ async function resolveTenantConfigCached(tenantKey, tenantResolver) {
953
+ return defaultTenantConfigCache.getOrResolve(
954
+ tenantKey,
955
+ (key) => resolveTenantConfig(key, tenantResolver)
956
+ );
911
957
  }
912
958
 
913
959
  // src/application/domain-facade.ts
@@ -917,33 +963,7 @@ function isLocalhost2() {
917
963
  return isLocalhost(hostname);
918
964
  }
919
965
  function extractTenantKey() {
920
- const runtime = getAuthRuntime();
921
- if (runtime) {
922
- const url = runtime.urlProvider.getCurrentUrl();
923
- return extractTenantKeyFromUrl(url, runtime.configProvider, getStorage, getStorageKey);
924
- }
925
- return extractTenantKeyLegacy();
926
- }
927
- function extractTenantKeyLegacy() {
928
- if (typeof window === "undefined") return Config.getClientCode();
929
- try {
930
- const propsConfigStr = getStorage(getStorageKey("props_config"));
931
- if (propsConfigStr) {
932
- const propsConfig = JSON.parse(propsConfigStr);
933
- if (propsConfig.tenantKey?.trim()) return propsConfig.tenantKey.trim();
934
- }
935
- const stored = getStorage(getStorageKey("tenant_key"));
936
- if (stored?.trim()) return stored.trim();
937
- const urlParams = new URLSearchParams(window.location.search);
938
- const t = urlParams.get("t");
939
- if (t?.trim()) return t.trim();
940
- const hostname = window.location.hostname;
941
- const parts = hostname.split(".");
942
- if (parts.length >= 2 && parts[0] && parts[0].toLowerCase() !== "productlms") return parts[0].trim();
943
- return Config.getClientCode();
944
- } catch {
945
- return Config.getClientCode();
946
- }
966
+ return resolveTenantKey();
947
967
  }
948
968
  function isCallbackUrl() {
949
969
  const runtime = getAuthRuntime();
@@ -953,219 +973,6 @@ function isCallbackUrl() {
953
973
  return params.has("code") && params.has("state");
954
974
  }
955
975
 
956
- // src/application/events/AuthEvents.ts
957
- var AuthEventNames = {
958
- LOGIN_STARTED: "LOGIN_STARTED",
959
- LOGIN_SUCCESS: "LOGIN_SUCCESS",
960
- LOGIN_FAILED: "LOGIN_FAILED",
961
- CALLBACK_SUCCESS: "CALLBACK_SUCCESS",
962
- CALLBACK_FAILED: "CALLBACK_FAILED",
963
- TOKEN_REFRESH_SUCCESS: "TOKEN_REFRESH_SUCCESS",
964
- TOKEN_REFRESH_FAILED: "TOKEN_REFRESH_FAILED",
965
- TENANT_SWITCHED: "TENANT_SWITCHED",
966
- LOGOUT_COMPLETED: "LOGOUT_COMPLETED",
967
- BOOTSTRAP_STATE_DETECTED: "BOOTSTRAP_STATE_DETECTED",
968
- REDIRECT_LOOP_RISK: "REDIRECT_LOOP_RISK"
969
- };
970
- var listeners = /* @__PURE__ */ new Map();
971
- function getHandlers(event) {
972
- let set = listeners.get(event);
973
- if (!set) {
974
- set = /* @__PURE__ */ new Set();
975
- listeners.set(event, set);
976
- }
977
- return set;
978
- }
979
- function getWildcardHandlers() {
980
- let set = listeners.get("*");
981
- if (!set) {
982
- set = /* @__PURE__ */ new Set();
983
- listeners.set("*", set);
984
- }
985
- return set;
986
- }
987
- function emitAuthEvent(event, payload) {
988
- const handlers = getHandlers(event);
989
- const wildcard = getWildcardHandlers();
990
- const p = payload ?? {};
991
- handlers.forEach((fn) => {
992
- try {
993
- fn(p);
994
- } catch {
995
- }
996
- });
997
- wildcard.forEach((fn) => {
998
- try {
999
- fn({ ...p, event });
1000
- } catch {
1001
- }
1002
- });
1003
- }
1004
- function subscribeAuthEvent(event, handler) {
1005
- const set = event === "*" ? getWildcardHandlers() : getHandlers(event);
1006
- set.add(handler);
1007
- return () => set.delete(handler);
1008
- }
1009
-
1010
- // src/application/security/redirectLoopGuard.ts
1011
- var DEFAULT_MAX_REDIRECTS = 5;
1012
- var WINDOW_MS = 60 * 1e3;
1013
- var redirectCount = 0;
1014
- var firstRedirectAt = 0;
1015
- function resetRedirectLoopGuard() {
1016
- redirectCount = 0;
1017
- firstRedirectAt = 0;
1018
- }
1019
- function checkAndIncrementRedirect(maxAttempts = DEFAULT_MAX_REDIRECTS) {
1020
- const now = Date.now();
1021
- if (now - firstRedirectAt > WINDOW_MS) {
1022
- redirectCount = 0;
1023
- firstRedirectAt = now;
1024
- }
1025
- if (redirectCount >= maxAttempts) {
1026
- emitAuthEvent(AuthEventNames.REDIRECT_LOOP_RISK, {
1027
- message: `Redirect attempt limit (${maxAttempts}) exceeded`
1028
- });
1029
- return false;
1030
- }
1031
- redirectCount++;
1032
- return true;
1033
- }
1034
-
1035
- // src/strategies/oauth2-pkce/oauth.ts
1036
- function storeTenantConfig(config) {
1037
- const validation = validateTenantId(config.tenantId);
1038
- if (!validation.isValid) {
1039
- logger.warn("Tenant ID validation failed", { tenantId: config.tenantId });
1040
- }
1041
- const authServerToStore = Config.getAuthServerUrl() || config.authServer;
1042
- setStorage(STORAGE_KEYS.authServerUrl, authServerToStore);
1043
- setStorage(STORAGE_KEYS.clientId, config.client.clientId);
1044
- setStorage(STORAGE_KEYS.redirectUri, Config.getRedirectUri());
1045
- setStorage(STORAGE_KEYS.tenantId, config.tenantId);
1046
- if (config.tenantKey) {
1047
- setStorage(STORAGE_KEYS.tenantKey, config.tenantKey);
1048
- }
1049
- }
1050
- async function initiateAuthFlow(config) {
1051
- try {
1052
- logger.info("Initiating OAuth authorization flow");
1053
- const authServerFromProps = Config.getAuthServerUrl();
1054
- const authServerEffective = (authServerFromProps || config?.authServer || "").replace(/\/+$/, "");
1055
- const redirectUriEffective = Config.getRedirectUri();
1056
- const storedTenantId = getStorage(STORAGE_KEYS.tenantId);
1057
- const storedClientId = getStorage(STORAGE_KEYS.clientId);
1058
- const tenantIdEffective = storedTenantId || config?.tenantId || "";
1059
- const clientIdEffective = storedClientId || config?.client?.clientId || "";
1060
- logger.debug("[initiateAuthFlow] Resolved inputs", {
1061
- authServerFromProps: authServerFromProps ? "(provided)" : "(empty)",
1062
- authServerEffective,
1063
- redirectUriEffective,
1064
- tenantIdEffective,
1065
- clientIdEffective,
1066
- scopes: config?.client?.scopes,
1067
- hasRuntime: !!getAuthRuntime()
1068
- });
1069
- const token = getAccessToken();
1070
- if (token && !isAccessTokenExpired()) {
1071
- logger.debug("Valid token already exists, skipping auth flow");
1072
- clearFlowFlags();
1073
- return;
1074
- }
1075
- if (isCallbackUrl()) {
1076
- logger.debug("Callback URL detected - clearing flow flag to allow callback processing");
1077
- clearFlowFlags();
1078
- return;
1079
- }
1080
- if (isFlowInProgress()) {
1081
- if (isFlowFlagStale(3e3)) {
1082
- logger.info("Stale auth flow flag detected, clearing and proceeding");
1083
- clearFlowFlags();
1084
- } else {
1085
- logger.debug("Auth flow already in progress (recent), skipping duplicate redirect");
1086
- return;
1087
- }
1088
- }
1089
- setFlowFlags();
1090
- if (!(Config.getAuthServerUrl() || config?.authServer) || !config.client?.clientId || !config.client?.redirectUri) {
1091
- logger.error("[initiateAuthFlow] Missing required tenant config", {
1092
- authServerEffective,
1093
- clientId: config?.client?.clientId,
1094
- redirectUriFromTenantApi: config?.client?.redirectUri,
1095
- redirectUriEffective
1096
- });
1097
- throw new Error("Invalid tenant configuration for auth flow");
1098
- }
1099
- storeTenantConfig(config);
1100
- logger.debug("[initiateAuthFlow] Stored tenant config, generating PKCE");
1101
- const codeVerifier = generateCodeVerifier();
1102
- const codeChallenge = await generateCodeChallenge(codeVerifier);
1103
- const state = generateState();
1104
- removeStorage(STORAGE_KEYS.codeVerifier);
1105
- removeStorage(STORAGE_KEYS.state);
1106
- setStorage(STORAGE_KEYS.codeVerifier, codeVerifier);
1107
- setStorage(STORAGE_KEYS.state, state);
1108
- const authUrl = buildAuthorizationUrl(config, codeChallenge, state);
1109
- logger.debug("[initiateAuthFlow] Built authorization URL", {
1110
- authServerEffective,
1111
- urlPrefix: authUrl?.slice(0, 80)
1112
- });
1113
- if (!authUrl || !(authUrl.startsWith("http://") || authUrl.startsWith("https://"))) {
1114
- throw new Error("Invalid authorization URL generated");
1115
- }
1116
- if (!checkAndIncrementRedirect()) {
1117
- logger.warn("Redirect loop protection: max redirect attempts exceeded");
1118
- clearFlowFlags();
1119
- return;
1120
- }
1121
- logger.info("Redirecting to authorization server");
1122
- emitAuthEvent(AuthEventNames.LOGIN_STARTED, { tenantKey: config.tenantKey });
1123
- const runtime = getAuthRuntime();
1124
- if (runtime) {
1125
- logger.debug("[initiateAuthFlow] Redirect via runtime.urlProvider.redirect");
1126
- runtime.urlProvider.redirect(authUrl);
1127
- } else if (typeof window !== "undefined") {
1128
- logger.debug("[initiateAuthFlow] Redirect via window.location.replace");
1129
- window.location.replace(authUrl);
1130
- } else {
1131
- logger.error("[initiateAuthFlow] No runtime and no window; cannot redirect", { authUrl });
1132
- }
1133
- } catch (error) {
1134
- clearFlowFlags();
1135
- logger.error("Failed to initiate auth flow", {
1136
- error: error instanceof Error ? error.message : String(error)
1137
- });
1138
- throw error;
1139
- }
1140
- }
1141
- function buildAuthorizationUrl(config, codeChallenge, state) {
1142
- const authServerBase = (Config.getAuthServerUrl() || config.authServer || "").replace(/\/+$/, "");
1143
- if (isLocalhost2()) {
1144
- logger.debug("Building authorization URL", {
1145
- authServer: authServerBase,
1146
- clientId: config.client.clientId,
1147
- redirectUri: config.client.redirectUri,
1148
- scopes: config.client.scopes
1149
- });
1150
- }
1151
- const storedTenantId = getStorage(STORAGE_KEYS.tenantId);
1152
- const storedClientId = getStorage(STORAGE_KEYS.clientId);
1153
- const tenantIdToUse = storedTenantId || config.tenantId;
1154
- const clientIdToUse = storedClientId || config.client.clientId;
1155
- const redirectUriToUse = Config.getRedirectUri();
1156
- const params = new URLSearchParams({
1157
- client_id: `public-${clientIdToUse}`,
1158
- tenant_id: tenantIdToUse,
1159
- redirect_uri: redirectUriToUse,
1160
- response_type: DEFAULT_RESPONSE_TYPE,
1161
- scope: config.client.scopes,
1162
- state,
1163
- code_challenge: codeChallenge,
1164
- code_challenge_method: DEFAULT_CODE_CHALLENGE_METHOD
1165
- });
1166
- return `${authServerBase}/connect/authorize?${params.toString()}`;
1167
- }
1168
-
1169
976
  // src/strategies/oauth2-pkce/token-exchange.ts
1170
977
  function storeTokens(tokenData) {
1171
978
  const expiresIn = tokenData.expires_in || 3600;
@@ -1325,6 +1132,31 @@ async function refreshAccessToken() {
1325
1132
  })();
1326
1133
  return refreshPromise;
1327
1134
  }
1135
+
1136
+ // src/application/security/redirectLoopGuard.ts
1137
+ var DEFAULT_MAX_REDIRECTS = 5;
1138
+ var WINDOW_MS = 60 * 1e3;
1139
+ var redirectCount = 0;
1140
+ var firstRedirectAt = 0;
1141
+ function resetRedirectLoopGuard() {
1142
+ redirectCount = 0;
1143
+ firstRedirectAt = 0;
1144
+ }
1145
+ function checkAndIncrementRedirect(maxAttempts = DEFAULT_MAX_REDIRECTS) {
1146
+ const now = Date.now();
1147
+ if (now - firstRedirectAt > WINDOW_MS) {
1148
+ redirectCount = 0;
1149
+ firstRedirectAt = now;
1150
+ }
1151
+ if (redirectCount >= maxAttempts) {
1152
+ emitAuthEvent(AuthEventNames.REDIRECT_LOOP_RISK, {
1153
+ message: `Redirect attempt limit (${maxAttempts}) exceeded`
1154
+ });
1155
+ return false;
1156
+ }
1157
+ redirectCount++;
1158
+ return true;
1159
+ }
1328
1160
  function isProcessed() {
1329
1161
  return getStorage(getStorageKey("callback_processed")) === "true";
1330
1162
  }
@@ -1343,15 +1175,6 @@ function clearProcessing() {
1343
1175
  removeStorage(getStorageKey("callback_processed"));
1344
1176
  }
1345
1177
  var urlCleaned = false;
1346
- function cleanUrl() {
1347
- if (urlCleaned) return;
1348
- if (isProcessing()) return;
1349
- const runtime = getAuthRuntime();
1350
- const clean = runtime ? `${runtime.urlProvider.getCurrentUrl().origin}${runtime.urlProvider.getCurrentUrl().pathname}` : typeof window !== "undefined" ? `${window.location.origin}${window.location.pathname}` : "/";
1351
- if (runtime) runtime.urlProvider.redirect(clean);
1352
- else if (typeof window !== "undefined") window.location.replace(clean);
1353
- urlCleaned = true;
1354
- }
1355
1178
  function cleanUrlSoft() {
1356
1179
  if (urlCleaned) return;
1357
1180
  if (isProcessing()) return;
@@ -1362,13 +1185,20 @@ function cleanUrlSoft() {
1362
1185
  urlCleaned = true;
1363
1186
  }
1364
1187
  function clearAllAuthFlags() {
1365
- removeStorage(getStorageKey("force_login"));
1366
- removeStorage(getStorageKey("logout_in_progress"));
1367
1188
  clearFlowFlags();
1368
1189
  }
1369
1190
  async function handleCallback() {
1191
+ const runtimeSearch = getAuthRuntime()?.urlProvider.getCurrentUrl().search;
1192
+ const windowSearch = typeof window !== "undefined" ? window.location.search : "";
1193
+ logger.debug("[Callback] handleCallback invoked", {
1194
+ isProcessed: isProcessed(),
1195
+ isProcessing: isProcessing(),
1196
+ isCallbackUrl: isCallbackUrl(),
1197
+ runtimeSearch: runtimeSearch ?? "(none)",
1198
+ windowSearch: windowSearch || "(none)"
1199
+ });
1370
1200
  if (isProcessed()) {
1371
- logger.debug("Callback already processed in this session - preventing duplicate processing");
1201
+ logger.warn("[Callback] Early exit: callback already processed");
1372
1202
  if (isCallbackUrl()) {
1373
1203
  if (!isProcessing()) cleanUrlSoft();
1374
1204
  }
@@ -1376,31 +1206,16 @@ async function handleCallback() {
1376
1206
  return true;
1377
1207
  }
1378
1208
  if (isProcessing()) {
1379
- logger.debug("Callback processing already in progress - waiting for completion");
1209
+ logger.warn("[Callback] Early exit: callback processing already in progress");
1380
1210
  return true;
1381
1211
  }
1382
- if (getStorage(getStorageKey("force_login")) === "true") {
1383
- logger.info("Force-login flag present \u2192 callback skipped (logout occurred)");
1384
- const runtime = getAuthRuntime();
1385
- if (runtime && runtime.urlProvider.getCurrentUrl().search) cleanUrlSoft();
1386
- else if (typeof window !== "undefined" && window.location.search) cleanUrlSoft();
1387
- clearAllAuthFlags();
1388
- clearProcessing();
1389
- return false;
1390
- }
1391
1212
  const storedTenantKey = getTenantKey();
1392
- let currentTenantKey = null;
1393
- try {
1394
- const propsConfigStr = getStorage(getStorageKey("props_config"));
1395
- if (propsConfigStr) {
1396
- const propsConfig = JSON.parse(propsConfigStr);
1397
- currentTenantKey = propsConfig.tenantKey || null;
1398
- }
1399
- } catch {
1400
- }
1401
- if (!currentTenantKey) currentTenantKey = storedTenantKey;
1213
+ const currentTenantKey = resolveTenantKey() ?? storedTenantKey;
1402
1214
  if (currentTenantKey && storedTenantKey && currentTenantKey !== storedTenantKey) {
1403
- logger.info("TenantKey changed - bypassing callback, starting fresh login");
1215
+ logger.warn("[Callback] Early exit: tenant key changed before exchange", {
1216
+ currentTenantKey,
1217
+ storedTenantKey
1218
+ });
1404
1219
  clearAllStorage();
1405
1220
  clearAllAuthFlags();
1406
1221
  clearProcessing();
@@ -1410,7 +1225,10 @@ async function handleCallback() {
1410
1225
  if (isCallbackUrl()) cleanUrlSoft();
1411
1226
  return false;
1412
1227
  }
1413
- if (!isCallbackUrl()) return false;
1228
+ if (!isCallbackUrl()) {
1229
+ logger.warn("[Callback] Early exit: URL is not callback URL");
1230
+ return false;
1231
+ }
1414
1232
  markProcessing();
1415
1233
  clearFlowFlags();
1416
1234
  urlCleaned = false;
@@ -1421,44 +1239,33 @@ async function handleCallback() {
1421
1239
  const state = params.get("state");
1422
1240
  const error = params.get("error");
1423
1241
  if (error || !code || !state) {
1424
- logger.error("Invalid OAuth callback params", { error, code, state });
1242
+ logger.error("[Callback] Early exit: invalid OAuth callback params", { error, code, state });
1425
1243
  clearAllAuthFlags();
1426
1244
  clearProcessing();
1427
- markProcessed();
1428
1245
  cleanUrlSoft();
1429
1246
  return false;
1430
1247
  }
1431
1248
  const storedState = getState();
1432
1249
  if (!storedState || storedState !== state) {
1433
1250
  const currentTenantKeyFromUrl = extractTenantKey();
1434
- let storedTenantKeyFromProps = null;
1435
- try {
1436
- const propsConfigStr = getStorage(getStorageKey("props_config"));
1437
- if (propsConfigStr) {
1438
- const propsConfig = JSON.parse(propsConfigStr);
1439
- storedTenantKeyFromProps = propsConfig.tenantKey || null;
1440
- }
1441
- } catch {
1442
- }
1443
- const tenantKeyChanged = currentTenantKeyFromUrl && storedTenantKeyFromProps && currentTenantKeyFromUrl !== storedTenantKeyFromProps;
1251
+ const resolvedTenantKey = resolveTenantKey();
1252
+ const tenantKeyChanged = currentTenantKeyFromUrl && resolvedTenantKey && currentTenantKeyFromUrl !== resolvedTenantKey;
1444
1253
  if (tenantKeyChanged) {
1445
- logger.info("OAuth state mismatch due to tenantKey change - expected behavior, starting fresh login");
1254
+ logger.warn("[Callback] Early exit: state mismatch due to tenant key change");
1446
1255
  } else {
1447
- logger.error("OAuth state mismatch - redirecting to login");
1256
+ logger.error("[Callback] Early exit: OAuth state mismatch - redirecting to login");
1448
1257
  }
1449
1258
  clearAllAuthFlags();
1450
1259
  clearProcessing();
1451
- markProcessed();
1452
1260
  cleanUrlSoft();
1453
1261
  setStorage(getStorageKey("state_mismatch"), "true");
1454
1262
  return false;
1455
1263
  }
1456
1264
  const codeVerifier = getCodeVerifier();
1457
1265
  if (!codeVerifier) {
1458
- logger.error("Missing PKCE verifier");
1266
+ logger.error("[Callback] Early exit: missing PKCE verifier");
1459
1267
  clearAllAuthFlags();
1460
1268
  clearProcessing();
1461
- markProcessed();
1462
1269
  cleanUrlSoft();
1463
1270
  return false;
1464
1271
  }
@@ -1466,25 +1273,29 @@ async function handleCallback() {
1466
1273
  const redirectUri = getRedirectUri();
1467
1274
  const tenantId = getTenantId();
1468
1275
  if (!clientId || !redirectUri || !tenantId) {
1469
- logger.error("Missing OAuth config during callback");
1276
+ logger.error("[Callback] Early exit: missing OAuth config during callback", {
1277
+ hasClientId: !!clientId,
1278
+ hasRedirectUri: !!redirectUri,
1279
+ hasTenantId: !!tenantId
1280
+ });
1470
1281
  clearAllAuthFlags();
1471
1282
  clearProcessing();
1472
- markProcessed();
1473
1283
  cleanUrlSoft();
1474
1284
  return false;
1475
1285
  }
1476
1286
  const existingToken = getAccessToken();
1477
1287
  if (existingToken && !isAccessTokenExpired()) {
1478
- logger.debug("Valid token already exists - skipping code exchange");
1288
+ logger.warn("[Callback] Early exit: valid token already exists - skipping code exchange");
1479
1289
  removeStorage(STORAGE_KEYS.codeVerifier);
1480
1290
  removeStorage(STORAGE_KEYS.state);
1481
1291
  clearAllAuthFlags();
1482
1292
  markProcessed();
1483
- cleanUrl();
1293
+ cleanUrlSoft();
1484
1294
  resetRedirectLoopGuard();
1485
1295
  emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1486
1296
  return true;
1487
1297
  }
1298
+ logger.info("[Callback] Entering token exchange");
1488
1299
  const tokenData = await exchangeCodeForTokens({
1489
1300
  code,
1490
1301
  codeVerifier,
@@ -1504,7 +1315,7 @@ async function handleCallback() {
1504
1315
  removeStorage(STORAGE_KEYS.state);
1505
1316
  clearAllAuthFlags();
1506
1317
  markProcessed();
1507
- cleanUrl();
1318
+ cleanUrlSoft();
1508
1319
  logger.info("OAuth callback processed successfully");
1509
1320
  resetRedirectLoopGuard();
1510
1321
  emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
@@ -1551,170 +1362,19 @@ async function handleCallback() {
1551
1362
  const tokenValid = existingToken && !isAccessTokenExpired();
1552
1363
  if (tokenValid) {
1553
1364
  markProcessed();
1554
- cleanUrlSoft();
1555
- resetRedirectLoopGuard();
1556
- emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1557
- return true;
1558
- }
1559
- emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1560
- error: err instanceof Error ? err.message : String(err)
1561
- });
1562
- clearProcessing();
1563
- if (isCallbackUrl()) cleanUrlSoft();
1564
- return false;
1565
- }
1566
- }
1567
-
1568
- // src/infrastructure/tenant/TenantConfigCache.ts
1569
- var DEFAULT_TTL_MS = 5 * 60 * 1e3;
1570
- var TenantConfigCache = class {
1571
- constructor(ttlMs = DEFAULT_TTL_MS) {
1572
- this.cache = /* @__PURE__ */ new Map();
1573
- this.inFlight = /* @__PURE__ */ new Map();
1574
- this.ttlMs = ttlMs;
1575
- }
1576
- get(tenantKey) {
1577
- const entry = this.cache.get(tenantKey);
1578
- if (!entry) return null;
1579
- if (Date.now() >= entry.expiresAt) {
1580
- this.cache.delete(tenantKey);
1581
- return null;
1582
- }
1583
- return entry.config;
1584
- }
1585
- set(tenantKey, config) {
1586
- this.cache.set(tenantKey, {
1587
- config,
1588
- expiresAt: Date.now() + this.ttlMs
1589
- });
1590
- }
1591
- invalidate(tenantKey) {
1592
- if (tenantKey) {
1593
- this.cache.delete(tenantKey);
1594
- this.inFlight.delete(tenantKey);
1595
- } else {
1596
- this.cache.clear();
1597
- this.inFlight.clear();
1598
- }
1599
- }
1600
- async getOrResolve(tenantKey, resolver) {
1601
- if (!tenantKey || !tenantKey.trim()) return null;
1602
- const key = tenantKey.trim();
1603
- const cached = this.get(key);
1604
- if (cached) {
1605
- logger.debug("Tenant config from cache", { tenantKey: key });
1606
- return cached;
1607
- }
1608
- const existing = this.inFlight.get(key);
1609
- if (existing) {
1610
- logger.debug("Tenant resolution reused (in-flight)", { tenantKey: key });
1611
- return existing;
1612
- }
1613
- const promise = (async () => {
1614
- try {
1615
- const config = await resolver(key);
1616
- if (config) this.set(key, config);
1617
- return config;
1618
- } finally {
1619
- this.inFlight.delete(key);
1620
- }
1621
- })();
1622
- this.inFlight.set(key, promise);
1623
- return promise;
1624
- }
1625
- };
1626
-
1627
- // src/infrastructure/tenant/tenantResolution.ts
1628
- var defaultTenantConfigCache = new TenantConfigCache();
1629
- function getTenantKeyFromRuntime(url, config) {
1630
- try {
1631
- const propsConfigStr = getStorage(getStorageKey("props_config"));
1632
- if (propsConfigStr) {
1633
- const propsConfig = JSON.parse(propsConfigStr);
1634
- if (propsConfig.tenantKey?.trim()) return propsConfig.tenantKey.trim();
1635
- return null;
1636
- }
1637
- } catch {
1638
- }
1639
- return extractTenantKeyFromUrl(url, config, getStorage, getStorageKey);
1640
- }
1641
- function getPropsConfig() {
1642
- try {
1643
- const propsConfigStr = getStorage(getStorageKey("props_config"));
1644
- if (!propsConfigStr) return null;
1645
- const parsed = JSON.parse(propsConfigStr);
1646
- if (parsed && typeof parsed === "object" && "authServerUrl" in parsed) {
1647
- delete parsed.authServerUrl;
1648
- }
1649
- return parsed;
1650
- } catch {
1651
- removeStorage(getStorageKey("props_config"));
1652
- return null;
1653
- }
1654
- }
1655
- function mergeConfigWithProps(apiEnvConfig, propsConfig, tenantKey) {
1656
- return {
1657
- tenantId: propsConfig.tenantId || apiEnvConfig.tenantId,
1658
- authServer: apiEnvConfig.authServer,
1659
- tenantKey: tenantKey || apiEnvConfig.tenantKey,
1660
- client: {
1661
- clientId: propsConfig.clientId || apiEnvConfig.client.clientId,
1662
- redirectUri: apiEnvConfig.client.redirectUri,
1663
- scopes: apiEnvConfig.client.scopes
1664
- }
1665
- };
1666
- }
1667
- async function resolveTenantConfig(tenantKey, tenantResolver) {
1668
- if (!tenantKey) return null;
1669
- const propsConfig = getPropsConfig();
1670
- const resolved = await tenantResolver.resolve(tenantKey);
1671
- if (!resolved) return null;
1672
- removeStorage(getStorageKey("props_config"));
1673
- return {
1674
- ...propsConfig ? mergeConfigWithProps(resolved, propsConfig, tenantKey) : resolved,
1675
- tenantKey: tenantKey || resolved.tenantKey
1676
- };
1677
- }
1678
- async function resolveTenantConfigCached(tenantKey, tenantResolver) {
1679
- if (!tenantKey) return null;
1680
- return defaultTenantConfigCache.getOrResolve(
1681
- tenantKey,
1682
- (key) => resolveTenantConfig(key, tenantResolver)
1683
- );
1684
- }
1685
-
1686
- // src/application/flows/ForceLoginFlow.ts
1687
- var ForceLoginFlow = class {
1688
- async execute(context) {
1689
- const { runtime } = context;
1690
- const { urlProvider, logger: log } = runtime;
1691
- const getUrl = () => urlProvider.getCurrentUrl();
1692
- if (isCallbackUrlFromUrl(getUrl())) {
1693
- await handleCallback();
1694
- if (isCallbackUrlFromUrl(getUrl())) {
1695
- log.warn("Callback URL still present after cleanup, forcing URL clean");
1696
- const u = getUrl();
1697
- urlProvider.redirect(`${u.origin}${u.pathname}`);
1698
- return { completed: true };
1699
- }
1700
- }
1701
- clearFlowFlags();
1702
- const tenantKey = getTenantKeyFromRuntime(getUrl(), runtime.configProvider);
1703
- if (!tenantKey) return { completed: true };
1704
- const tenantConfig = await resolveTenantConfigCached(tenantKey, runtime.tenantResolver);
1705
- if (!tenantConfig) {
1706
- log.error("Tenant config resolution failed for force login", { tenantKey });
1707
- throw new SanitizedError(
1708
- "TENANT_CONFIG_RESOLUTION_FAILED",
1709
- "Unable to resolve tenant configuration",
1710
- new Error(`Tenant config resolution failed for tenantKey: ${tenantKey}`)
1711
- );
1365
+ cleanUrlSoft();
1366
+ resetRedirectLoopGuard();
1367
+ emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
1368
+ return true;
1712
1369
  }
1713
- storeTenantConfig(tenantConfig);
1714
- await initiateAuthFlow(tenantConfig);
1715
- return { completed: true };
1370
+ emitAuthEvent(AuthEventNames.CALLBACK_FAILED, {
1371
+ error: err instanceof Error ? err.message : String(err)
1372
+ });
1373
+ clearProcessing();
1374
+ if (isCallbackUrl()) cleanUrlSoft();
1375
+ return false;
1716
1376
  }
1717
- };
1377
+ }
1718
1378
 
1719
1379
  // src/application/flows/CallbackFlow.ts
1720
1380
  var CallbackFlow = class {
@@ -1785,6 +1445,206 @@ function getAccessToken2() {
1785
1445
  return getAccessToken();
1786
1446
  }
1787
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;
1565
+ }
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
1574
+ });
1575
+ throw new Error("Invalid tenant configuration for auth flow");
1576
+ }
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");
1596
+ }
1597
+ if (!checkAndIncrementRedirect()) {
1598
+ logger.warn("Redirect loop protection: max redirect attempts exceeded");
1599
+ clearFlowFlags();
1600
+ return;
1601
+ }
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 });
1613
+ }
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;
1620
+ }
1621
+ }
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
+ });
1631
+ }
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()}`;
1646
+ }
1647
+
1788
1648
  // src/application/flows/RefreshFlow.ts
1789
1649
  var RefreshFlow = class {
1790
1650
  async execute(context) {
@@ -1803,12 +1663,16 @@ var RefreshFlow = class {
1803
1663
  error: error instanceof Error ? error.message : String(error)
1804
1664
  });
1805
1665
  }
1806
- const tenantKey = getTenantKeyFromRuntime(getUrl(), runtime.configProvider);
1666
+ const tenantKey = resolveTenantKey();
1667
+ log.debug("[RefreshFlow] Tenant key resolved", {
1668
+ tenantKey: tenantKey ?? "(null)",
1669
+ source: tenantKey ? "resolved" : "none"
1670
+ });
1807
1671
  if (!tenantKey) {
1808
1672
  throw new SanitizedError(
1809
1673
  "TENANT_KEY_RESOLUTION_FAILED",
1810
1674
  "Unable to resolve tenant",
1811
- new Error("Tenant key missing")
1675
+ new Error(`Tenant key missing for refresh flow at url=${getUrl().href}`)
1812
1676
  );
1813
1677
  }
1814
1678
  const tenantConfig = await resolveTenantConfigCached(tenantKey, runtime.tenantResolver);
@@ -1837,12 +1701,17 @@ var TenantSwitchFlow = class {
1837
1701
  emitAuthEvent(AuthEventNames.TENANT_SWITCHED, { tenantKey: newTenantKey ?? void 0 });
1838
1702
  removeStorage(getStorageKey("tenant_switched"));
1839
1703
  removeStorage(getStorageKey("new_tenant_key"));
1840
- const tenantKey = newTenantKey || getTenantKeyFromRuntime(getUrl(), runtime.configProvider);
1704
+ const tenantKey = (newTenantKey || resolveTenantKey())?.trim() || null;
1705
+ log.debug("[TenantSwitchFlow] Tenant key resolved", {
1706
+ tenantKey: tenantKey ?? "(null)",
1707
+ fromNewTenantKey: newTenantKey ?? "(null)",
1708
+ source: newTenantKey ? "new_tenant_key" : tenantKey ? "resolved" : "none"
1709
+ });
1841
1710
  if (!tenantKey) {
1842
1711
  throw new SanitizedError(
1843
1712
  "TENANT_KEY_RESOLUTION_FAILED",
1844
1713
  "Unable to resolve tenant after switch",
1845
- new Error("Tenant key missing after switch")
1714
+ new Error(`Tenant key missing after switch at url=${getUrl().href}`)
1846
1715
  );
1847
1716
  }
1848
1717
  const tenantConfig = await resolveTenantConfigCached(tenantKey, runtime.tenantResolver);
@@ -1853,6 +1722,16 @@ var TenantSwitchFlow = class {
1853
1722
  new Error(`Tenant config resolution failed for tenantKey: ${tenantKey}`)
1854
1723
  );
1855
1724
  }
1725
+ try {
1726
+ const cfg = getAuthConfig();
1727
+ setStorage(getStorageKey("host_config"), JSON.stringify({
1728
+ ...cfg,
1729
+ tenantKey,
1730
+ tenantCode: tenantKey,
1731
+ clientCode: tenantConfig.client?.clientCode ?? cfg.clientCode ?? ""
1732
+ }));
1733
+ } catch {
1734
+ }
1856
1735
  setTenantKey(tenantKey);
1857
1736
  storeTenantConfig(tenantConfig);
1858
1737
  await initiateAuthFlow(tenantConfig);
@@ -1877,13 +1756,16 @@ var LoginFlow = class {
1877
1756
  }
1878
1757
  const wasStateMismatch = getStorage(getStorageKey("state_mismatch")) === "true";
1879
1758
  if (wasStateMismatch) removeStorage(getStorageKey("state_mismatch"));
1880
- const tenantKey = getTenantKeyFromRuntime(url, runtime.configProvider);
1881
- log.debug("[LoginFlow] Tenant key resolved", { tenantKey: tenantKey ?? "(null)" });
1759
+ const tenantKey = resolveTenantKey();
1760
+ log.debug("[LoginFlow] Tenant key resolved", {
1761
+ tenantKey: tenantKey ?? "(null)",
1762
+ source: tenantKey ? "resolved" : "none"
1763
+ });
1882
1764
  if (!tenantKey) {
1883
1765
  throw new SanitizedError(
1884
1766
  "TENANT_KEY_RESOLUTION_FAILED",
1885
1767
  "Unable to resolve tenant",
1886
- new Error("Tenant key missing")
1768
+ new Error(`Tenant key missing for login flow at url=${url.href}`)
1887
1769
  );
1888
1770
  }
1889
1771
  log.debug("[LoginFlow] Resolving tenant config", { tenantKey });
@@ -1912,7 +1794,6 @@ var LoginFlow = class {
1912
1794
  };
1913
1795
 
1914
1796
  // src/application/flows/getFlowForState.ts
1915
- var forceLoginFlow = new ForceLoginFlow();
1916
1797
  var callbackFlow = new CallbackFlow();
1917
1798
  var authenticatedFlow = new AuthenticatedFlow();
1918
1799
  var refreshFlow = new RefreshFlow();
@@ -1920,8 +1801,6 @@ var tenantSwitchFlow = new TenantSwitchFlow();
1920
1801
  var loginFlow = new LoginFlow();
1921
1802
  function getFlowForState(state) {
1922
1803
  switch (state) {
1923
- case "FORCE_LOGIN" /* FORCE_LOGIN */:
1924
- return forceLoginFlow;
1925
1804
  case "PROCESSING_CALLBACK" /* PROCESSING_CALLBACK */:
1926
1805
  return callbackFlow;
1927
1806
  case "AUTHENTICATED" /* AUTHENTICATED */:
@@ -2142,34 +2021,8 @@ function checkCookiesDeleted() {
2142
2021
  function handleCookieDeletion() {
2143
2022
  try {
2144
2023
  logger.info("Cookies deleted detected - clearing session and redirecting to login");
2145
- const forceLoginSet = getStorage(getStorageKey("force_login")) === "true";
2146
- const logoutInProgress = getStorage(getStorageKey("logout_in_progress")) === "true";
2147
- if (forceLoginSet || logoutInProgress) {
2148
- logger.debug(
2149
- "Force login or logout in progress - skipping bootstrap from cookie watcher (avoids duplicate by-code API call)"
2150
- );
2151
- return;
2152
- }
2153
2024
  clearAuthStorage();
2154
- try {
2155
- if (typeof window !== "undefined") {
2156
- const forceLogin = window.localStorage.getItem(getStorageKey("force_login"));
2157
- const logoutInProgressPreserve = window.localStorage.getItem(
2158
- getStorageKey("logout_in_progress")
2159
- );
2160
- clearAuthSessionStorage();
2161
- if (forceLogin === "true")
2162
- window.localStorage.setItem(getStorageKey("force_login"), "true");
2163
- if (logoutInProgressPreserve === "true")
2164
- window.localStorage.setItem(getStorageKey("logout_in_progress"), "true");
2165
- } else {
2166
- clearAuthSessionStorage();
2167
- }
2168
- } catch (error) {
2169
- logger.warn("Failed to clear sessionStorage", {
2170
- error: error instanceof Error ? error.message : String(error)
2171
- });
2172
- }
2025
+ clearAuthSessionStorage();
2173
2026
  const cb = getSessionClearedCallback();
2174
2027
  if (cb) {
2175
2028
  Promise.resolve(cb()).catch((error) => {
@@ -2203,13 +2056,6 @@ function startCookieWatcher() {
2203
2056
  allCookies: initialCookies
2204
2057
  });
2205
2058
  watchInterval = setInterval(async () => {
2206
- const logoutInProgress = typeof window !== "undefined" && window.localStorage.getItem(getStorageKey("logout_in_progress")) === "true";
2207
- if (logoutInProgress) {
2208
- logger.debug("Logout detected. Cookie watcher will not trigger bootstrap.");
2209
- lastCookieSnapshot = getCookieSnapshot();
2210
- lastCookieMap = getCookieMap();
2211
- return;
2212
- }
2213
2059
  const cookiesDeleted = checkCookiesDeleted();
2214
2060
  if (cookiesDeleted) {
2215
2061
  logger.info("Cookies deleted detected by cookie watcher");
@@ -2295,11 +2141,6 @@ function startStorageWatcher() {
2295
2141
  storageEventListener = handleStorageEvent;
2296
2142
  window.addEventListener("storage", storageEventListener);
2297
2143
  const pollInterval = setInterval(() => {
2298
- const logoutInProgress = typeof window !== "undefined" && window.localStorage.getItem(getStorageKey("logout_in_progress")) === "true";
2299
- if (logoutInProgress) {
2300
- logger.debug("Logout detected. Storage watcher will not trigger bootstrap.");
2301
- return;
2302
- }
2303
2144
  const wasCleared = checkStorageCleared();
2304
2145
  if (wasCleared) {
2305
2146
  logger.info("Detected manual sessionStorage clear (polling)! Redirecting to login...");
@@ -2372,6 +2213,27 @@ function buildUrl(baseUrl, path) {
2372
2213
  url.pathname = url.pathname.endsWith("/") ? `${url.pathname}${normalizedPath.slice(1)}` : `${url.pathname}${normalizedPath}`;
2373
2214
  return url.toString();
2374
2215
  }
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
+ }
2375
2237
  function getCsrfToken() {
2376
2238
  try {
2377
2239
  if (typeof document === "undefined") return null;
@@ -2479,9 +2341,14 @@ function releaseLogoutLock() {
2479
2341
  }
2480
2342
  }
2481
2343
  async function logout(tenantKey) {
2482
- setStorage(getStorageKey("force_login"), "true");
2344
+ const preservedHostConfig = snapshotHostConfig();
2483
2345
  if (tenantKey?.trim()) {
2484
- setStorage(getStorageKey("preserved_tenant_key"), tenantKey.trim());
2346
+ const updated = {
2347
+ ...preservedHostConfig ?? {},
2348
+ tenantKey: tenantKey.trim(),
2349
+ tenantCode: tenantKey.trim()
2350
+ };
2351
+ setStorage(getStorageKey("host_config"), JSON.stringify(updated));
2485
2352
  }
2486
2353
  if (!acquireLogoutLock()) {
2487
2354
  return { success: true, message: "Logout already in progress." };
@@ -2489,7 +2356,8 @@ async function logout(tenantKey) {
2489
2356
  try {
2490
2357
  stopStorageWatcher();
2491
2358
  stopCookieWatcher();
2492
- const apiAuthBaseUrl = Config.getAuthServerUrl();
2359
+ const authConfig = getAuthConfig();
2360
+ const apiAuthBaseUrl = authConfig.authServerUrl || Config.getAuthServerUrl();
2493
2361
  let serverLogoutSuccess = false;
2494
2362
  let serverResponse;
2495
2363
  if (apiAuthBaseUrl) {
@@ -2508,25 +2376,7 @@ async function logout(tenantKey) {
2508
2376
  logger.warn("API auth base URL not configured, performing local logout only");
2509
2377
  }
2510
2378
  performLocalLogoutCleanup();
2511
- const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
2512
- if (preservedTenantKey) {
2513
- const propsKey = getStorageKey("props_config");
2514
- let existing = {};
2515
- try {
2516
- const raw = getStorage(propsKey);
2517
- if (raw) {
2518
- existing = JSON.parse(raw);
2519
- }
2520
- } catch {
2521
- existing = {};
2522
- }
2523
- const merged = {
2524
- ...existing,
2525
- tenantKey: preservedTenantKey
2526
- };
2527
- setStorage(propsKey, JSON.stringify(merged));
2528
- removeStorage(getStorageKey("preserved_tenant_key"));
2529
- }
2379
+ restoreHostConfig(preservedHostConfig);
2530
2380
  resetCookieWatcher();
2531
2381
  startStorageWatcher();
2532
2382
  releaseLogoutLock();
@@ -2536,7 +2386,7 @@ async function logout(tenantKey) {
2536
2386
  });
2537
2387
  emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
2538
2388
  serverLogoutSuccess,
2539
- tenantKey: getStorage(getStorageKey("preserved_tenant_key")) || void 0,
2389
+ tenantKey: tenantKey?.trim() || preservedHostConfig?.tenantKey || void 0,
2540
2390
  message: serverLogoutSuccess ? "Server logout success" : "Local logout only"
2541
2391
  });
2542
2392
  return {
@@ -2549,25 +2399,7 @@ async function logout(tenantKey) {
2549
2399
  error: error instanceof Error ? error.message : String(error)
2550
2400
  });
2551
2401
  performLocalLogoutCleanup();
2552
- const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
2553
- if (preservedTenantKey) {
2554
- const propsKey = getStorageKey("props_config");
2555
- let existing = {};
2556
- try {
2557
- const raw = getStorage(propsKey);
2558
- if (raw) {
2559
- existing = JSON.parse(raw);
2560
- }
2561
- } catch {
2562
- existing = {};
2563
- }
2564
- const merged = {
2565
- ...existing,
2566
- tenantKey: preservedTenantKey
2567
- };
2568
- setStorage(propsKey, JSON.stringify(merged));
2569
- removeStorage(getStorageKey("preserved_tenant_key"));
2570
- }
2402
+ restoreHostConfig(preservedHostConfig);
2571
2403
  releaseLogoutLock();
2572
2404
  return {
2573
2405
  success: true,
@@ -2920,13 +2752,19 @@ function AuthProvider({
2920
2752
  }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
2921
2753
  useEffect(() => {
2922
2754
  try {
2755
+ const hostConfigKey = getStorageKey("host_config");
2756
+ const raw = getStorage(hostConfigKey);
2757
+ const existing = raw ? JSON.parse(raw) : {};
2923
2758
  const payload = {
2759
+ ...existing,
2924
2760
  tenantCode: mergedTenantCode ?? "",
2761
+ tenantKey: mergedTenantCode ?? "",
2762
+ appId: appId ?? "",
2925
2763
  apiBaseUrl: mergedApiBaseUrl ?? "",
2926
2764
  authServerUrl: mergedAuthServerUrl ?? "",
2927
2765
  callbackUrl: mergedCallbackUrl ?? ""
2928
2766
  };
2929
- setStorage(getStorageKey("host_config"), JSON.stringify(payload));
2767
+ setStorage(hostConfigKey, JSON.stringify(payload));
2930
2768
  } catch {
2931
2769
  }
2932
2770
  }, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
@@ -2970,24 +2808,18 @@ function AuthProvider({
2970
2808
  const currentUrl = window.location.href;
2971
2809
  const isCallback = currentUrl.includes("code=") || currentUrl.includes("state=");
2972
2810
  if (isCallback) {
2973
- const cleanUrl2 = window.location.origin + window.location.pathname;
2974
- window.history.replaceState({}, document.title, cleanUrl2);
2811
+ const cleanUrl = window.location.origin + window.location.pathname;
2812
+ window.history.replaceState({}, document.title, cleanUrl);
2975
2813
  }
2976
2814
  }
2977
2815
  removeStorage(getStorageKey("access_token"));
2978
2816
  removeStorage(getStorageKey("refresh_token"));
2979
2817
  removeStorage(getStorageKey("expires_at"));
2980
- removeStorage(getStorageKey("id_token"));
2981
2818
  removeStorage(getStorageKey("state"));
2982
2819
  removeStorage(getStorageKey("code_verifier"));
2983
2820
  removeStorage(getStorageKey("callback_processed"));
2984
2821
  clearCallbackProcessing();
2985
2822
  clearFlowFlags();
2986
- removeStorage(getStorageKey("tenant_id"));
2987
- if (typeof window !== "undefined") {
2988
- window.localStorage.removeItem(getStorageKey("force_login"));
2989
- window.localStorage.removeItem(getStorageKey("logout_in_progress"));
2990
- }
2991
2823
  setStorage(getStorageKey("props_changed"), "true");
2992
2824
  setStorage(getStorageKey("tenant_switched"), "true");
2993
2825
  setStorage(getStorageKey("new_tenant_key"), resolvedTenantKey2);
@@ -3000,7 +2832,17 @@ function AuthProvider({
3000
2832
  } else if (previousTenantKey) {
3001
2833
  logger.debug("[AuthProvider] TenantKey unchanged", { tenantKey: resolvedTenantKey2 });
3002
2834
  }
3003
- setStorage(getStorageKey("props_config"), JSON.stringify({ tenantKey: resolvedTenantKey2 }));
2835
+ 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,
2841
+ tenantKey: resolvedTenantKey2,
2842
+ tenantCode: resolvedTenantKey2
2843
+ }));
2844
+ } catch {
2845
+ }
3004
2846
  setStorage(getStorageKey("tenant_key"), resolvedTenantKey2);
3005
2847
  } else {
3006
2848
  logger.debug("[AuthProvider] No tenant code determined");
@@ -3199,17 +3041,29 @@ function AuthCallback({
3199
3041
  setStatus("loading");
3200
3042
  setMessage("Completing sign-in...");
3201
3043
  const handled = await handleCallback();
3202
- if (handled) {
3203
- const token = getAccessToken();
3204
- if (token) setTokens(token);
3205
- setStatus("success");
3206
- setMessage("Authentication successful. Redirecting...");
3207
- window.location.replace(successPath);
3208
- } else {
3044
+ if (!handled) {
3209
3045
  setStatus("error");
3210
3046
  setMessage("Authentication failed. Please try again.");
3211
3047
  window.history.replaceState({}, document.title, window.location.origin + window.location.pathname);
3048
+ return;
3049
+ }
3050
+ const startedAt = Date.now();
3051
+ const MAX_WAIT_MS = 5e3;
3052
+ while (true) {
3053
+ const token = getAccessToken();
3054
+ if (token) {
3055
+ setTokens(token);
3056
+ setStatus("success");
3057
+ setMessage("Authentication successful. Redirecting...");
3058
+ window.location.replace(successPath);
3059
+ return;
3060
+ }
3061
+ if (Date.now() - startedAt > MAX_WAIT_MS) break;
3062
+ await new Promise((r) => setTimeout(r, 200));
3212
3063
  }
3064
+ setStatus("error");
3065
+ setMessage("Authentication did not complete. Please try again.");
3066
+ window.history.replaceState({}, document.title, window.location.origin + window.location.pathname);
3213
3067
  };
3214
3068
  runCallback();
3215
3069
  }, [homePath, loginPath, successPath, setTokens]);
@@ -3390,6 +3244,6 @@ function useAuthInit() {
3390
3244
  );
3391
3245
  }
3392
3246
 
3393
- 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, getAuthRuntime, getConfig, getStorageAdapter, getStorageNamespace, getAccessToken as getStoredAccessToken, isAccessTokenExpired, isCallbackUrl, logout, refreshAccessToken, resetCookieWatcher, resolveConfig, resolveTenant, sanitizeErrorMessage, setAuthRuntime, setConfig, setStorageContext, startCookieWatcher, stopCookieWatcher, subscribeAuthEvent, useAuth, useAuthContext, useAuthInit };
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 };
3394
3248
  //# sourceMappingURL=index.mjs.map
3395
3249
  //# sourceMappingURL=index.mjs.map