tenneo-auth-plugin 0.1.6 → 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 +205 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -48
- 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]);
|
|
@@ -1998,6 +1998,8 @@ function setDefaultAuthEngine(engine) {
|
|
|
1998
1998
|
}
|
|
1999
1999
|
|
|
2000
2000
|
// src/application/token.ts
|
|
2001
|
+
var lastNoRefreshLog = 0;
|
|
2002
|
+
var NO_REFRESH_LOG_INTERVAL_MS = 5e3;
|
|
2001
2003
|
async function ensureValidAccessToken() {
|
|
2002
2004
|
const engine = getDefaultAuthEngine();
|
|
2003
2005
|
if (engine) {
|
|
@@ -2010,7 +2012,11 @@ async function ensureValidAccessToken() {
|
|
|
2010
2012
|
if (token && !isAccessTokenExpired()) return token;
|
|
2011
2013
|
const refreshToken = getRefreshToken();
|
|
2012
2014
|
if (!refreshToken) {
|
|
2013
|
-
|
|
2015
|
+
const now = Date.now();
|
|
2016
|
+
if (now - lastNoRefreshLog >= NO_REFRESH_LOG_INTERVAL_MS) {
|
|
2017
|
+
lastNoRefreshLog = now;
|
|
2018
|
+
logger.debug("No refresh token - cannot refresh; login required");
|
|
2019
|
+
}
|
|
2014
2020
|
throw new Error("No refresh token; login required");
|
|
2015
2021
|
}
|
|
2016
2022
|
return await refreshAccessToken();
|
|
@@ -2476,7 +2482,74 @@ function resetCookieWatcher() {
|
|
|
2476
2482
|
authCookies
|
|
2477
2483
|
});
|
|
2478
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
|
+
}
|
|
2479
2547
|
function stopStorageWatcher() {
|
|
2548
|
+
isWatching2 = false;
|
|
2549
|
+
if (storageEventListener) {
|
|
2550
|
+
window.removeEventListener("storage", storageEventListener);
|
|
2551
|
+
storageEventListener = null;
|
|
2552
|
+
}
|
|
2480
2553
|
const win = window;
|
|
2481
2554
|
const intervalId = win.__tenneo_auth_storage_watcher_interval;
|
|
2482
2555
|
if (intervalId) {
|
|
@@ -2526,51 +2599,68 @@ function buildUrl(baseUrl, path) {
|
|
|
2526
2599
|
url.pathname = url.pathname.endsWith("/") ? `${url.pathname}${normalizedPath.slice(1)}` : `${url.pathname}${normalizedPath}`;
|
|
2527
2600
|
return url.toString();
|
|
2528
2601
|
}
|
|
2529
|
-
|
|
2602
|
+
function getCsrfToken() {
|
|
2530
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
|
+
}
|
|
2531
2628
|
const response = await fetch(logoutUrl, {
|
|
2532
|
-
method
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
2536
|
-
Accept: "application/json"
|
|
2537
|
-
},
|
|
2629
|
+
method,
|
|
2630
|
+
mode: "cors",
|
|
2631
|
+
headers,
|
|
2538
2632
|
credentials: "include"
|
|
2539
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
|
+
}
|
|
2540
2642
|
const data = await response.json().catch(() => ({}));
|
|
2541
|
-
logger.info(
|
|
2643
|
+
logger.info(`Server logout response (${method})`, { response: data });
|
|
2542
2644
|
if (data && (data.success === true || data.logout === true)) {
|
|
2543
2645
|
return { success: true, response: data };
|
|
2544
2646
|
}
|
|
2545
|
-
logger.warn("Server logout response invalid
|
|
2647
|
+
logger.warn("Server logout response invalid payload", {
|
|
2648
|
+
method,
|
|
2649
|
+
response: data
|
|
2650
|
+
});
|
|
2546
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;
|
|
2547
2659
|
} catch (error) {
|
|
2548
|
-
logger.
|
|
2660
|
+
logger.warn("Server logout failed (both POST and GET)", {
|
|
2549
2661
|
error: error instanceof Error ? error.message : String(error)
|
|
2550
2662
|
});
|
|
2551
|
-
|
|
2552
|
-
const response = await fetch(logoutUrl, {
|
|
2553
|
-
method: "GET",
|
|
2554
|
-
headers: {
|
|
2555
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
2556
|
-
Accept: "application/json"
|
|
2557
|
-
},
|
|
2558
|
-
credentials: "include"
|
|
2559
|
-
});
|
|
2560
|
-
const data = await response.json().catch(() => ({}));
|
|
2561
|
-
logger.info("Server logout response (GET)", { response: data });
|
|
2562
|
-
if (data && (data.success === true || data.logout === true)) {
|
|
2563
|
-
return { success: true, response: data };
|
|
2564
|
-
}
|
|
2565
|
-
logger.warn("Server logout response invalid (GET)", { response: data });
|
|
2566
|
-
return { success: false, response: data };
|
|
2567
|
-
} catch (getError) {
|
|
2568
|
-
logger.warn("Server logout failed (both POST and GET)", {
|
|
2569
|
-
postError: error instanceof Error ? error.message : String(error),
|
|
2570
|
-
getError: getError instanceof Error ? getError.message : String(getError)
|
|
2571
|
-
});
|
|
2572
|
-
return { success: false };
|
|
2573
|
-
}
|
|
2663
|
+
return { success: false };
|
|
2574
2664
|
}
|
|
2575
2665
|
}
|
|
2576
2666
|
function performLocalLogoutCleanup() {
|
|
@@ -2583,18 +2673,46 @@ function performLocalLogoutCleanup() {
|
|
|
2583
2673
|
});
|
|
2584
2674
|
}
|
|
2585
2675
|
clearClientCookies();
|
|
2586
|
-
|
|
2676
|
+
try {
|
|
2677
|
+
clearAuthStorage();
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
logger.warn("clearAuthStorage failed (ignored)", {
|
|
2680
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2587
2683
|
logger.info("Local logout cleanup completed");
|
|
2588
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
|
+
}
|
|
2589
2708
|
async function logout(tenantKey) {
|
|
2590
|
-
setStorage(getStorageKey("logout_in_progress"), "true");
|
|
2591
2709
|
setStorage(getStorageKey("force_login"), "true");
|
|
2592
|
-
if (tenantKey?.trim())
|
|
2593
|
-
|
|
2594
|
-
|
|
2710
|
+
if (tenantKey?.trim()) {
|
|
2711
|
+
setStorage(getStorageKey("preserved_tenant_key"), tenantKey.trim());
|
|
2712
|
+
}
|
|
2713
|
+
if (!acquireLogoutLock()) {
|
|
2595
2714
|
return { success: true, message: "Logout already in progress." };
|
|
2596
2715
|
}
|
|
2597
|
-
setStorage(getStorageKey("logout_processing"), "true");
|
|
2598
2716
|
try {
|
|
2599
2717
|
stopStorageWatcher();
|
|
2600
2718
|
stopCookieWatcher();
|
|
@@ -2607,20 +2725,45 @@ async function logout(tenantKey) {
|
|
|
2607
2725
|
const logoutResult = await callServerLogout(logoutUrl);
|
|
2608
2726
|
serverLogoutSuccess = logoutResult.success;
|
|
2609
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");
|
|
2610
2736
|
}
|
|
2611
2737
|
performLocalLogoutCleanup();
|
|
2612
2738
|
const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
|
|
2613
2739
|
if (preservedTenantKey) {
|
|
2614
|
-
|
|
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));
|
|
2615
2755
|
removeStorage(getStorageKey("preserved_tenant_key"));
|
|
2616
2756
|
}
|
|
2617
2757
|
resetCookieWatcher();
|
|
2618
|
-
|
|
2758
|
+
startStorageWatcher();
|
|
2759
|
+
releaseLogoutLock();
|
|
2619
2760
|
logger.info("Logout completed \u2013 flags set, storage cleared, fresh login required", {
|
|
2620
2761
|
serverLogoutSuccess,
|
|
2621
2762
|
serverResponse
|
|
2622
2763
|
});
|
|
2623
2764
|
emitAuthEvent(AuthEventNames.LOGOUT_COMPLETED, {
|
|
2765
|
+
serverLogoutSuccess,
|
|
2766
|
+
tenantKey: getStorage(getStorageKey("preserved_tenant_key")) || void 0,
|
|
2624
2767
|
message: serverLogoutSuccess ? "Server logout success" : "Local logout only"
|
|
2625
2768
|
});
|
|
2626
2769
|
return {
|
|
@@ -2635,10 +2778,24 @@ async function logout(tenantKey) {
|
|
|
2635
2778
|
performLocalLogoutCleanup();
|
|
2636
2779
|
const preservedTenantKey = getStorage(getStorageKey("preserved_tenant_key"));
|
|
2637
2780
|
if (preservedTenantKey) {
|
|
2638
|
-
|
|
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));
|
|
2639
2796
|
removeStorage(getStorageKey("preserved_tenant_key"));
|
|
2640
2797
|
}
|
|
2641
|
-
|
|
2798
|
+
releaseLogoutLock();
|
|
2642
2799
|
return {
|
|
2643
2800
|
success: true,
|
|
2644
2801
|
message: "Logged out locally due to an error. Fresh login required."
|