emilsoftware-utilities 2.0.0-dev.74 → 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 (30) hide show
  1. package/dist/accessi/resource-server/token.js +62 -18
  2. package/dist/accessi/resource-server/token.js.map +1 -1
  3. package/dist/accessi/resource-server/types.d.ts +22 -0
  4. package/dist/accessi/resource-server/types.js.map +1 -1
  5. package/dist/accessi/server/index.d.ts +1 -1
  6. package/dist/accessi/server/index.js +2 -1
  7. package/dist/accessi/server/index.js.map +1 -1
  8. package/dist/accessi-module/AccessiModule.d.ts +12 -3
  9. package/dist/accessi-module/AccessiModule.js.map +1 -1
  10. package/dist/accessi-module/Services/AuthService/AuthService.js +2 -2
  11. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  12. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.d.ts +9 -1
  13. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +53 -1
  14. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
  15. package/dist/accessi-module/index.d.ts +2 -0
  16. package/dist/accessi-module/index.js +8 -1
  17. package/dist/accessi-module/index.js.map +1 -1
  18. package/dist/accessi-module/jwt/jwt.strategy.js +1 -35
  19. package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
  20. package/dist/accessi-module/middleware/authenticateGen.js +1 -35
  21. package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
  22. package/dist/accessi-module/security/jwtTokenOptions.d.ts +44 -9
  23. package/dist/accessi-module/security/jwtTokenOptions.js +118 -7
  24. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
  25. package/dist/accessi-module/security/validateOptions.js +31 -4
  26. package/dist/accessi-module/security/validateOptions.js.map +1 -1
  27. package/dist/emilsoftware-utilities-2.0.0-dev.75.tgz +0 -0
  28. package/docs/jwt-key-rotation.md +143 -0
  29. package/package.json +4 -3
  30. package/dist/emilsoftware-utilities-2.0.0-dev.74.tgz +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"validateOptions.js","sourceRoot":"","sources":["../../../src/accessi-module/security/validateOptions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\n\nexport interface AccessiOptionsValidationIssue {\n code: string;\n message: string;\n}\n\nexport interface AccessiOptionsValidationResult {\n errors: AccessiOptionsValidationIssue[];\n warnings: AccessiOptionsValidationIssue[];\n}\n\nfunction isHttpUrl(value: string | undefined): boolean {\n if (!value) return false;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\nfunction positive(value: number | undefined): boolean {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n/**\n * Validazione fail-fast della configurazione Accessi.\n *\n * `createAccessiService` usa `assertValidAccessiOptions`; i bootstrap esistenti\n * possono usare `validateAccessiOptions` per loggare warning senza rompere il\n * comportamento storico.\n */\nexport function validateAccessiOptions(options: AccessiOptions): AccessiOptionsValidationResult {\n const errors: AccessiOptionsValidationIssue[] = [];\n const warnings: AccessiOptionsValidationIssue[] = [];\n\n const addError = (code: string, message: string) => errors.push({ code, message });\n const addWarning = (code: string, message: string) => warnings.push({ code, message });\n\n if (!options?.databaseOptions) {\n addError('ACCESSI_DB_OPTIONS_MISSING', 'databaseOptions e obbligatorio.');\n }\n\n const jwt = options?.jwtOptions;\n const secret = jwt?.secret || process.env.ACC_JWT_SECRET;\n if (!secret) {\n addError('ACCESSI_JWT_SECRET_MISSING', 'jwtOptions.secret e obbligatorio.');\n } else if (secret.length < 32) {\n addWarning('ACCESSI_JWT_SECRET_WEAK', 'jwtOptions.secret dovrebbe avere almeno 32 caratteri.');\n }\n\n if (!jwt?.expiresIn) {\n addError('ACCESSI_JWT_EXPIRES_MISSING', 'jwtOptions.expiresIn e obbligatorio.');\n }\n\n const algorithm = jwt?.algorithm ?? 'HS256';\n if (algorithm.startsWith('HS')) {\n if (jwt?.privateKey || jwt?.publicKey) {\n addWarning(\n 'ACCESSI_JWT_ASYMMETRIC_KEYS_WITH_HMAC',\n 'Sono configurate chiavi asimmetriche ma algorithm e HS256: verranno ignorate.',\n );\n }\n } else {\n if (!jwt?.privateKey) {\n addError('ACCESSI_JWT_PRIVATE_KEY_MISSING', `jwtOptions.privateKey e obbligatoria per ${algorithm}.`);\n }\n if (!jwt?.publicKey && !jwt?.privateKey) {\n addError('ACCESSI_JWT_PUBLIC_KEY_MISSING', `jwtOptions.publicKey e obbligatoria per ${algorithm}.`);\n }\n if (!jwt?.keyId) {\n addWarning('ACCESSI_JWT_KEY_ID_MISSING', 'jwtOptions.keyId e consigliato per la rotazione JWKS.');\n }\n if (!jwt?.jwksPath) {\n addWarning(\n 'ACCESSI_JWKS_PATH_DEFAULT',\n 'jwksPath non configurato: verra usato accessi/.well-known/jwks.json.',\n );\n }\n }\n\n if (!jwt?.issuer) {\n addWarning('ACCESSI_JWT_ISSUER_MISSING', 'jwtOptions.issuer e consigliato in multi-servizio.');\n }\n if (!jwt?.audience) {\n addWarning('ACCESSI_JWT_AUDIENCE_MISSING', 'jwtOptions.audience e consigliato in multi-servizio.');\n }\n\n if (!options?.encryptionKey) {\n addError('ACCESSI_ENCRYPTION_KEY_MISSING', 'encryptionKey e obbligatoria.');\n } else if (options.encryptionKey.length < 16) {\n addWarning('ACCESSI_ENCRYPTION_KEY_WEAK', 'encryptionKey dovrebbe avere almeno 16 caratteri.');\n }\n\n if (!isHttpUrl(options?.confirmationEmailUrl)) {\n addWarning('ACCESSI_CONFIRMATION_URL_INVALID', 'confirmationEmailUrl dovrebbe essere un URL http/https.');\n }\n if (!isHttpUrl(options?.confirmationEmailReturnUrl)) {\n addWarning('ACCESSI_CONFIRMATION_RETURN_URL_INVALID', 'confirmationEmailReturnUrl dovrebbe essere un URL http/https.');\n }\n if (options?.publicBaseUrl && !isHttpUrl(options.publicBaseUrl)) {\n addWarning('ACCESSI_PUBLIC_BASE_URL_INVALID', 'publicBaseUrl dovrebbe essere un URL http/https.');\n }\n\n if (options?.publicAuthRateLimit?.login && !positive(options.publicAuthRateLimit.login.windowMs)) {\n addError('ACCESSI_RATE_LIMIT_WINDOW_INVALID', 'publicAuthRateLimit.login.windowMs deve essere positivo.');\n }\n if (options?.publicAuthRateLimit?.login && !positive(options.publicAuthRateLimit.login.maxAttempts)) {\n addError('ACCESSI_RATE_LIMIT_ATTEMPTS_INVALID', 'publicAuthRateLimit.login.maxAttempts deve essere positivo.');\n }\n if (options?.edgeRateLimit) {\n if (options.edgeRateLimit.windowMs !== undefined && !positive(options.edgeRateLimit.windowMs)) {\n addError('ACCESSI_EDGE_RATE_LIMIT_WINDOW_INVALID', 'edgeRateLimit.windowMs deve essere positivo.');\n }\n if (options.edgeRateLimit.maxAttempts !== undefined && !positive(options.edgeRateLimit.maxAttempts)) {\n addError('ACCESSI_EDGE_RATE_LIMIT_ATTEMPTS_INVALID', 'edgeRateLimit.maxAttempts deve essere positivo.');\n }\n }\n if (options?.consoleEntry?.ttlMs !== undefined && !positive(options.consoleEntry.ttlMs)) {\n addError('ACCESSI_CONSOLE_ENTRY_TTL_INVALID', 'consoleEntry.ttlMs deve essere positivo.');\n }\n\n return { errors, warnings };\n}\n\n/** Lancia un errore aggregato se la configurazione non e valida. */\nexport function assertValidAccessiOptions(options: AccessiOptions): void {\n const result = validateAccessiOptions(options);\n if (result.errors.length === 0) return;\n throw new Error(\n `Configurazione Accessi non valida:\\n${result.errors\n .map((issue) => `- ${issue.code}: ${issue.message}`)\n .join('\\n')}`,\n );\n}\n"]}
1
+ {"version":3,"file":"validateOptions.js","sourceRoot":"","sources":["../../../src/accessi-module/security/validateOptions.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPublicKey } from 'crypto';\nimport type { AccessiOptions } from '../AccessiModule';\n\nexport interface AccessiOptionsValidationIssue {\n code: string;\n message: string;\n}\n\nexport interface AccessiOptionsValidationResult {\n errors: AccessiOptionsValidationIssue[];\n warnings: AccessiOptionsValidationIssue[];\n}\n\nfunction isHttpUrl(value: string | undefined): boolean {\n if (!value) return false;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\nfunction positive(value: number | undefined): boolean {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n/**\n * Validazione fail-fast della configurazione Accessi.\n *\n * `createAccessiService` usa `assertValidAccessiOptions`; i bootstrap esistenti\n * possono usare `validateAccessiOptions` per loggare warning senza rompere il\n * comportamento storico.\n */\nexport function validateAccessiOptions(options: AccessiOptions): AccessiOptionsValidationResult {\n const errors: AccessiOptionsValidationIssue[] = [];\n const warnings: AccessiOptionsValidationIssue[] = [];\n\n const addError = (code: string, message: string) => errors.push({ code, message });\n const addWarning = (code: string, message: string) => warnings.push({ code, message });\n\n if (!options?.databaseOptions) {\n addError('ACCESSI_DB_OPTIONS_MISSING', 'databaseOptions e obbligatorio.');\n }\n\n const jwt = options?.jwtOptions;\n const secret = jwt?.secret || process.env.ACC_JWT_SECRET;\n if (!secret) {\n addError('ACCESSI_JWT_SECRET_MISSING', 'jwtOptions.secret e obbligatorio.');\n } else if (secret.length < 32) {\n addWarning('ACCESSI_JWT_SECRET_WEAK', 'jwtOptions.secret dovrebbe avere almeno 32 caratteri.');\n }\n\n if (!jwt?.expiresIn) {\n addError('ACCESSI_JWT_EXPIRES_MISSING', 'jwtOptions.expiresIn e obbligatorio.');\n }\n\n const algorithm = jwt?.algorithm ?? 'HS256';\n if (algorithm.startsWith('HS')) {\n if (jwt?.privateKey || jwt?.publicKey) {\n addWarning(\n 'ACCESSI_JWT_ASYMMETRIC_KEYS_WITH_HMAC',\n 'Sono configurate chiavi asimmetriche ma algorithm e HS256: verranno ignorate.',\n );\n }\n } else {\n if (!jwt?.privateKey) {\n addError('ACCESSI_JWT_PRIVATE_KEY_MISSING', `jwtOptions.privateKey e obbligatoria per ${algorithm}.`);\n }\n if (!jwt?.publicKey && !jwt?.privateKey) {\n addError('ACCESSI_JWT_PUBLIC_KEY_MISSING', `jwtOptions.publicKey e obbligatoria per ${algorithm}.`);\n }\n if (!jwt?.keyId) {\n addWarning('ACCESSI_JWT_KEY_ID_MISSING', 'jwtOptions.keyId e consigliato per la rotazione JWKS.');\n }\n if (!jwt?.jwksPath) {\n addWarning(\n 'ACCESSI_JWKS_PATH_DEFAULT',\n 'jwksPath non configurato: verra usato accessi/.well-known/jwks.json.',\n );\n }\n }\n\n const previousKeys = jwt?.previousPublicKeys ?? [];\n if (previousKeys.length > 0 && algorithm.startsWith('HS')) {\n addWarning(\n 'ACCESSI_JWT_PREVIOUS_KEYS_WITH_HMAC',\n 'previousPublicKeys e configurato ma algorithm e HS*: le chiavi verranno ignorate.',\n );\n }\n if (!algorithm.startsWith('HS') && previousKeys.length > 0) {\n const knownKeyIds = new Set<string>();\n if (jwt?.keyId) knownKeyIds.add(jwt.keyId);\n previousKeys.forEach((previous, index) => {\n try {\n createPublicKey(previous.publicKey);\n } catch {\n addError(\n 'ACCESSI_JWT_PREVIOUS_KEY_INVALID',\n `previousPublicKeys[${index}] non e una chiave pubblica PEM valida.`,\n );\n }\n if (!previous.keyId) {\n addWarning(\n 'ACCESSI_JWT_PREVIOUS_KEY_ID_MISSING',\n `previousPublicKeys[${index}] non ha keyId: verra usato il fingerprint della chiave.`,\n );\n return;\n }\n if (knownKeyIds.has(previous.keyId)) {\n addWarning(\n 'ACCESSI_JWT_PREVIOUS_KEY_DUPLICATE_KID',\n `previousPublicKeys[${index}].keyId \"${previous.keyId}\" e duplicato: vince la chiave corrente.`,\n );\n return;\n }\n knownKeyIds.add(previous.keyId);\n });\n }\n\n if (!jwt?.issuer) {\n addWarning('ACCESSI_JWT_ISSUER_MISSING', 'jwtOptions.issuer e consigliato in multi-servizio.');\n }\n if (!jwt?.audience) {\n addWarning('ACCESSI_JWT_AUDIENCE_MISSING', 'jwtOptions.audience e consigliato in multi-servizio.');\n }\n\n if (!options?.encryptionKey) {\n addError('ACCESSI_ENCRYPTION_KEY_MISSING', 'encryptionKey e obbligatoria.');\n } else if (options.encryptionKey.length < 16) {\n addWarning('ACCESSI_ENCRYPTION_KEY_WEAK', 'encryptionKey dovrebbe avere almeno 16 caratteri.');\n }\n\n if (!isHttpUrl(options?.confirmationEmailUrl)) {\n addWarning('ACCESSI_CONFIRMATION_URL_INVALID', 'confirmationEmailUrl dovrebbe essere un URL http/https.');\n }\n if (!isHttpUrl(options?.confirmationEmailReturnUrl)) {\n addWarning('ACCESSI_CONFIRMATION_RETURN_URL_INVALID', 'confirmationEmailReturnUrl dovrebbe essere un URL http/https.');\n }\n if (options?.publicBaseUrl && !isHttpUrl(options.publicBaseUrl)) {\n addWarning('ACCESSI_PUBLIC_BASE_URL_INVALID', 'publicBaseUrl dovrebbe essere un URL http/https.');\n }\n\n if (options?.publicAuthRateLimit?.login && !positive(options.publicAuthRateLimit.login.windowMs)) {\n addError('ACCESSI_RATE_LIMIT_WINDOW_INVALID', 'publicAuthRateLimit.login.windowMs deve essere positivo.');\n }\n if (options?.publicAuthRateLimit?.login && !positive(options.publicAuthRateLimit.login.maxAttempts)) {\n addError('ACCESSI_RATE_LIMIT_ATTEMPTS_INVALID', 'publicAuthRateLimit.login.maxAttempts deve essere positivo.');\n }\n if (options?.edgeRateLimit) {\n if (options.edgeRateLimit.windowMs !== undefined && !positive(options.edgeRateLimit.windowMs)) {\n addError('ACCESSI_EDGE_RATE_LIMIT_WINDOW_INVALID', 'edgeRateLimit.windowMs deve essere positivo.');\n }\n if (options.edgeRateLimit.maxAttempts !== undefined && !positive(options.edgeRateLimit.maxAttempts)) {\n addError('ACCESSI_EDGE_RATE_LIMIT_ATTEMPTS_INVALID', 'edgeRateLimit.maxAttempts deve essere positivo.');\n }\n }\n if (options?.consoleEntry?.ttlMs !== undefined && !positive(options.consoleEntry.ttlMs)) {\n addError('ACCESSI_CONSOLE_ENTRY_TTL_INVALID', 'consoleEntry.ttlMs deve essere positivo.');\n }\n\n return { errors, warnings };\n}\n\n/** Lancia un errore aggregato se la configurazione non e valida. */\nexport function assertValidAccessiOptions(options: AccessiOptions): void {\n const result = validateAccessiOptions(options);\n if (result.errors.length === 0) return;\n throw new Error(\n `Configurazione Accessi non valida:\\n${result.errors\n .map((issue) => `- ${issue.code}: ${issue.message}`)\n .join('\\n')}`,\n );\n}\n"]}
@@ -0,0 +1,143 @@
1
+ # Rotazione delle chiavi JWT
2
+
3
+ Accessi firma gli access token (e i refresh token, se abilitati) con una chiave
4
+ corrente e pubblica le chiavi pubbliche nella JWKS. La rotazione non deve
5
+ invalidare i token gia emessi: durante il rollout la JWKS contiene la chiave
6
+ nuova e quelle precedenti, e la verifica accetta entrambe.
7
+
8
+ Riferimenti:
9
+
10
+ - `jwtOptions.keyId`, `jwtOptions.previousPublicKeys`, `jwtOptions.onUnknownKeyId`
11
+ - `generate-accessi-jwt-keys.cjs` (prima generazione)
12
+ - `rotate-accessi-jwt-keys.cjs` (rotazione)
13
+
14
+ ## Modello
15
+
16
+ | Elemento | Ruolo |
17
+ | --- | --- |
18
+ | `jwtOptions.privateKey` / `publicKey` | chiave corrente: firma i nuovi token |
19
+ | `jwtOptions.keyId` | `kid` pubblicato nell'header e nella JWKS |
20
+ | `jwtOptions.previousPublicKeys` | chiavi pubbliche ancora accettate in verifica |
21
+ | `ACCESSI_JWT_PREVIOUS_PUBLIC_KEYS` | le stesse chiavi lette da env, in JSON |
22
+ | `jwtOptions.onUnknownKeyId` | alert quando un token usa un `kid` sconosciuto |
23
+
24
+ Senza `keyId`, Accessi usa il fingerprint SHA-256 della chiave pubblica
25
+ (`base64url`, primi 16 caratteri). Lo stesso valore viene usato dallo script di
26
+ rotazione e dal resource server.
27
+
28
+ ## Configurazione da env
29
+
30
+ ```bash
31
+ ACCESSI_JWT_ALGORITHM=RS256
32
+ ACCESSI_JWT_PRIVATE_KEY="$(cat jwt-private.pem)"
33
+ ACCESSI_JWT_PUBLIC_KEY="$(cat jwt-public.pem)"
34
+ ACCESSI_JWT_KEY_ID=2026-10
35
+ ACCESSI_JWT_PREVIOUS_PUBLIC_KEYS='[
36
+ { "keyId": "2026-04", "publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" }
37
+ ]'
38
+ ```
39
+
40
+ Forme accettate nell'array:
41
+
42
+ - stringa PEM;
43
+ - oggetto `{ "publicKey": "<PEM>", "keyId": "<kid>", "algorithm": "RS256" }`.
44
+
45
+ `keyId` e `algorithm` sono opzionali. Le sequenze `\n` letterali vengono
46
+ convertite in newline, cosi il valore resta su una riga nel secret manager. Un
47
+ JSON malformato o una chiave non PEM bloccano il boot (`ACCESSI_JWT_PREVIOUS_KEY_INVALID`),
48
+ invece di lasciare token non verificabili in produzione.
49
+
50
+ Con `HS256` le chiavi precedenti sono ignorate: la rotazione HMAC richiede di
51
+ cambiare `jwtOptions.secret`, che invalida tutti i token. Per una rotazione
52
+ senza interruzioni usare RS256 o ES256.
53
+
54
+ ## Procedura di rotazione
55
+
56
+ 1. **Genera la nuova coppia** con lo script, passando la chiave corrente come
57
+ precedente:
58
+
59
+ ```bash
60
+ npm run accessi:rotate-jwt-keys -- \
61
+ --out ./keys-2026-10 \
62
+ --current-public-key-file ./keys-2026-04/jwt-public.pem \
63
+ --current-key-id 2026-04
64
+ ```
65
+
66
+ Lo script scrive `jwt-private.pem`, `jwt-public.pem`, `jwks.json` e
67
+ `rotation.json` (contiene i valori `ACCESSI_*` da mettere nel secret manager).
68
+ Con `--previous-file` o `--previous` importa le chiavi precedenti gia in uso;
69
+ `--retain N` limita quante mantenerne (default 3).
70
+
71
+ 2. **Consumatori con JWKS**: nessuna azione. Al primo token con `kid` nuovo il
72
+ resource server forza un refresh della JWKS (throttle default 5 s,
73
+ `jwt.jwks.minRefreshIntervalMs`) e continua a verificare i token vecchi con le
74
+ chiavi precedenti.
75
+
76
+ 3. **Consumatori con `publicKey` statica**: distribuisci prima la nuova chiave
77
+ pubblica (o passa a `jwt.jwks`), poi cambia la chiave di firma. Chi usa la
78
+ sola chiave statica non puo conoscere in anticipo la chiave nuova.
79
+
80
+ 4. **Deploy di Accessi** con i nuovi valori. La JWKS pubblica subito nuova
81
+ chiave + precedenti, quindi i token emessi prima restano validi.
82
+
83
+ 5. **Verifica il rollout**:
84
+
85
+ ```bash
86
+ node scripts/rotate-accessi-jwt-keys.cjs --out ./keys-2026-10 \
87
+ --check https://accessi.example.com/api/accessi/.well-known/jwks.json --only-check
88
+ ```
89
+
90
+ Il comando fallisce se un `kid` del documento non e pubblicato. Esegui la
91
+ verifica su ogni istanza dietro il load balancer (o sull'endpoint pubblico)
92
+ prima di considerare conclusa la rotazione.
93
+
94
+ 6. **Rimuovi le chiavi precedenti** solo dopo la durata massima dei token:
95
+ `max(jwtOptions.expiresIn, jwtOptions.refreshExpiresIn)`. Finche restano in
96
+ `previousPublicKeys` i refresh token firmati con la vecchia chiave continuano
97
+ a funzionare.
98
+
99
+ ## Rollback
100
+
101
+ Se il deploy della nuova chiave va storto, ripristina i valori precedenti di
102
+ `privateKey`, `publicKey`, `keyId` e `previousPublicKeys`: i token firmati con la
103
+ chiave nuova smettono di essere validi, quelli vecchi tornano a esserlo. Per
104
+ questo la vecchia chiave privata va conservata nel secret manager finche la
105
+ rotazione non e conclusa.
106
+
107
+ ## Alert su `kid` sconosciuto
108
+
109
+ Un `kid` assente da chiave corrente e precedenti indica una rotazione incompleta
110
+ o un token manomesso. Collega l'evento al tuo sistema di allerta:
111
+
112
+ ```ts
113
+ const accessiOptions: AccessiOptions = {
114
+ // ...
115
+ jwtOptions: {
116
+ // ...
117
+ onUnknownKeyId: ({ keyId, algorithm, purpose }) => {
118
+ logger.warn({ keyId, algorithm, purpose }, 'JWT kid sconosciuto');
119
+ metrics.increment('accessi_jwt_unknown_kid');
120
+ },
121
+ },
122
+ };
123
+
124
+ // Resource server con JWKS remoto
125
+ const jwtOptions: AccessiResourceServerJwtOptions = {
126
+ jwks: { url: 'https://accessi.example.com/api/accessi/.well-known/jwks.json' },
127
+ onUnknownKeyId: ({ kid, algorithm, source }) => {
128
+ logger.warn({ kid, algorithm, source }, 'kid non presente nella JWKS');
129
+ },
130
+ };
131
+ ```
132
+
133
+ L'evento non cambia l'esito dell'autenticazione: l'eccezione di un logger o di
134
+ un webhook non trasforma un `401` in un `500`.
135
+
136
+ ## Cache e tempi
137
+
138
+ - La JWKS Accessi risponde con `Cache-Control: public, max-age=300`.
139
+ - Il resource server usa `cacheTtlMs` (default 300000) e, su `kid` sconosciuto,
140
+ un solo refresh forzato non piu frequente di `minRefreshIntervalMs` (default
141
+ 5000). Un flood di `kid` inventati non puo martellare la JWKS.
142
+ - Con CDN o reverse proxy davanti alla JWKS, non impostare un TTL superiore a
143
+ `cacheTtlMs`: la rotazione resterebbe invisibile fino alla scadenza della cache.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emilsoftware-utilities",
3
- "version": "2.0.0-dev.74",
3
+ "version": "2.0.0-dev.75",
4
4
  "description": "Utilities for EmilSoftware",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -132,14 +132,15 @@
132
132
  "accessi:serve": "ts-node src/scripts/startAccessiService.ts",
133
133
  "accessi:serve:dist": "node dist/scripts/startAccessiService.js",
134
134
  "accessi:generate-jwt-keys": "node scripts/generate-accessi-jwt-keys.cjs",
135
+ "accessi:rotate-jwt-keys": "node scripts/rotate-accessi-jwt-keys.cjs",
135
136
  "db:catalog:accessi": "ts-node src/accessi-module/database-updates/runAccessiCatalogMigrations.ts",
136
137
  "db:catalog:accessi:check": "ts-node src/accessi-module/database-updates/runAccessiCatalogMigrations.ts --check",
137
138
  "prepublishOnly": "npm run build",
138
139
  "typecheck": "tsc --noEmit",
139
140
  "lint": "node scripts/lint.cjs",
140
141
  "test": "npm run typecheck && npm run lint && npm run test:unit",
141
- "test:unit": "node --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs",
142
- "test:coverage": "node --experimental-test-coverage --test-coverage-lines=50 --test-coverage-functions=60 --test-coverage-branches=60 --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs",
142
+ "test:unit": "node --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs test/jwt-key-rotation.cjs",
143
+ "test:coverage": "node --experimental-test-coverage --test-coverage-lines=50 --test-coverage-functions=60 --test-coverage-branches=60 --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs test/jwt-key-rotation.cjs",
143
144
  "test:accessi": "npm run test:unit",
144
145
  "test:all": "npm test && npm run test:integration && npm run test:integration:redis",
145
146
  "test:integration": "node test/integration/run-firebird.cjs 5",