emilsoftware-utilities 2.0.0-dev.73 → 2.0.0-dev.75

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 (45) hide show
  1. package/README.MD +2 -0
  2. package/dist/accessi/resource-server/token.js +62 -18
  3. package/dist/accessi/resource-server/token.js.map +1 -1
  4. package/dist/accessi/resource-server/types.d.ts +22 -0
  5. package/dist/accessi/resource-server/types.js.map +1 -1
  6. package/dist/accessi/server/index.d.ts +1 -1
  7. package/dist/accessi/server/index.js +2 -1
  8. package/dist/accessi/server/index.js.map +1 -1
  9. package/dist/accessi/server/standalone.d.ts +2 -4
  10. package/dist/accessi/server/standalone.js +4 -79
  11. package/dist/accessi/server/standalone.js.map +1 -1
  12. package/dist/accessi-module/AccessiModule.d.ts +12 -3
  13. package/dist/accessi-module/AccessiModule.js.map +1 -1
  14. package/dist/accessi-module/Console/console.js +2 -1
  15. package/dist/accessi-module/Console/wiki.js +2 -1
  16. package/dist/accessi-module/Console/wiki.js.map +1 -1
  17. package/dist/accessi-module/Services/AuthService/AuthService.js +2 -2
  18. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  19. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.d.ts +9 -1
  20. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +53 -1
  21. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
  22. package/dist/accessi-module/health/nest.d.ts +31 -0
  23. package/dist/accessi-module/health/nest.js +89 -0
  24. package/dist/accessi-module/health/nest.js.map +1 -0
  25. package/dist/accessi-module/health/readiness.d.ts +53 -0
  26. package/dist/accessi-module/health/readiness.js +110 -0
  27. package/dist/accessi-module/health/readiness.js.map +1 -0
  28. package/dist/accessi-module/index.d.ts +6 -0
  29. package/dist/accessi-module/index.js +16 -1
  30. package/dist/accessi-module/index.js.map +1 -1
  31. package/dist/accessi-module/jwt/jwt.strategy.js +1 -35
  32. package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
  33. package/dist/accessi-module/middleware/authenticateGen.js +1 -35
  34. package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
  35. package/dist/accessi-module/security/jwtTokenOptions.d.ts +44 -9
  36. package/dist/accessi-module/security/jwtTokenOptions.js +118 -7
  37. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
  38. package/dist/accessi-module/security/validateOptions.js +31 -4
  39. package/dist/accessi-module/security/validateOptions.js.map +1 -1
  40. package/dist/emilsoftware-utilities-2.0.0-dev.75.tgz +0 -0
  41. package/docs/jwt-key-rotation.md +143 -0
  42. package/docs/proxy-ip.md +99 -0
  43. package/docs/standalone-service.md +25 -0
  44. package/package.json +5 -3
  45. package/dist/emilsoftware-utilities-2.0.0-dev.73.tgz +0 -0
package/README.MD CHANGED
@@ -259,6 +259,7 @@ lista utenti apre un pannello in cui inserire l'URL dell'applicazione e generare
259
259
  - La backdoor mock `admin`/`demo` non e mai attiva quando `NODE_ENV=production`.
260
260
  - Gli allegati sono **protetti di default**: senza `allegatiOptions.authorize` le richieste rispondono 403. Per la migrazione da versioni precedenti impostare `requireAuthorization: false` (sconsigliato in produzione).
261
261
  - Upload allegati: limite di 1 file / 90 MB applicato da Multer e validazione dei magic byte oltre al mimetype dichiarato.
262
+ - **IP reali dietro proxy**: `http.trustProxy` deve corrispondere al numero di proxy davanti al servizio (default `false`). Rate limit e audit usano `req.ip`: un valore errato rende i bucket inefficaci o aggirabili via `X-Forwarded-For`. Guida per Nginx/Traefik/Cloudflare: [docs/proxy-ip.md](docs/proxy-ip.md).
262
263
 
263
264
  ## Integrazione
264
265
 
@@ -267,6 +268,7 @@ lista utenti apre un pannello in cui inserire l'URL dell'applicazione e generare
267
268
  | Backend Express esistente | `await initializeAccessiModule(app, accessiOptions)` monta le rotte `/api/accessi` su un host Nest isolato |
268
269
  | Applicazione NestJS | importa `AccessiModule.forRoot(accessiOptions)` (e/o `AllegatiModule.forRoot(allegatiOptions)`) |
269
270
  | Accessi + Allegati insieme | `EmilsoftwareModule.forRoot({ accessiOptions, allegatiOptions })` |
271
+ | Health/readiness in host Nest | `createAccessiHealthModule({ accessiOptions })` espone `/health/live` e `/health/ready` con lo stesso contratto del servizio standalone |
270
272
  | Client multi-istanza / multi-tenant | `createAccessiClient({ baseUrl, getToken })` isola base URL e token per istanza (concorrenza sicura); `configureAccessiClient` resta il default globale |
271
273
  | Client per scope | `accessi/client/public` (frontend), `accessi/client/admin` (tool admin), `accessi/client/service` (backend fratelli): stesse tipi del client completo, nessuna operazione fuori scope |
272
274
 
@@ -111,20 +111,34 @@ function fetchJwks(options) {
111
111
  });
112
112
  }
113
113
  function selectJwk(keys, kid, algorithm) {
114
- const candidates = keys.filter((key) => {
114
+ return keys.find((key) => {
115
115
  const keyAlg = typeof key.alg === "string" ? key.alg : undefined;
116
- const keyKid = typeof key.kid === "string" ? key.kid : undefined;
117
116
  if (keyAlg && keyAlg !== algorithm)
118
117
  return false;
119
- if (kid && keyKid && keyKid !== kid)
120
- return false;
121
- return true;
118
+ if (!kid)
119
+ return true;
120
+ return typeof key.kid === "string" && key.kid === kid;
122
121
  });
123
- const selected = candidates[0];
124
- if (!selected) {
125
- throw (0, errors_1.accessiAuthError)(401, "AUTH_TOKEN_INVALID", "Chiave JWKS non trovata per il token.");
122
+ }
123
+ /** Fingerprint della chiave pubblica, identico a quello usato dalla JWKS Accessi. */
124
+ function fingerprintPublicKey(publicKey) {
125
+ const der = (0, crypto_1.createPublicKey)(publicKey).export({ type: "spki", format: "der" });
126
+ return (0, crypto_1.createHash)("sha256").update(der).digest("base64url").slice(0, 16);
127
+ }
128
+ function jwkToPem(jwk) {
129
+ return (0, crypto_1.createPublicKey)({ key: jwk, format: "jwk" })
130
+ .export({ type: "spki", format: "pem" })
131
+ .toString();
132
+ }
133
+ /** L'alert non deve mai cambiare l'esito dell'autenticazione. */
134
+ function notifyUnknownKeyId(options, event) {
135
+ var _a;
136
+ try {
137
+ (_a = options.onUnknownKeyId) === null || _a === void 0 ? void 0 : _a.call(options, event);
138
+ }
139
+ catch (_b) {
140
+ // Un logger o webhook guasto non trasforma un 401 in un 500.
126
141
  }
127
- return selected;
128
142
  }
129
143
  /**
130
144
  * Crea un verificatore locale riutilizzabile.
@@ -133,7 +147,7 @@ function selectJwk(keys, kid, algorithm) {
133
147
  * JWKS e legata all'istanza del verificatore.
134
148
  */
135
149
  function createAccessiTokenVerifier(options) {
136
- var _a, _b;
150
+ var _a, _b, _c, _d;
137
151
  const algorithms = defaultAlgorithms(options);
138
152
  if (algorithms.length === 0) {
139
153
  throw (0, errors_1.accessiAuthError)(500, "AUTH_CONFIG_INVALID", "Configurare almeno una tra jwt.secret, jwt.serverSecret, jwt.publicKey e jwt.jwks.");
@@ -141,20 +155,28 @@ function createAccessiTokenVerifier(options) {
141
155
  const clockTolerance = (_a = options.clockToleranceSeconds) !== null && _a !== void 0 ? _a : 5;
142
156
  const hmacSecret = (_b = options.secret) !== null && _b !== void 0 ? _b : (options.serverSecret ? (0, keys_1.deriveAccessiAccessSecret)(options.serverSecret) : undefined);
143
157
  let jwksCache;
144
- const getJwksKeys = () => __awaiter(this, void 0, void 0, function* () {
158
+ const jwksMinRefreshInterval = Math.max(0, (_d = (_c = options.jwks) === null || _c === void 0 ? void 0 : _c.minRefreshIntervalMs) !== null && _d !== void 0 ? _d : 5000);
159
+ const getJwksKeys = (...args_1) => __awaiter(this, [...args_1], void 0, function* (forceRefresh = false) {
145
160
  var _a;
146
161
  if (!options.jwks)
147
162
  return [];
148
- if (jwksCache && jwksCache.expiresAt > Date.now())
163
+ const now = Date.now();
164
+ if (!forceRefresh && jwksCache && jwksCache.expiresAt > now)
165
+ return jwksCache.keys;
166
+ // Refresh forzato ma troppo ravvicinato: riusa la cache per non martellare la JWKS.
167
+ if (forceRefresh && jwksCache && now - jwksCache.fetchedAt < jwksMinRefreshInterval) {
149
168
  return jwksCache.keys;
169
+ }
150
170
  const keys = yield fetchJwks(options.jwks);
151
171
  jwksCache = {
152
172
  keys,
153
- expiresAt: Date.now() + Math.max(1000, (_a = options.jwks.cacheTtlMs) !== null && _a !== void 0 ? _a : 300000),
173
+ expiresAt: now + Math.max(1000, (_a = options.jwks.cacheTtlMs) !== null && _a !== void 0 ? _a : 300000),
174
+ fetchedAt: now,
154
175
  };
155
176
  return keys;
156
177
  });
157
178
  const verify = (token) => __awaiter(this, void 0, void 0, function* () {
179
+ var _a;
158
180
  const decoded = jwt.decode(token, { complete: true });
159
181
  if (!decoded || typeof decoded === "string" || !isRecord(decoded.header)) {
160
182
  throw (0, errors_1.accessiAuthError)(401, "AUTH_TOKEN_INVALID", "Token JWT non valido.");
@@ -163,7 +185,11 @@ function createAccessiTokenVerifier(options) {
163
185
  if (!algorithm || !algorithms.includes(algorithm)) {
164
186
  throw (0, errors_1.accessiAuthError)(401, "AUTH_TOKEN_INVALID", "Algoritmo JWT non consentito.");
165
187
  }
188
+ const kid = typeof decoded.header.kid === "string" && decoded.header.kid !== ""
189
+ ? decoded.header.kid
190
+ : undefined;
166
191
  let key;
192
+ let kidMismatch = false;
167
193
  if (algorithmFamily(algorithm) === "hmac") {
168
194
  if (!hmacSecret) {
169
195
  throw (0, errors_1.accessiAuthError)(500, "AUTH_CONFIG_INVALID", "jwt.secret o jwt.serverSecret non configurato per token HS*.");
@@ -172,12 +198,27 @@ function createAccessiTokenVerifier(options) {
172
198
  }
173
199
  else if (options.publicKey) {
174
200
  key = options.publicKey;
201
+ if (kid) {
202
+ const expectedKeyId = (_a = options.keyId) !== null && _a !== void 0 ? _a : fingerprintPublicKey(options.publicKey);
203
+ kidMismatch = kid !== expectedKeyId;
204
+ }
175
205
  }
176
206
  else if (options.jwks) {
177
- const keys = yield getJwksKeys();
178
- const kid = typeof decoded.header.kid === "string" ? decoded.header.kid : undefined;
179
- key = (0, crypto_1.createPublicKey)({ key: selectJwk(keys, kid, algorithm), format: "jwk" })
180
- .export({ type: "spki", format: "pem" });
207
+ let keys = yield getJwksKeys();
208
+ let selected = selectJwk(keys, kid, algorithm);
209
+ if (!selected && kid) {
210
+ // La JWKS in cache potrebbe precedere una rotazione appena avvenuta.
211
+ keys = yield getJwksKeys(true);
212
+ selected = selectJwk(keys, kid, algorithm);
213
+ }
214
+ if (!selected) {
215
+ if (kid) {
216
+ notifyUnknownKeyId(options, { kid, algorithm, source: "jwks" });
217
+ throw (0, errors_1.accessiAuthError)(401, "AUTH_TOKEN_INVALID", `Chiave JWKS non trovata per kid "${kid}".`);
218
+ }
219
+ throw (0, errors_1.accessiAuthError)(401, "AUTH_TOKEN_INVALID", "Chiave JWKS non trovata per il token.");
220
+ }
221
+ key = jwkToPem(selected);
181
222
  }
182
223
  else {
183
224
  throw (0, errors_1.accessiAuthError)(500, "AUTH_CONFIG_INVALID", "jwt.publicKey/jwt.jwks non configurati.");
@@ -188,7 +229,10 @@ function createAccessiTokenVerifier(options) {
188
229
  ? { audience: options.audience }
189
230
  : {})), { clockTolerance }));
190
231
  }
191
- catch (_a) {
232
+ catch (_b) {
233
+ if (kidMismatch && kid) {
234
+ notifyUnknownKeyId(options, { kid, algorithm, source: "publicKey" });
235
+ }
192
236
  throw (0, errors_1.accessiAuthError)(401, "AUTH_TOKEN_INVALID", "Token non valido o scaduto.");
193
237
  }
194
238
  if (!isRecord(verified)) {
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/accessi/resource-server/token.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPublicKey } from \"crypto\";\nimport type { JsonWebKey } from \"crypto\";\nimport * as jwt from \"jsonwebtoken\";\nimport type { AccessiAccessTokenPayload } from \"../contracts\";\nimport { extractAccessiBearerToken, resolveCodiceUtenteFromTokenPayload } from \"../contracts\";\nimport { accessiAuthError } from \"./errors\";\nimport { deriveAccessiAccessSecret } from \"./keys\";\nimport type { AccessiJwtAlgorithm, AccessiResourceServerJwtOptions } from \"./types\";\n\n/** Esito della verifica locale di un access token. */\nexport interface AccessiTokenVerification {\n token: string;\n payload: AccessiAccessTokenPayload;\n codiceUtente: number;\n user: Record<string, unknown>;\n}\n\n/** Verificatore riutilizzabile: conserva la cache JWKS tra una richiesta e l'altra. */\nexport interface AccessiTokenVerifier {\n verify(token: string): Promise<AccessiTokenVerification>;\n verifyAuthorizationHeader(header: unknown): Promise<AccessiTokenVerification>;\n}\n\ninterface JwksCacheEntry {\n keys: Array<Record<string, unknown>>;\n expiresAt: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction extractUser(payload: Record<string, unknown>): Record<string, unknown> {\n const userData = isRecord(payload.userData) ? payload.userData : undefined;\n const utente = isRecord(payload.utente)\n ? payload.utente\n : isRecord(userData?.utente)\n ? userData.utente\n : undefined;\n return utente ?? {};\n}\n\nfunction defaultAlgorithms(options: AccessiResourceServerJwtOptions): AccessiJwtAlgorithm[] {\n if (options.algorithms && options.algorithms.length > 0) return options.algorithms;\n\n const algorithms: AccessiJwtAlgorithm[] = [];\n if (options.secret || options.serverSecret) algorithms.push(\"HS256\");\n if (options.publicKey || options.jwks) {\n algorithms.push(\"RS256\", \"RS384\", \"RS512\", \"ES256\", \"ES384\", \"ES512\");\n }\n return algorithms;\n}\n\nfunction algorithmFamily(algorithm: string): \"hmac\" | \"asymmetric\" {\n return algorithm.startsWith(\"HS\") ? \"hmac\" : \"asymmetric\";\n}\n\nasync function fetchJwks(\n options: NonNullable<AccessiResourceServerJwtOptions[\"jwks\"]>,\n): Promise<Array<Record<string, unknown>>> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw accessiAuthError(500, \"AUTH_CONFIG_INVALID\", \"fetch non disponibile per caricare la JWKS.\");\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3000);\n try {\n const response = await fetchImpl(options.url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!response.ok) {\n throw accessiAuthError(\n 503,\n \"AUTH_JWKS_UNAVAILABLE\",\n `JWKS non disponibile (HTTP ${response.status}).`,\n );\n }\n const payload: unknown = await response.json();\n if (!isRecord(payload) || !Array.isArray(payload.keys)) {\n throw accessiAuthError(503, \"AUTH_JWKS_UNAVAILABLE\", \"Risposta JWKS non valida.\");\n }\n return payload.keys.filter(isRecord);\n } catch (error) {\n if (error && typeof error === \"object\" && \"code\" in error && \"status\" in error) {\n throw error;\n }\n throw accessiAuthError(503, \"AUTH_JWKS_UNAVAILABLE\", \"JWKS non raggiungibile.\");\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction selectJwk(\n keys: Array<Record<string, unknown>>,\n kid: string | undefined,\n algorithm: string,\n): JsonWebKey {\n const candidates = keys.filter((key) => {\n const keyAlg = typeof key.alg === \"string\" ? key.alg : undefined;\n const keyKid = typeof key.kid === \"string\" ? key.kid : undefined;\n if (keyAlg && keyAlg !== algorithm) return false;\n if (kid && keyKid && keyKid !== kid) return false;\n return true;\n });\n const selected = candidates[0];\n if (!selected) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Chiave JWKS non trovata per il token.\");\n }\n return selected as unknown as JsonWebKey;\n}\n\n/**\n * Crea un verificatore locale riutilizzabile.\n *\n * Supporta HMAC, chiavi pubbliche PEM (RS ed ES) e JWKS remoto. La cache\n * JWKS e legata all'istanza del verificatore.\n */\nexport function createAccessiTokenVerifier(options: AccessiResourceServerJwtOptions): AccessiTokenVerifier {\n const algorithms = defaultAlgorithms(options);\n if (algorithms.length === 0) {\n throw accessiAuthError(\n 500,\n \"AUTH_CONFIG_INVALID\",\n \"Configurare almeno una tra jwt.secret, jwt.serverSecret, jwt.publicKey e jwt.jwks.\",\n );\n }\n\n const clockTolerance = options.clockToleranceSeconds ?? 5;\n const hmacSecret = options.secret\n ?? (options.serverSecret ? deriveAccessiAccessSecret(options.serverSecret) : undefined);\n let jwksCache: JwksCacheEntry | undefined;\n\n const getJwksKeys = async (): Promise<Array<Record<string, unknown>>> => {\n if (!options.jwks) return [];\n if (jwksCache && jwksCache.expiresAt > Date.now()) return jwksCache.keys;\n const keys = await fetchJwks(options.jwks);\n jwksCache = {\n keys,\n expiresAt: Date.now() + Math.max(1_000, options.jwks.cacheTtlMs ?? 300_000),\n };\n return keys;\n };\n\n const verify = async (token: string): Promise<AccessiTokenVerification> => {\n const decoded = jwt.decode(token, { complete: true });\n if (!decoded || typeof decoded === \"string\" || !isRecord(decoded.header)) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Token JWT non valido.\");\n }\n\n const algorithm = typeof decoded.header.alg === \"string\" ? decoded.header.alg : undefined;\n if (!algorithm || !algorithms.includes(algorithm as AccessiJwtAlgorithm)) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Algoritmo JWT non consentito.\");\n }\n\n let key: jwt.Secret;\n if (algorithmFamily(algorithm) === \"hmac\") {\n if (!hmacSecret) {\n throw accessiAuthError(\n 500,\n \"AUTH_CONFIG_INVALID\",\n \"jwt.secret o jwt.serverSecret non configurato per token HS*.\",\n );\n }\n key = hmacSecret;\n } else if (options.publicKey) {\n key = options.publicKey;\n } else if (options.jwks) {\n const keys = await getJwksKeys();\n const kid = typeof decoded.header.kid === \"string\" ? decoded.header.kid : undefined;\n key = createPublicKey({ key: selectJwk(keys, kid, algorithm), format: \"jwk\" })\n .export({ type: \"spki\", format: \"pem\" });\n } else {\n throw accessiAuthError(500, \"AUTH_CONFIG_INVALID\", \"jwt.publicKey/jwt.jwks non configurati.\");\n }\n\n let verified: unknown;\n try {\n verified = jwt.verify(token, key, {\n algorithms: [algorithm as jwt.Algorithm],\n ...(options.issuer ? { issuer: options.issuer } : {}),\n ...(options.audience\n ? { audience: options.audience as jwt.VerifyOptions[\"audience\"] }\n : {}),\n clockTolerance,\n });\n } catch {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Token non valido o scaduto.\");\n }\n\n if (!isRecord(verified)) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Payload JWT non valido.\");\n }\n if (verified.typ !== \"access\") {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Il token non e un access token Accessi.\");\n }\n\n const codiceUtente = resolveCodiceUtenteFromTokenPayload(verified);\n if (!codiceUtente) {\n throw accessiAuthError(401, \"AUTH_USER_CODE_MISSING\", \"Token privo di un utente valido.\");\n }\n\n return {\n token,\n payload: verified as AccessiAccessTokenPayload,\n codiceUtente,\n user: extractUser(verified),\n };\n };\n\n return {\n verify,\n async verifyAuthorizationHeader(header: unknown): Promise<AccessiTokenVerification> {\n const token = extractAccessiBearerToken(header);\n if (!token) {\n throw accessiAuthError(401, \"AUTH_TOKEN_MISSING\", \"Token Bearer mancante o malformato.\");\n }\n return verify(token);\n },\n };\n}\n\n/** Converte un header `Authorization` in verifica del token, senza cache JWKS condivisa. */\nexport async function verifyAccessiAccessToken(\n header: unknown,\n options: AccessiResourceServerJwtOptions,\n): Promise<AccessiTokenVerification> {\n return createAccessiTokenVerifier(options).verifyAuthorizationHeader(header);\n}\n"]}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/accessi/resource-server/token.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, createPublicKey } from \"crypto\";\nimport type { JsonWebKey } from \"crypto\";\nimport * as jwt from \"jsonwebtoken\";\nimport type { AccessiAccessTokenPayload } from \"../contracts\";\nimport { extractAccessiBearerToken, resolveCodiceUtenteFromTokenPayload } from \"../contracts\";\nimport { accessiAuthError } from \"./errors\";\nimport { deriveAccessiAccessSecret } from \"./keys\";\nimport type { AccessiJwtAlgorithm, AccessiResourceServerJwtOptions, AccessiUnknownKeyIdEvent } from \"./types\";\n\n/** Esito della verifica locale di un access token. */\nexport interface AccessiTokenVerification {\n token: string;\n payload: AccessiAccessTokenPayload;\n codiceUtente: number;\n user: Record<string, unknown>;\n}\n\n/** Verificatore riutilizzabile: conserva la cache JWKS tra una richiesta e l'altra. */\nexport interface AccessiTokenVerifier {\n verify(token: string): Promise<AccessiTokenVerification>;\n verifyAuthorizationHeader(header: unknown): Promise<AccessiTokenVerification>;\n}\n\ninterface JwksCacheEntry {\n keys: Array<Record<string, unknown>>;\n expiresAt: number;\n fetchedAt: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction extractUser(payload: Record<string, unknown>): Record<string, unknown> {\n const userData = isRecord(payload.userData) ? payload.userData : undefined;\n const utente = isRecord(payload.utente)\n ? payload.utente\n : isRecord(userData?.utente)\n ? userData.utente\n : undefined;\n return utente ?? {};\n}\n\nfunction defaultAlgorithms(options: AccessiResourceServerJwtOptions): AccessiJwtAlgorithm[] {\n if (options.algorithms && options.algorithms.length > 0) return options.algorithms;\n\n const algorithms: AccessiJwtAlgorithm[] = [];\n if (options.secret || options.serverSecret) algorithms.push(\"HS256\");\n if (options.publicKey || options.jwks) {\n algorithms.push(\"RS256\", \"RS384\", \"RS512\", \"ES256\", \"ES384\", \"ES512\");\n }\n return algorithms;\n}\n\nfunction algorithmFamily(algorithm: string): \"hmac\" | \"asymmetric\" {\n return algorithm.startsWith(\"HS\") ? \"hmac\" : \"asymmetric\";\n}\n\nasync function fetchJwks(\n options: NonNullable<AccessiResourceServerJwtOptions[\"jwks\"]>,\n): Promise<Array<Record<string, unknown>>> {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw accessiAuthError(500, \"AUTH_CONFIG_INVALID\", \"fetch non disponibile per caricare la JWKS.\");\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3000);\n try {\n const response = await fetchImpl(options.url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!response.ok) {\n throw accessiAuthError(\n 503,\n \"AUTH_JWKS_UNAVAILABLE\",\n `JWKS non disponibile (HTTP ${response.status}).`,\n );\n }\n const payload: unknown = await response.json();\n if (!isRecord(payload) || !Array.isArray(payload.keys)) {\n throw accessiAuthError(503, \"AUTH_JWKS_UNAVAILABLE\", \"Risposta JWKS non valida.\");\n }\n return payload.keys.filter(isRecord);\n } catch (error) {\n if (error && typeof error === \"object\" && \"code\" in error && \"status\" in error) {\n throw error;\n }\n throw accessiAuthError(503, \"AUTH_JWKS_UNAVAILABLE\", \"JWKS non raggiungibile.\");\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction selectJwk(\n keys: Array<Record<string, unknown>>,\n kid: string | undefined,\n algorithm: string,\n): Record<string, unknown> | undefined {\n return keys.find((key) => {\n const keyAlg = typeof key.alg === \"string\" ? key.alg : undefined;\n if (keyAlg && keyAlg !== algorithm) return false;\n if (!kid) return true;\n return typeof key.kid === \"string\" && key.kid === kid;\n });\n}\n\n/** Fingerprint della chiave pubblica, identico a quello usato dalla JWKS Accessi. */\nfunction fingerprintPublicKey(publicKey: string | Buffer): string {\n const der = createPublicKey(publicKey).export({ type: \"spki\", format: \"der\" });\n return createHash(\"sha256\").update(der).digest(\"base64url\").slice(0, 16);\n}\n\nfunction jwkToPem(jwk: Record<string, unknown>): string {\n return createPublicKey({ key: jwk as unknown as JsonWebKey, format: \"jwk\" })\n .export({ type: \"spki\", format: \"pem\" })\n .toString();\n}\n\n/** L'alert non deve mai cambiare l'esito dell'autenticazione. */\nfunction notifyUnknownKeyId(\n options: AccessiResourceServerJwtOptions,\n event: AccessiUnknownKeyIdEvent,\n): void {\n try {\n options.onUnknownKeyId?.(event);\n } catch {\n // Un logger o webhook guasto non trasforma un 401 in un 500.\n }\n}\n\n/**\n * Crea un verificatore locale riutilizzabile.\n *\n * Supporta HMAC, chiavi pubbliche PEM (RS ed ES) e JWKS remoto. La cache\n * JWKS e legata all'istanza del verificatore.\n */\nexport function createAccessiTokenVerifier(options: AccessiResourceServerJwtOptions): AccessiTokenVerifier {\n const algorithms = defaultAlgorithms(options);\n if (algorithms.length === 0) {\n throw accessiAuthError(\n 500,\n \"AUTH_CONFIG_INVALID\",\n \"Configurare almeno una tra jwt.secret, jwt.serverSecret, jwt.publicKey e jwt.jwks.\",\n );\n }\n\n const clockTolerance = options.clockToleranceSeconds ?? 5;\n const hmacSecret = options.secret\n ?? (options.serverSecret ? deriveAccessiAccessSecret(options.serverSecret) : undefined);\n let jwksCache: JwksCacheEntry | undefined;\n const jwksMinRefreshInterval = Math.max(0, options.jwks?.minRefreshIntervalMs ?? 5_000);\n\n const getJwksKeys = async (forceRefresh = false): Promise<Array<Record<string, unknown>>> => {\n if (!options.jwks) return [];\n const now = Date.now();\n if (!forceRefresh && jwksCache && jwksCache.expiresAt > now) return jwksCache.keys;\n // Refresh forzato ma troppo ravvicinato: riusa la cache per non martellare la JWKS.\n if (forceRefresh && jwksCache && now - jwksCache.fetchedAt < jwksMinRefreshInterval) {\n return jwksCache.keys;\n }\n const keys = await fetchJwks(options.jwks);\n jwksCache = {\n keys,\n expiresAt: now + Math.max(1_000, options.jwks.cacheTtlMs ?? 300_000),\n fetchedAt: now,\n };\n return keys;\n };\n\n const verify = async (token: string): Promise<AccessiTokenVerification> => {\n const decoded = jwt.decode(token, { complete: true });\n if (!decoded || typeof decoded === \"string\" || !isRecord(decoded.header)) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Token JWT non valido.\");\n }\n\n const algorithm = typeof decoded.header.alg === \"string\" ? decoded.header.alg : undefined;\n if (!algorithm || !algorithms.includes(algorithm as AccessiJwtAlgorithm)) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Algoritmo JWT non consentito.\");\n }\n\n const kid = typeof decoded.header.kid === \"string\" && decoded.header.kid !== \"\"\n ? decoded.header.kid\n : undefined;\n let key: jwt.Secret;\n let kidMismatch = false;\n if (algorithmFamily(algorithm) === \"hmac\") {\n if (!hmacSecret) {\n throw accessiAuthError(\n 500,\n \"AUTH_CONFIG_INVALID\",\n \"jwt.secret o jwt.serverSecret non configurato per token HS*.\",\n );\n }\n key = hmacSecret;\n } else if (options.publicKey) {\n key = options.publicKey;\n if (kid) {\n const expectedKeyId = options.keyId ?? fingerprintPublicKey(options.publicKey);\n kidMismatch = kid !== expectedKeyId;\n }\n } else if (options.jwks) {\n let keys = await getJwksKeys();\n let selected = selectJwk(keys, kid, algorithm);\n if (!selected && kid) {\n // La JWKS in cache potrebbe precedere una rotazione appena avvenuta.\n keys = await getJwksKeys(true);\n selected = selectJwk(keys, kid, algorithm);\n }\n if (!selected) {\n if (kid) {\n notifyUnknownKeyId(options, { kid, algorithm, source: \"jwks\" });\n throw accessiAuthError(\n 401,\n \"AUTH_TOKEN_INVALID\",\n `Chiave JWKS non trovata per kid \"${kid}\".`,\n );\n }\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Chiave JWKS non trovata per il token.\");\n }\n key = jwkToPem(selected);\n } else {\n throw accessiAuthError(500, \"AUTH_CONFIG_INVALID\", \"jwt.publicKey/jwt.jwks non configurati.\");\n }\n\n let verified: unknown;\n try {\n verified = jwt.verify(token, key, {\n algorithms: [algorithm as jwt.Algorithm],\n ...(options.issuer ? { issuer: options.issuer } : {}),\n ...(options.audience\n ? { audience: options.audience as jwt.VerifyOptions[\"audience\"] }\n : {}),\n clockTolerance,\n });\n } catch {\n if (kidMismatch && kid) {\n notifyUnknownKeyId(options, { kid, algorithm, source: \"publicKey\" });\n }\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Token non valido o scaduto.\");\n }\n\n if (!isRecord(verified)) {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Payload JWT non valido.\");\n }\n if (verified.typ !== \"access\") {\n throw accessiAuthError(401, \"AUTH_TOKEN_INVALID\", \"Il token non e un access token Accessi.\");\n }\n\n const codiceUtente = resolveCodiceUtenteFromTokenPayload(verified);\n if (!codiceUtente) {\n throw accessiAuthError(401, \"AUTH_USER_CODE_MISSING\", \"Token privo di un utente valido.\");\n }\n\n return {\n token,\n payload: verified as AccessiAccessTokenPayload,\n codiceUtente,\n user: extractUser(verified),\n };\n };\n\n return {\n verify,\n async verifyAuthorizationHeader(header: unknown): Promise<AccessiTokenVerification> {\n const token = extractAccessiBearerToken(header);\n if (!token) {\n throw accessiAuthError(401, \"AUTH_TOKEN_MISSING\", \"Token Bearer mancante o malformato.\");\n }\n return verify(token);\n },\n };\n}\n\n/** Converte un header `Authorization` in verifica del token, senza cache JWKS condivisa. */\nexport async function verifyAccessiAccessToken(\n header: unknown,\n options: AccessiResourceServerJwtOptions,\n): Promise<AccessiTokenVerification> {\n return createAccessiTokenVerifier(options).verifyAuthorizationHeader(header);\n}\n"]}
@@ -4,6 +4,13 @@ import type { AccessTokenRevocationStore } from "../../accessi-module/security/a
4
4
  import type { RefreshTokenRotationStore } from "../../accessi-module/security/refreshTokenRotation";
5
5
  /** Algoritmi JWT supportati dal resource server. */
6
6
  export type AccessiJwtAlgorithm = "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512" | "ES256" | "ES384" | "ES512";
7
+ /** Evento emesso quando un token usa un `kid` assente dalla configurazione. */
8
+ export interface AccessiUnknownKeyIdEvent {
9
+ kid: string;
10
+ algorithm?: string;
11
+ /** Fonte della chiave: JWKS remota o chiave pubblica statica. */
12
+ source: "jwks" | "publicKey";
13
+ }
7
14
  /** Opzioni di verifica locale del JWT. Almeno una tra `secret`, `publicKey` e `jwks` e obbligatoria. */
8
15
  export interface AccessiResourceServerJwtOptions {
9
16
  /** Chiave HMAC gia derivata (access key). Alternativa a `serverSecret`. */
@@ -22,8 +29,23 @@ export interface AccessiResourceServerJwtOptions {
22
29
  url: string;
23
30
  cacheTtlMs?: number;
24
31
  timeoutMs?: number;
32
+ /**
33
+ * Distanza minima tra due refresh forzati quando arriva un `kid` sconosciuto.
34
+ * Default: 5000 ms. Evita che token con `kid` inventati martellino la JWKS.
35
+ */
36
+ minRefreshIntervalMs?: number;
25
37
  fetch?: typeof fetch;
26
38
  };
39
+ /**
40
+ * `kid` della chiave statica in `publicKey`. Se assente si usa il fingerprint
41
+ * della chiave, lo stesso calcolato dalla JWKS Accessi.
42
+ */
43
+ keyId?: string;
44
+ /**
45
+ * Alert quando un token non e verificabile per un `kid` non presente nella
46
+ * chiave configurata o nella JWKS. Utile per intercettare rotazioni incomplete.
47
+ */
48
+ onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;
27
49
  /** Default: HS256 con `secret`, altrimenti RS/ES con `publicKey`/`jwks`. */
28
50
  algorithms?: AccessiJwtAlgorithm[];
29
51
  /** Issuer atteso (`iss`). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/accessi/resource-server/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n AccessiAuthorizationOptions,\n AccessiGrant,\n GrantsResult,\n} from \"../contracts\";\nimport type { SessionRevocationStore } from \"../../accessi-module/security/sessionRevocation\";\nimport type { AccessTokenRevocationStore } from \"../../accessi-module/security/accessTokenRevocation\";\nimport type { RefreshTokenRotationStore } from \"../../accessi-module/security/refreshTokenRotation\";\n\n/** Algoritmi JWT supportati dal resource server. */\nexport type AccessiJwtAlgorithm =\n | \"HS256\"\n | \"HS384\"\n | \"HS512\"\n | \"RS256\"\n | \"RS384\"\n | \"RS512\"\n | \"ES256\"\n | \"ES384\"\n | \"ES512\";\n\n/** Opzioni di verifica locale del JWT. Almeno una tra `secret`, `publicKey` e `jwks` e obbligatoria. */\nexport interface AccessiResourceServerJwtOptions {\n /** Chiave HMAC gia derivata (access key). Alternativa a `serverSecret`. */\n secret?: string;\n /**\n * Segreto Accessi grezzo (`ACCESSI_JWT_SECRET`).\n *\n * Il resource server deriva automaticamente la chiave access con la stessa\n * KDF del server: e il modo consigliato quando si condivide il segreto HS256.\n */\n serverSecret?: string;\n /** Chiave pubblica PEM per algoritmi RS ed ES. */\n publicKey?: string | Buffer;\n /** JWKS remoto per verificare token firmati con chiavi asimmetriche. */\n jwks?: {\n url: string;\n cacheTtlMs?: number;\n timeoutMs?: number;\n fetch?: typeof fetch;\n };\n /** Default: HS256 con `secret`, altrimenti RS/ES con `publicKey`/`jwks`. */\n algorithms?: AccessiJwtAlgorithm[];\n /** Issuer atteso (`iss`). */\n issuer?: string;\n /** Audience attesa (`aud`). */\n audience?: string | string[];\n /** Tolleranza orologio in secondi. Default: 5. */\n clockToleranceSeconds?: number;\n}\n\n/** Configurazione del client di introspection verso Accessi Service. */\nexport interface AccessiIntrospectionOptions {\n /** URL completo, es. `https://accessi.example.com/api/accessi/auth/introspect`. */\n url: string;\n /** Service token Accessi con scope `accessi:introspect`. */\n serviceToken: string;\n /** TTL della cache locale dei risultati attivi. Default: 30000 ms. */\n cacheTtlMs?: number;\n /** Timeout HTTP. Default: 3000 ms. */\n timeoutMs?: number;\n /** Fetch iniettabile per test o runtime non-Node. */\n fetch?: typeof fetch;\n}\n\n/** Risultato dell'introspection. */\nexport interface AccessiIntrospectionResult {\n active: boolean;\n user?: Record<string, unknown>;\n codiceUtente?: number;\n grants?: GrantsResult;\n}\n\n/** Modalita di autenticazione del resource server. */\nexport type AccessiResourceServerMode = \"local\" | \"introspection\";\n\n/**\n * Configurazione di un backend fratello che non possiede il database Accessi.\n *\n * - `mode: \"local\"` verifica il JWT con chiave locale e, se servono grant, usa\n * l'introspection per leggerli.\n * - `mode: \"introspection\"` delega la verifica del token ad Accessi Service.\n */\nexport interface AccessiResourceServerOptions {\n mode?: AccessiResourceServerMode;\n jwt?: AccessiResourceServerJwtOptions;\n introspection?: AccessiIntrospectionOptions;\n /** Policy di autorizzazione opzionale applicata dopo l'autenticazione. */\n authorization?: AccessiAuthorizationOptions;\n /** Store condiviso di revoca. Opzionale. */\n revocation?: {\n /** Revoca per utente: invalida tutti i token emessi prima del cutoff. */\n store?: SessionRevocationStore;\n /** Revoca dei singoli access token tramite `jti`. */\n accessTokenStore?: AccessTokenRevocationStore;\n /** Store di rotazione refresh: applica la revoca delle famiglie sospette. */\n familyStore?: RefreshTokenRotationStore;\n };\n /** Audit strutturato degli errori di autenticazione. Default: true. */\n audit?: boolean;\n}\n\n/** Dati allegati alla request dopo l'autenticazione. */\nexport interface AccessiRequestContext {\n token: string;\n user: Record<string, unknown>;\n codiceUtente?: number;\n grants?: GrantsResult;\n payload?: Record<string, unknown>;\n}\n\n/** Grant esposto per compatibilita con il middleware storico. */\nexport type AccessiRequestGrants = AccessiGrant[];\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/accessi/resource-server/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n AccessiAuthorizationOptions,\n AccessiGrant,\n GrantsResult,\n} from \"../contracts\";\nimport type { SessionRevocationStore } from \"../../accessi-module/security/sessionRevocation\";\nimport type { AccessTokenRevocationStore } from \"../../accessi-module/security/accessTokenRevocation\";\nimport type { RefreshTokenRotationStore } from \"../../accessi-module/security/refreshTokenRotation\";\n\n/** Algoritmi JWT supportati dal resource server. */\nexport type AccessiJwtAlgorithm =\n | \"HS256\"\n | \"HS384\"\n | \"HS512\"\n | \"RS256\"\n | \"RS384\"\n | \"RS512\"\n | \"ES256\"\n | \"ES384\"\n | \"ES512\";\n\n/** Evento emesso quando un token usa un `kid` assente dalla configurazione. */\nexport interface AccessiUnknownKeyIdEvent {\n kid: string;\n algorithm?: string;\n /** Fonte della chiave: JWKS remota o chiave pubblica statica. */\n source: \"jwks\" | \"publicKey\";\n}\n\n/** Opzioni di verifica locale del JWT. Almeno una tra `secret`, `publicKey` e `jwks` e obbligatoria. */\nexport interface AccessiResourceServerJwtOptions {\n /** Chiave HMAC gia derivata (access key). Alternativa a `serverSecret`. */\n secret?: string;\n /**\n * Segreto Accessi grezzo (`ACCESSI_JWT_SECRET`).\n *\n * Il resource server deriva automaticamente la chiave access con la stessa\n * KDF del server: e il modo consigliato quando si condivide il segreto HS256.\n */\n serverSecret?: string;\n /** Chiave pubblica PEM per algoritmi RS ed ES. */\n publicKey?: string | Buffer;\n /** JWKS remoto per verificare token firmati con chiavi asimmetriche. */\n jwks?: {\n url: string;\n cacheTtlMs?: number;\n timeoutMs?: number;\n /**\n * Distanza minima tra due refresh forzati quando arriva un `kid` sconosciuto.\n * Default: 5000 ms. Evita che token con `kid` inventati martellino la JWKS.\n */\n minRefreshIntervalMs?: number;\n fetch?: typeof fetch;\n };\n /**\n * `kid` della chiave statica in `publicKey`. Se assente si usa il fingerprint\n * della chiave, lo stesso calcolato dalla JWKS Accessi.\n */\n keyId?: string;\n /**\n * Alert quando un token non e verificabile per un `kid` non presente nella\n * chiave configurata o nella JWKS. Utile per intercettare rotazioni incomplete.\n */\n onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;\n /** Default: HS256 con `secret`, altrimenti RS/ES con `publicKey`/`jwks`. */\n algorithms?: AccessiJwtAlgorithm[];\n /** Issuer atteso (`iss`). */\n issuer?: string;\n /** Audience attesa (`aud`). */\n audience?: string | string[];\n /** Tolleranza orologio in secondi. Default: 5. */\n clockToleranceSeconds?: number;\n}\n\n/** Configurazione del client di introspection verso Accessi Service. */\nexport interface AccessiIntrospectionOptions {\n /** URL completo, es. `https://accessi.example.com/api/accessi/auth/introspect`. */\n url: string;\n /** Service token Accessi con scope `accessi:introspect`. */\n serviceToken: string;\n /** TTL della cache locale dei risultati attivi. Default: 30000 ms. */\n cacheTtlMs?: number;\n /** Timeout HTTP. Default: 3000 ms. */\n timeoutMs?: number;\n /** Fetch iniettabile per test o runtime non-Node. */\n fetch?: typeof fetch;\n}\n\n/** Risultato dell'introspection. */\nexport interface AccessiIntrospectionResult {\n active: boolean;\n user?: Record<string, unknown>;\n codiceUtente?: number;\n grants?: GrantsResult;\n}\n\n/** Modalita di autenticazione del resource server. */\nexport type AccessiResourceServerMode = \"local\" | \"introspection\";\n\n/**\n * Configurazione di un backend fratello che non possiede il database Accessi.\n *\n * - `mode: \"local\"` verifica il JWT con chiave locale e, se servono grant, usa\n * l'introspection per leggerli.\n * - `mode: \"introspection\"` delega la verifica del token ad Accessi Service.\n */\nexport interface AccessiResourceServerOptions {\n mode?: AccessiResourceServerMode;\n jwt?: AccessiResourceServerJwtOptions;\n introspection?: AccessiIntrospectionOptions;\n /** Policy di autorizzazione opzionale applicata dopo l'autenticazione. */\n authorization?: AccessiAuthorizationOptions;\n /** Store condiviso di revoca. Opzionale. */\n revocation?: {\n /** Revoca per utente: invalida tutti i token emessi prima del cutoff. */\n store?: SessionRevocationStore;\n /** Revoca dei singoli access token tramite `jti`. */\n accessTokenStore?: AccessTokenRevocationStore;\n /** Store di rotazione refresh: applica la revoca delle famiglie sospette. */\n familyStore?: RefreshTokenRotationStore;\n };\n /** Audit strutturato degli errori di autenticazione. Default: true. */\n audit?: boolean;\n}\n\n/** Dati allegati alla request dopo l'autenticazione. */\nexport interface AccessiRequestContext {\n token: string;\n user: Record<string, unknown>;\n codiceUtente?: number;\n grants?: GrantsResult;\n payload?: Record<string, unknown>;\n}\n\n/** Grant esposto per compatibilita con il middleware storico. */\nexport type AccessiRequestGrants = AccessiGrant[];\n"]}
@@ -8,4 +8,4 @@
8
8
  export * from "../../accessi-module";
9
9
  export * from "./standalone";
10
10
  export type { AccessiOptions as AccessiServerOptions } from "../../accessi-module";
11
- export { createAccessiOptionsFromEnv, createCatalogScriptsOptionsFromEnv, } from "../../accessi-module/database-updates/accessiOptionsFromEnv";
11
+ export { createAccessiOptionsFromEnv, createCatalogScriptsOptionsFromEnv, parseAccessiPreviousPublicKeys, } from "../../accessi-module/database-updates/accessiOptionsFromEnv";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.createCatalogScriptsOptionsFromEnv = exports.createAccessiOptionsFromEnv = void 0;
17
+ exports.parseAccessiPreviousPublicKeys = exports.createCatalogScriptsOptionsFromEnv = exports.createAccessiOptionsFromEnv = void 0;
18
18
  /**
19
19
  * Entry point server Accessi.
20
20
  *
@@ -27,4 +27,5 @@ __exportStar(require("./standalone"), exports);
27
27
  var accessiOptionsFromEnv_1 = require("../../accessi-module/database-updates/accessiOptionsFromEnv");
28
28
  Object.defineProperty(exports, "createAccessiOptionsFromEnv", { enumerable: true, get: function () { return accessiOptionsFromEnv_1.createAccessiOptionsFromEnv; } });
29
29
  Object.defineProperty(exports, "createCatalogScriptsOptionsFromEnv", { enumerable: true, get: function () { return accessiOptionsFromEnv_1.createCatalogScriptsOptionsFromEnv; } });
30
+ Object.defineProperty(exports, "parseAccessiPreviousPublicKeys", { enumerable: true, get: function () { return accessiOptionsFromEnv_1.parseAccessiPreviousPublicKeys; } });
30
31
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/accessi/server/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;GAMG;AACH,uDAAqC;AACrC,+CAA6B;AAE7B,qGAGqE;AAFnE,oIAAA,2BAA2B,OAAA;AAC3B,2IAAA,kCAAkC,OAAA","sourcesContent":["/**\n * Entry point server Accessi.\n *\n * Questo e l'unico entry point che puo importare database, SMTP, migrazioni,\n * console e servizi Nest completi. I backend fratelli devono usare\n * `accessi/resource-server` e `accessi/client`.\n */\nexport * from \"../../accessi-module\";\nexport * from \"./standalone\";\nexport type { AccessiOptions as AccessiServerOptions } from \"../../accessi-module\";\nexport {\n createAccessiOptionsFromEnv,\n createCatalogScriptsOptionsFromEnv,\n} from \"../../accessi-module/database-updates/accessiOptionsFromEnv\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/accessi/server/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;GAMG;AACH,uDAAqC;AACrC,+CAA6B;AAE7B,qGAIqE;AAHnE,oIAAA,2BAA2B,OAAA;AAC3B,2IAAA,kCAAkC,OAAA;AAClC,uIAAA,8BAA8B,OAAA","sourcesContent":["/**\n * Entry point server Accessi.\n *\n * Questo e l'unico entry point che puo importare database, SMTP, migrazioni,\n * console e servizi Nest completi. I backend fratelli devono usare\n * `accessi/resource-server` e `accessi/client`.\n */\nexport * from \"../../accessi-module\";\nexport * from \"./standalone\";\nexport type { AccessiOptions as AccessiServerOptions } from \"../../accessi-module\";\nexport {\n createAccessiOptionsFromEnv,\n createCatalogScriptsOptionsFromEnv,\n parseAccessiPreviousPublicKeys,\n} from \"../../accessi-module/database-updates/accessiOptionsFromEnv\";\n"]}
@@ -2,10 +2,8 @@ import type { Application } from 'express';
2
2
  import type { Server } from 'http';
3
3
  import type { AccessiModuleInitializationResult } from '../../accessi-module';
4
4
  import type { AccessiOptions } from '../../accessi-module/AccessiModule';
5
- export interface AccessiReadinessCheck {
6
- name: string;
7
- check: () => boolean | Promise<boolean>;
8
- }
5
+ import type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';
6
+ export type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';
9
7
  export interface AccessiServiceOptions {
10
8
  /** Applicazione Express gia esistente. Default: nuova app. */
11
9
  app?: Application;
@@ -19,8 +19,7 @@ const Logger_1 = require("../../Logger");
19
19
  const Orm_1 = require("../../Orm");
20
20
  const accessi_module_1 = require("../../accessi-module");
21
21
  const accessiOptionsFromEnv_1 = require("../../accessi-module/database-updates/accessiOptionsFromEnv");
22
- const accessiSchema_1 = require("../../accessi-module/database-updates/accessiSchema");
23
- const AccessiDatabaseUpdater_1 = require("../../accessi-module/database-updates/AccessiDatabaseUpdater");
22
+ const readiness_1 = require("../../accessi-module/health/readiness");
24
23
  const validateOptions_1 = require("../../accessi-module/security/validateOptions");
25
24
  function normalizePath(value) {
26
25
  return `/${value.replace(/^\/+|\/+$/g, '')}`;
@@ -66,44 +65,6 @@ function createMetrics() {
66
65
  };
67
66
  return { middleware, render };
68
67
  }
69
- function collectHealthStores(options) {
70
- var _a, _b, _c, _d, _e, _f, _g;
71
- const stores = [];
72
- const seen = new Set();
73
- const add = (name, store) => {
74
- if (!store || seen.has(store) || typeof store.healthCheck !== 'function')
75
- return;
76
- seen.add(store);
77
- stores.push({ name, store });
78
- };
79
- add('publicAuthRateLimit', (_a = options.publicAuthRateLimit) === null || _a === void 0 ? void 0 : _a.store);
80
- add('serviceTokenRateLimit', (_b = options.serviceTokens) === null || _b === void 0 ? void 0 : _b.rateLimitStore);
81
- add('consoleEntry', (_c = options.consoleEntry) === null || _c === void 0 ? void 0 : _c.store);
82
- add('edgeRateLimit', (_d = options.edgeRateLimit) === null || _d === void 0 ? void 0 : _d.store);
83
- add('sessionRevocation', (_e = options.sessionRevocation) === null || _e === void 0 ? void 0 : _e.store);
84
- add('accessTokenRevocation', (_f = options.accessTokenRevocation) === null || _f === void 0 ? void 0 : _f.store);
85
- add('refreshTokenRotation', (_g = options.refreshTokenRotation) === null || _g === void 0 ? void 0 : _g.store);
86
- return stores;
87
- }
88
- function checkDatabase(options) {
89
- return __awaiter(this, void 0, void 0, function* () {
90
- try {
91
- const version = yield AccessiDatabaseUpdater_1.AccessiDatabaseUpdater.getCurrentVersion(options);
92
- if (!version) {
93
- return { ok: false, detail: 'schema Accessi non inizializzato' };
94
- }
95
- return {
96
- ok: true,
97
- detail: version === accessiSchema_1.ACCESSI_SCHEMA_VERSION
98
- ? version
99
- : `${version} (atteso ${accessiSchema_1.ACCESSI_SCHEMA_VERSION})`,
100
- };
101
- }
102
- catch (error) {
103
- return { ok: false, detail: error instanceof Error ? error.message : String(error) };
104
- }
105
- });
106
- }
107
68
  /**
108
69
  * Crea un servizio Accessi standalone con health, readiness, metriche e shutdown.
109
70
  *
@@ -129,10 +90,7 @@ function createAccessiService() {
129
90
  let stopping = false;
130
91
  app.use(metrics.middleware);
131
92
  app.get(`${healthPath}/live`, (_req, res) => {
132
- res.status(200).json({
133
- status: 'ok',
134
- uptimeSeconds: Math.floor(process.uptime()),
135
- });
93
+ res.status(200).json((0, readiness_1.createAccessiLiveReport)());
136
94
  });
137
95
  app.get(`${healthPath}/ready`, (_req, res) => __awaiter(this, void 0, void 0, function* () {
138
96
  if (!initialized || !nestApp) {
@@ -142,41 +100,8 @@ function createAccessiService() {
142
100
  });
143
101
  return;
144
102
  }
145
- const checks = {
146
- bootstrap: { ok: true },
147
- database: yield checkDatabase(accessiOptions),
148
- };
149
- const stores = collectHealthStores(accessiOptions);
150
- yield Promise.all(stores.map((_a) => __awaiter(this, [_a], void 0, function* ({ name, store }) {
151
- var _b;
152
- try {
153
- checks[name] = { ok: Boolean(yield ((_b = store.healthCheck) === null || _b === void 0 ? void 0 : _b.call(store))) };
154
- }
155
- catch (error) {
156
- checks[name] = {
157
- ok: false,
158
- detail: error instanceof Error ? error.message : String(error),
159
- };
160
- }
161
- })));
162
- for (const check of readinessChecks) {
163
- try {
164
- const ok = yield check.check();
165
- checks[check.name] = { ok: Boolean(ok) };
166
- }
167
- catch (error) {
168
- checks[check.name] = {
169
- ok: false,
170
- detail: error instanceof Error ? error.message : String(error),
171
- };
172
- }
173
- }
174
- const ok = Object.values(checks).every((check) => check.ok);
175
- res.status(ok ? 200 : 503).json({
176
- status: ok ? 'ok' : 'error',
177
- checks,
178
- timestamp: new Date().toISOString(),
179
- });
103
+ const report = yield (0, readiness_1.checkAccessiReadiness)(accessiOptions, readinessChecks);
104
+ res.status((0, readiness_1.readinessHttpStatus)(report)).json(report);
180
105
  }));
181
106
  if (options.metrics !== false) {
182
107
  app.get(`${healthPath}/metrics`, (_req, res) => {
@@ -1 +1 @@
1
- {"version":3,"file":"standalone.js","sourceRoot":"","sources":["../../../src/accessi/server/standalone.ts"],"names":[],"mappings":"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","sourcesContent":["import express from 'express';\nimport type { Application, Request, Response } from 'express';\nimport type { Server } from 'http';\nimport { Logger } from '../../Logger';\nimport { Orm } from '../../Orm';\nimport { initializeAccessiModule } from '../../accessi-module';\nimport type {\n AccessiModuleInitializationResult,\n} from '../../accessi-module';\nimport type { AccessiOptions } from '../../accessi-module/AccessiModule';\nimport { createAccessiOptionsFromEnv } from '../../accessi-module/database-updates/accessiOptionsFromEnv';\nimport {\n ACCESSI_SCHEMA_VERSION,\n} from '../../accessi-module/database-updates/accessiSchema';\nimport { AccessiDatabaseUpdater } from '../../accessi-module/database-updates/AccessiDatabaseUpdater';\nimport { assertValidAccessiOptions, validateAccessiOptions } from '../../accessi-module/security/validateOptions';\n\nexport interface AccessiReadinessCheck {\n name: string;\n check: () => boolean | Promise<boolean>;\n}\n\nexport interface AccessiServiceOptions {\n /** Applicazione Express gia esistente. Default: nuova app. */\n app?: Application;\n /** Configurazione completa. Default: `createAccessiOptionsFromEnv()`. */\n accessiOptions?: AccessiOptions;\n port?: number;\n host?: string;\n /** Path base degli endpoint health. Default: `/health`. */\n healthPath?: string;\n /** Espone `/health/metrics` in formato Prometheus. Default: true. */\n metrics?: boolean;\n /** Check aggiuntivi per la readiness. */\n readinessChecks?: AccessiReadinessCheck[];\n /** Timeout chiusura server. Default: 10000 ms. */\n shutdownTimeoutMs?: number;\n}\n\nexport interface AccessiService {\n app: Application;\n nestApp: AccessiModuleInitializationResult['nestApp'];\n document: AccessiModuleInitializationResult['document'];\n start(): Promise<Server>;\n stop(): Promise<void>;\n}\n\ninterface HealthCheckResult {\n ok: boolean;\n detail?: string;\n}\n\nfunction normalizePath(value: string): string {\n return `/${value.replace(/^\\/+|\\/+$/g, '')}`;\n}\n\nfunction createMetrics() {\n const startedAt = Date.now();\n let requests = 0;\n let inFlight = 0;\n const statuses = new Map<string, number>();\n\n const middleware = (_req: Request, res: Response, next: () => void): void => {\n requests += 1;\n inFlight += 1;\n res.on('finish', () => {\n inFlight = Math.max(0, inFlight - 1);\n const status = String(res.statusCode);\n statuses.set(status, (statuses.get(status) ?? 0) + 1);\n });\n next();\n };\n\n const render = (): string => {\n const memory = process.memoryUsage();\n const lines = [\n '# HELP accessi_http_requests_total Richieste HTTP totali.',\n '# TYPE accessi_http_requests_total counter',\n ...[...statuses.entries()].map(\n ([status, count]) => `accessi_http_requests_total{status=\"${status}\"} ${count}`,\n ),\n '# HELP accessi_http_in_flight Richieste HTTP in corso.',\n '# TYPE accessi_http_in_flight gauge',\n `accessi_http_in_flight ${inFlight}`,\n '# HELP accessi_process_uptime_seconds Uptime del processo.',\n '# TYPE accessi_process_uptime_seconds gauge',\n `accessi_process_uptime_seconds ${Math.floor((Date.now() - startedAt) / 1000)}`,\n '# HELP accessi_process_memory_bytes Memoria del processo.',\n '# TYPE accessi_process_memory_bytes gauge',\n `accessi_process_memory_bytes{type=\"rss\"} ${memory.rss}`,\n `accessi_process_memory_bytes{type=\"heapUsed\"} ${memory.heapUsed}`,\n `accessi_process_memory_bytes{type=\"heapTotal\"} ${memory.heapTotal}`,\n '# HELP accessi_http_requests_started_total Richieste HTTP avviate.',\n '# TYPE accessi_http_requests_started_total counter',\n `accessi_http_requests_started_total ${requests}`,\n ];\n return `${lines.join('\\n')}\\n`;\n };\n\n return { middleware, render };\n}\n\nfunction collectHealthStores(options: AccessiOptions): Array<{ name: string; store: { healthCheck?: () => Promise<boolean> } }> {\n const stores: Array<{ name: string; store: { healthCheck?: () => Promise<boolean> } }> = [];\n const seen = new Set<unknown>();\n\n const add = (name: string, store: { healthCheck?: () => Promise<boolean> } | undefined): void => {\n if (!store || seen.has(store) || typeof store.healthCheck !== 'function') return;\n seen.add(store);\n stores.push({ name, store });\n };\n\n add('publicAuthRateLimit', options.publicAuthRateLimit?.store);\n add('serviceTokenRateLimit', options.serviceTokens?.rateLimitStore);\n add('consoleEntry', options.consoleEntry?.store);\n add('edgeRateLimit', options.edgeRateLimit?.store);\n add('sessionRevocation', options.sessionRevocation?.store);\n add('accessTokenRevocation', options.accessTokenRevocation?.store);\n add('refreshTokenRotation', options.refreshTokenRotation?.store);\n return stores;\n}\n\nasync function checkDatabase(options: AccessiOptions): Promise<HealthCheckResult> {\n try {\n const version = await AccessiDatabaseUpdater.getCurrentVersion(options);\n if (!version) {\n return { ok: false, detail: 'schema Accessi non inizializzato' };\n }\n return {\n ok: true,\n detail: version === ACCESSI_SCHEMA_VERSION\n ? version\n : `${version} (atteso ${ACCESSI_SCHEMA_VERSION})`,\n };\n } catch (error) {\n return { ok: false, detail: error instanceof Error ? error.message : String(error) };\n }\n}\n\n/**\n * Crea un servizio Accessi standalone con health, readiness, metriche e shutdown.\n *\n * Non apre la porta finche non viene chiamato `start()`.\n */\nexport async function createAccessiService(\n options: AccessiServiceOptions = {},\n): Promise<AccessiService> {\n const logger = new Logger('AccessiService');\n const accessiOptions = options.accessiOptions ?? createAccessiOptionsFromEnv();\n assertValidAccessiOptions(accessiOptions);\n for (const warning of validateAccessiOptions(accessiOptions).warnings) {\n logger.warning(`Configurazione Accessi: ${warning.code} - ${warning.message}`);\n }\n\n const app = options.app ?? express();\n const healthPath = normalizePath(options.healthPath ?? '/health');\n const metrics = createMetrics();\n const readinessChecks = options.readinessChecks ?? [];\n let initialized = false;\n let nestApp: AccessiModuleInitializationResult['nestApp'] | undefined;\n let document: AccessiModuleInitializationResult['document'] | undefined;\n let server: Server | undefined;\n let stopping = false;\n\n app.use(metrics.middleware);\n\n app.get(`${healthPath}/live`, (_req: Request, res: Response) => {\n res.status(200).json({\n status: 'ok',\n uptimeSeconds: Math.floor(process.uptime()),\n });\n });\n\n app.get(`${healthPath}/ready`, async (_req: Request, res: Response) => {\n if (!initialized || !nestApp) {\n res.status(503).json({\n status: 'error',\n checks: { bootstrap: { ok: false, detail: 'bootstrap non completato' } },\n });\n return;\n }\n\n const checks: Record<string, HealthCheckResult> = {\n bootstrap: { ok: true },\n database: await checkDatabase(accessiOptions),\n };\n\n const stores = collectHealthStores(accessiOptions);\n await Promise.all(stores.map(async ({ name, store }) => {\n try {\n checks[name] = { ok: Boolean(await store.healthCheck?.()) };\n } catch (error) {\n checks[name] = {\n ok: false,\n detail: error instanceof Error ? error.message : String(error),\n };\n }\n }));\n\n for (const check of readinessChecks) {\n try {\n const ok = await check.check();\n checks[check.name] = { ok: Boolean(ok) };\n } catch (error) {\n checks[check.name] = {\n ok: false,\n detail: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n const ok = Object.values(checks).every((check) => check.ok);\n res.status(ok ? 200 : 503).json({\n status: ok ? 'ok' : 'error',\n checks,\n timestamp: new Date().toISOString(),\n });\n });\n\n if (options.metrics !== false) {\n app.get(`${healthPath}/metrics`, (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/plain; version=0.0.4');\n res.status(200).send(metrics.render());\n });\n }\n\n const result = await initializeAccessiModule(app, accessiOptions);\n nestApp = result.nestApp;\n document = result.document;\n initialized = true;\n\n const stop = async (): Promise<void> => {\n if (stopping) return;\n stopping = true;\n const timeout = Math.max(1_000, options.shutdownTimeoutMs ?? 10_000);\n await Promise.race([\n new Promise<void>((resolve) => {\n if (!server) return resolve();\n server.close(() => resolve());\n }),\n new Promise<void>((resolve) => setTimeout(resolve, timeout)),\n ]);\n try {\n await nestApp?.close();\n } finally {\n await Orm.closePools();\n }\n };\n\n const start = async (): Promise<Server> => {\n if (server) return server;\n const port = options.port ?? Number(process.env.PORT ?? 3000);\n server = await new Promise<Server>((resolve, reject) => {\n const listener = options.host\n ? app.listen(port, options.host, () => resolve(listener))\n : app.listen(port, () => resolve(listener));\n listener.once('error', reject);\n });\n const address = server.address();\n const actualPort = typeof address === 'object' && address ? address.port : port;\n logger.info(`Accessi Service in ascolto su http://${options.host ?? '0.0.0.0'}:${actualPort}`);\n return server;\n };\n\n return {\n app,\n get nestApp() {\n if (!nestApp) throw new Error('Accessi Service non inizializzato.');\n return nestApp;\n },\n get document() {\n if (!document) throw new Error('Documento OpenAPI non disponibile.');\n return document;\n },\n start,\n stop,\n };\n}\n\n/**\n * Avvia Accessi Service e registra SIGTERM/SIGINT per lo shutdown graceful.\n */\nexport async function startAccessiService(options: AccessiServiceOptions = {}): Promise<AccessiService> {\n const service = await createAccessiService(options);\n await service.start();\n\n let shuttingDown = false;\n const shutdown = async (signal: string): Promise<void> => {\n if (shuttingDown) return;\n shuttingDown = true;\n new Logger('AccessiService').info(`Ricevuto ${signal}: arresto in corso.`);\n await service.stop();\n };\n\n process.once('SIGTERM', () => { void shutdown('SIGTERM'); });\n process.once('SIGINT', () => { void shutdown('SIGINT'); });\n return service;\n}\n"]}
1
+ {"version":3,"file":"standalone.js","sourceRoot":"","sources":["../../../src/accessi/server/standalone.ts"],"names":[],"mappings":"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","sourcesContent":["import express from 'express';\nimport type { Application, Request, Response } from 'express';\nimport type { Server } from 'http';\nimport { Logger } from '../../Logger';\nimport { Orm } from '../../Orm';\nimport { initializeAccessiModule } from '../../accessi-module';\nimport type {\n AccessiModuleInitializationResult,\n} from '../../accessi-module';\nimport type { AccessiOptions } from '../../accessi-module/AccessiModule';\nimport { createAccessiOptionsFromEnv } from '../../accessi-module/database-updates/accessiOptionsFromEnv';\nimport {\n checkAccessiReadiness,\n createAccessiLiveReport,\n readinessHttpStatus,\n} from '../../accessi-module/health/readiness';\nimport type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';\nimport { assertValidAccessiOptions, validateAccessiOptions } from '../../accessi-module/security/validateOptions';\n\nexport type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';\n\n\nexport interface AccessiServiceOptions {\n /** Applicazione Express gia esistente. Default: nuova app. */\n app?: Application;\n /** Configurazione completa. Default: `createAccessiOptionsFromEnv()`. */\n accessiOptions?: AccessiOptions;\n port?: number;\n host?: string;\n /** Path base degli endpoint health. Default: `/health`. */\n healthPath?: string;\n /** Espone `/health/metrics` in formato Prometheus. Default: true. */\n metrics?: boolean;\n /** Check aggiuntivi per la readiness. */\n readinessChecks?: AccessiReadinessCheck[];\n /** Timeout chiusura server. Default: 10000 ms. */\n shutdownTimeoutMs?: number;\n}\n\nexport interface AccessiService {\n app: Application;\n nestApp: AccessiModuleInitializationResult['nestApp'];\n document: AccessiModuleInitializationResult['document'];\n start(): Promise<Server>;\n stop(): Promise<void>;\n}\n\nfunction normalizePath(value: string): string {\n return `/${value.replace(/^\\/+|\\/+$/g, '')}`;\n}\n\nfunction createMetrics() {\n const startedAt = Date.now();\n let requests = 0;\n let inFlight = 0;\n const statuses = new Map<string, number>();\n\n const middleware = (_req: Request, res: Response, next: () => void): void => {\n requests += 1;\n inFlight += 1;\n res.on('finish', () => {\n inFlight = Math.max(0, inFlight - 1);\n const status = String(res.statusCode);\n statuses.set(status, (statuses.get(status) ?? 0) + 1);\n });\n next();\n };\n\n const render = (): string => {\n const memory = process.memoryUsage();\n const lines = [\n '# HELP accessi_http_requests_total Richieste HTTP totali.',\n '# TYPE accessi_http_requests_total counter',\n ...[...statuses.entries()].map(\n ([status, count]) => `accessi_http_requests_total{status=\"${status}\"} ${count}`,\n ),\n '# HELP accessi_http_in_flight Richieste HTTP in corso.',\n '# TYPE accessi_http_in_flight gauge',\n `accessi_http_in_flight ${inFlight}`,\n '# HELP accessi_process_uptime_seconds Uptime del processo.',\n '# TYPE accessi_process_uptime_seconds gauge',\n `accessi_process_uptime_seconds ${Math.floor((Date.now() - startedAt) / 1000)}`,\n '# HELP accessi_process_memory_bytes Memoria del processo.',\n '# TYPE accessi_process_memory_bytes gauge',\n `accessi_process_memory_bytes{type=\"rss\"} ${memory.rss}`,\n `accessi_process_memory_bytes{type=\"heapUsed\"} ${memory.heapUsed}`,\n `accessi_process_memory_bytes{type=\"heapTotal\"} ${memory.heapTotal}`,\n '# HELP accessi_http_requests_started_total Richieste HTTP avviate.',\n '# TYPE accessi_http_requests_started_total counter',\n `accessi_http_requests_started_total ${requests}`,\n ];\n return `${lines.join('\\n')}\\n`;\n };\n\n return { middleware, render };\n}\n\n/**\n * Crea un servizio Accessi standalone con health, readiness, metriche e shutdown.\n *\n * Non apre la porta finche non viene chiamato `start()`.\n */\nexport async function createAccessiService(\n options: AccessiServiceOptions = {},\n): Promise<AccessiService> {\n const logger = new Logger('AccessiService');\n const accessiOptions = options.accessiOptions ?? createAccessiOptionsFromEnv();\n assertValidAccessiOptions(accessiOptions);\n for (const warning of validateAccessiOptions(accessiOptions).warnings) {\n logger.warning(`Configurazione Accessi: ${warning.code} - ${warning.message}`);\n }\n\n const app = options.app ?? express();\n const healthPath = normalizePath(options.healthPath ?? '/health');\n const metrics = createMetrics();\n const readinessChecks = options.readinessChecks ?? [];\n let initialized = false;\n let nestApp: AccessiModuleInitializationResult['nestApp'] | undefined;\n let document: AccessiModuleInitializationResult['document'] | undefined;\n let server: Server | undefined;\n let stopping = false;\n\n app.use(metrics.middleware);\n\n app.get(`${healthPath}/live`, (_req: Request, res: Response) => {\n res.status(200).json(createAccessiLiveReport());\n });\n\n app.get(`${healthPath}/ready`, async (_req: Request, res: Response) => {\n if (!initialized || !nestApp) {\n res.status(503).json({\n status: 'error',\n checks: { bootstrap: { ok: false, detail: 'bootstrap non completato' } },\n });\n return;\n }\n\n const report = await checkAccessiReadiness(accessiOptions, readinessChecks);\n res.status(readinessHttpStatus(report)).json(report);\n });\n\n if (options.metrics !== false) {\n app.get(`${healthPath}/metrics`, (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/plain; version=0.0.4');\n res.status(200).send(metrics.render());\n });\n }\n\n const result = await initializeAccessiModule(app, accessiOptions);\n nestApp = result.nestApp;\n document = result.document;\n initialized = true;\n\n const stop = async (): Promise<void> => {\n if (stopping) return;\n stopping = true;\n const timeout = Math.max(1_000, options.shutdownTimeoutMs ?? 10_000);\n await Promise.race([\n new Promise<void>((resolve) => {\n if (!server) return resolve();\n server.close(() => resolve());\n }),\n new Promise<void>((resolve) => setTimeout(resolve, timeout)),\n ]);\n try {\n await nestApp?.close();\n } finally {\n await Orm.closePools();\n }\n };\n\n const start = async (): Promise<Server> => {\n if (server) return server;\n const port = options.port ?? Number(process.env.PORT ?? 3000);\n server = await new Promise<Server>((resolve, reject) => {\n const listener = options.host\n ? app.listen(port, options.host, () => resolve(listener))\n : app.listen(port, () => resolve(listener));\n listener.once('error', reject);\n });\n const address = server.address();\n const actualPort = typeof address === 'object' && address ? address.port : port;\n logger.info(`Accessi Service in ascolto su http://${options.host ?? '0.0.0.0'}:${actualPort}`);\n return server;\n };\n\n return {\n app,\n get nestApp() {\n if (!nestApp) throw new Error('Accessi Service non inizializzato.');\n return nestApp;\n },\n get document() {\n if (!document) throw new Error('Documento OpenAPI non disponibile.');\n return document;\n },\n start,\n stop,\n };\n}\n\n/**\n * Avvia Accessi Service e registra SIGTERM/SIGINT per lo shutdown graceful.\n */\nexport async function startAccessiService(options: AccessiServiceOptions = {}): Promise<AccessiService> {\n const service = await createAccessiService(options);\n await service.start();\n\n let shuttingDown = false;\n const shutdown = async (signal: string): Promise<void> => {\n if (shuttingDown) return;\n shuttingDown = true;\n new Logger('AccessiService').info(`Ricevuto ${signal}: arresto in corso.`);\n await service.stop();\n };\n\n process.once('SIGTERM', () => { void shutdown('SIGTERM'); });\n process.once('SIGINT', () => { void shutdown('SIGINT'); });\n return service;\n}\n"]}
@@ -8,7 +8,7 @@ import { Options } from 'node-firebird';
8
8
  import { DynamicModule } from '@nestjs/common';
9
9
  import type { INestApplication, LoggerService } from '@nestjs/common';
10
10
  import type { OpenApiSetupOptions } from '../openapi/OpenApiConfig';
11
- import type { AccessiAccessTokenAlgorithm } from './security/jwtTokenOptions';
11
+ import type { AccessiAccessTokenAlgorithm, AccessiUnknownKeyIdEvent } from './security/jwtTokenOptions';
12
12
  import type { ConsoleEntryStore } from './security/consoleEntry';
13
13
  import type { RateLimitStore } from './security/rateLimitStore';
14
14
  import type { AccessiEdgeRateLimitOptions } from './security/edgeRateLimit';
@@ -33,12 +33,21 @@ export interface JwtOptions {
33
33
  keyId?: string;
34
34
  /** Path pubblico della JWKS. Default `accessi/.well-known/jwks.json` con algoritmi asimmetrici. */
35
35
  jwksPath?: string;
36
- /** Chiavi pubbliche precedenti, esposte in JWKS durante la rotazione. */
36
+ /**
37
+ * Chiavi pubbliche precedenti, esposte in JWKS e accettate in verifica
38
+ * durante la rotazione. Se `keyId` e assente si usa il fingerprint della
39
+ * chiave, lo stesso calcolato dalla JWKS.
40
+ */
37
41
  previousPublicKeys?: Array<{
38
42
  publicKey: string;
39
- keyId: string;
43
+ keyId?: string;
40
44
  algorithm?: AccessiAccessTokenAlgorithm;
41
45
  }>;
46
+ /**
47
+ * Alert quando un token presenta un `kid` assente da chiave corrente e
48
+ * chiavi precedenti (tipico di una rotazione non completata).
49
+ */
50
+ onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;
42
51
  /** Durata compatibile con `jsonwebtoken`, per esempio `24h` o `3600s`. */
43
52
  expiresIn: string;
44
53
  /**