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.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: "https://localhost:5020/api/v1",
915
- API_AUTH_BASE_URL: "https://localhost:5001",
916
- AUTH_SERVER_URL: "https://localhost:5001",
917
- DEFAULT_DOMAIN: "https://localhost:5001"
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
- logger.debug("No refresh token - cannot refresh; login required");
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
- async function callServerLogout(logoutUrl) {
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: "POST",
2533
- headers: {
2534
- "Content-Type": "application/x-www-form-urlencoded",
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("Server logout response (POST)", { response: data });
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 (POST)", { response: data });
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.debug("POST logout failed, trying GET", {
2660
+ logger.warn("Server logout failed (both POST and GET)", {
2549
2661
  error: error instanceof Error ? error.message : String(error)
2550
2662
  });
2551
- try {
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
- clearAllStorage();
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()) setStorage(getStorageKey("preserved_tenant_key"), tenantKey.trim());
2593
- if (getStorage(getStorageKey("logout_processing")) === "true") {
2594
- logger.debug("Logout already in progress \u2013 skipping duplicate call");
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
- setStorage(getStorageKey("props_config"), JSON.stringify({ tenantKey: preservedTenantKey }));
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
- removeStorage(getStorageKey("logout_processing"));
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
- setStorage(getStorageKey("props_config"), JSON.stringify({ tenantKey: preservedTenantKey }));
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
- removeStorage(getStorageKey("logout_processing"));
2798
+ releaseLogoutLock();
2642
2799
  return {
2643
2800
  success: true,
2644
2801
  message: "Logged out locally due to an error. Fresh login required."