tenneo-auth-plugin 0.1.7 → 0.1.8
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 +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +198 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +198 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -911,10 +911,10 @@ var DEFAULT_REFRESH_GRANT_TYPE = OAUTH_CONSTANTS.REFRESH_GRANT_TYPE;
|
|
|
911
911
|
|
|
912
912
|
// src/config/index.ts
|
|
913
913
|
var INTERNAL_DEFAULTS = {
|
|
914
|
-
API_TENANT_RESOLUTION_BASE_URL: "
|
|
915
|
-
API_AUTH_BASE_URL: "
|
|
916
|
-
AUTH_SERVER_URL: "
|
|
917
|
-
DEFAULT_DOMAIN: "
|
|
914
|
+
API_TENANT_RESOLUTION_BASE_URL: "http://localhost:5021/api/v1",
|
|
915
|
+
API_AUTH_BASE_URL: "http://localhost:5000",
|
|
916
|
+
AUTH_SERVER_URL: "http://localhost:5000",
|
|
917
|
+
DEFAULT_DOMAIN: "http://localhost:5000"
|
|
918
918
|
};
|
|
919
919
|
function envOr(key) {
|
|
920
920
|
const v = getEnvValue([`VITE_${key}`, key]);
|
|
@@ -2482,7 +2482,74 @@ function resetCookieWatcher() {
|
|
|
2482
2482
|
authCookies
|
|
2483
2483
|
});
|
|
2484
2484
|
}
|
|
2485
|
+
|
|
2486
|
+
// src/infrastructure/watchers/storage-watcher.ts
|
|
2487
|
+
var isWatching2 = false;
|
|
2488
|
+
var lastStorageCheck = null;
|
|
2489
|
+
var storageEventListener = null;
|
|
2490
|
+
function checkStorageCleared() {
|
|
2491
|
+
try {
|
|
2492
|
+
const hasAnyAuthData = Object.values(STORAGE_KEYS).some(
|
|
2493
|
+
(key) => getStorage(key) !== null
|
|
2494
|
+
);
|
|
2495
|
+
if (lastStorageCheck === "had_data" && !hasAnyAuthData) return true;
|
|
2496
|
+
lastStorageCheck = hasAnyAuthData ? "had_data" : "no_data";
|
|
2497
|
+
return false;
|
|
2498
|
+
} catch {
|
|
2499
|
+
return false;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function handleStorageEvent(event) {
|
|
2503
|
+
if (typeof window !== "undefined" && event.storageArea === window.sessionStorage) {
|
|
2504
|
+
const wasCleared = checkStorageCleared();
|
|
2505
|
+
if (wasCleared) {
|
|
2506
|
+
logger.info("Detected manual sessionStorage clear! Redirecting to login...");
|
|
2507
|
+
const cb = getSessionClearedCallback();
|
|
2508
|
+
if (cb) {
|
|
2509
|
+
Promise.resolve(cb()).catch((error) => {
|
|
2510
|
+
logger.error("Failed to redirect to login after manual clear", {
|
|
2511
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2512
|
+
});
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
function startStorageWatcher() {
|
|
2519
|
+
if (isWatching2) return;
|
|
2520
|
+
isWatching2 = true;
|
|
2521
|
+
checkStorageCleared();
|
|
2522
|
+
storageEventListener = handleStorageEvent;
|
|
2523
|
+
window.addEventListener("storage", storageEventListener);
|
|
2524
|
+
const pollInterval = setInterval(() => {
|
|
2525
|
+
const logoutInProgress = typeof window !== "undefined" && window.localStorage.getItem(getStorageKey("logout_in_progress")) === "true";
|
|
2526
|
+
if (logoutInProgress) {
|
|
2527
|
+
logger.debug("Logout detected. Storage watcher will not trigger bootstrap.");
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
const wasCleared = checkStorageCleared();
|
|
2531
|
+
if (wasCleared) {
|
|
2532
|
+
logger.info("Detected manual sessionStorage clear (polling)! Redirecting to login...");
|
|
2533
|
+
clearInterval(pollInterval);
|
|
2534
|
+
const cb = getSessionClearedCallback();
|
|
2535
|
+
if (cb) {
|
|
2536
|
+
Promise.resolve(cb()).catch((error) => {
|
|
2537
|
+
logger.error("Failed to redirect to login after manual clear", {
|
|
2538
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2539
|
+
});
|
|
2540
|
+
if (!isWatching2) startStorageWatcher();
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
}, 1e3);
|
|
2545
|
+
window.__tenneo_auth_storage_watcher_interval = pollInterval;
|
|
2546
|
+
}
|
|
2485
2547
|
function stopStorageWatcher() {
|
|
2548
|
+
isWatching2 = false;
|
|
2549
|
+
if (storageEventListener) {
|
|
2550
|
+
window.removeEventListener("storage", storageEventListener);
|
|
2551
|
+
storageEventListener = null;
|
|
2552
|
+
}
|
|
2486
2553
|
const win = window;
|
|
2487
2554
|
const intervalId = win.__tenneo_auth_storage_watcher_interval;
|
|
2488
2555
|
if (intervalId) {
|
|
@@ -2532,51 +2599,68 @@ function buildUrl(baseUrl, path) {
|
|
|
2532
2599
|
url.pathname = url.pathname.endsWith("/") ? `${url.pathname}${normalizedPath.slice(1)}` : `${url.pathname}${normalizedPath}`;
|
|
2533
2600
|
return url.toString();
|
|
2534
2601
|
}
|
|
2535
|
-
|
|
2602
|
+
function getCsrfToken() {
|
|
2536
2603
|
try {
|
|
2604
|
+
if (typeof document === "undefined") return null;
|
|
2605
|
+
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
2606
|
+
if (meta?.content) return meta.content;
|
|
2607
|
+
const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/i);
|
|
2608
|
+
if (match) return decodeURIComponent(match[1]);
|
|
2609
|
+
return null;
|
|
2610
|
+
} catch {
|
|
2611
|
+
return null;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
async function callServerLogout(logoutUrl) {
|
|
2615
|
+
const csrfToken = getCsrfToken();
|
|
2616
|
+
async function doRequest(method) {
|
|
2617
|
+
const isPost = method === "POST";
|
|
2618
|
+
const headers = {
|
|
2619
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
2620
|
+
Accept: "application/json, */*"
|
|
2621
|
+
};
|
|
2622
|
+
if (isPost) {
|
|
2623
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
2624
|
+
}
|
|
2625
|
+
if (csrfToken) {
|
|
2626
|
+
headers["X-CSRF-Token"] = csrfToken;
|
|
2627
|
+
}
|
|
2537
2628
|
const response = await fetch(logoutUrl, {
|
|
2538
|
-
method
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
2542
|
-
Accept: "application/json"
|
|
2543
|
-
},
|
|
2629
|
+
method,
|
|
2630
|
+
mode: "cors",
|
|
2631
|
+
headers,
|
|
2544
2632
|
credentials: "include"
|
|
2545
2633
|
});
|
|
2634
|
+
if (!response.ok) {
|
|
2635
|
+
logger.warn("Server logout HTTP error", {
|
|
2636
|
+
method,
|
|
2637
|
+
status: response.status,
|
|
2638
|
+
statusText: response.statusText
|
|
2639
|
+
});
|
|
2640
|
+
return { success: false };
|
|
2641
|
+
}
|
|
2546
2642
|
const data = await response.json().catch(() => ({}));
|
|
2547
|
-
logger.info(
|
|
2643
|
+
logger.info(`Server logout response (${method})`, { response: data });
|
|
2548
2644
|
if (data && (data.success === true || data.logout === true)) {
|
|
2549
2645
|
return { success: true, response: data };
|
|
2550
2646
|
}
|
|
2551
|
-
logger.warn("Server logout response invalid
|
|
2647
|
+
logger.warn("Server logout response invalid payload", {
|
|
2648
|
+
method,
|
|
2649
|
+
response: data
|
|
2650
|
+
});
|
|
2552
2651
|
return { success: false, response: data };
|
|
2652
|
+
}
|
|
2653
|
+
try {
|
|
2654
|
+
const postResult = await doRequest("POST");
|
|
2655
|
+
if (postResult.success) return postResult;
|
|
2656
|
+
logger.debug("POST logout did not succeed, trying GET");
|
|
2657
|
+
const getResult = await doRequest("GET");
|
|
2658
|
+
return getResult;
|
|
2553
2659
|
} catch (error) {
|
|
2554
|
-
logger.
|
|
2660
|
+
logger.warn("Server logout failed (both POST and GET)", {
|
|
2555
2661
|
error: error instanceof Error ? error.message : String(error)
|
|
2556
2662
|
});
|
|
2557
|
-
|
|
2558
|
-
const response = await fetch(logoutUrl, {
|
|
2559
|
-
method: "GET",
|
|
2560
|
-
headers: {
|
|
2561
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
2562
|
-
Accept: "application/json"
|
|
2563
|
-
},
|
|
2564
|
-
credentials: "include"
|
|
2565
|
-
});
|
|
2566
|
-
const data = await response.json().catch(() => ({}));
|
|
2567
|
-
logger.info("Server logout response (GET)", { response: data });
|
|
2568
|
-
if (data && (data.success === true || data.logout === true)) {
|
|
2569
|
-
return { success: true, response: data };
|
|
2570
|
-
}
|
|
2571
|
-
logger.warn("Server logout response invalid (GET)", { response: data });
|
|
2572
|
-
return { success: false, response: data };
|
|
2573
|
-
} catch (getError) {
|
|
2574
|
-
logger.warn("Server logout failed (both POST and GET)", {
|
|
2575
|
-
postError: error instanceof Error ? error.message : String(error),
|
|
2576
|
-
getError: getError instanceof Error ? getError.message : String(getError)
|
|
2577
|
-
});
|
|
2578
|
-
return { success: false };
|
|
2579
|
-
}
|
|
2663
|
+
return { success: false };
|
|
2580
2664
|
}
|
|
2581
2665
|
}
|
|
2582
2666
|
function performLocalLogoutCleanup() {
|
|
@@ -2589,18 +2673,46 @@ function performLocalLogoutCleanup() {
|
|
|
2589
2673
|
});
|
|
2590
2674
|
}
|
|
2591
2675
|
clearClientCookies();
|
|
2592
|
-
|
|
2676
|
+
try {
|
|
2677
|
+
clearAuthStorage();
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
logger.warn("clearAuthStorage failed (ignored)", {
|
|
2680
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2593
2683
|
logger.info("Local logout cleanup completed");
|
|
2594
2684
|
}
|
|
2685
|
+
function acquireLogoutLock() {
|
|
2686
|
+
const LOCK_KEY = getStorageKey("logout_lock");
|
|
2687
|
+
const now = Date.now();
|
|
2688
|
+
const existingLock = getStorage(LOCK_KEY);
|
|
2689
|
+
if (existingLock) {
|
|
2690
|
+
const lockTime = Number(existingLock);
|
|
2691
|
+
if (!Number.isNaN(lockTime) && now - lockTime < 5e3) {
|
|
2692
|
+
logger.debug("Logout lock active \u2013 skipping duplicate logout", {
|
|
2693
|
+
now,
|
|
2694
|
+
lockTime
|
|
2695
|
+
});
|
|
2696
|
+
return false;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
setStorage(LOCK_KEY, String(now));
|
|
2700
|
+
return true;
|
|
2701
|
+
}
|
|
2702
|
+
function releaseLogoutLock() {
|
|
2703
|
+
try {
|
|
2704
|
+
removeStorage(getStorageKey("logout_lock"));
|
|
2705
|
+
} catch {
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2595
2708
|
async function logout(tenantKey) {
|
|
2596
|
-
setStorage(getStorageKey("logout_in_progress"), "true");
|
|
2597
2709
|
setStorage(getStorageKey("force_login"), "true");
|
|
2598
|
-
if (tenantKey?.trim())
|
|
2599
|
-
|
|
2600
|
-
|
|
2710
|
+
if (tenantKey?.trim()) {
|
|
2711
|
+
setStorage(getStorageKey("preserved_tenant_key"), tenantKey.trim());
|
|
2712
|
+
}
|
|
2713
|
+
if (!acquireLogoutLock()) {
|
|
2601
2714
|
return { success: true, message: "Logout already in progress." };
|
|
2602
2715
|
}
|
|
2603
|
-
setStorage(getStorageKey("logout_processing"), "true");
|
|
2604
2716
|
try {
|
|
2605
2717
|
stopStorageWatcher();
|
|
2606
2718
|
stopCookieWatcher();
|
|
@@ -2613,20 +2725,45 @@ async function logout(tenantKey) {
|
|
|
2613
2725
|
const logoutResult = await callServerLogout(logoutUrl);
|
|
2614
2726
|
serverLogoutSuccess = logoutResult.success;
|
|
2615
2727
|
serverResponse = logoutResult.response;
|
|
2728
|
+
if (!serverLogoutSuccess) {
|
|
2729
|
+
logger.warn("Server logout failed \u2013 session may still exist", {
|
|
2730
|
+
logoutUrl,
|
|
2731
|
+
serverResponse
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
} else {
|
|
2735
|
+
logger.warn("API auth base URL not configured, performing local logout only");
|
|
2616
2736
|
}
|
|
2617
2737
|
performLocalLogoutCleanup();
|
|
2618
2738
|
const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
|
|
2619
2739
|
if (preservedTenantKey) {
|
|
2620
|
-
|
|
2740
|
+
const propsKey = getStorageKey("props_config");
|
|
2741
|
+
let existing = {};
|
|
2742
|
+
try {
|
|
2743
|
+
const raw = getStorage(propsKey);
|
|
2744
|
+
if (raw) {
|
|
2745
|
+
existing = JSON.parse(raw);
|
|
2746
|
+
}
|
|
2747
|
+
} catch {
|
|
2748
|
+
existing = {};
|
|
2749
|
+
}
|
|
2750
|
+
const merged = {
|
|
2751
|
+
...existing,
|
|
2752
|
+
tenantKey: preservedTenantKey
|
|
2753
|
+
};
|
|
2754
|
+
setStorage(propsKey, JSON.stringify(merged));
|
|
2621
2755
|
removeStorage(getStorageKey("preserved_tenant_key"));
|
|
2622
2756
|
}
|
|
2623
2757
|
resetCookieWatcher();
|
|
2624
|
-
|
|
2758
|
+
startStorageWatcher();
|
|
2759
|
+
releaseLogoutLock();
|
|
2625
2760
|
logger.info("Logout completed \u2013 flags set, storage cleared, fresh login required", {
|
|
2626
2761
|
serverLogoutSuccess,
|
|
2627
2762
|
serverResponse
|
|
2628
2763
|
});
|
|
2629
2764
|
emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
|
|
2765
|
+
serverLogoutSuccess,
|
|
2766
|
+
tenantKey: getStorage(getStorageKey("preserved_tenant_key")) || void 0,
|
|
2630
2767
|
message: serverLogoutSuccess ? "Server logout success" : "Local logout only"
|
|
2631
2768
|
});
|
|
2632
2769
|
return {
|
|
@@ -2641,10 +2778,24 @@ async function logout(tenantKey) {
|
|
|
2641
2778
|
performLocalLogoutCleanup();
|
|
2642
2779
|
const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
|
|
2643
2780
|
if (preservedTenantKey) {
|
|
2644
|
-
|
|
2781
|
+
const propsKey = getStorageKey("props_config");
|
|
2782
|
+
let existing = {};
|
|
2783
|
+
try {
|
|
2784
|
+
const raw = getStorage(propsKey);
|
|
2785
|
+
if (raw) {
|
|
2786
|
+
existing = JSON.parse(raw);
|
|
2787
|
+
}
|
|
2788
|
+
} catch {
|
|
2789
|
+
existing = {};
|
|
2790
|
+
}
|
|
2791
|
+
const merged = {
|
|
2792
|
+
...existing,
|
|
2793
|
+
tenantKey: preservedTenantKey
|
|
2794
|
+
};
|
|
2795
|
+
setStorage(propsKey, JSON.stringify(merged));
|
|
2645
2796
|
removeStorage(getStorageKey("preserved_tenant_key"));
|
|
2646
2797
|
}
|
|
2647
|
-
|
|
2798
|
+
releaseLogoutLock();
|
|
2648
2799
|
return {
|
|
2649
2800
|
success: true,
|
|
2650
2801
|
message: "Logged out locally due to an error. Fresh login required."
|