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.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]);
@@ -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
- async function callServerLogout(logoutUrl) {
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: "POST",
2539
- headers: {
2540
- "Content-Type": "application/x-www-form-urlencoded",
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("Server logout response (POST)", { response: data });
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 (POST)", { response: data });
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.debug("POST logout failed, trying GET", {
2660
+ logger.warn("Server logout failed (both POST and GET)", {
2555
2661
  error: error instanceof Error ? error.message : String(error)
2556
2662
  });
2557
- try {
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
- 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
+ }
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()) setStorage(getStorageKey("preserved_tenant_key"), tenantKey.trim());
2599
- if (getStorage(getStorageKey("logout_processing")) === "true") {
2600
- 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()) {
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
- 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));
2621
2755
  removeStorage(getStorageKey("preserved_tenant_key"));
2622
2756
  }
2623
2757
  resetCookieWatcher();
2624
- removeStorage(getStorageKey("logout_processing"));
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
- 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));
2645
2796
  removeStorage(getStorageKey("preserved_tenant_key"));
2646
2797
  }
2647
- removeStorage(getStorageKey("logout_processing"));
2798
+ releaseLogoutLock();
2648
2799
  return {
2649
2800
  success: true,
2650
2801
  message: "Logged out locally due to an error. Fresh login required."