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