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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/accessi-module/AccessiModule.d.ts +8 -1
  2. package/dist/accessi-module/AccessiModule.js.map +1 -1
  3. package/dist/accessi-module/Console/accessiFetch.d.ts +7 -0
  4. package/dist/accessi-module/Console/accessiFetch.js +45 -7
  5. package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
  6. package/dist/accessi-module/Console/console.js +84 -24
  7. package/dist/accessi-module/Console/console.js.map +1 -1
  8. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.d.ts +2 -2
  9. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.js.map +1 -1
  10. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.d.ts +2 -2
  11. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js.map +1 -1
  12. package/dist/accessi-module/Console/index.html +1 -1
  13. package/dist/accessi-module/Console/wiki.js +3 -2
  14. package/dist/accessi-module/Console/wiki.js.map +1 -1
  15. package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +5 -3
  16. package/dist/accessi-module/Controllers/AccessiConsoleController.js +47 -12
  17. package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -1
  18. package/dist/accessi-module/Controllers/AuthController.js +35 -7
  19. package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
  20. package/dist/accessi-module/Controllers/ConsoleEntryController.js +8 -0
  21. package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
  22. package/dist/accessi-module/Dtos/GetUserByTokenRequest.d.ts +1 -1
  23. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js +3 -2
  24. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js.map +1 -1
  25. package/dist/accessi-module/Dtos/RefreshTokenDtos.d.ts +1 -1
  26. package/dist/accessi-module/Dtos/RefreshTokenDtos.js +4 -1
  27. package/dist/accessi-module/Dtos/RefreshTokenDtos.js.map +1 -1
  28. package/dist/accessi-module/index.d.ts +4 -0
  29. package/dist/accessi-module/index.js +30 -14
  30. package/dist/accessi-module/index.js.map +1 -1
  31. package/dist/accessi-module/security/authCookies.d.ts +104 -0
  32. package/dist/accessi-module/security/authCookies.js +240 -0
  33. package/dist/accessi-module/security/authCookies.js.map +1 -0
  34. package/dist/accessi-module/security/securityHeaders.d.ts +89 -0
  35. package/dist/accessi-module/security/securityHeaders.js +155 -0
  36. package/dist/accessi-module/security/securityHeaders.js.map +1 -0
  37. package/dist/accessi-module/security/validateOptions.js +34 -0
  38. package/dist/accessi-module/security/validateOptions.js.map +1 -1
  39. package/dist/emilsoftware-utilities-2.0.0-dev.77.tgz +0 -0
  40. package/dist/initEmilsoftwareModule.js +13 -11
  41. package/dist/initEmilsoftwareModule.js.map +1 -1
  42. package/docs/auth-cookies.md +123 -0
  43. package/docs/security-headers.md +151 -0
  44. package/package.json +3 -3
  45. package/dist/emilsoftware-utilities-2.0.0-dev.75.tgz +0 -0
@@ -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"]}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateAccessiOptions = validateAccessiOptions;
4
4
  exports.assertValidAccessiOptions = assertValidAccessiOptions;
5
5
  const crypto_1 = require("crypto");
6
+ const authCookies_1 = require("./authCookies");
6
7
  function isHttpUrl(value) {
7
8
  if (!value)
8
9
  return false;
@@ -128,6 +129,39 @@ function validateAccessiOptions(options) {
128
129
  if (((_e = options === null || options === void 0 ? void 0 : options.consoleEntry) === null || _e === void 0 ? void 0 : _e.ttlMs) !== undefined && !positive(options.consoleEntry.ttlMs)) {
129
130
  addError('ACCESSI_CONSOLE_ENTRY_TTL_INVALID', 'consoleEntry.ttlMs deve essere positivo.');
130
131
  }
132
+ const authCookie = (0, authCookies_1.resolveAccessiAuthCookieOptions)(options !== null && options !== void 0 ? options : {});
133
+ if (authCookie) {
134
+ const names = (0, authCookies_1.accessiCookieNames)(authCookie);
135
+ names.forEach((name, index) => {
136
+ if (!(0, authCookies_1.isValidAccessiCookieName)(name)) {
137
+ addError('ACCESSI_AUTH_COOKIE_NAME_INVALID', `Il nome cookie "${names[index]}" non e valido.`);
138
+ }
139
+ });
140
+ if (new Set(names).size !== names.length) {
141
+ addError('ACCESSI_AUTH_COOKIE_NAME_DUPLICATE', 'I nomi dei cookie access, refresh e CSRF devono essere diversi.');
142
+ }
143
+ if (authCookie.sameSite === 'none' && !authCookie.secure) {
144
+ addError('ACCESSI_AUTH_COOKIE_SAMESITE_NONE_INSECURE', 'SameSite=None richiede secure: true.');
145
+ }
146
+ if (!authCookie.accessPath.startsWith('/')) {
147
+ addError('ACCESSI_AUTH_COOKIE_PATH_INVALID', 'accessPath deve iniziare con "/".');
148
+ }
149
+ if (!authCookie.refreshPath.startsWith('/')) {
150
+ addError('ACCESSI_AUTH_COOKIE_PATH_INVALID', 'refreshPath deve iniziare con "/".');
151
+ }
152
+ if (!authCookie.csrfPath.startsWith('/')) {
153
+ addError('ACCESSI_AUTH_COOKIE_PATH_INVALID', 'csrfPath deve iniziare con "/".');
154
+ }
155
+ if (authCookie.csrf === 'off') {
156
+ addWarning('ACCESSI_AUTH_COOKIE_CSRF_DISABLED', 'CSRF disattivato in modalita cookie: l host deve applicare una propria protezione.');
157
+ }
158
+ if (authCookie.exposeTokens) {
159
+ addWarning('ACCESSI_AUTH_COOKIE_TOKENS_EXPOSED', 'exposeTokens restituisce i token nel body: riduce il beneficio di httpOnly.');
160
+ }
161
+ if (!(jwt === null || jwt === void 0 ? void 0 : jwt.refreshExpiresIn) && authCookie.refreshFromCookie) {
162
+ addWarning('ACCESSI_AUTH_COOKIE_REFRESH_DISABLED', 'refreshFromCookie e attivo ma jwtOptions.refreshExpiresIn non e configurato.');
163
+ }
164
+ }
131
165
  return { errors, warnings };
132
166
  }
133
167
  /** Lancia un errore aggregato se la configurazione non e valida. */
@@ -1 +1 @@
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"]}
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';\nimport {\n accessiCookieNames,\n isValidAccessiCookieName,\n resolveAccessiAuthCookieOptions,\n} from './authCookies';\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 const authCookie = resolveAccessiAuthCookieOptions(options ?? {});\n if (authCookie) {\n const names = accessiCookieNames(authCookie);\n names.forEach((name, index) => {\n if (!isValidAccessiCookieName(name)) {\n addError(\n 'ACCESSI_AUTH_COOKIE_NAME_INVALID',\n `Il nome cookie \"${names[index]}\" non e valido.`,\n );\n }\n });\n if (new Set(names).size !== names.length) {\n addError(\n 'ACCESSI_AUTH_COOKIE_NAME_DUPLICATE',\n 'I nomi dei cookie access, refresh e CSRF devono essere diversi.',\n );\n }\n if (authCookie.sameSite === 'none' && !authCookie.secure) {\n addError(\n 'ACCESSI_AUTH_COOKIE_SAMESITE_NONE_INSECURE',\n 'SameSite=None richiede secure: true.',\n );\n }\n if (!authCookie.accessPath.startsWith('/')) {\n addError('ACCESSI_AUTH_COOKIE_PATH_INVALID', 'accessPath deve iniziare con \"/\".');\n }\n if (!authCookie.refreshPath.startsWith('/')) {\n addError('ACCESSI_AUTH_COOKIE_PATH_INVALID', 'refreshPath deve iniziare con \"/\".');\n }\n if (!authCookie.csrfPath.startsWith('/')) {\n addError('ACCESSI_AUTH_COOKIE_PATH_INVALID', 'csrfPath deve iniziare con \"/\".');\n }\n if (authCookie.csrf === 'off') {\n addWarning(\n 'ACCESSI_AUTH_COOKIE_CSRF_DISABLED',\n 'CSRF disattivato in modalita cookie: l host deve applicare una propria protezione.',\n );\n }\n if (authCookie.exposeTokens) {\n addWarning(\n 'ACCESSI_AUTH_COOKIE_TOKENS_EXPOSED',\n 'exposeTokens restituisce i token nel body: riduce il beneficio di httpOnly.',\n );\n }\n if (!jwt?.refreshExpiresIn && authCookie.refreshFromCookie) {\n addWarning(\n 'ACCESSI_AUTH_COOKIE_REFRESH_DISABLED',\n 'refreshFromCookie e attivo ma jwtOptions.refreshExpiresIn non e configurato.',\n );\n }\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,8 @@ 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");
30
+ const authCookies_1 = require("./accessi-module/security/authCookies");
29
31
  /**
30
32
  * Initializes the unified module that combines Accessi and Allegati functionality
31
33
  * @param app Express application instance
@@ -65,17 +67,17 @@ function initEmilsoftwareModule(app, options) {
65
67
  if (edgeRateLimit) {
66
68
  app.use(edgeRateLimit);
67
69
  }
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
- });
70
+ if (options === null || options === void 0 ? void 0 : options.accessiOptions) {
71
+ const authCookie = (0, authCookies_1.createAccessiAuthCookieMiddleware)(options.accessiOptions, isNestRoute);
72
+ if (authCookie) {
73
+ app.use(authCookie);
74
+ }
75
+ }
76
+ if ((options === null || options === void 0 ? void 0 : options.accessiOptions) && ((_f = options.accessiOptions.http) === null || _f === void 0 ? void 0 : _f.securityHeaders) !== false) {
77
+ 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) });
78
+ if (securityHeaders) {
79
+ app.use(securityHeaders);
80
+ }
79
81
  }
80
82
  app.use((req, res, next) => {
81
83
  if (!isNestRoute(req.path)) {
@@ -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":";;;;;;;;;;;;;;AAyCA,wDAkHC;AA3JD,sDAAwD;AACxD,uCAA2C;AAC3C,+DAA0D;AAG1D,2DAKiC;AACjC,qCAAkC;AAClC,6DAA+E;AAC/E,iFAIqD;AACrD,oEAAgF;AAChF,4DAAgE;AAChE,mFAA0F;AAC1F,qGAAkG;AAClG,yDAAsF;AACtF,2EAA+F;AAC/F,+EAAmG;AACnG,uEAA0F;AAU1F;;;;;GAKG;AACH,SAAsB,sBAAsB,CACxC,GAAgB,EAChB,OAA4B;;;QAE5B,MAAM,MAAM,GAAW,IAAI,eAAM,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,EAAE,CAAC;YAC1B,IAAA,gDAA8B,GAAE,CAAC;QACrC,CAAC;QAED,IAAI,CAAC;YACD,gFAAgF;YAChF,wEAAwE;YACxE,yEAAyE;YACzE,wEAAwE;YACxE,wBAAwB;YACxB,8EAA8E;YAC9E,MAAM,eAAe,GAAG,IAAA,4CAA4B,EAChD,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,0CAAE,OAAO,EAChC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,CACnB,CAAC;YAEF,MAAM,WAAW,GAAG,IAAA,iBAAO,GAAE,CAAC;YAC9B,IAAI,CAAA,MAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,0CAAE,IAAI,0CAAE,UAAU,MAAK,SAAS,EAAE,CAAC;gBAC1D,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBAC/D,WAAW,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3E,CAAC;YACD,MAAM,QAAQ,GAAG,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc;gBACpC,CAAC,CAAC,IAAA,yBAAkB,EAAC,OAAO,CAAC,cAAc,CAAC;gBAC5C,CAAC,CAAC,SAAS,CAAC;YAChB,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CACjC,IAAI,KAAK,MAAM;gBACf,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBACxB,IAAA,oCAAoB,EAAC,IAAI,EAAE,eAAe,CAAC;gBAC3C,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC;YAClD,MAAM,aAAa,GAAG,CAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,0CAAE,aAAa;gBACxD,CAAC,CAAC,IAAA,oDAAoC,kCAC/B,OAAO,CAAC,cAAc,CAAC,aAAa,KACvC,KAAK,EAAE,MAAA,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,KAAK,mCAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IACpF;gBACF,CAAC,CAAC,SAAS,CAAC;YAChB,IAAI,aAAa,EAAE,CAAC;gBAChB,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC3B,CAAC;YACD,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,EAAE,CAAC;gBAC1B,MAAM,UAAU,GAAG,IAAA,+CAAiC,EAAC,OAAO,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;gBAC1F,IAAI,UAAU,EAAE,CAAC;oBACb,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACxB,CAAC;YACL,CAAC;YACD,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,KAAI,CAAA,MAAA,OAAO,CAAC,cAAc,CAAC,IAAI,0CAAE,eAAe,MAAK,KAAK,EAAE,CAAC;gBACpF,MAAM,eAAe,GAAG,IAAA,wDAAsC,EAC1D,MAAA,OAAO,CAAC,cAAc,CAAC,IAAI,0CAAE,eAAe,EAC5C,WAAW,EACX,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,oCAAoB,EAAC,IAAI,EAAE,eAAe,CAAC,EAAE,CACzE,CAAC;gBACF,IAAI,eAAe,EAAE,CAAC;oBAClB,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;gBAC7B,CAAC;YACL,CAAC;YAED,GAAG,CAAC,GAAG,CAAC,CAAC,GAAY,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBAChC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,OAAO,IAAI,EAAE,CAAC;gBAClB,CAAC;gBACD,OAAO,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YAEH,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,EAAE,CAAC;gBAC1B,IAAA,uBAAgB,EAAC,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;gBAC9C,IAAI,QAAQ,EAAE,CAAC;oBACX,MAAM,CAAC,IAAI,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;gBAC5D,CAAC;YACL,CAAC;YAED,MAAM,mBAAmB,GAAG,IAAI,iCAAc,CAAC,WAAW,CAAC,CAAC;YAC5D,MAAM,OAAO,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,uCAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,mBAAmB,kBAC7F,UAAU,EAAE,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,MAAK,KAAK,IACtC,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,EAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EACxD,CAAC;YAEH,IAAI,CAAA,MAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,0CAAE,IAAI,0CAAE,IAAI,MAAK,KAAK,EAAE,CAAC;gBAChD,OAAO,CAAC,UAAU,CAAC,MAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,0CAAE,IAAI,0CAAE,IAAI,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,EAAE,CAAC;gBAC1B,iFAAiF;gBACjF,OAAO,CAAC,cAAc,CAAC,IAAA,+CAA2B,GAAE,CAAC,CAAC;gBACtD,OAAO,CAAC,gBAAgB,CAAC,IAAI,uDAA0B,EAAE,CAAC,CAAC;YAC/D,CAAC;YAED,OAAO,CAAC,eAAe,CAAC,KAAK,EAAE;gBAC3B,OAAO,EAAE,IAAA,wCAAwB,EAAC,eAAe,CAAC;aACrD,CAAC,CAAC;YAEH,oEAAoE;YACpE,MAAM,QAAQ,GAAG,IAAA,4BAAY,EAAC,OAAO,EAAE,eAAe,CAAC,CAAC;YAExD,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,EAAE,CAAC;gBAC1B,MAAM,eAAe,GAAG,IAAA,8CAA6B,EAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACpE,GAAG,CAAC,MAAM,CAAC,kBAAkB,GAAG,eAAe,CAAC,sBAAsB,CAAC;gBACvE,IAAA,uCAAqB,EAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC;gBAC9D,IAAA,8BAAiB,EAAC,OAAO,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;YAC/D,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;YAE5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,EAAE,CAAC;gBAC1B,IAAA,+CAA6B,EAAC,KAAK,CAAC,CAAC;YACzC,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;YAC/D,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;CAAA","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';\nimport { createAccessiAuthCookieMiddleware } from './accessi-module/security/authCookies';\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) {\n const authCookie = createAccessiAuthCookieMiddleware(options.accessiOptions, isNestRoute);\n if (authCookie) {\n app.use(authCookie);\n }\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,123 @@
1
+ # Autenticazione via cookie httpOnly e CSRF
2
+
3
+ Bearer resta il default: nessun comportamento cambia finché non si abilita
4
+ esplicitamente la modalità cookie.
5
+
6
+ ```ts
7
+ const accessiOptions: AccessiOptions = {
8
+ // ...
9
+ authCookie: {
10
+ enabled: true,
11
+ secure: true,
12
+ sameSite: 'lax',
13
+ },
14
+ };
15
+ ```
16
+
17
+ Con `authCookie` presente (e `enabled` diverso da `false`) Accessi:
18
+
19
+ - invia access e refresh token in cookie `HttpOnly`, con `Max-Age` derivato
20
+ dalla scadenza reale del token;
21
+ - non restituisce più i token nel body JSON (salvo `exposeTokens: true`);
22
+ - accetta l'access token dal cookie su tutte le rotte Accessi, senza toccare
23
+ guard, middleware e resource server già esistenti;
24
+ - emette un cookie CSRF leggibile dal client e richiede il relativo header sulle
25
+ richieste che modificano stato;
26
+ - legge il refresh token dal cookie quando il body di `/auth/refresh` è vuoto;
27
+ - cancella i cookie a revoca del token corrente o di tutte le sessioni.
28
+
29
+ ## Opzioni
30
+
31
+ | Opzione | Default | Note |
32
+ | --- | --- | --- |
33
+ | `enabled` | `true` se l'oggetto è presente | `false` disabilita |
34
+ | `secure` | `true` | disattivare solo in sviluppo HTTP |
35
+ | `sameSite` | `lax` | `none` richiede `secure: true` |
36
+ | `domain` | assente | impostare solo se serve la condivisione tra sottodomini |
37
+ | `accessTokenName` | `accessi_access` | |
38
+ | `refreshTokenName` | `accessi_refresh` | |
39
+ | `csrfTokenName` | `accessi_csrf` | non httpOnly, serve al double-submit |
40
+ | `accessPath` | `/api/accessi` | |
41
+ | `refreshPath` | `/api/accessi/auth` | |
42
+ | `csrfPath` | `/` | |
43
+ | `csrfHeaderName` | `x-csrf-token` | case-insensitive |
44
+ | `csrf` | `double-submit` | `off` solo con protezione CSRF esterna |
45
+ | `exposeTokens` | `false` | `true` reintroduce i token nel body |
46
+ | `refreshFromCookie` | `true` | |
47
+
48
+ I nomi dei tre cookie devono essere validi e distinti: la validazione
49
+ fail-fast lo controlla all'avvio.
50
+
51
+ ## Flusso
52
+
53
+ 1. `POST /api/accessi/auth/login` (eventuali passi passwordless/2FA inclusi)
54
+ risponde con i profili utente e i `Set-Cookie` di access, refresh e CSRF.
55
+ `Result.token` e `Result.refreshToken` sono assenti.
56
+ 2. Il browser chiama le API con `credentials: 'include'`; il middleware copia il
57
+ cookie access nell'header `Authorization` interno, quindi le guard esistenti
58
+ funzionano invariate.
59
+ 3. Per `POST`, `PUT`, `PATCH` e `DELETE` il client invia
60
+ `x-csrf-token: <valore del cookie accessi_csrf>`. Token mancante o diverso
61
+ produce `403 AUTH_CSRF_INVALID`.
62
+ 4. `POST /api/accessi/auth/refresh` può essere chiamato con body `{}`: il refresh
63
+ token viene letto dal cookie. I nuovi token ruotano i cookie.
64
+ 5. `POST /api/accessi/auth/revoke-token` senza `jti` revoca il token corrente e
65
+ cancella i cookie; `revoke-sessions` cancella tutti i cookie dell'utente.
66
+
67
+ ### SPA
68
+
69
+ ```ts
70
+ await fetch(`${base}/api/accessi/auth/login`, {
71
+ method: 'POST',
72
+ credentials: 'include',
73
+ headers: { 'Content-Type': 'application/json' },
74
+ body: JSON.stringify({ email, password }),
75
+ });
76
+
77
+ const csrf = document.cookie
78
+ .split('; ')
79
+ .find((entry) => entry.startsWith('accessi_csrf='))
80
+ ?.split('=')[1];
81
+
82
+ await fetch(`${base}/api/accessi/user/set-stato`, {
83
+ method: 'POST',
84
+ credentials: 'include',
85
+ headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
86
+ body: JSON.stringify(payload),
87
+ });
88
+ ```
89
+
90
+ Se la SPA e l'API sono su origini diverse servono `sameSite: 'none'`,
91
+ `secure: true`, CORS con `credentials: true` e `X-CSRF-Token` tra gli header
92
+ esposti. Il token CSRF è anche restituito nell'header di risposta
93
+ `X-CSRF-Token`, così una SPA su altra origine può leggerlo senza accedere al
94
+ cookie dell'API. Preferire, quando possibile, la stessa origine dietro un
95
+ reverse proxy.
96
+
97
+ ## Endpoint esenti da CSRF
98
+
99
+ Sono le entry point che non hanno ancora una sessione: login, refresh, verifica e
100
+ reinvio 2FA, conferma reset password, invio email di reset, bootstrap admin,
101
+ scambio federated e registrazione pubblica. Tutte le altre richieste non sicure
102
+ autenticate via cookie richiedono l'header.
103
+
104
+ Se un host applica già una propria protezione CSRF può impostare `csrf: 'off'`;
105
+ la validazione emette il warning `ACCESSI_AUTH_COOKIE_CSRF_DISABLED`.
106
+
107
+ ## Console amministrativa
108
+
109
+ La shell console riceve dal server i flag `__ACCESSI_AUTH_COOKIE__` e i nomi di
110
+ cookie/header CSRF; in modalità cookie usa `credentials: 'include'`, invia il
111
+ token CSRF e non richiede un JWT in `sessionStorage`. Il ticket di ingresso
112
+ console (`#entry=`) imposta i cookie al posto del token nel body.
113
+
114
+ ## Note operative
115
+
116
+ - Non impostare `Cache-Control` pubblico sulle risposte di login/refresh: i
117
+ `Set-Cookie` non devono essere condivisi.
118
+ - Con HTTPS terminato dal reverse proxy, inoltrare `X-Forwarded-Proto` e
119
+ configurare `http.trustProxy` su Accessi.
120
+ - I cookie sono un'alternativa a Bearer, non un'aggiunta: se il client invia
121
+ `Authorization`, quel token vince e il CSRF non viene applicato.
122
+ - La revoca per `jti` e la revoca sessioni restano valide anche con cookie: il
123
+ cookie contiene lo stesso JWT emesso in modalità Bearer.