emilsoftware-utilities 2.0.0-dev.76 → 2.0.0-dev.78

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.
Files changed (75) hide show
  1. package/dist/accessi/resource-server/token.js +2 -0
  2. package/dist/accessi/resource-server/token.js.map +1 -1
  3. package/dist/accessi/server/standalone.js +2 -1
  4. package/dist/accessi/server/standalone.js.map +1 -1
  5. package/dist/accessi-module/AccessiModule.d.ts +9 -0
  6. package/dist/accessi-module/AccessiModule.js.map +1 -1
  7. package/dist/accessi-module/Console/accessiFetch.d.ts +7 -0
  8. package/dist/accessi-module/Console/accessiFetch.js +45 -7
  9. package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
  10. package/dist/accessi-module/Console/console.js +83 -22
  11. package/dist/accessi-module/Console/console.js.map +1 -1
  12. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.d.ts +2 -2
  13. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.js.map +1 -1
  14. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.d.ts +2 -2
  15. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js.map +1 -1
  16. package/dist/accessi-module/Console/wiki.js +2 -0
  17. package/dist/accessi-module/Console/wiki.js.map +1 -1
  18. package/dist/accessi-module/Controllers/AccessiConsoleController.js +10 -2
  19. package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -1
  20. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js +9 -0
  21. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js.map +1 -1
  22. package/dist/accessi-module/Controllers/AuthController.js +102 -15
  23. package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
  24. package/dist/accessi-module/Controllers/ConsoleEntryController.js +17 -0
  25. package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
  26. package/dist/accessi-module/Dtos/GetUserByTokenRequest.d.ts +1 -1
  27. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js +3 -2
  28. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js.map +1 -1
  29. package/dist/accessi-module/Dtos/RefreshTokenDtos.d.ts +1 -1
  30. package/dist/accessi-module/Dtos/RefreshTokenDtos.js +4 -1
  31. package/dist/accessi-module/Dtos/RefreshTokenDtos.js.map +1 -1
  32. package/dist/accessi-module/Services/AuthService/AuthService.js +8 -2
  33. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  34. package/dist/accessi-module/index.d.ts +6 -0
  35. package/dist/accessi-module/index.js +31 -2
  36. package/dist/accessi-module/index.js.map +1 -1
  37. package/dist/accessi-module/observability/metrics.d.ts +79 -0
  38. package/dist/accessi-module/observability/metrics.js +216 -0
  39. package/dist/accessi-module/observability/metrics.js.map +1 -0
  40. package/dist/accessi-module/observability/redisMetrics.d.ts +12 -0
  41. package/dist/accessi-module/observability/redisMetrics.js +44 -0
  42. package/dist/accessi-module/observability/redisMetrics.js.map +1 -0
  43. package/dist/accessi-module/security/accessTokenRevocation.js +5 -3
  44. package/dist/accessi-module/security/accessTokenRevocation.js.map +1 -1
  45. package/dist/accessi-module/security/authCookies.d.ts +104 -0
  46. package/dist/accessi-module/security/authCookies.js +240 -0
  47. package/dist/accessi-module/security/authCookies.js.map +1 -0
  48. package/dist/accessi-module/security/consoleEntry.js +5 -3
  49. package/dist/accessi-module/security/consoleEntry.js.map +1 -1
  50. package/dist/accessi-module/security/edgeRateLimit.js +3 -0
  51. package/dist/accessi-module/security/edgeRateLimit.js.map +1 -1
  52. package/dist/accessi-module/security/jwks.js +3 -0
  53. package/dist/accessi-module/security/jwks.js.map +1 -1
  54. package/dist/accessi-module/security/jwtTokenOptions.js +2 -0
  55. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
  56. package/dist/accessi-module/security/publicAuthRateLimit.js +3 -0
  57. package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
  58. package/dist/accessi-module/security/rateLimitStore.js +4 -2
  59. package/dist/accessi-module/security/rateLimitStore.js.map +1 -1
  60. package/dist/accessi-module/security/refreshTokenRotation.js +7 -5
  61. package/dist/accessi-module/security/refreshTokenRotation.js.map +1 -1
  62. package/dist/accessi-module/security/serviceTokenRateLimit.js +3 -0
  63. package/dist/accessi-module/security/serviceTokenRateLimit.js.map +1 -1
  64. package/dist/accessi-module/security/sessionRevocation.js +7 -3
  65. package/dist/accessi-module/security/sessionRevocation.js.map +1 -1
  66. package/dist/accessi-module/security/validateOptions.js +34 -0
  67. package/dist/accessi-module/security/validateOptions.js.map +1 -1
  68. package/dist/emilsoftware-utilities-2.0.0-dev.78.tgz +0 -0
  69. package/dist/initEmilsoftwareModule.js +7 -0
  70. package/dist/initEmilsoftwareModule.js.map +1 -1
  71. package/docs/auth-cookies.md +123 -0
  72. package/docs/metrics.md +153 -0
  73. package/docs/redis-load-testing.md +88 -0
  74. package/package.json +4 -3
  75. package/dist/emilsoftware-utilities-2.0.0-dev.76.tgz +0 -0
@@ -27,21 +27,54 @@ function payloadString(payload, key) {
27
27
  const value = payload[key];
28
28
  return typeof value === "string" && value.length > 0 ? value : void 0;
29
29
  }
30
+ function readBrowserCookie(name) {
31
+ if (typeof document === "undefined" || !document.cookie) return null;
32
+ for (const part of document.cookie.split(";")) {
33
+ const separator = part.indexOf("=");
34
+ if (separator < 0) continue;
35
+ if (part.slice(0, separator).trim() !== name) continue;
36
+ try {
37
+ return decodeURIComponent(part.slice(separator + 1).trim());
38
+ } catch {
39
+ return part.slice(separator + 1).trim();
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ function isCookieAuthMode() {
45
+ return typeof window !== "undefined" && window.__ACCESSI_AUTH_COOKIE__ === true;
46
+ }
47
+ var UNSAFE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
30
48
  async function accessiFetch(url, options) {
31
49
  const config = scopedConfig;
50
+ const cookieMode2 = isCookieAuthMode();
32
51
  const headers = new Headers(options.headers);
33
52
  headers.set("Accept", "application/json");
34
53
  if (options.body !== void 0 && !headers.has("Content-Type")) {
35
54
  headers.set("Content-Type", "application/json");
36
55
  }
56
+ if (cookieMode2) {
57
+ const method = (options.method ?? "GET").toUpperCase();
58
+ if (UNSAFE_METHODS.has(method)) {
59
+ const csrfCookie = window.__ACCESSI_CSRF_COOKIE_NAME__ ?? "accessi_csrf";
60
+ const csrfHeader = window.__ACCESSI_CSRF_HEADER_NAME__ ?? "x-csrf-token";
61
+ const csrfToken = readBrowserCookie(csrfCookie);
62
+ if (csrfToken) headers.set(csrfHeader, csrfToken);
63
+ }
64
+ }
37
65
  const resolvedToken = config ? config.getToken ? await config.getToken() : config.token ?? null : tokenProvider ? await tokenProvider() : accessToken;
38
- const authenticatedRequest = resolvedToken !== null && resolvedToken !== void 0;
66
+ const isAuthEntryRequest = /\/(?:auth\/(?:login|refresh|two-factor\/(?:verify|resend))|console\/entry\/exchange)\b/.test(url);
67
+ const authenticatedRequest = !isAuthEntryRequest && (cookieMode2 || resolvedToken !== null && resolvedToken !== void 0);
39
68
  if (resolvedToken) headers.set("Authorization", `Bearer ${resolvedToken}`);
40
69
  const runtimeBaseUrl = config?.baseUrl !== void 0 ? config.baseUrl : baseUrl ?? (typeof window !== "undefined" ? window.__ACCESSI_API_BASE_URL__ ?? null : null);
41
70
  const requestUrl = runtimeBaseUrl && url.startsWith("/") ? `${runtimeBaseUrl.replace(/\/+$/, "")}${url}` : url;
42
71
  notifyNetworkActivity(true);
43
72
  try {
44
- const response = await fetch(requestUrl, { ...options, headers });
73
+ const response = await fetch(requestUrl, {
74
+ ...options,
75
+ headers,
76
+ ...cookieMode2 ? { credentials: "include" } : {}
77
+ });
45
78
  const payload = await response.json().catch(() => void 0);
46
79
  if (!response.ok) {
47
80
  const message = payloadString(payload, "message") ?? payloadString(payload, "error") ?? `Errore HTTP ${response.status}`;
@@ -78,6 +111,21 @@ var getUserByToken = async (getUserByTokenRequest, options) => {
78
111
  }
79
112
  );
80
113
  };
114
+ var getRevokeTokenUrl = () => {
115
+ return `/api/accessi/auth/revoke-token`;
116
+ };
117
+ var revokeToken = async (revokeTokenRequest, options) => {
118
+ return accessiFetch(
119
+ getRevokeTokenUrl(),
120
+ {
121
+ ...options,
122
+ method: "POST",
123
+ body: JSON.stringify(
124
+ revokeTokenRequest
125
+ )
126
+ }
127
+ );
128
+ };
81
129
  var getLoginUrl = () => {
82
130
  return `/api/accessi/auth/login`;
83
131
  };
@@ -1061,6 +1109,8 @@ var WIKI_SECTIONS = [
1061
1109
  ["publicAuthRateLimit", "PublicAuthRateLimitOptions", "no", "Limiti per endpoint pubblici; store condiviso opzionale (Redis) per il cluster."],
1062
1110
  ["edgeRateLimit", "AccessiEdgeRateLimitOptions", "no", "Limite grossolano per IP davanti alle rotte Accessi; opzionale, store condiviso supportato."],
1063
1111
  ["http", "{ cors, trustProxy, securityHeaders }", "no", "CORS, IP reali dietro proxy e header di sicurezza con CSP a nonce per la console e direttive configurabili."],
1112
+ ["authCookie", "{ enabled, secure, sameSite, csrf, ... }", "no", "Cookie HttpOnly e CSRF double-submit al posto del Bearer. Default disattivato."],
1113
+ ["metrics", "{ enabled, sink, prefix }", "no", "Metriche Prometheus di dominio (login, 2FA, refresh, rate limit, store) e sink esterno opzionale."],
1064
1114
  ["publicRegistration", "{ enabled }", "no", "Registrazione pubblica: disattivata per impostazione predefinita."],
1065
1115
  ["federatedAuthentication", "FederatedAuthenticationOptions", "no", `Abilita l'SSO generico.`],
1066
1116
  ["extensionFieldsOptions", "ExtensionFieldsOptions[]", "no", "Tabelle esterne allegate al profilo."],
@@ -2255,6 +2305,7 @@ var consoleMarkerIndex = window.location.pathname.indexOf(consoleMarker);
2255
2305
  var consoleBasePath = consoleMarkerIndex >= 0 ? window.location.pathname.slice(0, consoleMarkerIndex + consoleMarker.length) : `${window.location.pathname.replace(/\/$/, "")}`;
2256
2306
  var USERS_PAGE_SIZE = 25;
2257
2307
  var token = sessionStorage.getItem("accessi-console-token");
2308
+ var cookieMode = typeof window !== "undefined" && window.__ACCESSI_AUTH_COOKIE__ === true;
2258
2309
  var currentUser = null;
2259
2310
  var federatedAuthenticationAvailable = false;
2260
2311
  var usersPage = 0;
@@ -2708,18 +2759,22 @@ async function completeLoginStep(response) {
2708
2759
  byId("login-submit").textContent = "Accedi";
2709
2760
  return;
2710
2761
  }
2711
- if (!response.token?.value) throw new Error("Risposta di autenticazione incompleta.");
2712
- token = response.token.value;
2713
- sessionStorage.setItem("accessi-console-token", token);
2714
- setAccessiConsoleToken(token);
2762
+ if (response.token?.value) {
2763
+ token = response.token.value;
2764
+ sessionStorage.setItem("accessi-console-token", token);
2765
+ setAccessiConsoleToken(token);
2766
+ } else if (!cookieMode) {
2767
+ throw new Error("Risposta di autenticazione incompleta.");
2768
+ }
2715
2769
  resetLogin();
2716
2770
  await bootstrap();
2717
2771
  }
2718
2772
  async function bootstrap() {
2773
+ const hadToken = token !== null;
2719
2774
  try {
2720
2775
  setAccessiConsoleToken(token);
2721
- if (!token) throw new Error("Inserisci le credenziali amministrative.");
2722
- const payload = result(await getUserByToken({ token }));
2776
+ if (!token && !cookieMode) throw new Error("Inserisci le credenziali amministrative.");
2777
+ const payload = result(await getUserByToken({ token: token ?? "" }));
2723
2778
  currentUser = payload.userData;
2724
2779
  requireConsoleAccess();
2725
2780
  applyRoleNavigation();
@@ -2736,19 +2791,20 @@ async function bootstrap() {
2736
2791
  federatedAuthenticationAvailable = false;
2737
2792
  sessionStorage.removeItem("accessi-console-token");
2738
2793
  finishBoot("login");
2739
- byId("login-error").textContent = error instanceof Error ? error.message : "Sessione non valida.";
2794
+ byId("login-error").textContent = cookieMode && !hadToken ? "" : error instanceof Error ? error.message : "Sessione non valida.";
2740
2795
  }
2741
2796
  }
2742
2797
  async function exchangeConsoleEntry(ticket) {
2743
2798
  try {
2744
2799
  const session = result(await exchangeConsoleEntryTicket({ ticket }));
2745
2800
  const value = session.token?.value;
2746
- if (typeof value !== "string" || value === "") {
2801
+ if (typeof value === "string" && value !== "") {
2802
+ token = value;
2803
+ sessionStorage.setItem("accessi-console-token", value);
2804
+ setAccessiConsoleToken(value);
2805
+ } else if (!cookieMode) {
2747
2806
  throw new Error("Apertura automatica della console non riuscita.");
2748
2807
  }
2749
- token = value;
2750
- sessionStorage.setItem("accessi-console-token", value);
2751
- setAccessiConsoleToken(value);
2752
2808
  await bootstrap();
2753
2809
  } catch (error) {
2754
2810
  token = null;
@@ -4036,13 +4092,18 @@ byId("resend-code").onclick = async () => {
4036
4092
  }
4037
4093
  };
4038
4094
  byId("logout").onclick = () => {
4039
- resetLogin();
4040
- token = null;
4041
- currentUser = null;
4042
- setAccessiConsoleToken(null);
4043
- sessionStorage.removeItem("accessi-console-token");
4044
- window.history.replaceState(null, "", consoleBasePath);
4045
- void bootstrap();
4095
+ void (async () => {
4096
+ if (cookieMode) {
4097
+ await revokeToken({}).catch(() => void 0);
4098
+ }
4099
+ resetLogin();
4100
+ token = null;
4101
+ currentUser = null;
4102
+ setAccessiConsoleToken(null);
4103
+ sessionStorage.removeItem("accessi-console-token");
4104
+ window.history.replaceState(null, "", consoleBasePath);
4105
+ void bootstrap();
4106
+ })();
4046
4107
  };
4047
4108
  byId("nav").onclick = (event) => {
4048
4109
  const link = event.target.closest("a[data-view]");
@@ -4054,7 +4115,7 @@ window.addEventListener("popstate", () => {
4054
4115
  if (token && currentUser) handleAction(() => show(viewFromLocation()));
4055
4116
  });
4056
4117
  window.addEventListener(ACCESSI_UNAUTHORIZED_EVENT, () => {
4057
- if (!token) return;
4118
+ if (!token && !cookieMode) return;
4058
4119
  token = null;
4059
4120
  currentUser = null;
4060
4121
  setAccessiConsoleToken(null);
@@ -4102,7 +4163,7 @@ if (entryTicket) {
4102
4163
  window.history.replaceState(null, "", window.location.pathname);
4103
4164
  finishBoot("login");
4104
4165
  showTwoFactor({ challengeId: ssoChallenge, resendAfterSeconds: 60 });
4105
- } else if (token) {
4166
+ } else if (token || cookieMode) {
4106
4167
  void bootstrap();
4107
4168
  } else {
4108
4169
  finishBoot("login");