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.
- package/dist/accessi/resource-server/token.js +62 -18
- package/dist/accessi/resource-server/token.js.map +1 -1
- package/dist/accessi/resource-server/types.d.ts +22 -0
- package/dist/accessi/resource-server/types.js.map +1 -1
- package/dist/accessi/server/index.d.ts +1 -1
- package/dist/accessi/server/index.js +2 -1
- package/dist/accessi/server/index.js.map +1 -1
- package/dist/accessi-module/AccessiModule.d.ts +14 -4
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/console.js +2 -2
- package/dist/accessi-module/Console/index.html +1 -1
- package/dist/accessi-module/Console/wiki.js +2 -2
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +5 -3
- package/dist/accessi-module/Controllers/AccessiConsoleController.js +39 -12
- package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -1
- package/dist/accessi-module/Services/AuthService/AuthService.js +2 -2
- package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.d.ts +9 -1
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +53 -1
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
- package/dist/accessi-module/index.d.ts +4 -0
- package/dist/accessi-module/index.js +22 -14
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/accessi-module/jwt/jwt.strategy.js +1 -35
- package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
- package/dist/accessi-module/middleware/authenticateGen.js +1 -35
- package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
- package/dist/accessi-module/security/jwtTokenOptions.d.ts +44 -9
- package/dist/accessi-module/security/jwtTokenOptions.js +118 -7
- package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
- package/dist/accessi-module/security/securityHeaders.d.ts +89 -0
- package/dist/accessi-module/security/securityHeaders.js +155 -0
- package/dist/accessi-module/security/securityHeaders.js.map +1 -0
- package/dist/accessi-module/security/validateOptions.js +31 -4
- package/dist/accessi-module/security/validateOptions.js.map +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.76.tgz +0 -0
- package/dist/initEmilsoftwareModule.js +6 -11
- package/dist/initEmilsoftwareModule.js.map +1 -1
- package/docs/jwt-key-rotation.md +143 -0
- package/docs/security-headers.md +151 -0
- package/package.json +4 -3
- 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.
|
|
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",
|
|
Binary file
|