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

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 (43) 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 +14 -4
  9. package/dist/accessi-module/AccessiModule.js.map +1 -1
  10. package/dist/accessi-module/Console/console.js +2 -2
  11. package/dist/accessi-module/Console/index.html +1 -1
  12. package/dist/accessi-module/Console/wiki.js +2 -2
  13. package/dist/accessi-module/Console/wiki.js.map +1 -1
  14. package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +5 -3
  15. package/dist/accessi-module/Controllers/AccessiConsoleController.js +39 -12
  16. package/dist/accessi-module/Controllers/AccessiConsoleController.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/index.d.ts +4 -0
  23. package/dist/accessi-module/index.js +22 -14
  24. package/dist/accessi-module/index.js.map +1 -1
  25. package/dist/accessi-module/jwt/jwt.strategy.js +1 -35
  26. package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
  27. package/dist/accessi-module/middleware/authenticateGen.js +1 -35
  28. package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
  29. package/dist/accessi-module/security/jwtTokenOptions.d.ts +44 -9
  30. package/dist/accessi-module/security/jwtTokenOptions.js +118 -7
  31. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
  32. package/dist/accessi-module/security/securityHeaders.d.ts +89 -0
  33. package/dist/accessi-module/security/securityHeaders.js +155 -0
  34. package/dist/accessi-module/security/securityHeaders.js.map +1 -0
  35. package/dist/accessi-module/security/validateOptions.js +31 -4
  36. package/dist/accessi-module/security/validateOptions.js.map +1 -1
  37. package/dist/emilsoftware-utilities-2.0.0-dev.76.tgz +0 -0
  38. package/dist/initEmilsoftwareModule.js +6 -11
  39. package/dist/initEmilsoftwareModule.js.map +1 -1
  40. package/docs/jwt-key-rotation.md +143 -0
  41. package/docs/security-headers.md +151 -0
  42. package/package.json +4 -3
  43. package/dist/emilsoftware-utilities-2.0.0-dev.74.tgz +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"initEmilsoftwareModule.js","sourceRoot":"","sources":["../src/initEmilsoftwareModule.ts"],"names":[],"mappings":"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","sourcesContent":["import express, { Application, Request } from \"express\";\nimport { NestFactory } from \"@nestjs/core\";\nimport { ExpressAdapter } from \"@nestjs/platform-express\";\nimport type { INestApplication } from \"@nestjs/common\";\nimport type { OpenAPIObject } from \"@nestjs/swagger\";\nimport {\n buildOpenApiExcludePaths,\n isOpenApiRequestPath,\n resolveAccessiOpenApiOptions,\n setupOpenApi\n} from \"./openapi/OpenApiConfig\";\nimport { Logger } from \"./Logger\";\nimport { EmilsoftwareModule, EmilsoftwareOptions } from \"./EmilsoftwareModule\";\nimport {\n beginAccessiAuthInitialization,\n failAccessiAuthInitialization,\n setAccessiAuthService\n} from \"./accessi-module/middleware/authenticateGen\";\nimport { registerAccessiModuleServices } from \"./accessi-module/accessiRuntime\";\nimport { logAccessiStartup } from \"./accessi-module/startupLog\";\nimport { createAccessiValidationPipe } from './accessi-module/security/accessiValidation';\nimport { AccessiHttpExceptionFilter } from './accessi-module/security/AccessiHttpExceptionFilter';\nimport { getAccessiJwksPath, setupAccessiJwks } from './accessi-module/security/jwks';\nimport { createAccessiEdgeRateLimitMiddleware } from './accessi-module/security/edgeRateLimit';\n\n/** Esito del bootstrap del modulo unificato. */\nexport interface EmilsoftwareModuleInitializationResult {\n /** Sotto-app Nest che serve Accessi, Allegati e la documentazione. */\n nestApp: INestApplication;\n /** Documento OpenAPI generato, mergiabile nel documento unico dell'host. */\n document: OpenAPIObject;\n}\n\n/**\n * Initializes the unified module that combines Accessi and Allegati functionality\n * @param app Express application instance\n * @param options Configuration options for both modules\n * @returns Il sotto-app Nest e il documento OpenAPI generato.\n */\nexport async function initEmilsoftwareModule(\n app: Application,\n options: EmilsoftwareOptions\n): Promise<EmilsoftwareModuleInitializationResult> {\n const logger: Logger = new Logger(initEmilsoftwareModule.name);\n if (options?.accessiOptions) {\n beginAccessiAuthInitialization();\n }\n\n try {\n // Isolate Nest on a dedicated Express instance and dispatch only module routes.\n // This keeps pre-existing routes/middlewares on the host app untouched.\n // Prefisso OpenAPI configurabile (default `accessi/openapi`), cosi' piu'\n // documentazioni possono convivere (backend su `/openapi`, moduli sotto\n // `/<modulo>/openapi`).\n // Precedenza: EmilsoftwareOptions.openapi > AccessiOptions.openapi > default.\n const resolvedOpenApi = resolveAccessiOpenApiOptions(\n options?.accessiOptions?.openapi,\n options?.openapi\n );\n\n const nestHostApp = express();\n if (options?.accessiOptions?.http?.trustProxy !== undefined) {\n app.set('trust proxy', options.accessiOptions.http.trustProxy);\n nestHostApp.set('trust proxy', options.accessiOptions.http.trustProxy);\n }\n const jwksPath = options?.accessiOptions\n ? getAccessiJwksPath(options.accessiOptions)\n : undefined;\n const isNestRoute = (path: string) =>\n path === \"/api\" ||\n path.startsWith(\"/api/\") ||\n isOpenApiRequestPath(path, resolvedOpenApi) ||\n (jwksPath !== undefined && path === jwksPath);\n const edgeRateLimit = options?.accessiOptions?.edgeRateLimit\n ? createAccessiEdgeRateLimitMiddleware({\n ...options.accessiOptions.edgeRateLimit,\n match: options.accessiOptions.edgeRateLimit.match ?? ((path) => isNestRoute(path)),\n })\n : undefined;\n if (edgeRateLimit) {\n app.use(edgeRateLimit);\n }\n if (options?.accessiOptions?.http?.securityHeaders !== false) {\n app.use((req: Request, res, next) => {\n if (!isNestRoute(req.path)) {\n return next();\n }\n res.setHeader('X-Content-Type-Options', 'nosniff');\n res.setHeader('X-Frame-Options', 'SAMEORIGIN');\n res.setHeader('Referrer-Policy', 'no-referrer');\n res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');\n return next();\n });\n }\n\n app.use((req: Request, res, next) => {\n if (!isNestRoute(req.path)) {\n return next();\n }\n return nestHostApp(req, res, next);\n });\n\n if (options?.accessiOptions) {\n setupAccessiJwks(app, options.accessiOptions);\n if (jwksPath) {\n logger.info(`JWKS accessi disponibile su: ${jwksPath}`);\n }\n }\n\n const nestExpressInstance = new ExpressAdapter(nestHostApp);\n const nestApp = await NestFactory.create(EmilsoftwareModule.forRoot(options), nestExpressInstance, {\n bufferLogs: options?.bufferLogs !== false,\n ...(options?.logger ? { logger: options.logger } : {})\n });\n\n if (options?.accessiOptions?.http?.cors !== false) {\n nestApp.enableCors(options?.accessiOptions?.http?.cors);\n }\n if (options?.accessiOptions) {\n // Keep the Accessi API contract identical for standalone and unified bootstraps.\n nestApp.useGlobalPipes(createAccessiValidationPipe());\n nestApp.useGlobalFilters(new AccessiHttpExceptionFilter());\n }\n\n nestApp.setGlobalPrefix('api', {\n exclude: buildOpenApiExcludePaths(resolvedOpenApi)\n });\n\n // Documentazione OpenAPI del modulo unificato (Accessi + Allegati).\n const document = setupOpenApi(nestApp, resolvedOpenApi);\n\n await nestApp.init();\n if (options?.accessiOptions) {\n const accessiServices = registerAccessiModuleServices(app, nestApp);\n app.locals.accessiAuthService = accessiServices.authenticateGenService;\n setAccessiAuthService(accessiServices.authenticateGenService);\n logAccessiStartup(options.accessiOptions, resolvedOpenApi);\n }\n\n logger.info(\"Emilsoftware module initialized successfully\");\n\n return { nestApp, document };\n } catch (error) {\n if (options?.accessiOptions) {\n failAccessiAuthInitialization(error);\n }\n logger.error(\"Error initializing Emilsoftware Module:\", error);\n throw error;\n }\n}\n"]}
1
+ {"version":3,"file":"initEmilsoftwareModule.js","sourceRoot":"","sources":["../src/initEmilsoftwareModule.ts"],"names":[],"mappings":"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","sourcesContent":["import express, { Application, Request } from \"express\";\nimport { NestFactory } from \"@nestjs/core\";\nimport { ExpressAdapter } from \"@nestjs/platform-express\";\nimport type { INestApplication } from \"@nestjs/common\";\nimport type { OpenAPIObject } from \"@nestjs/swagger\";\nimport {\n buildOpenApiExcludePaths,\n isOpenApiRequestPath,\n resolveAccessiOpenApiOptions,\n setupOpenApi\n} from \"./openapi/OpenApiConfig\";\nimport { Logger } from \"./Logger\";\nimport { EmilsoftwareModule, EmilsoftwareOptions } from \"./EmilsoftwareModule\";\nimport {\n beginAccessiAuthInitialization,\n failAccessiAuthInitialization,\n setAccessiAuthService\n} from \"./accessi-module/middleware/authenticateGen\";\nimport { registerAccessiModuleServices } from \"./accessi-module/accessiRuntime\";\nimport { logAccessiStartup } from \"./accessi-module/startupLog\";\nimport { createAccessiValidationPipe } from './accessi-module/security/accessiValidation';\nimport { AccessiHttpExceptionFilter } from './accessi-module/security/AccessiHttpExceptionFilter';\nimport { getAccessiJwksPath, setupAccessiJwks } from './accessi-module/security/jwks';\nimport { createAccessiEdgeRateLimitMiddleware } from './accessi-module/security/edgeRateLimit';\nimport { createAccessiSecurityHeadersMiddleware } from './accessi-module/security/securityHeaders';\n\n/** Esito del bootstrap del modulo unificato. */\nexport interface EmilsoftwareModuleInitializationResult {\n /** Sotto-app Nest che serve Accessi, Allegati e la documentazione. */\n nestApp: INestApplication;\n /** Documento OpenAPI generato, mergiabile nel documento unico dell'host. */\n document: OpenAPIObject;\n}\n\n/**\n * Initializes the unified module that combines Accessi and Allegati functionality\n * @param app Express application instance\n * @param options Configuration options for both modules\n * @returns Il sotto-app Nest e il documento OpenAPI generato.\n */\nexport async function initEmilsoftwareModule(\n app: Application,\n options: EmilsoftwareOptions\n): Promise<EmilsoftwareModuleInitializationResult> {\n const logger: Logger = new Logger(initEmilsoftwareModule.name);\n if (options?.accessiOptions) {\n beginAccessiAuthInitialization();\n }\n\n try {\n // Isolate Nest on a dedicated Express instance and dispatch only module routes.\n // This keeps pre-existing routes/middlewares on the host app untouched.\n // Prefisso OpenAPI configurabile (default `accessi/openapi`), cosi' piu'\n // documentazioni possono convivere (backend su `/openapi`, moduli sotto\n // `/<modulo>/openapi`).\n // Precedenza: EmilsoftwareOptions.openapi > AccessiOptions.openapi > default.\n const resolvedOpenApi = resolveAccessiOpenApiOptions(\n options?.accessiOptions?.openapi,\n options?.openapi\n );\n\n const nestHostApp = express();\n if (options?.accessiOptions?.http?.trustProxy !== undefined) {\n app.set('trust proxy', options.accessiOptions.http.trustProxy);\n nestHostApp.set('trust proxy', options.accessiOptions.http.trustProxy);\n }\n const jwksPath = options?.accessiOptions\n ? getAccessiJwksPath(options.accessiOptions)\n : undefined;\n const isNestRoute = (path: string) =>\n path === \"/api\" ||\n path.startsWith(\"/api/\") ||\n isOpenApiRequestPath(path, resolvedOpenApi) ||\n (jwksPath !== undefined && path === jwksPath);\n const edgeRateLimit = options?.accessiOptions?.edgeRateLimit\n ? createAccessiEdgeRateLimitMiddleware({\n ...options.accessiOptions.edgeRateLimit,\n match: options.accessiOptions.edgeRateLimit.match ?? ((path) => isNestRoute(path)),\n })\n : undefined;\n if (edgeRateLimit) {\n app.use(edgeRateLimit);\n }\n if (options?.accessiOptions && options.accessiOptions.http?.securityHeaders !== false) {\n const securityHeaders = createAccessiSecurityHeadersMiddleware(\n options.accessiOptions.http?.securityHeaders,\n isNestRoute,\n { isOpenApiUi: (path) => isOpenApiRequestPath(path, resolvedOpenApi) },\n );\n if (securityHeaders) {\n app.use(securityHeaders);\n }\n }\n\n app.use((req: Request, res, next) => {\n if (!isNestRoute(req.path)) {\n return next();\n }\n return nestHostApp(req, res, next);\n });\n\n if (options?.accessiOptions) {\n setupAccessiJwks(app, options.accessiOptions);\n if (jwksPath) {\n logger.info(`JWKS accessi disponibile su: ${jwksPath}`);\n }\n }\n\n const nestExpressInstance = new ExpressAdapter(nestHostApp);\n const nestApp = await NestFactory.create(EmilsoftwareModule.forRoot(options), nestExpressInstance, {\n bufferLogs: options?.bufferLogs !== false,\n ...(options?.logger ? { logger: options.logger } : {})\n });\n\n if (options?.accessiOptions?.http?.cors !== false) {\n nestApp.enableCors(options?.accessiOptions?.http?.cors);\n }\n if (options?.accessiOptions) {\n // Keep the Accessi API contract identical for standalone and unified bootstraps.\n nestApp.useGlobalPipes(createAccessiValidationPipe());\n nestApp.useGlobalFilters(new AccessiHttpExceptionFilter());\n }\n\n nestApp.setGlobalPrefix('api', {\n exclude: buildOpenApiExcludePaths(resolvedOpenApi)\n });\n\n // Documentazione OpenAPI del modulo unificato (Accessi + Allegati).\n const document = setupOpenApi(nestApp, resolvedOpenApi);\n\n await nestApp.init();\n if (options?.accessiOptions) {\n const accessiServices = registerAccessiModuleServices(app, nestApp);\n app.locals.accessiAuthService = accessiServices.authenticateGenService;\n setAccessiAuthService(accessiServices.authenticateGenService);\n logAccessiStartup(options.accessiOptions, resolvedOpenApi);\n }\n\n logger.info(\"Emilsoftware module initialized successfully\");\n\n return { nestApp, document };\n } catch (error) {\n if (options?.accessiOptions) {\n failAccessiAuthInitialization(error);\n }\n logger.error(\"Error initializing Emilsoftware Module:\", error);\n throw error;\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.
@@ -0,0 +1,151 @@
1
+ # Header di sicurezza e CSP
2
+
3
+ Accessi applica header di sicurezza alle proprie rotte (`/api/accessi/*`, JWKS e
4
+ documentazione OpenAPI). La configurazione avviene in `http.securityHeaders`:
5
+
6
+ ```ts
7
+ const accessiOptions: AccessiOptions = {
8
+ // ...
9
+ http: {
10
+ securityHeaders: {
11
+ frameOptions: 'SAMEORIGIN',
12
+ referrerPolicy: 'no-referrer',
13
+ permissionsPolicy: 'camera=(), microphone=(), geolocation=()',
14
+ cacheControl: 'no-store, max-age=0',
15
+ contentSecurityPolicy: {
16
+ reportOnly: false,
17
+ connectSrc: ['https://api.example.com'],
18
+ reportUri: 'https://csp.example.com/report',
19
+ directives: {
20
+ 'img-src': ["'self'", 'data:', 'https:'],
21
+ },
22
+ },
23
+ },
24
+ },
25
+ };
26
+ ```
27
+
28
+ `securityHeaders: false` disabilita tutto; `securityHeaders: true` (o l'omissione)
29
+ usa i default. La forma storica booleana resta valida.
30
+
31
+ ## Default
32
+
33
+ | Header | Valore | Note |
34
+ | --- | --- | --- |
35
+ | `X-Content-Type-Options` | `nosniff` | sempre presente |
36
+ | `X-Frame-Options` | `SAMEORIGIN` | `false` per non inviarlo |
37
+ | `Referrer-Policy` | `no-referrer` | `false` per non inviarla |
38
+ | `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` | `false` per non inviarla |
39
+ | `Cache-Control` (shell console) | `no-store, max-age=0` | `false` per non inviarlo |
40
+ | `Content-Security-Policy` | vedi sotto | `false` per disabilitarla |
41
+
42
+ La shell della console risponde sempre con `Cache-Control: no-store`,
43
+ `Referrer-Policy: no-referrer` e `X-Content-Type-Options: nosniff`, anche con
44
+ `securityHeaders: false`, per non regredire rispetto alle versioni precedenti.
45
+
46
+ ## Content Security Policy
47
+
48
+ Direttive di base:
49
+
50
+ ```
51
+ default-src 'self'
52
+ base-uri 'none'
53
+ object-src 'none'
54
+ frame-ancestors 'self'
55
+ form-action 'self'
56
+ img-src 'self' data:
57
+ font-src 'self'
58
+ style-src 'self'
59
+ script-src 'self'
60
+ connect-src 'self'
61
+ ```
62
+
63
+ La policy cambia per superficie:
64
+
65
+ - **Console** (`/api/accessi/console*`): i due script inline della shell ricevono
66
+ un nonce casuale per richiesta (`script-src 'self' 'nonce-...'`). Non viene mai
67
+ usato `'unsafe-inline'`. Se la console chiama l'API su un'altra origine, quella
68
+ origine viene aggiunta automaticamente a `connect-src` da
69
+ `consoleApiBaseUrl`.
70
+ - **OpenAPI** (`/accessi/openapi*`): la UI Swagger genera script e stili inline a
71
+ runtime, quindi la policy include `'unsafe-inline'` per `script-src` e
72
+ `style-src`. È l'unica eccezione documentata; per rimuoverla impostare
73
+ `contentSecurityPolicy.swaggerUnsafeInline: false` e servire una UI Swagger
74
+ compatibile con nonce/hash.
75
+ - **API e JWKS**: policy stretta, senza `'unsafe-inline'`.
76
+
77
+ `directives` vince sui default: una direttiva impostata a `false` viene rimossa,
78
+ altrimenti i valori sostituiscono quelli predefiniti. `connectSrc` aggiunge
79
+ origini a `connect-src`; `reportUri` aggiunge `report-uri`.
80
+
81
+ ### Rollout con Report-Only
82
+
83
+ 1. Imposta `contentSecurityPolicy.reportOnly: true` e un `reportUri`.
84
+ 2. Raccogli le violazioni per almeno un ciclo di utilizzo della console.
85
+ 3. Aggiungi le origini legittime a `connectSrc` o alle direttive.
86
+ 4. Passa a `reportOnly: false`.
87
+
88
+ ## Reverse proxy
89
+
90
+ Accessi non imposta HSTS: va configurato sul terminatore TLS insieme ai
91
+ redirect HTTP→HTTPS.
92
+
93
+ ### Nginx
94
+
95
+ ```nginx
96
+ location / {
97
+ proxy_pass http://accessi_upstream;
98
+ proxy_set_header Host $host;
99
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
100
+ proxy_set_header X-Forwarded-Proto $scheme;
101
+
102
+ # Non rimuovere gli header CSP inviati da Accessi.
103
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
104
+ }
105
+ ```
106
+
107
+ `add_header` di Nginx si aggiunge solo se la risposta non ha già un header con
108
+ lo stesso nome: non duplicare `Content-Security-Policy` a questo livello. Se il
109
+ proxy deve imporre una policy propria, disattiva quella di Accessi
110
+ (`contentSecurityPolicy: false`) e mantieni una sola policy, consapevole che la
111
+ console richiede il nonce (il proxy non può generarlo al posto dell'app).
112
+
113
+ ### Traefik
114
+
115
+ ```yaml
116
+ http:
117
+ middlewares:
118
+ accessi-headers:
119
+ headers:
120
+ stsSeconds: 31536000
121
+ stsIncludeSubdomains: true
122
+ contentTypeNosniff: true
123
+ referrerPolicy: no-referrer
124
+ customResponseHeaders:
125
+ Permissions-Policy: "camera=(), microphone=(), geolocation=()"
126
+ ```
127
+
128
+ Non aggiungere `Content-Security-Policy` in `customResponseHeaders` se Accessi la
129
+ invia già: due header CSP vengono applicati come intersezione e la console
130
+ smetterebbe di funzionare. Per una policy unica, gestiscila in un solo punto.
131
+
132
+ ### Cloudflare
133
+
134
+ - Abilita "Always Use HTTPS" e HSTS nelle impostazioni TLS/Edge.
135
+ - Se usi Transform Rules per aggiungere header, escludi
136
+ `Content-Security-Policy`: Cloudflare non può generare il nonce della console.
137
+ - Verifica che "Email Obfuscation" o altre feature che iniettano script inline
138
+ siano disattivate sulle pagine della console, altrimenti violano la CSP.
139
+ - Con Cloudflare davanti alla JWKS, mantieni il TTL della cache allineato a
140
+ `jwt.jwks.cacheTtlMs` (default 300 s): un TTL più alto ritarda la rotazione
141
+ delle chiavi.
142
+
143
+ ## Verifica
144
+
145
+ ```bash
146
+ curl -sSI https://accessi.example.com/api/accessi/console | grep -i -E 'content-security|x-frame|referrer|permissions'
147
+ ```
148
+
149
+ La CSP della console cambia a ogni risposta per via del nonce: due richieste
150
+ consecutive devono restituire policy diverse. Il nonce nell'header deve
151
+ coincidere con quello dei due script inline nella pagina.
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.76",
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 test/security-headers.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 test/security-headers.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",