tenneo-auth-plugin 1.0.0 → 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.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +568 -740
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +568 -741
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
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
|
-
|
|
507
|
+
const value = readHostConfig().clientId;
|
|
508
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
592
509
|
}
|
|
593
510
|
function getRedirectUri() {
|
|
594
|
-
|
|
511
|
+
const value = readHostConfig().redirectUri;
|
|
512
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
595
513
|
}
|
|
596
514
|
function getTenantId() {
|
|
597
|
-
|
|
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/
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
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/
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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())
|
|
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("
|
|
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
|
-
|
|
1435
|
-
|
|
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.
|
|
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("
|
|
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("
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1318
|
+
cleanUrlSoft();
|
|
1508
1319
|
logger.info("OAuth callback processed successfully");
|
|
1509
1320
|
resetRedirectLoopGuard();
|
|
1510
1321
|
emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
|
|
@@ -1548,173 +1359,22 @@ async function handleCallback() {
|
|
|
1548
1359
|
}
|
|
1549
1360
|
clearAllAuthFlags();
|
|
1550
1361
|
const existingToken = getAccessToken();
|
|
1551
|
-
const tokenValid = existingToken && !isAccessTokenExpired();
|
|
1552
|
-
if (tokenValid) {
|
|
1553
|
-
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
|
-
);
|
|
1362
|
+
const tokenValid = existingToken && !isAccessTokenExpired();
|
|
1363
|
+
if (tokenValid) {
|
|
1364
|
+
markProcessed();
|
|
1365
|
+
cleanUrlSoft();
|
|
1366
|
+
resetRedirectLoopGuard();
|
|
1367
|
+
emitAuthEvent(AuthEventNames.CALLBACK_SUCCESS);
|
|
1368
|
+
return true;
|
|
1712
1369
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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,21 +1663,16 @@ var RefreshFlow = class {
|
|
|
1803
1663
|
error: error instanceof Error ? error.message : String(error)
|
|
1804
1664
|
});
|
|
1805
1665
|
}
|
|
1806
|
-
const
|
|
1807
|
-
const tenantKeyFromStorage = getStorage(getStorageKey("tenant_key"));
|
|
1808
|
-
const tenantKeyFromConfig = runtime.configProvider.getClientCode?.() ?? "";
|
|
1809
|
-
const tenantKey = (tenantKeyFromRuntime || tenantKeyFromStorage || tenantKeyFromConfig)?.trim();
|
|
1666
|
+
const tenantKey = resolveTenantKey();
|
|
1810
1667
|
log.debug("[RefreshFlow] Tenant key resolved", {
|
|
1811
1668
|
tenantKey: tenantKey ?? "(null)",
|
|
1812
|
-
|
|
1813
|
-
fromStorage: tenantKeyFromStorage ?? "(null)",
|
|
1814
|
-
fromConfig: tenantKeyFromConfig || "(null)"
|
|
1669
|
+
source: tenantKey ? "resolved" : "none"
|
|
1815
1670
|
});
|
|
1816
1671
|
if (!tenantKey) {
|
|
1817
1672
|
throw new SanitizedError(
|
|
1818
1673
|
"TENANT_KEY_RESOLUTION_FAILED",
|
|
1819
1674
|
"Unable to resolve tenant",
|
|
1820
|
-
new Error(
|
|
1675
|
+
new Error(`Tenant key missing for refresh flow at url=${getUrl().href}`)
|
|
1821
1676
|
);
|
|
1822
1677
|
}
|
|
1823
1678
|
const tenantConfig = await resolveTenantConfigCached(tenantKey, runtime.tenantResolver);
|
|
@@ -1846,22 +1701,17 @@ var TenantSwitchFlow = class {
|
|
|
1846
1701
|
emitAuthEvent(AuthEventNames.TENANT_SWITCHED, { tenantKey: newTenantKey ?? void 0 });
|
|
1847
1702
|
removeStorage(getStorageKey("tenant_switched"));
|
|
1848
1703
|
removeStorage(getStorageKey("new_tenant_key"));
|
|
1849
|
-
const
|
|
1850
|
-
const tenantKeyFromStorage = getStorage(getStorageKey("tenant_key"));
|
|
1851
|
-
const tenantKeyFromConfig = runtime.configProvider.getClientCode?.() ?? "";
|
|
1852
|
-
const tenantKey = (newTenantKey || tenantKeyFromRuntime || tenantKeyFromStorage || tenantKeyFromConfig)?.trim();
|
|
1704
|
+
const tenantKey = (newTenantKey || resolveTenantKey())?.trim() || null;
|
|
1853
1705
|
log.debug("[TenantSwitchFlow] Tenant key resolved", {
|
|
1854
1706
|
tenantKey: tenantKey ?? "(null)",
|
|
1855
1707
|
fromNewTenantKey: newTenantKey ?? "(null)",
|
|
1856
|
-
|
|
1857
|
-
fromStorage: tenantKeyFromStorage ?? "(null)",
|
|
1858
|
-
fromConfig: tenantKeyFromConfig || "(null)"
|
|
1708
|
+
source: newTenantKey ? "new_tenant_key" : tenantKey ? "resolved" : "none"
|
|
1859
1709
|
});
|
|
1860
1710
|
if (!tenantKey) {
|
|
1861
1711
|
throw new SanitizedError(
|
|
1862
1712
|
"TENANT_KEY_RESOLUTION_FAILED",
|
|
1863
1713
|
"Unable to resolve tenant after switch",
|
|
1864
|
-
new Error(
|
|
1714
|
+
new Error(`Tenant key missing after switch at url=${getUrl().href}`)
|
|
1865
1715
|
);
|
|
1866
1716
|
}
|
|
1867
1717
|
const tenantConfig = await resolveTenantConfigCached(tenantKey, runtime.tenantResolver);
|
|
@@ -1872,6 +1722,16 @@ var TenantSwitchFlow = class {
|
|
|
1872
1722
|
new Error(`Tenant config resolution failed for tenantKey: ${tenantKey}`)
|
|
1873
1723
|
);
|
|
1874
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
|
+
}
|
|
1875
1735
|
setTenantKey(tenantKey);
|
|
1876
1736
|
storeTenantConfig(tenantConfig);
|
|
1877
1737
|
await initiateAuthFlow(tenantConfig);
|
|
@@ -1896,21 +1756,16 @@ var LoginFlow = class {
|
|
|
1896
1756
|
}
|
|
1897
1757
|
const wasStateMismatch = getStorage(getStorageKey("state_mismatch")) === "true";
|
|
1898
1758
|
if (wasStateMismatch) removeStorage(getStorageKey("state_mismatch"));
|
|
1899
|
-
const
|
|
1900
|
-
const tenantKeyFromStorage = getStorage(getStorageKey("tenant_key"));
|
|
1901
|
-
const tenantKeyFromConfig = runtime.configProvider.getClientCode?.() ?? "";
|
|
1902
|
-
const tenantKey = (tenantKeyFromRuntime || tenantKeyFromStorage || tenantKeyFromConfig)?.trim();
|
|
1759
|
+
const tenantKey = resolveTenantKey();
|
|
1903
1760
|
log.debug("[LoginFlow] Tenant key resolved", {
|
|
1904
1761
|
tenantKey: tenantKey ?? "(null)",
|
|
1905
|
-
|
|
1906
|
-
fromStorage: tenantKeyFromStorage ?? "(null)",
|
|
1907
|
-
fromConfig: tenantKeyFromConfig || "(null)"
|
|
1762
|
+
source: tenantKey ? "resolved" : "none"
|
|
1908
1763
|
});
|
|
1909
1764
|
if (!tenantKey) {
|
|
1910
1765
|
throw new SanitizedError(
|
|
1911
1766
|
"TENANT_KEY_RESOLUTION_FAILED",
|
|
1912
1767
|
"Unable to resolve tenant",
|
|
1913
|
-
new Error(
|
|
1768
|
+
new Error(`Tenant key missing for login flow at url=${url.href}`)
|
|
1914
1769
|
);
|
|
1915
1770
|
}
|
|
1916
1771
|
log.debug("[LoginFlow] Resolving tenant config", { tenantKey });
|
|
@@ -1939,7 +1794,6 @@ var LoginFlow = class {
|
|
|
1939
1794
|
};
|
|
1940
1795
|
|
|
1941
1796
|
// src/application/flows/getFlowForState.ts
|
|
1942
|
-
var forceLoginFlow = new ForceLoginFlow();
|
|
1943
1797
|
var callbackFlow = new CallbackFlow();
|
|
1944
1798
|
var authenticatedFlow = new AuthenticatedFlow();
|
|
1945
1799
|
var refreshFlow = new RefreshFlow();
|
|
@@ -1947,8 +1801,6 @@ var tenantSwitchFlow = new TenantSwitchFlow();
|
|
|
1947
1801
|
var loginFlow = new LoginFlow();
|
|
1948
1802
|
function getFlowForState(state) {
|
|
1949
1803
|
switch (state) {
|
|
1950
|
-
case "FORCE_LOGIN" /* FORCE_LOGIN */:
|
|
1951
|
-
return forceLoginFlow;
|
|
1952
1804
|
case "PROCESSING_CALLBACK" /* PROCESSING_CALLBACK */:
|
|
1953
1805
|
return callbackFlow;
|
|
1954
1806
|
case "AUTHENTICATED" /* AUTHENTICATED */:
|
|
@@ -2169,34 +2021,8 @@ function checkCookiesDeleted() {
|
|
|
2169
2021
|
function handleCookieDeletion() {
|
|
2170
2022
|
try {
|
|
2171
2023
|
logger.info("Cookies deleted detected - clearing session and redirecting to login");
|
|
2172
|
-
const forceLoginSet = getStorage(getStorageKey("force_login")) === "true";
|
|
2173
|
-
const logoutInProgress = getStorage(getStorageKey("logout_in_progress")) === "true";
|
|
2174
|
-
if (forceLoginSet || logoutInProgress) {
|
|
2175
|
-
logger.debug(
|
|
2176
|
-
"Force login or logout in progress - skipping bootstrap from cookie watcher (avoids duplicate by-code API call)"
|
|
2177
|
-
);
|
|
2178
|
-
return;
|
|
2179
|
-
}
|
|
2180
2024
|
clearAuthStorage();
|
|
2181
|
-
|
|
2182
|
-
if (typeof window !== "undefined") {
|
|
2183
|
-
const forceLogin = window.localStorage.getItem(getStorageKey("force_login"));
|
|
2184
|
-
const logoutInProgressPreserve = window.localStorage.getItem(
|
|
2185
|
-
getStorageKey("logout_in_progress")
|
|
2186
|
-
);
|
|
2187
|
-
clearAuthSessionStorage();
|
|
2188
|
-
if (forceLogin === "true")
|
|
2189
|
-
window.localStorage.setItem(getStorageKey("force_login"), "true");
|
|
2190
|
-
if (logoutInProgressPreserve === "true")
|
|
2191
|
-
window.localStorage.setItem(getStorageKey("logout_in_progress"), "true");
|
|
2192
|
-
} else {
|
|
2193
|
-
clearAuthSessionStorage();
|
|
2194
|
-
}
|
|
2195
|
-
} catch (error) {
|
|
2196
|
-
logger.warn("Failed to clear sessionStorage", {
|
|
2197
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2198
|
-
});
|
|
2199
|
-
}
|
|
2025
|
+
clearAuthSessionStorage();
|
|
2200
2026
|
const cb = getSessionClearedCallback();
|
|
2201
2027
|
if (cb) {
|
|
2202
2028
|
Promise.resolve(cb()).catch((error) => {
|
|
@@ -2230,13 +2056,6 @@ function startCookieWatcher() {
|
|
|
2230
2056
|
allCookies: initialCookies
|
|
2231
2057
|
});
|
|
2232
2058
|
watchInterval = setInterval(async () => {
|
|
2233
|
-
const logoutInProgress = typeof window !== "undefined" && window.localStorage.getItem(getStorageKey("logout_in_progress")) === "true";
|
|
2234
|
-
if (logoutInProgress) {
|
|
2235
|
-
logger.debug("Logout detected. Cookie watcher will not trigger bootstrap.");
|
|
2236
|
-
lastCookieSnapshot = getCookieSnapshot();
|
|
2237
|
-
lastCookieMap = getCookieMap();
|
|
2238
|
-
return;
|
|
2239
|
-
}
|
|
2240
2059
|
const cookiesDeleted = checkCookiesDeleted();
|
|
2241
2060
|
if (cookiesDeleted) {
|
|
2242
2061
|
logger.info("Cookies deleted detected by cookie watcher");
|
|
@@ -2322,11 +2141,6 @@ function startStorageWatcher() {
|
|
|
2322
2141
|
storageEventListener = handleStorageEvent;
|
|
2323
2142
|
window.addEventListener("storage", storageEventListener);
|
|
2324
2143
|
const pollInterval = setInterval(() => {
|
|
2325
|
-
const logoutInProgress = typeof window !== "undefined" && window.localStorage.getItem(getStorageKey("logout_in_progress")) === "true";
|
|
2326
|
-
if (logoutInProgress) {
|
|
2327
|
-
logger.debug("Logout detected. Storage watcher will not trigger bootstrap.");
|
|
2328
|
-
return;
|
|
2329
|
-
}
|
|
2330
2144
|
const wasCleared = checkStorageCleared();
|
|
2331
2145
|
if (wasCleared) {
|
|
2332
2146
|
logger.info("Detected manual sessionStorage clear (polling)! Redirecting to login...");
|
|
@@ -2399,6 +2213,27 @@ function buildUrl(baseUrl, path) {
|
|
|
2399
2213
|
url.pathname = url.pathname.endsWith("/") ? `${url.pathname}${normalizedPath.slice(1)}` : `${url.pathname}${normalizedPath}`;
|
|
2400
2214
|
return url.toString();
|
|
2401
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
|
+
}
|
|
2402
2237
|
function getCsrfToken() {
|
|
2403
2238
|
try {
|
|
2404
2239
|
if (typeof document === "undefined") return null;
|
|
@@ -2506,9 +2341,14 @@ function releaseLogoutLock() {
|
|
|
2506
2341
|
}
|
|
2507
2342
|
}
|
|
2508
2343
|
async function logout(tenantKey) {
|
|
2509
|
-
|
|
2344
|
+
const preservedHostConfig = snapshotHostConfig();
|
|
2510
2345
|
if (tenantKey?.trim()) {
|
|
2511
|
-
|
|
2346
|
+
const updated = {
|
|
2347
|
+
...preservedHostConfig ?? {},
|
|
2348
|
+
tenantKey: tenantKey.trim(),
|
|
2349
|
+
tenantCode: tenantKey.trim()
|
|
2350
|
+
};
|
|
2351
|
+
setStorage(getStorageKey("host_config"), JSON.stringify(updated));
|
|
2512
2352
|
}
|
|
2513
2353
|
if (!acquireLogoutLock()) {
|
|
2514
2354
|
return { success: true, message: "Logout already in progress." };
|
|
@@ -2516,7 +2356,8 @@ async function logout(tenantKey) {
|
|
|
2516
2356
|
try {
|
|
2517
2357
|
stopStorageWatcher();
|
|
2518
2358
|
stopCookieWatcher();
|
|
2519
|
-
const
|
|
2359
|
+
const authConfig = getAuthConfig();
|
|
2360
|
+
const apiAuthBaseUrl = authConfig.authServerUrl || Config.getAuthServerUrl();
|
|
2520
2361
|
let serverLogoutSuccess = false;
|
|
2521
2362
|
let serverResponse;
|
|
2522
2363
|
if (apiAuthBaseUrl) {
|
|
@@ -2535,25 +2376,7 @@ async function logout(tenantKey) {
|
|
|
2535
2376
|
logger.warn("API auth base URL not configured, performing local logout only");
|
|
2536
2377
|
}
|
|
2537
2378
|
performLocalLogoutCleanup();
|
|
2538
|
-
|
|
2539
|
-
if (preservedTenantKey) {
|
|
2540
|
-
const propsKey = getStorageKey("props_config");
|
|
2541
|
-
let existing = {};
|
|
2542
|
-
try {
|
|
2543
|
-
const raw = getStorage(propsKey);
|
|
2544
|
-
if (raw) {
|
|
2545
|
-
existing = JSON.parse(raw);
|
|
2546
|
-
}
|
|
2547
|
-
} catch {
|
|
2548
|
-
existing = {};
|
|
2549
|
-
}
|
|
2550
|
-
const merged = {
|
|
2551
|
-
...existing,
|
|
2552
|
-
tenantKey: preservedTenantKey
|
|
2553
|
-
};
|
|
2554
|
-
setStorage(propsKey, JSON.stringify(merged));
|
|
2555
|
-
removeStorage(getStorageKey("preserved_tenant_key"));
|
|
2556
|
-
}
|
|
2379
|
+
restoreHostConfig(preservedHostConfig);
|
|
2557
2380
|
resetCookieWatcher();
|
|
2558
2381
|
startStorageWatcher();
|
|
2559
2382
|
releaseLogoutLock();
|
|
@@ -2563,7 +2386,7 @@ async function logout(tenantKey) {
|
|
|
2563
2386
|
});
|
|
2564
2387
|
emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
|
|
2565
2388
|
serverLogoutSuccess,
|
|
2566
|
-
tenantKey:
|
|
2389
|
+
tenantKey: tenantKey?.trim() || preservedHostConfig?.tenantKey || void 0,
|
|
2567
2390
|
message: serverLogoutSuccess ? "Server logout success" : "Local logout only"
|
|
2568
2391
|
});
|
|
2569
2392
|
return {
|
|
@@ -2576,25 +2399,7 @@ async function logout(tenantKey) {
|
|
|
2576
2399
|
error: error instanceof Error ? error.message : String(error)
|
|
2577
2400
|
});
|
|
2578
2401
|
performLocalLogoutCleanup();
|
|
2579
|
-
|
|
2580
|
-
if (preservedTenantKey) {
|
|
2581
|
-
const propsKey = getStorageKey("props_config");
|
|
2582
|
-
let existing = {};
|
|
2583
|
-
try {
|
|
2584
|
-
const raw = getStorage(propsKey);
|
|
2585
|
-
if (raw) {
|
|
2586
|
-
existing = JSON.parse(raw);
|
|
2587
|
-
}
|
|
2588
|
-
} catch {
|
|
2589
|
-
existing = {};
|
|
2590
|
-
}
|
|
2591
|
-
const merged = {
|
|
2592
|
-
...existing,
|
|
2593
|
-
tenantKey: preservedTenantKey
|
|
2594
|
-
};
|
|
2595
|
-
setStorage(propsKey, JSON.stringify(merged));
|
|
2596
|
-
removeStorage(getStorageKey("preserved_tenant_key"));
|
|
2597
|
-
}
|
|
2402
|
+
restoreHostConfig(preservedHostConfig);
|
|
2598
2403
|
releaseLogoutLock();
|
|
2599
2404
|
return {
|
|
2600
2405
|
success: true,
|
|
@@ -2947,13 +2752,19 @@ function AuthProvider({
|
|
|
2947
2752
|
}, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
|
|
2948
2753
|
useEffect(() => {
|
|
2949
2754
|
try {
|
|
2755
|
+
const hostConfigKey = getStorageKey("host_config");
|
|
2756
|
+
const raw = getStorage(hostConfigKey);
|
|
2757
|
+
const existing = raw ? JSON.parse(raw) : {};
|
|
2950
2758
|
const payload = {
|
|
2759
|
+
...existing,
|
|
2951
2760
|
tenantCode: mergedTenantCode ?? "",
|
|
2761
|
+
tenantKey: mergedTenantCode ?? "",
|
|
2762
|
+
appId: appId ?? "",
|
|
2952
2763
|
apiBaseUrl: mergedApiBaseUrl ?? "",
|
|
2953
2764
|
authServerUrl: mergedAuthServerUrl ?? "",
|
|
2954
2765
|
callbackUrl: mergedCallbackUrl ?? ""
|
|
2955
2766
|
};
|
|
2956
|
-
setStorage(
|
|
2767
|
+
setStorage(hostConfigKey, JSON.stringify(payload));
|
|
2957
2768
|
} catch {
|
|
2958
2769
|
}
|
|
2959
2770
|
}, [mergedTenantCode, mergedApiBaseUrl, mergedAuthServerUrl, mergedCallbackUrl]);
|
|
@@ -2997,24 +2808,18 @@ function AuthProvider({
|
|
|
2997
2808
|
const currentUrl = window.location.href;
|
|
2998
2809
|
const isCallback = currentUrl.includes("code=") || currentUrl.includes("state=");
|
|
2999
2810
|
if (isCallback) {
|
|
3000
|
-
const
|
|
3001
|
-
window.history.replaceState({}, document.title,
|
|
2811
|
+
const cleanUrl = window.location.origin + window.location.pathname;
|
|
2812
|
+
window.history.replaceState({}, document.title, cleanUrl);
|
|
3002
2813
|
}
|
|
3003
2814
|
}
|
|
3004
2815
|
removeStorage(getStorageKey("access_token"));
|
|
3005
2816
|
removeStorage(getStorageKey("refresh_token"));
|
|
3006
2817
|
removeStorage(getStorageKey("expires_at"));
|
|
3007
|
-
removeStorage(getStorageKey("id_token"));
|
|
3008
2818
|
removeStorage(getStorageKey("state"));
|
|
3009
2819
|
removeStorage(getStorageKey("code_verifier"));
|
|
3010
2820
|
removeStorage(getStorageKey("callback_processed"));
|
|
3011
2821
|
clearCallbackProcessing();
|
|
3012
2822
|
clearFlowFlags();
|
|
3013
|
-
removeStorage(getStorageKey("tenant_id"));
|
|
3014
|
-
if (typeof window !== "undefined") {
|
|
3015
|
-
window.localStorage.removeItem(getStorageKey("force_login"));
|
|
3016
|
-
window.localStorage.removeItem(getStorageKey("logout_in_progress"));
|
|
3017
|
-
}
|
|
3018
2823
|
setStorage(getStorageKey("props_changed"), "true");
|
|
3019
2824
|
setStorage(getStorageKey("tenant_switched"), "true");
|
|
3020
2825
|
setStorage(getStorageKey("new_tenant_key"), resolvedTenantKey2);
|
|
@@ -3027,7 +2832,17 @@ function AuthProvider({
|
|
|
3027
2832
|
} else if (previousTenantKey) {
|
|
3028
2833
|
logger.debug("[AuthProvider] TenantKey unchanged", { tenantKey: resolvedTenantKey2 });
|
|
3029
2834
|
}
|
|
3030
|
-
|
|
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
|
+
}
|
|
3031
2846
|
setStorage(getStorageKey("tenant_key"), resolvedTenantKey2);
|
|
3032
2847
|
} else {
|
|
3033
2848
|
logger.debug("[AuthProvider] No tenant code determined");
|
|
@@ -3226,17 +3041,29 @@ function AuthCallback({
|
|
|
3226
3041
|
setStatus("loading");
|
|
3227
3042
|
setMessage("Completing sign-in...");
|
|
3228
3043
|
const handled = await handleCallback();
|
|
3229
|
-
if (handled) {
|
|
3230
|
-
const token = getAccessToken();
|
|
3231
|
-
if (token) setTokens(token);
|
|
3232
|
-
setStatus("success");
|
|
3233
|
-
setMessage("Authentication successful. Redirecting...");
|
|
3234
|
-
window.location.replace(successPath);
|
|
3235
|
-
} else {
|
|
3044
|
+
if (!handled) {
|
|
3236
3045
|
setStatus("error");
|
|
3237
3046
|
setMessage("Authentication failed. Please try again.");
|
|
3238
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));
|
|
3239
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);
|
|
3240
3067
|
};
|
|
3241
3068
|
runCallback();
|
|
3242
3069
|
}, [homePath, loginPath, successPath, setTokens]);
|
|
@@ -3417,6 +3244,6 @@ function useAuthInit() {
|
|
|
3417
3244
|
);
|
|
3418
3245
|
}
|
|
3419
3246
|
|
|
3420
|
-
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 };
|
|
3421
3248
|
//# sourceMappingURL=index.mjs.map
|
|
3422
3249
|
//# sourceMappingURL=index.mjs.map
|