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,9 +1,45 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveAccessiVerificationKeys = resolveAccessiVerificationKeys;
37
+ exports.verifyAccessiJwt = verifyAccessiJwt;
3
38
  exports.getAccessiAccessTokenSigningConfig = getAccessiAccessTokenSigningConfig;
4
39
  exports.getAccessiAccessTokenVerificationConfig = getAccessiAccessTokenVerificationConfig;
5
40
  exports.getAccessiRefreshTokenSigningConfig = getAccessiRefreshTokenSigningConfig;
6
41
  exports.getAccessiRefreshTokenVerificationConfig = getAccessiRefreshTokenVerificationConfig;
42
+ const jwt = __importStar(require("jsonwebtoken"));
7
43
  const passwordResetToken_1 = require("./passwordResetToken");
8
44
  function resolveAlgorithm(options) {
9
45
  var _a;
@@ -26,9 +62,16 @@ function getTokenSigningConfig(options, purpose) {
26
62
  : {})), (options.jwtOptions.keyId ? { keyid: options.jwtOptions.keyId } : {})),
27
63
  };
28
64
  }
29
- /** Configurazione di verifica: chiave pubblica per RS/ES, segreto derivato per HS. */
65
+ /**
66
+ * Configurazione di verifica.
67
+ *
68
+ * Con algoritmi asimmetrici accetta anche le chiavi precedenti: un token
69
+ * firmato prima di una rotazione resta valido finché la sua chiave pubblica
70
+ * compare in `previousPublicKeys`. I token legacy senza `kid` vengono provati
71
+ * su tutte le chiavi candidate, la corrente per prima.
72
+ */
30
73
  function getTokenVerificationConfig(options, purpose) {
31
- var _a;
74
+ var _a, _b, _c;
32
75
  const algorithm = resolveAlgorithm(options);
33
76
  const key = algorithm.startsWith("HS")
34
77
  ? (0, passwordResetToken_1.getAccessiJwtSecret)(options, purpose)
@@ -36,12 +79,80 @@ function getTokenVerificationConfig(options, purpose) {
36
79
  if (!key) {
37
80
  throw new Error(`jwtOptions.publicKey e obbligatoria per verificare token ${algorithm}.`);
38
81
  }
39
- return {
40
- key,
41
- verifyOptions: Object.assign(Object.assign({ algorithms: [algorithm] }, (options.jwtOptions.issuer ? { issuer: options.jwtOptions.issuer } : {})), (options.jwtOptions.audience
82
+ const algorithms = new Set([algorithm]);
83
+ const keysByKeyId = new Map();
84
+ const fallbackKeys = [key];
85
+ if (options.jwtOptions.keyId) {
86
+ keysByKeyId.set(options.jwtOptions.keyId, key);
87
+ }
88
+ if (!algorithm.startsWith("HS")) {
89
+ for (const previous of (_b = options.jwtOptions.previousPublicKeys) !== null && _b !== void 0 ? _b : []) {
90
+ const previousAlgorithm = (_c = previous.algorithm) !== null && _c !== void 0 ? _c : algorithm;
91
+ algorithms.add(previousAlgorithm);
92
+ fallbackKeys.push(previous.publicKey);
93
+ if (previous.keyId && !keysByKeyId.has(previous.keyId)) {
94
+ keysByKeyId.set(previous.keyId, previous.publicKey);
95
+ }
96
+ }
97
+ }
98
+ return Object.assign({ key, verifyOptions: Object.assign(Object.assign({ algorithms: [...algorithms] }, (options.jwtOptions.issuer ? { issuer: options.jwtOptions.issuer } : {})), (options.jwtOptions.audience
42
99
  ? { audience: options.jwtOptions.audience }
43
- : {})),
44
- };
100
+ : {})), keysByKeyId,
101
+ fallbackKeys,
102
+ purpose }, (options.jwtOptions.onUnknownKeyId
103
+ ? { onUnknownKeyId: options.jwtOptions.onUnknownKeyId }
104
+ : {}));
105
+ }
106
+ function readTokenHeader(token) {
107
+ const decoded = jwt.decode(token, { complete: true });
108
+ if (!decoded || typeof decoded === "string" || !decoded.header)
109
+ return {};
110
+ const header = decoded.header;
111
+ return Object.assign(Object.assign({}, (typeof header.kid === "string" && header.kid !== "" ? { keyId: header.kid } : {})), (typeof header.alg === "string" ? { algorithm: header.alg } : {}));
112
+ }
113
+ /**
114
+ * Chiavi da provare per un token.
115
+ *
116
+ * Un `kid` noto seleziona una sola chiave: non si ricade sulle altre, così un
117
+ * token con intestazione manomessa non viene verificato per tentativi.
118
+ */
119
+ function resolveAccessiVerificationKeys(config, token) {
120
+ var _a, _b;
121
+ const header = readTokenHeader(token);
122
+ // Con HMAC il kid non partecipa alla crittografia: un kid cambiato non deve
123
+ // invalidare token firmati con lo stesso segreto.
124
+ const hmacOnly = ((_a = config.verifyOptions.algorithms) !== null && _a !== void 0 ? _a : []).every((algorithm) => String(algorithm).startsWith("HS"));
125
+ if (!header.keyId || hmacOnly)
126
+ return config.fallbackKeys;
127
+ const key = config.keysByKeyId.get(header.keyId);
128
+ if (key)
129
+ return [key];
130
+ (_b = config.onUnknownKeyId) === null || _b === void 0 ? void 0 : _b.call(config, Object.assign(Object.assign({ keyId: header.keyId }, (header.algorithm ? { algorithm: header.algorithm } : {})), { purpose: config.purpose }));
131
+ return [];
132
+ }
133
+ /**
134
+ * Verifica un JWT Accessi con la chiave corretta (corrente o precedente).
135
+ *
136
+ * Lancia l'errore di `jsonwebtoken` quando nessuna chiave candidata verifica il
137
+ * token; per un `kid` sconosciuto lancia un errore dedicato dopo aver emesso
138
+ * l'evento di alert.
139
+ */
140
+ function verifyAccessiJwt(token, config) {
141
+ const keys = resolveAccessiVerificationKeys(config, token);
142
+ if (keys.length === 0) {
143
+ const { keyId } = readTokenHeader(token);
144
+ throw new Error(`Chiave JWT sconosciuta per kid "${keyId !== null && keyId !== void 0 ? keyId : "assente"}".`);
145
+ }
146
+ let lastError;
147
+ for (const key of keys) {
148
+ try {
149
+ return jwt.verify(token, key, config.verifyOptions);
150
+ }
151
+ catch (error) {
152
+ lastError = error;
153
+ }
154
+ }
155
+ throw lastError instanceof Error ? lastError : new Error("Token JWT non valido.");
45
156
  }
46
157
  function getAccessiAccessTokenSigningConfig(options) {
47
158
  return getTokenSigningConfig(options, "access");
@@ -1 +1 @@
1
- {"version":3,"file":"jwtTokenOptions.js","sourceRoot":"","sources":["../../../src/accessi-module/security/jwtTokenOptions.ts"],"names":[],"mappings":"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","sourcesContent":["import type * as jwt from \"jsonwebtoken\";\nimport type { AccessiOptions } from \"../AccessiModule\";\nimport { getAccessiJwtSecret } from \"./passwordResetToken\";\nimport type { AccessiJwtPurpose } from \"./passwordResetToken\";\n\n/** Algoritmi supportati per gli access token Accessi. */\nexport type AccessiAccessTokenAlgorithm =\n | \"HS256\"\n | \"HS384\"\n | \"HS512\"\n | \"RS256\"\n | \"RS384\"\n | \"RS512\"\n | \"ES256\"\n | \"ES384\"\n | \"ES512\";\n\nfunction resolveAlgorithm(options: AccessiOptions): AccessiAccessTokenAlgorithm {\n return options.jwtOptions.algorithm ?? \"HS256\";\n}\n\n/** Configurazione di firma: chiave, algoritmo e claim standard. */\nfunction getTokenSigningConfig(\n options: AccessiOptions,\n purpose: AccessiJwtPurpose,\n): {\n key: jwt.Secret;\n algorithm: AccessiAccessTokenAlgorithm;\n signOptions: Pick<jwt.SignOptions, \"issuer\" | \"audience\" | \"keyid\">;\n} {\n const algorithm = resolveAlgorithm(options);\n const key = algorithm.startsWith(\"HS\")\n ? getAccessiJwtSecret(options, purpose)\n : options.jwtOptions.privateKey;\n\n if (!key) {\n throw new Error(`jwtOptions.privateKey e obbligatoria per l'algoritmo ${algorithm}.`);\n }\n\n return {\n key,\n algorithm,\n signOptions: {\n ...(options.jwtOptions.issuer ? { issuer: options.jwtOptions.issuer } : {}),\n ...(options.jwtOptions.audience\n ? { audience: options.jwtOptions.audience as jwt.SignOptions[\"audience\"] }\n : {}),\n ...(options.jwtOptions.keyId ? { keyid: options.jwtOptions.keyId } : {}),\n },\n };\n}\n\n/** Configurazione di verifica: chiave pubblica per RS/ES, segreto derivato per HS. */\nfunction getTokenVerificationConfig(\n options: AccessiOptions,\n purpose: AccessiJwtPurpose,\n): {\n key: jwt.Secret;\n verifyOptions: jwt.VerifyOptions;\n} {\n const algorithm = resolveAlgorithm(options);\n const key = algorithm.startsWith(\"HS\")\n ? getAccessiJwtSecret(options, purpose)\n : (options.jwtOptions.publicKey ?? options.jwtOptions.privateKey);\n\n if (!key) {\n throw new Error(\n `jwtOptions.publicKey e obbligatoria per verificare token ${algorithm}.`,\n );\n }\n\n return {\n key,\n verifyOptions: {\n algorithms: [algorithm],\n ...(options.jwtOptions.issuer ? { issuer: options.jwtOptions.issuer } : {}),\n ...(options.jwtOptions.audience\n ? { audience: options.jwtOptions.audience as jwt.VerifyOptions[\"audience\"] }\n : {}),\n },\n };\n}\n\nexport function getAccessiAccessTokenSigningConfig(options: AccessiOptions) {\n return getTokenSigningConfig(options, \"access\");\n}\n\nexport function getAccessiAccessTokenVerificationConfig(options: AccessiOptions) {\n return getTokenVerificationConfig(options, \"access\");\n}\n\nexport function getAccessiRefreshTokenSigningConfig(options: AccessiOptions) {\n return getTokenSigningConfig(options, \"refresh\");\n}\n\nexport function getAccessiRefreshTokenVerificationConfig(options: AccessiOptions) {\n return getTokenVerificationConfig(options, \"refresh\");\n}\n"]}
1
+ {"version":3,"file":"jwtTokenOptions.js","sourceRoot":"","sources":["../../../src/accessi-module/security/jwtTokenOptions.ts"],"names":[],"mappings":"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","sourcesContent":["import * as jwt from \"jsonwebtoken\";\nimport type { AccessiOptions } from \"../AccessiModule\";\nimport { getAccessiJwtSecret } from \"./passwordResetToken\";\nimport type { AccessiJwtPurpose } from \"./passwordResetToken\";\n\n/** Algoritmi supportati per gli access token Accessi. */\nexport type AccessiAccessTokenAlgorithm =\n | \"HS256\"\n | \"HS384\"\n | \"HS512\"\n | \"RS256\"\n | \"RS384\"\n | \"RS512\"\n | \"ES256\"\n | \"ES384\"\n | \"ES512\";\n\n/**\n * Evento emesso quando un token presenta un `kid` che non appartiene né alla\n * chiave corrente né alle chiavi precedenti configurate.\n *\n * Serve per collegare un alert operativo (log, metrica, webhook) senza\n * accoppiare il modulo a uno specifico sistema di monitoraggio.\n */\nexport interface AccessiUnknownKeyIdEvent {\n keyId: string;\n algorithm?: string;\n /** `access` o `refresh`, per distinguere i token coinvolti. */\n purpose: AccessiJwtPurpose;\n}\n\n/** Configurazione di verifica con chiave corrente, chiavi precedenti e alert. */\nexport interface AccessiJwtVerificationConfig {\n /** Chiave corrente. Resta il campo principale per i consumer esistenti. */\n key: jwt.Secret;\n verifyOptions: jwt.VerifyOptions;\n /** Chiavi indicizzate per `kid`: la corrente vince sulle precedenti. */\n keysByKeyId: ReadonlyMap<string, jwt.Secret>;\n /** Chiavi provate per i token legacy privi di `kid`, corrente per prima. */\n fallbackKeys: readonly jwt.Secret[];\n purpose: AccessiJwtPurpose;\n onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;\n}\n\nfunction resolveAlgorithm(options: AccessiOptions): AccessiAccessTokenAlgorithm {\n return options.jwtOptions.algorithm ?? \"HS256\";\n}\n\n/** Configurazione di firma: chiave, algoritmo e claim standard. */\nfunction getTokenSigningConfig(\n options: AccessiOptions,\n purpose: AccessiJwtPurpose,\n): {\n key: jwt.Secret;\n algorithm: AccessiAccessTokenAlgorithm;\n signOptions: Pick<jwt.SignOptions, \"issuer\" | \"audience\" | \"keyid\">;\n} {\n const algorithm = resolveAlgorithm(options);\n const key = algorithm.startsWith(\"HS\")\n ? getAccessiJwtSecret(options, purpose)\n : options.jwtOptions.privateKey;\n\n if (!key) {\n throw new Error(`jwtOptions.privateKey e obbligatoria per l'algoritmo ${algorithm}.`);\n }\n\n return {\n key,\n algorithm,\n signOptions: {\n ...(options.jwtOptions.issuer ? { issuer: options.jwtOptions.issuer } : {}),\n ...(options.jwtOptions.audience\n ? { audience: options.jwtOptions.audience as jwt.SignOptions[\"audience\"] }\n : {}),\n ...(options.jwtOptions.keyId ? { keyid: options.jwtOptions.keyId } : {}),\n },\n };\n}\n\n/**\n * Configurazione di verifica.\n *\n * Con algoritmi asimmetrici accetta anche le chiavi precedenti: un token\n * firmato prima di una rotazione resta valido finché la sua chiave pubblica\n * compare in `previousPublicKeys`. I token legacy senza `kid` vengono provati\n * su tutte le chiavi candidate, la corrente per prima.\n */\nfunction getTokenVerificationConfig(\n options: AccessiOptions,\n purpose: AccessiJwtPurpose,\n): AccessiJwtVerificationConfig {\n const algorithm = resolveAlgorithm(options);\n const key = algorithm.startsWith(\"HS\")\n ? getAccessiJwtSecret(options, purpose)\n : (options.jwtOptions.publicKey ?? options.jwtOptions.privateKey);\n\n if (!key) {\n throw new Error(\n `jwtOptions.publicKey e obbligatoria per verificare token ${algorithm}.`,\n );\n }\n\n const algorithms = new Set<string>([algorithm]);\n const keysByKeyId = new Map<string, jwt.Secret>();\n const fallbackKeys: jwt.Secret[] = [key];\n\n if (options.jwtOptions.keyId) {\n keysByKeyId.set(options.jwtOptions.keyId, key);\n }\n\n if (!algorithm.startsWith(\"HS\")) {\n for (const previous of options.jwtOptions.previousPublicKeys ?? []) {\n const previousAlgorithm = previous.algorithm ?? algorithm;\n algorithms.add(previousAlgorithm);\n fallbackKeys.push(previous.publicKey);\n if (previous.keyId && !keysByKeyId.has(previous.keyId)) {\n keysByKeyId.set(previous.keyId, previous.publicKey);\n }\n }\n }\n\n return {\n key,\n verifyOptions: {\n algorithms: [...algorithms] as jwt.Algorithm[],\n ...(options.jwtOptions.issuer ? { issuer: options.jwtOptions.issuer } : {}),\n ...(options.jwtOptions.audience\n ? { audience: options.jwtOptions.audience as jwt.VerifyOptions[\"audience\"] }\n : {}),\n },\n keysByKeyId,\n fallbackKeys,\n purpose,\n ...(options.jwtOptions.onUnknownKeyId\n ? { onUnknownKeyId: options.jwtOptions.onUnknownKeyId }\n : {}),\n };\n}\n\nfunction readTokenHeader(token: string): { keyId?: string; algorithm?: string } {\n const decoded = jwt.decode(token, { complete: true });\n if (!decoded || typeof decoded === \"string\" || !decoded.header) return {};\n const header = decoded.header as { kid?: unknown; alg?: unknown };\n return {\n ...(typeof header.kid === \"string\" && header.kid !== \"\" ? { keyId: header.kid } : {}),\n ...(typeof header.alg === \"string\" ? { algorithm: header.alg } : {}),\n };\n}\n\n/**\n * Chiavi da provare per un token.\n *\n * Un `kid` noto seleziona una sola chiave: non si ricade sulle altre, così un\n * token con intestazione manomessa non viene verificato per tentativi.\n */\nexport function resolveAccessiVerificationKeys(\n config: AccessiJwtVerificationConfig,\n token: string,\n): readonly jwt.Secret[] {\n const header = readTokenHeader(token);\n // Con HMAC il kid non partecipa alla crittografia: un kid cambiato non deve\n // invalidare token firmati con lo stesso segreto.\n const hmacOnly = (config.verifyOptions.algorithms ?? []).every((algorithm) =>\n String(algorithm).startsWith(\"HS\"),\n );\n if (!header.keyId || hmacOnly) return config.fallbackKeys;\n\n const key = config.keysByKeyId.get(header.keyId);\n if (key) return [key];\n\n config.onUnknownKeyId?.({\n keyId: header.keyId,\n ...(header.algorithm ? { algorithm: header.algorithm } : {}),\n purpose: config.purpose,\n });\n return [];\n}\n\n/**\n * Verifica un JWT Accessi con la chiave corretta (corrente o precedente).\n *\n * Lancia l'errore di `jsonwebtoken` quando nessuna chiave candidata verifica il\n * token; per un `kid` sconosciuto lancia un errore dedicato dopo aver emesso\n * l'evento di alert.\n */\nexport function verifyAccessiJwt(token: string, config: AccessiJwtVerificationConfig): unknown {\n const keys = resolveAccessiVerificationKeys(config, token);\n if (keys.length === 0) {\n const { keyId } = readTokenHeader(token);\n throw new Error(`Chiave JWT sconosciuta per kid \"${keyId ?? \"assente\"}\".`);\n }\n\n let lastError: unknown;\n for (const key of keys) {\n try {\n return jwt.verify(token, key, config.verifyOptions);\n } catch (error) {\n lastError = error;\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(\"Token JWT non valido.\");\n}\n\nexport function getAccessiAccessTokenSigningConfig(options: AccessiOptions) {\n return getTokenSigningConfig(options, \"access\");\n}\n\nexport function getAccessiAccessTokenVerificationConfig(options: AccessiOptions) {\n return getTokenVerificationConfig(options, \"access\");\n}\n\nexport function getAccessiRefreshTokenSigningConfig(options: AccessiOptions) {\n return getTokenSigningConfig(options, \"refresh\");\n}\n\nexport function getAccessiRefreshTokenVerificationConfig(options: AccessiOptions) {\n return getTokenVerificationConfig(options, \"refresh\");\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import type { NextFunction, Request, Response } from 'express';
2
+ /** Direttive CSP personalizzabili; `false` rimuove la direttiva. */
3
+ export type AccessiCspDirectives = Record<string, string[] | false>;
4
+ /** Opzioni della Content Security Policy di Accessi. */
5
+ export interface AccessiCspOptions {
6
+ /** `false` disabilita solo la CSP, lasciando gli altri header. Default: abilitata. */
7
+ enabled?: boolean;
8
+ /** Invia `Content-Security-Policy-Report-Only` invece dell'header bloccante. */
9
+ reportOnly?: boolean;
10
+ /** Endpoint `report-uri` per la raccolta delle violazioni. */
11
+ reportUri?: string;
12
+ /** Origini aggiuntive per `connect-src`, es. API su un dominio diverso. */
13
+ connectSrc?: string[];
14
+ /**
15
+ * Override delle direttive (vincono sui default). Per esempio
16
+ * `{ "img-src": ["'self'", "data:", "https:"] }`.
17
+ */
18
+ directives?: AccessiCspDirectives;
19
+ /**
20
+ * La UI Swagger usa script e stili inline generati a runtime.
21
+ * Default `true`: la CSP di OpenAPI include `'unsafe-inline'` solo lì.
22
+ */
23
+ swaggerUnsafeInline?: boolean;
24
+ }
25
+ /** Header di sicurezza applicati alle rotte Accessi. */
26
+ export interface AccessiSecurityHeadersOptions {
27
+ /** `false` disabilita tutti gli header gestiti da Accessi. */
28
+ enabled?: boolean;
29
+ /** `X-Frame-Options`; `false` non lo invia. Default: `SAMEORIGIN`. */
30
+ frameOptions?: string | false;
31
+ /** `Referrer-Policy`; `false` non la invia. Default: `no-referrer`. */
32
+ referrerPolicy?: string | false;
33
+ /** `Permissions-Policy`; `false` non la invia. */
34
+ permissionsPolicy?: string | false;
35
+ /** `Cache-Control` della shell console. Default: `no-store, max-age=0`. */
36
+ cacheControl?: string | false;
37
+ /** CSP; `false` la disabilita. Default: abilitata. */
38
+ contentSecurityPolicy?: false | AccessiCspOptions;
39
+ }
40
+ export type AccessiSecurityHeadersSetting = boolean | AccessiSecurityHeadersOptions;
41
+ export interface ResolvedAccessiCsp {
42
+ reportOnly: boolean;
43
+ reportUri?: string;
44
+ connectSrc: string[];
45
+ directives: AccessiCspDirectives;
46
+ swaggerUnsafeInline: boolean;
47
+ }
48
+ export interface ResolvedAccessiSecurityHeaders {
49
+ frameOptions: string;
50
+ referrerPolicy: string;
51
+ permissionsPolicy: string;
52
+ cacheControl: string;
53
+ contentSecurityPolicy: false | ResolvedAccessiCsp;
54
+ }
55
+ /** Superficie della risposta: cambia solo la CSP, non gli altri header. */
56
+ export type AccessiCspSurface = 'console' | 'openapi' | 'api';
57
+ /**
58
+ * Risolve la configurazione degli header. `undefined` e `true` equivalgono ai
59
+ * default; `false` disabilita tutto. La forma booleana storica resta valida.
60
+ */
61
+ export declare function resolveAccessiSecurityHeaders(setting: AccessiSecurityHeadersSetting | undefined): ResolvedAccessiSecurityHeaders | undefined;
62
+ /**
63
+ * Costruisce la CSP per la superficie richiesta.
64
+ *
65
+ * La console usa un nonce per i due script inline della shell; la UI Swagger
66
+ * richiede `'unsafe-inline'` (documentato in `docs/security-headers.md`) e non
67
+ * riceve mai il nonce della console.
68
+ */
69
+ export declare function buildAccessiContentSecurityPolicy(resolved: ResolvedAccessiSecurityHeaders, surface: AccessiCspSurface, options?: {
70
+ nonce?: string;
71
+ connectSrc?: string[];
72
+ }): string | undefined;
73
+ /** Applica l'header CSP corretto per la superficie, se configurata. */
74
+ export declare function applyAccessiContentSecurityPolicy(res: Pick<Response, 'setHeader'>, resolved: ResolvedAccessiSecurityHeaders, surface: AccessiCspSurface, options?: {
75
+ nonce?: string;
76
+ connectSrc?: string[];
77
+ }): string | undefined;
78
+ /** Header comuni (frame, referrer, permessi) senza la CSP. */
79
+ export declare function applyAccessiBaseSecurityHeaders(res: Pick<Response, 'setHeader'>, resolved: ResolvedAccessiSecurityHeaders): void;
80
+ /**
81
+ * Middleware per host Express/Nest.
82
+ *
83
+ * Applica gli header solo alle rotte Accessi. La CSP della console viene
84
+ * sovrascritta dal controller con il nonce della richiesta; per la UI OpenAPI
85
+ * vale la variante Swagger.
86
+ */
87
+ export declare function createAccessiSecurityHeadersMiddleware(setting: AccessiSecurityHeadersSetting | undefined, isAccessiRoute: (path: string) => boolean, options?: {
88
+ isOpenApiUi?: (path: string) => boolean;
89
+ }): ((req: Request, res: Response, next: NextFunction) => void) | undefined;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAccessiSecurityHeaders = resolveAccessiSecurityHeaders;
4
+ exports.buildAccessiContentSecurityPolicy = buildAccessiContentSecurityPolicy;
5
+ exports.applyAccessiContentSecurityPolicy = applyAccessiContentSecurityPolicy;
6
+ exports.applyAccessiBaseSecurityHeaders = applyAccessiBaseSecurityHeaders;
7
+ exports.createAccessiSecurityHeadersMiddleware = createAccessiSecurityHeadersMiddleware;
8
+ const DEFAULTS = {
9
+ frameOptions: 'SAMEORIGIN',
10
+ referrerPolicy: 'no-referrer',
11
+ permissionsPolicy: 'camera=(), microphone=(), geolocation=()',
12
+ cacheControl: 'no-store, max-age=0',
13
+ };
14
+ /**
15
+ * Risolve la configurazione degli header. `undefined` e `true` equivalgono ai
16
+ * default; `false` disabilita tutto. La forma booleana storica resta valida.
17
+ */
18
+ function resolveAccessiSecurityHeaders(setting) {
19
+ var _a, _b, _c, _d, _e, _f;
20
+ if (setting === false)
21
+ return undefined;
22
+ const options = setting === true || setting === undefined ? {} : setting;
23
+ if (options.enabled === false)
24
+ return undefined;
25
+ const csp = options.contentSecurityPolicy;
26
+ return {
27
+ frameOptions: options.frameOptions === false ? '' : ((_a = options.frameOptions) !== null && _a !== void 0 ? _a : DEFAULTS.frameOptions),
28
+ referrerPolicy: options.referrerPolicy === false ? '' : ((_b = options.referrerPolicy) !== null && _b !== void 0 ? _b : DEFAULTS.referrerPolicy),
29
+ permissionsPolicy: options.permissionsPolicy === false
30
+ ? ''
31
+ : ((_c = options.permissionsPolicy) !== null && _c !== void 0 ? _c : DEFAULTS.permissionsPolicy),
32
+ cacheControl: options.cacheControl === false ? '' : ((_d = options.cacheControl) !== null && _d !== void 0 ? _d : DEFAULTS.cacheControl),
33
+ contentSecurityPolicy: csp === false || (csp === null || csp === void 0 ? void 0 : csp.enabled) === false
34
+ ? false
35
+ : Object.assign(Object.assign({ reportOnly: (csp === null || csp === void 0 ? void 0 : csp.reportOnly) === true }, ((csp === null || csp === void 0 ? void 0 : csp.reportUri) ? { reportUri: csp.reportUri } : {})), { connectSrc: (_e = csp === null || csp === void 0 ? void 0 : csp.connectSrc) !== null && _e !== void 0 ? _e : [], directives: (_f = csp === null || csp === void 0 ? void 0 : csp.directives) !== null && _f !== void 0 ? _f : {}, swaggerUnsafeInline: (csp === null || csp === void 0 ? void 0 : csp.swaggerUnsafeInline) !== false }),
36
+ };
37
+ }
38
+ function frameAncestors(frameOptions) {
39
+ switch (frameOptions.toUpperCase()) {
40
+ case 'DENY':
41
+ return "'none'";
42
+ case 'SAMEORIGIN':
43
+ default:
44
+ return "'self'";
45
+ }
46
+ }
47
+ const BASE_DIRECTIVES = {
48
+ 'default-src': ["'self'"],
49
+ 'base-uri': ["'none'"],
50
+ 'object-src': ["'none'"],
51
+ 'form-action': ["'self'"],
52
+ 'img-src': ["'self'", 'data:'],
53
+ 'font-src': ["'self'"],
54
+ 'style-src': ["'self'"],
55
+ 'script-src': ["'self'"],
56
+ 'connect-src': ["'self'"],
57
+ };
58
+ function listOf(directives, name) {
59
+ const value = directives[name];
60
+ return Array.isArray(value) ? value : [];
61
+ }
62
+ /**
63
+ * Costruisce la CSP per la superficie richiesta.
64
+ *
65
+ * La console usa un nonce per i due script inline della shell; la UI Swagger
66
+ * richiede `'unsafe-inline'` (documentato in `docs/security-headers.md`) e non
67
+ * riceve mai il nonce della console.
68
+ */
69
+ function buildAccessiContentSecurityPolicy(resolved, surface, options = {}) {
70
+ var _a;
71
+ const csp = resolved.contentSecurityPolicy;
72
+ if (!csp)
73
+ return undefined;
74
+ const directives = {};
75
+ for (const [name, value] of Object.entries(BASE_DIRECTIVES)) {
76
+ directives[name] = Array.isArray(value) ? [...value] : value;
77
+ }
78
+ if (resolved.frameOptions) {
79
+ directives['frame-ancestors'] = [frameAncestors(resolved.frameOptions)];
80
+ }
81
+ const connectSrc = new Set([...listOf(directives, 'connect-src'), ...csp.connectSrc, ...((_a = options.connectSrc) !== null && _a !== void 0 ? _a : [])]);
82
+ directives['connect-src'] = [...connectSrc];
83
+ if (surface === 'console') {
84
+ if (options.nonce) {
85
+ directives['script-src'] = [...listOf(directives, 'script-src'), `'nonce-${options.nonce}'`];
86
+ }
87
+ }
88
+ else if (surface === 'openapi' && csp.swaggerUnsafeInline) {
89
+ directives['script-src'] = [...listOf(directives, 'script-src'), "'unsafe-inline'"];
90
+ directives['style-src'] = [...listOf(directives, 'style-src'), "'unsafe-inline'"];
91
+ }
92
+ for (const [name, value] of Object.entries(csp.directives)) {
93
+ if (value === false)
94
+ delete directives[name];
95
+ else
96
+ directives[name] = [...value];
97
+ }
98
+ if (csp.reportUri)
99
+ directives['report-uri'] = [csp.reportUri];
100
+ return Object.entries(directives)
101
+ .filter(([, value]) => value !== false && value.length > 0)
102
+ .map(([name, value]) => `${name} ${value.join(' ')}`)
103
+ .join('; ');
104
+ }
105
+ /** Applica l'header CSP corretto per la superficie, se configurata. */
106
+ function applyAccessiContentSecurityPolicy(res, resolved, surface, options = {}) {
107
+ const csp = resolved.contentSecurityPolicy;
108
+ if (!csp)
109
+ return undefined;
110
+ const policy = buildAccessiContentSecurityPolicy(resolved, surface, options);
111
+ if (!policy)
112
+ return undefined;
113
+ res.setHeader(csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy', policy);
114
+ return policy;
115
+ }
116
+ /** Header comuni (frame, referrer, permessi) senza la CSP. */
117
+ function applyAccessiBaseSecurityHeaders(res, resolved) {
118
+ res.setHeader('X-Content-Type-Options', 'nosniff');
119
+ if (resolved.frameOptions)
120
+ res.setHeader('X-Frame-Options', resolved.frameOptions);
121
+ if (resolved.referrerPolicy)
122
+ res.setHeader('Referrer-Policy', resolved.referrerPolicy);
123
+ if (resolved.permissionsPolicy)
124
+ res.setHeader('Permissions-Policy', resolved.permissionsPolicy);
125
+ }
126
+ /**
127
+ * Middleware per host Express/Nest.
128
+ *
129
+ * Applica gli header solo alle rotte Accessi. La CSP della console viene
130
+ * sovrascritta dal controller con il nonce della richiesta; per la UI OpenAPI
131
+ * vale la variante Swagger.
132
+ */
133
+ function createAccessiSecurityHeadersMiddleware(setting, isAccessiRoute, options = {}) {
134
+ const resolved = resolveAccessiSecurityHeaders(setting);
135
+ if (!resolved)
136
+ return undefined;
137
+ return (req, res, next) => {
138
+ var _a;
139
+ if (!isAccessiRoute(req.path)) {
140
+ next();
141
+ return;
142
+ }
143
+ applyAccessiBaseSecurityHeaders(res, resolved);
144
+ if (resolved.contentSecurityPolicy) {
145
+ const surface = ((_a = options.isOpenApiUi) === null || _a === void 0 ? void 0 : _a.call(options, req.path))
146
+ ? 'openapi'
147
+ : req.path.startsWith('/api/accessi/console')
148
+ ? 'console'
149
+ : 'api';
150
+ applyAccessiContentSecurityPolicy(res, resolved, surface);
151
+ }
152
+ next();
153
+ };
154
+ }
155
+ //# sourceMappingURL=securityHeaders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"securityHeaders.js","sourceRoot":"","sources":["../../../src/accessi-module/security/securityHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import type { NextFunction, Request, Response } from 'express';\n\n/** Direttive CSP personalizzabili; `false` rimuove la direttiva. */\nexport type AccessiCspDirectives = Record<string, string[] | false>;\n\n/** Opzioni della Content Security Policy di Accessi. */\nexport interface AccessiCspOptions {\n /** `false` disabilita solo la CSP, lasciando gli altri header. Default: abilitata. */\n enabled?: boolean;\n /** Invia `Content-Security-Policy-Report-Only` invece dell'header bloccante. */\n reportOnly?: boolean;\n /** Endpoint `report-uri` per la raccolta delle violazioni. */\n reportUri?: string;\n /** Origini aggiuntive per `connect-src`, es. API su un dominio diverso. */\n connectSrc?: string[];\n /**\n * Override delle direttive (vincono sui default). Per esempio\n * `{ \"img-src\": [\"'self'\", \"data:\", \"https:\"] }`.\n */\n directives?: AccessiCspDirectives;\n /**\n * La UI Swagger usa script e stili inline generati a runtime.\n * Default `true`: la CSP di OpenAPI include `'unsafe-inline'` solo lì.\n */\n swaggerUnsafeInline?: boolean;\n}\n\n/** Header di sicurezza applicati alle rotte Accessi. */\nexport interface AccessiSecurityHeadersOptions {\n /** `false` disabilita tutti gli header gestiti da Accessi. */\n enabled?: boolean;\n /** `X-Frame-Options`; `false` non lo invia. Default: `SAMEORIGIN`. */\n frameOptions?: string | false;\n /** `Referrer-Policy`; `false` non la invia. Default: `no-referrer`. */\n referrerPolicy?: string | false;\n /** `Permissions-Policy`; `false` non la invia. */\n permissionsPolicy?: string | false;\n /** `Cache-Control` della shell console. Default: `no-store, max-age=0`. */\n cacheControl?: string | false;\n /** CSP; `false` la disabilita. Default: abilitata. */\n contentSecurityPolicy?: false | AccessiCspOptions;\n}\n\nexport type AccessiSecurityHeadersSetting = boolean | AccessiSecurityHeadersOptions;\n\nexport interface ResolvedAccessiCsp {\n reportOnly: boolean;\n reportUri?: string;\n connectSrc: string[];\n directives: AccessiCspDirectives;\n swaggerUnsafeInline: boolean;\n}\n\nexport interface ResolvedAccessiSecurityHeaders {\n frameOptions: string;\n referrerPolicy: string;\n permissionsPolicy: string;\n cacheControl: string;\n contentSecurityPolicy: false | ResolvedAccessiCsp;\n}\n\n/** Superficie della risposta: cambia solo la CSP, non gli altri header. */\nexport type AccessiCspSurface = 'console' | 'openapi' | 'api';\n\nconst DEFAULTS = {\n frameOptions: 'SAMEORIGIN',\n referrerPolicy: 'no-referrer',\n permissionsPolicy: 'camera=(), microphone=(), geolocation=()',\n cacheControl: 'no-store, max-age=0',\n} as const;\n\n/**\n * Risolve la configurazione degli header. `undefined` e `true` equivalgono ai\n * default; `false` disabilita tutto. La forma booleana storica resta valida.\n */\nexport function resolveAccessiSecurityHeaders(\n setting: AccessiSecurityHeadersSetting | undefined,\n): ResolvedAccessiSecurityHeaders | undefined {\n if (setting === false) return undefined;\n const options: AccessiSecurityHeadersOptions = setting === true || setting === undefined ? {} : setting;\n if (options.enabled === false) return undefined;\n\n const csp = options.contentSecurityPolicy;\n return {\n frameOptions: options.frameOptions === false ? '' : (options.frameOptions ?? DEFAULTS.frameOptions),\n referrerPolicy: options.referrerPolicy === false ? '' : (options.referrerPolicy ?? DEFAULTS.referrerPolicy),\n permissionsPolicy: options.permissionsPolicy === false\n ? ''\n : (options.permissionsPolicy ?? DEFAULTS.permissionsPolicy),\n cacheControl: options.cacheControl === false ? '' : (options.cacheControl ?? DEFAULTS.cacheControl),\n contentSecurityPolicy: csp === false || csp?.enabled === false\n ? false\n : {\n reportOnly: csp?.reportOnly === true,\n ...(csp?.reportUri ? { reportUri: csp.reportUri } : {}),\n connectSrc: csp?.connectSrc ?? [],\n directives: csp?.directives ?? {},\n swaggerUnsafeInline: csp?.swaggerUnsafeInline !== false,\n },\n };\n}\n\nfunction frameAncestors(frameOptions: string): string {\n switch (frameOptions.toUpperCase()) {\n case 'DENY':\n return \"'none'\";\n case 'SAMEORIGIN':\n default:\n return \"'self'\";\n }\n}\n\nconst BASE_DIRECTIVES: AccessiCspDirectives = {\n 'default-src': [\"'self'\"],\n 'base-uri': [\"'none'\"],\n 'object-src': [\"'none'\"],\n 'form-action': [\"'self'\"],\n 'img-src': [\"'self'\", 'data:'],\n 'font-src': [\"'self'\"],\n 'style-src': [\"'self'\"],\n 'script-src': [\"'self'\"],\n 'connect-src': [\"'self'\"],\n};\n\nfunction listOf(directives: AccessiCspDirectives, name: string): string[] {\n const value = directives[name];\n return Array.isArray(value) ? value : [];\n}\n\n/**\n * Costruisce la CSP per la superficie richiesta.\n *\n * La console usa un nonce per i due script inline della shell; la UI Swagger\n * richiede `'unsafe-inline'` (documentato in `docs/security-headers.md`) e non\n * riceve mai il nonce della console.\n */\nexport function buildAccessiContentSecurityPolicy(\n resolved: ResolvedAccessiSecurityHeaders,\n surface: AccessiCspSurface,\n options: { nonce?: string; connectSrc?: string[] } = {},\n): string | undefined {\n const csp = resolved.contentSecurityPolicy;\n if (!csp) return undefined;\n\n const directives: AccessiCspDirectives = {};\n for (const [name, value] of Object.entries(BASE_DIRECTIVES)) {\n directives[name] = Array.isArray(value) ? [...value] : value;\n }\n if (resolved.frameOptions) {\n directives['frame-ancestors'] = [frameAncestors(resolved.frameOptions)];\n }\n\n const connectSrc = new Set([...listOf(directives, 'connect-src'), ...csp.connectSrc, ...(options.connectSrc ?? [])]);\n directives['connect-src'] = [...connectSrc];\n\n if (surface === 'console') {\n if (options.nonce) {\n directives['script-src'] = [...listOf(directives, 'script-src'), `'nonce-${options.nonce}'`];\n }\n } else if (surface === 'openapi' && csp.swaggerUnsafeInline) {\n directives['script-src'] = [...listOf(directives, 'script-src'), \"'unsafe-inline'\"];\n directives['style-src'] = [...listOf(directives, 'style-src'), \"'unsafe-inline'\"];\n }\n\n for (const [name, value] of Object.entries(csp.directives)) {\n if (value === false) delete directives[name];\n else directives[name] = [...value];\n }\n if (csp.reportUri) directives['report-uri'] = [csp.reportUri];\n\n return Object.entries(directives)\n .filter(([, value]) => value !== false && value.length > 0)\n .map(([name, value]) => `${name} ${(value as string[]).join(' ')}`)\n .join('; ');\n}\n\n/** Applica l'header CSP corretto per la superficie, se configurata. */\nexport function applyAccessiContentSecurityPolicy(\n res: Pick<Response, 'setHeader'>,\n resolved: ResolvedAccessiSecurityHeaders,\n surface: AccessiCspSurface,\n options: { nonce?: string; connectSrc?: string[] } = {},\n): string | undefined {\n const csp = resolved.contentSecurityPolicy;\n if (!csp) return undefined;\n const policy = buildAccessiContentSecurityPolicy(resolved, surface, options);\n if (!policy) return undefined;\n res.setHeader(\n csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy',\n policy,\n );\n return policy;\n}\n\n/** Header comuni (frame, referrer, permessi) senza la CSP. */\nexport function applyAccessiBaseSecurityHeaders(\n res: Pick<Response, 'setHeader'>,\n resolved: ResolvedAccessiSecurityHeaders,\n): void {\n res.setHeader('X-Content-Type-Options', 'nosniff');\n if (resolved.frameOptions) res.setHeader('X-Frame-Options', resolved.frameOptions);\n if (resolved.referrerPolicy) res.setHeader('Referrer-Policy', resolved.referrerPolicy);\n if (resolved.permissionsPolicy) res.setHeader('Permissions-Policy', resolved.permissionsPolicy);\n}\n\n/**\n * Middleware per host Express/Nest.\n *\n * Applica gli header solo alle rotte Accessi. La CSP della console viene\n * sovrascritta dal controller con il nonce della richiesta; per la UI OpenAPI\n * vale la variante Swagger.\n */\nexport function createAccessiSecurityHeadersMiddleware(\n setting: AccessiSecurityHeadersSetting | undefined,\n isAccessiRoute: (path: string) => boolean,\n options: { isOpenApiUi?: (path: string) => boolean } = {},\n): ((req: Request, res: Response, next: NextFunction) => void) | undefined {\n const resolved = resolveAccessiSecurityHeaders(setting);\n if (!resolved) return undefined;\n\n return (req: Request, res: Response, next: NextFunction): void => {\n if (!isAccessiRoute(req.path)) {\n next();\n return;\n }\n applyAccessiBaseSecurityHeaders(res, resolved);\n if (resolved.contentSecurityPolicy) {\n const surface: AccessiCspSurface = options.isOpenApiUi?.(req.path)\n ? 'openapi'\n : req.path.startsWith('/api/accessi/console')\n ? 'console'\n : 'api';\n applyAccessiContentSecurityPolicy(res, resolved, surface);\n }\n next();\n };\n}\n"]}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateAccessiOptions = validateAccessiOptions;
4
4
  exports.assertValidAccessiOptions = assertValidAccessiOptions;
5
+ const crypto_1 = require("crypto");
5
6
  function isHttpUrl(value) {
6
7
  if (!value)
7
8
  return false;
@@ -24,7 +25,7 @@ function positive(value) {
24
25
  * comportamento storico.
25
26
  */
26
27
  function validateAccessiOptions(options) {
27
- var _a, _b, _c, _d;
28
+ var _a, _b, _c, _d, _e;
28
29
  const errors = [];
29
30
  const warnings = [];
30
31
  const addError = (code, message) => errors.push({ code, message });
@@ -63,6 +64,32 @@ function validateAccessiOptions(options) {
63
64
  addWarning('ACCESSI_JWKS_PATH_DEFAULT', 'jwksPath non configurato: verra usato accessi/.well-known/jwks.json.');
64
65
  }
65
66
  }
67
+ const previousKeys = (_b = jwt === null || jwt === void 0 ? void 0 : jwt.previousPublicKeys) !== null && _b !== void 0 ? _b : [];
68
+ if (previousKeys.length > 0 && algorithm.startsWith('HS')) {
69
+ addWarning('ACCESSI_JWT_PREVIOUS_KEYS_WITH_HMAC', 'previousPublicKeys e configurato ma algorithm e HS*: le chiavi verranno ignorate.');
70
+ }
71
+ if (!algorithm.startsWith('HS') && previousKeys.length > 0) {
72
+ const knownKeyIds = new Set();
73
+ if (jwt === null || jwt === void 0 ? void 0 : jwt.keyId)
74
+ knownKeyIds.add(jwt.keyId);
75
+ previousKeys.forEach((previous, index) => {
76
+ try {
77
+ (0, crypto_1.createPublicKey)(previous.publicKey);
78
+ }
79
+ catch (_a) {
80
+ addError('ACCESSI_JWT_PREVIOUS_KEY_INVALID', `previousPublicKeys[${index}] non e una chiave pubblica PEM valida.`);
81
+ }
82
+ if (!previous.keyId) {
83
+ addWarning('ACCESSI_JWT_PREVIOUS_KEY_ID_MISSING', `previousPublicKeys[${index}] non ha keyId: verra usato il fingerprint della chiave.`);
84
+ return;
85
+ }
86
+ if (knownKeyIds.has(previous.keyId)) {
87
+ addWarning('ACCESSI_JWT_PREVIOUS_KEY_DUPLICATE_KID', `previousPublicKeys[${index}].keyId "${previous.keyId}" e duplicato: vince la chiave corrente.`);
88
+ return;
89
+ }
90
+ knownKeyIds.add(previous.keyId);
91
+ });
92
+ }
66
93
  if (!(jwt === null || jwt === void 0 ? void 0 : jwt.issuer)) {
67
94
  addWarning('ACCESSI_JWT_ISSUER_MISSING', 'jwtOptions.issuer e consigliato in multi-servizio.');
68
95
  }
@@ -84,10 +111,10 @@ function validateAccessiOptions(options) {
84
111
  if ((options === null || options === void 0 ? void 0 : options.publicBaseUrl) && !isHttpUrl(options.publicBaseUrl)) {
85
112
  addWarning('ACCESSI_PUBLIC_BASE_URL_INVALID', 'publicBaseUrl dovrebbe essere un URL http/https.');
86
113
  }
87
- if (((_b = options === null || options === void 0 ? void 0 : options.publicAuthRateLimit) === null || _b === void 0 ? void 0 : _b.login) && !positive(options.publicAuthRateLimit.login.windowMs)) {
114
+ if (((_c = options === null || options === void 0 ? void 0 : options.publicAuthRateLimit) === null || _c === void 0 ? void 0 : _c.login) && !positive(options.publicAuthRateLimit.login.windowMs)) {
88
115
  addError('ACCESSI_RATE_LIMIT_WINDOW_INVALID', 'publicAuthRateLimit.login.windowMs deve essere positivo.');
89
116
  }
90
- if (((_c = options === null || options === void 0 ? void 0 : options.publicAuthRateLimit) === null || _c === void 0 ? void 0 : _c.login) && !positive(options.publicAuthRateLimit.login.maxAttempts)) {
117
+ if (((_d = options === null || options === void 0 ? void 0 : options.publicAuthRateLimit) === null || _d === void 0 ? void 0 : _d.login) && !positive(options.publicAuthRateLimit.login.maxAttempts)) {
91
118
  addError('ACCESSI_RATE_LIMIT_ATTEMPTS_INVALID', 'publicAuthRateLimit.login.maxAttempts deve essere positivo.');
92
119
  }
93
120
  if (options === null || options === void 0 ? void 0 : options.edgeRateLimit) {
@@ -98,7 +125,7 @@ function validateAccessiOptions(options) {
98
125
  addError('ACCESSI_EDGE_RATE_LIMIT_ATTEMPTS_INVALID', 'edgeRateLimit.maxAttempts deve essere positivo.');
99
126
  }
100
127
  }
101
- if (((_d = options === null || options === void 0 ? void 0 : options.consoleEntry) === null || _d === void 0 ? void 0 : _d.ttlMs) !== undefined && !positive(options.consoleEntry.ttlMs)) {
128
+ if (((_e = options === null || options === void 0 ? void 0 : options.consoleEntry) === null || _e === void 0 ? void 0 : _e.ttlMs) !== undefined && !positive(options.consoleEntry.ttlMs)) {
102
129
  addError('ACCESSI_CONSOLE_ENTRY_TTL_INVALID', 'consoleEntry.ttlMs deve essere positivo.');
103
130
  }
104
131
  return { errors, warnings };
@@ -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"]}
@@ -26,6 +26,7 @@ const accessiValidation_1 = require("./accessi-module/security/accessiValidation
26
26
  const AccessiHttpExceptionFilter_1 = require("./accessi-module/security/AccessiHttpExceptionFilter");
27
27
  const jwks_1 = require("./accessi-module/security/jwks");
28
28
  const edgeRateLimit_1 = require("./accessi-module/security/edgeRateLimit");
29
+ const securityHeaders_1 = require("./accessi-module/security/securityHeaders");
29
30
  /**
30
31
  * Initializes the unified module that combines Accessi and Allegati functionality
31
32
  * @param app Express application instance
@@ -65,17 +66,11 @@ function initEmilsoftwareModule(app, options) {
65
66
  if (edgeRateLimit) {
66
67
  app.use(edgeRateLimit);
67
68
  }
68
- if (((_g = (_f = options === null || options === void 0 ? void 0 : options.accessiOptions) === null || _f === void 0 ? void 0 : _f.http) === null || _g === void 0 ? void 0 : _g.securityHeaders) !== false) {
69
- app.use((req, res, next) => {
70
- if (!isNestRoute(req.path)) {
71
- return next();
72
- }
73
- res.setHeader('X-Content-Type-Options', 'nosniff');
74
- res.setHeader('X-Frame-Options', 'SAMEORIGIN');
75
- res.setHeader('Referrer-Policy', 'no-referrer');
76
- res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
77
- return next();
78
- });
69
+ if ((options === null || options === void 0 ? void 0 : options.accessiOptions) && ((_f = options.accessiOptions.http) === null || _f === void 0 ? void 0 : _f.securityHeaders) !== false) {
70
+ const securityHeaders = (0, securityHeaders_1.createAccessiSecurityHeadersMiddleware)((_g = options.accessiOptions.http) === null || _g === void 0 ? void 0 : _g.securityHeaders, isNestRoute, { isOpenApiUi: (path) => (0, OpenApiConfig_1.isOpenApiRequestPath)(path, resolvedOpenApi) });
71
+ if (securityHeaders) {
72
+ app.use(securityHeaders);
73
+ }
79
74
  }
80
75
  app.use((req, res, next) => {
81
76
  if (!isNestRoute(req.path)) {