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

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 (89) hide show
  1. package/dist/accessi/client/generated/admin.d.ts +21 -1
  2. package/dist/accessi/client/generated/admin.js +35 -1
  3. package/dist/accessi/client/generated/admin.js.map +1 -1
  4. package/dist/accessi/resource-server/token.js +2 -0
  5. package/dist/accessi/resource-server/token.js.map +1 -1
  6. package/dist/accessi/server/standalone.js +2 -1
  7. package/dist/accessi/server/standalone.js.map +1 -1
  8. package/dist/accessi-module/AccessiModule.d.ts +6 -0
  9. package/dist/accessi-module/AccessiModule.js +4 -0
  10. package/dist/accessi-module/AccessiModule.js.map +1 -1
  11. package/dist/accessi-module/Console/console.js +2 -0
  12. package/dist/accessi-module/Console/generated/emilsoftware-utilities.d.ts +21 -1
  13. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js +35 -1
  14. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -1
  15. package/dist/accessi-module/Console/generated/model/exportAuditOrder.d.ts +11 -0
  16. package/dist/accessi-module/Console/generated/model/exportAuditOrder.js +15 -0
  17. package/dist/accessi-module/Console/generated/model/exportAuditOrder.js.map +1 -0
  18. package/dist/accessi-module/Console/generated/model/exportAuditParams.d.ts +37 -0
  19. package/dist/accessi-module/Console/generated/model/exportAuditParams.js +3 -0
  20. package/dist/accessi-module/Console/generated/model/exportAuditParams.js.map +1 -0
  21. package/dist/accessi-module/Console/generated/model/index.d.ts +4 -0
  22. package/dist/accessi-module/Console/generated/model/index.js +4 -0
  23. package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
  24. package/dist/accessi-module/Console/generated/model/queryAuditOrder.d.ts +11 -0
  25. package/dist/accessi-module/Console/generated/model/queryAuditOrder.js +15 -0
  26. package/dist/accessi-module/Console/generated/model/queryAuditOrder.js.map +1 -0
  27. package/dist/accessi-module/Console/generated/model/queryAuditParams.d.ts +37 -0
  28. package/dist/accessi-module/Console/generated/model/queryAuditParams.js +3 -0
  29. package/dist/accessi-module/Console/generated/model/queryAuditParams.js.map +1 -0
  30. package/dist/accessi-module/Console/wiki.js +2 -0
  31. package/dist/accessi-module/Console/wiki.js.map +1 -1
  32. package/dist/accessi-module/Controllers/AccessiAuditController.d.ts +16 -0
  33. package/dist/accessi-module/Controllers/AccessiAuditController.js +155 -0
  34. package/dist/accessi-module/Controllers/AccessiAuditController.js.map +1 -0
  35. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js +9 -0
  36. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js.map +1 -1
  37. package/dist/accessi-module/Controllers/AuthController.js +82 -8
  38. package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
  39. package/dist/accessi-module/Controllers/ConsoleEntryController.js +15 -0
  40. package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
  41. package/dist/accessi-module/Dtos/AuditDtos.d.ts +10 -0
  42. package/dist/accessi-module/Dtos/AuditDtos.js +68 -0
  43. package/dist/accessi-module/Dtos/AuditDtos.js.map +1 -0
  44. package/dist/accessi-module/Dtos/index.d.ts +1 -0
  45. package/dist/accessi-module/Dtos/index.js +1 -0
  46. package/dist/accessi-module/Dtos/index.js.map +1 -1
  47. package/dist/accessi-module/Services/AuthService/AuthService.js +8 -2
  48. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  49. package/dist/accessi-module/index.d.ts +9 -1
  50. package/dist/accessi-module/index.js +27 -2
  51. package/dist/accessi-module/index.js.map +1 -1
  52. package/dist/accessi-module/observability/metrics.d.ts +79 -0
  53. package/dist/accessi-module/observability/metrics.js +216 -0
  54. package/dist/accessi-module/observability/metrics.js.map +1 -0
  55. package/dist/accessi-module/observability/redisMetrics.d.ts +12 -0
  56. package/dist/accessi-module/observability/redisMetrics.js +44 -0
  57. package/dist/accessi-module/observability/redisMetrics.js.map +1 -0
  58. package/dist/accessi-module/security/accessTokenRevocation.js +5 -3
  59. package/dist/accessi-module/security/accessTokenRevocation.js.map +1 -1
  60. package/dist/accessi-module/security/audit.d.ts +68 -3
  61. package/dist/accessi-module/security/audit.js +177 -4
  62. package/dist/accessi-module/security/audit.js.map +1 -1
  63. package/dist/accessi-module/security/auditSinks.d.ts +55 -0
  64. package/dist/accessi-module/security/auditSinks.js +228 -0
  65. package/dist/accessi-module/security/auditSinks.js.map +1 -0
  66. package/dist/accessi-module/security/consoleEntry.js +5 -3
  67. package/dist/accessi-module/security/consoleEntry.js.map +1 -1
  68. package/dist/accessi-module/security/edgeRateLimit.js +3 -0
  69. package/dist/accessi-module/security/edgeRateLimit.js.map +1 -1
  70. package/dist/accessi-module/security/jwks.js +3 -0
  71. package/dist/accessi-module/security/jwks.js.map +1 -1
  72. package/dist/accessi-module/security/jwtTokenOptions.js +4 -0
  73. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
  74. package/dist/accessi-module/security/publicAuthRateLimit.js +10 -0
  75. package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
  76. package/dist/accessi-module/security/rateLimitStore.js +4 -2
  77. package/dist/accessi-module/security/rateLimitStore.js.map +1 -1
  78. package/dist/accessi-module/security/refreshTokenRotation.js +7 -5
  79. package/dist/accessi-module/security/refreshTokenRotation.js.map +1 -1
  80. package/dist/accessi-module/security/serviceTokenRateLimit.js +3 -0
  81. package/dist/accessi-module/security/serviceTokenRateLimit.js.map +1 -1
  82. package/dist/accessi-module/security/sessionRevocation.js +7 -3
  83. package/dist/accessi-module/security/sessionRevocation.js.map +1 -1
  84. package/dist/emilsoftware-utilities-2.0.0-dev.79.tgz +0 -0
  85. package/docs/audit.md +136 -0
  86. package/docs/metrics.md +153 -0
  87. package/docs/redis-load-testing.md +88 -0
  88. package/package.json +4 -3
  89. package/dist/emilsoftware-utilities-2.0.0-dev.77.tgz +0 -0
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ var AccessiAuditController_1;
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.AccessiAuditController = void 0;
26
+ const common_1 = require("@nestjs/common");
27
+ const swagger_1 = require("@nestjs/swagger");
28
+ const Utilities_1 = require("../../Utilities");
29
+ const Dtos_1 = require("../Dtos");
30
+ const jwt_strategy_1 = require("../jwt/jwt.strategy");
31
+ const accessControl_1 = require("../security/accessControl");
32
+ const audit_1 = require("../security/audit");
33
+ /** Dimensione massima della pagina usata dall'export NDJSON. */
34
+ const EXPORT_PAGE_SIZE = 1000;
35
+ /** Limite di sicurezza per un singolo export. */
36
+ const EXPORT_MAX_RECORDS = 100000;
37
+ /**
38
+ * Consultazione ed export dell'audit persistente.
39
+ *
40
+ * Richiede un utente super/admin Accessi. Se non e configurato uno store
41
+ * interrogabile (`audit.store` o un sink con `query`), risponde 501 con un
42
+ * codice stabile: l'host puo continuare a usare il solo log strutturato.
43
+ */
44
+ let AccessiAuditController = AccessiAuditController_1 = class AccessiAuditController {
45
+ store() {
46
+ const store = (0, audit_1.getAccessiAuditStore)();
47
+ if (!store) {
48
+ throw new common_1.HttpException({
49
+ code: 'ACCESSI_AUDIT_STORE_DISABLED',
50
+ message: 'Audit persistente non configurato su questa istanza.',
51
+ }, common_1.HttpStatus.NOT_IMPLEMENTED);
52
+ }
53
+ return store;
54
+ }
55
+ static toQuery(request) {
56
+ var _a;
57
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (((_a = request.event) === null || _a === void 0 ? void 0 : _a.trim()) ? { event: request.event.trim() } : {})), (request.from ? { from: request.from } : {})), (request.to ? { to: request.to } : {})), (request.codiceUtente !== undefined ? { codiceUtente: request.codiceUtente } : {})), (request.limit !== undefined ? { limit: request.limit } : {})), (request.offset !== undefined ? { offset: request.offset } : {})), (request.order ? { order: request.order } : {}));
58
+ }
59
+ authorize(request) {
60
+ const user = (0, accessControl_1.getAuthenticatedAccessiUser)(request);
61
+ (0, accessControl_1.ensureConsoleAccess)(user, 'La consultazione audit richiede un superutente o un admin Accessi.');
62
+ }
63
+ query(request, query, res) {
64
+ return __awaiter(this, void 0, void 0, function* () {
65
+ try {
66
+ this.authorize(request);
67
+ const page = yield this.store().query(AccessiAuditController_1.toQuery(query));
68
+ return Utilities_1.RestUtilities.sendBaseResponse(res, page);
69
+ }
70
+ catch (error) {
71
+ return Utilities_1.RestUtilities.sendErrorMessage(res, error, AccessiAuditController_1.name, error instanceof common_1.HttpException ? error.getStatus() : common_1.HttpStatus.INTERNAL_SERVER_ERROR);
72
+ }
73
+ });
74
+ }
75
+ export(request, query, res) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ try {
78
+ this.authorize(request);
79
+ const store = this.store();
80
+ const filter = AccessiAuditController_1.toQuery(Object.assign(Object.assign({}, query), { limit: undefined, offset: undefined }));
81
+ const date = new Date().toISOString().slice(0, 10);
82
+ res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');
83
+ res.setHeader('Cache-Control', 'no-store, max-age=0');
84
+ res.setHeader('Content-Disposition', `attachment; filename="accessi-audit-${date}.ndjson"`);
85
+ let offset = 0;
86
+ let written = 0;
87
+ for (;;) {
88
+ const remaining = EXPORT_MAX_RECORDS - written;
89
+ if (remaining <= 0)
90
+ break;
91
+ const page = yield store.query(Object.assign(Object.assign({}, filter), { limit: Math.min(EXPORT_PAGE_SIZE, remaining), offset }));
92
+ for (const record of page.records) {
93
+ res.write(`${JSON.stringify(record)}\n`);
94
+ written += 1;
95
+ }
96
+ offset += page.records.length;
97
+ if (page.records.length === 0 || offset >= page.total)
98
+ break;
99
+ }
100
+ res.end();
101
+ return res;
102
+ }
103
+ catch (error) {
104
+ if (res.headersSent) {
105
+ // L'export e gia iniziato: chiudere in modo esplicito invece di inviare JSON sopra l'NDJSON.
106
+ res.end();
107
+ return res;
108
+ }
109
+ return Utilities_1.RestUtilities.sendErrorMessage(res, error, AccessiAuditController_1.name, error instanceof common_1.HttpException ? error.getStatus() : common_1.HttpStatus.INTERNAL_SERVER_ERROR);
110
+ }
111
+ });
112
+ }
113
+ };
114
+ exports.AccessiAuditController = AccessiAuditController;
115
+ __decorate([
116
+ (0, common_1.Get)(),
117
+ (0, swagger_1.ApiOperation)({
118
+ operationId: 'queryAudit',
119
+ summary: 'Elenca gli eventi di audit persistiti',
120
+ description: 'Richiede audit.store o un sink interrogabile. Supporta filtro per evento, intervallo, utente e paginazione.',
121
+ }),
122
+ (0, swagger_1.ApiOkResponse)({ description: 'Pagina di record di audit redatti.' }),
123
+ (0, swagger_1.ApiResponse)({ status: 401, type: Dtos_1.ErrorResponse, description: 'Token Accessi mancante o non valido.' }),
124
+ (0, swagger_1.ApiResponse)({ status: 403, type: Dtos_1.ErrorResponse, description: 'Utente non abilitato alla console.' }),
125
+ (0, swagger_1.ApiResponse)({ status: 501, type: Dtos_1.ErrorResponse, description: 'Audit persistente non configurato.' }),
126
+ __param(0, (0, common_1.Req)()),
127
+ __param(1, (0, common_1.Query)()),
128
+ __param(2, (0, common_1.Res)()),
129
+ __metadata("design:type", Function),
130
+ __metadata("design:paramtypes", [Object, Dtos_1.AuditQueryRequest, Object]),
131
+ __metadata("design:returntype", Promise)
132
+ ], AccessiAuditController.prototype, "query", null);
133
+ __decorate([
134
+ (0, common_1.Get)('export'),
135
+ (0, swagger_1.ApiOperation)({
136
+ operationId: 'exportAudit',
137
+ summary: 'Esporta gli eventi di audit in NDJSON',
138
+ description: 'Stessi filtri della query, senza paginazione. Il file contiene record gia redatti; limite di sicurezza 100000 record.',
139
+ }),
140
+ (0, swagger_1.ApiResponse)({ status: 200, description: 'NDJSON scaricabile.' }),
141
+ (0, swagger_1.ApiResponse)({ status: 501, type: Dtos_1.ErrorResponse, description: 'Audit persistente non configurato.' }),
142
+ __param(0, (0, common_1.Req)()),
143
+ __param(1, (0, common_1.Query)()),
144
+ __param(2, (0, common_1.Res)()),
145
+ __metadata("design:type", Function),
146
+ __metadata("design:paramtypes", [Object, Dtos_1.AuditQueryRequest, Object]),
147
+ __metadata("design:returntype", Promise)
148
+ ], AccessiAuditController.prototype, "export", null);
149
+ exports.AccessiAuditController = AccessiAuditController = AccessiAuditController_1 = __decorate([
150
+ (0, swagger_1.ApiTags)('Audit'),
151
+ (0, swagger_1.ApiBearerAuth)(),
152
+ (0, common_1.Controller)('accessi/audit'),
153
+ (0, common_1.UseGuards)(jwt_strategy_1.JwtSimpleGuard)
154
+ ], AccessiAuditController);
155
+ //# sourceMappingURL=AccessiAuditController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccessiAuditController.js","sourceRoot":"","sources":["../../../src/accessi-module/Controllers/AccessiAuditController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Controller,\n Get,\n HttpException,\n HttpStatus,\n Query,\n Req,\n Res,\n UseGuards,\n} from '@nestjs/common';\nimport {\n ApiBearerAuth,\n ApiOkResponse,\n ApiOperation,\n ApiResponse,\n ApiTags,\n} from '@nestjs/swagger';\nimport { Request, Response } from 'express';\nimport { RestUtilities } from '../../Utilities';\nimport { AuditQueryRequest, ErrorResponse } from '../Dtos';\nimport { JwtSimpleGuard } from '../jwt/jwt.strategy';\nimport { ensureConsoleAccess, getAuthenticatedAccessiUser } from '../security/accessControl';\nimport { getAccessiAuditStore } from '../security/audit';\nimport type { AccessiAuditQuery, AccessiAuditQueryableSink } from '../security/audit';\n\n/** Dimensione massima della pagina usata dall'export NDJSON. */\nconst EXPORT_PAGE_SIZE = 1_000;\n/** Limite di sicurezza per un singolo export. */\nconst EXPORT_MAX_RECORDS = 100_000;\n\n/**\n * Consultazione ed export dell'audit persistente.\n *\n * Richiede un utente super/admin Accessi. Se non e configurato uno store\n * interrogabile (`audit.store` o un sink con `query`), risponde 501 con un\n * codice stabile: l'host puo continuare a usare il solo log strutturato.\n */\n@ApiTags('Audit')\n@ApiBearerAuth()\n@Controller('accessi/audit')\n@UseGuards(JwtSimpleGuard)\nexport class AccessiAuditController {\n private store(): AccessiAuditQueryableSink {\n const store = getAccessiAuditStore();\n if (!store) {\n throw new HttpException(\n {\n code: 'ACCESSI_AUDIT_STORE_DISABLED',\n message: 'Audit persistente non configurato su questa istanza.',\n },\n HttpStatus.NOT_IMPLEMENTED,\n );\n }\n return store;\n }\n\n private static toQuery(request: AuditQueryRequest): AccessiAuditQuery {\n return {\n ...(request.event?.trim() ? { event: request.event.trim() } : {}),\n ...(request.from ? { from: request.from } : {}),\n ...(request.to ? { to: request.to } : {}),\n ...(request.codiceUtente !== undefined ? { codiceUtente: request.codiceUtente } : {}),\n ...(request.limit !== undefined ? { limit: request.limit } : {}),\n ...(request.offset !== undefined ? { offset: request.offset } : {}),\n ...(request.order ? { order: request.order } : {}),\n };\n }\n\n private authorize(request: Request): void {\n const user = getAuthenticatedAccessiUser(request);\n ensureConsoleAccess(user, 'La consultazione audit richiede un superutente o un admin Accessi.');\n }\n\n @Get()\n @ApiOperation({\n operationId: 'queryAudit',\n summary: 'Elenca gli eventi di audit persistiti',\n description:\n 'Richiede audit.store o un sink interrogabile. Supporta filtro per evento, intervallo, utente e paginazione.',\n })\n @ApiOkResponse({ description: 'Pagina di record di audit redatti.' })\n @ApiResponse({ status: 401, type: ErrorResponse, description: 'Token Accessi mancante o non valido.' })\n @ApiResponse({ status: 403, type: ErrorResponse, description: 'Utente non abilitato alla console.' })\n @ApiResponse({ status: 501, type: ErrorResponse, description: 'Audit persistente non configurato.' })\n async query(\n @Req() request: Request,\n @Query() query: AuditQueryRequest,\n @Res() res: Response,\n ): Promise<Response> {\n try {\n this.authorize(request);\n const page = await this.store().query(AccessiAuditController.toQuery(query));\n return RestUtilities.sendBaseResponse(res, page);\n } catch (error) {\n return RestUtilities.sendErrorMessage(\n res,\n error,\n AccessiAuditController.name,\n error instanceof HttpException ? error.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR,\n );\n }\n }\n\n @Get('export')\n @ApiOperation({\n operationId: 'exportAudit',\n summary: 'Esporta gli eventi di audit in NDJSON',\n description:\n 'Stessi filtri della query, senza paginazione. Il file contiene record gia redatti; limite di sicurezza 100000 record.',\n })\n @ApiResponse({ status: 200, description: 'NDJSON scaricabile.' })\n @ApiResponse({ status: 501, type: ErrorResponse, description: 'Audit persistente non configurato.' })\n async export(\n @Req() request: Request,\n @Query() query: AuditQueryRequest,\n @Res() res: Response,\n ): Promise<Response | void> {\n try {\n this.authorize(request);\n const store = this.store();\n const filter = AccessiAuditController.toQuery({ ...query, limit: undefined, offset: undefined });\n const date = new Date().toISOString().slice(0, 10);\n res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');\n res.setHeader('Cache-Control', 'no-store, max-age=0');\n res.setHeader('Content-Disposition', `attachment; filename=\"accessi-audit-${date}.ndjson\"`);\n\n let offset = 0;\n let written = 0;\n for (;;) {\n const remaining = EXPORT_MAX_RECORDS - written;\n if (remaining <= 0) break;\n const page = await store.query({\n ...filter,\n limit: Math.min(EXPORT_PAGE_SIZE, remaining),\n offset,\n });\n for (const record of page.records) {\n res.write(`${JSON.stringify(record)}\\n`);\n written += 1;\n }\n offset += page.records.length;\n if (page.records.length === 0 || offset >= page.total) break;\n }\n res.end();\n return res;\n } catch (error) {\n if (res.headersSent) {\n // L'export e gia iniziato: chiudere in modo esplicito invece di inviare JSON sopra l'NDJSON.\n res.end();\n return res;\n }\n return RestUtilities.sendErrorMessage(\n res,\n error,\n AccessiAuditController.name,\n error instanceof HttpException ? error.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR,\n );\n }\n }\n}\n"]}
@@ -30,6 +30,7 @@ const Dtos_1 = require("../Dtos");
30
30
  const PermissionService_1 = require("../Services/PermissionService/PermissionService");
31
31
  const AuthService_1 = require("../Services/AuthService/AuthService");
32
32
  const serviceTokenGuard_1 = require("../security/serviceTokenGuard");
33
+ const metrics_1 = require("../observability/metrics");
33
34
  /**
34
35
  * Endpoint service-to-service per validare un access token Accessi.
35
36
  *
@@ -45,21 +46,29 @@ let AccessiIntrospectionController = AccessiIntrospectionController_1 = class Ac
45
46
  introspect(body, res) {
46
47
  return __awaiter(this, void 0, void 0, function* () {
47
48
  var _a;
49
+ const startedAt = Date.now();
48
50
  try {
49
51
  if (!(body === null || body === void 0 ? void 0 : body.token)) {
52
+ (0, metrics_1.recordAccessiMetric)('accessi_introspection_total', 1, { result: 'bad_request' });
50
53
  return Utilities_1.RestUtilities.sendErrorMessage(res, 'Token non fornito.', AccessiIntrospectionController_1.name, common_1.HttpStatus.BAD_REQUEST);
51
54
  }
52
55
  const introspection = yield this.authService.introspectAccessToken(body.token);
53
56
  if (!introspection.active || !introspection.codiceUtente) {
57
+ (0, metrics_1.recordAccessiMetric)('accessi_introspection_total', 1, { result: 'inactive' });
54
58
  return Utilities_1.RestUtilities.sendBaseResponse(res, { active: false });
55
59
  }
56
60
  const user = (_a = introspection.payload) === null || _a === void 0 ? void 0 : _a.utente;
57
61
  const grants = yield this.permissionService.getUserRolesAndGrants(introspection.codiceUtente);
62
+ (0, metrics_1.recordAccessiMetric)('accessi_introspection_total', 1, { result: 'active' });
58
63
  return Utilities_1.RestUtilities.sendBaseResponse(res, Object.assign(Object.assign({ active: true }, (user ? { user } : {})), { grants }));
59
64
  }
60
65
  catch (error) {
66
+ (0, metrics_1.recordAccessiMetric)('accessi_introspection_total', 1, { result: 'error' });
61
67
  return Utilities_1.RestUtilities.sendErrorMessage(res, error, AccessiIntrospectionController_1.name);
62
68
  }
69
+ finally {
70
+ (0, metrics_1.recordAccessiDuration)('accessi_introspection_duration_ms', Date.now() - startedAt);
71
+ }
63
72
  });
64
73
  }
65
74
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AccessiIntrospectionController.js","sourceRoot":"","sources":["../../../src/accessi-module/Controllers/AccessiIntrospectionController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAoF;AACpF,6CAOyB;AAEzB,+CAAgD;AAChD,kCAIiB;AACjB,uFAAoF;AACpF,qEAAkE;AAClE,qEAA6F;AAE7F;;;;;;GAMG;AAKI,IAAM,8BAA8B,sCAApC,MAAM,8BAA8B;IACzC,YACmB,WAAwB,EACxB,iBAAoC;QADpC,gBAAW,GAAX,WAAW,CAAa;QACxB,sBAAiB,GAAjB,iBAAiB,CAAmB;IACpD,CAAC;IAcE,UAAU,CACN,IAAmC,EACpC,GAAa;;;YAEpB,IAAI,CAAC;gBACH,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAAA,EAAE,CAAC;oBACjB,OAAO,yBAAa,CAAC,gBAAgB,CACnC,GAAG,EACH,oBAAoB,EACpB,gCAA8B,CAAC,IAAI,EACnC,mBAAU,CAAC,WAAW,CACvB,CAAC;gBACJ,CAAC;gBAED,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC/E,IAAI,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC;oBACzD,OAAO,yBAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAChE,CAAC;gBAED,MAAM,IAAI,GAAG,MAAC,aAAa,CAAC,OAA4C,0CAAE,MAAM,CAAC;gBACjF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;gBAC9F,OAAO,yBAAa,CAAC,gBAAgB,CAAC,GAAG,gCACvC,MAAM,EAAE,IAAI,IACT,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACzB,MAAM,IACN,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,yBAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,KAAK,EAAE,gCAA8B,CAAC,IAAI,CAAC,CAAC;YACzF,CAAC;QACH,CAAC;KAAA;CACF,CAAA;AAhDY,wEAA8B;AAkBnC;IAZL,IAAA,aAAI,EAAC,YAAY,CAAC;IAClB,IAAA,6CAAyB,EAAC,oBAAoB,CAAC;IAC/C,IAAA,sBAAY,EAAC;QACZ,OAAO,EAAE,kCAAkC;QAC3C,WAAW,EAAE,wBAAwB;QACrC,WAAW,EACT,0IAA0I;KAC7I,CAAC;IACD,IAAA,iBAAO,EAAC,EAAE,IAAI,EAAE,oCAA6B,EAAE,CAAC;IAChD,IAAA,uBAAa,EAAC,EAAE,WAAW,EAAE,6BAA6B,EAAE,IAAI,EAAE,qCAA8B,EAAE,CAAC;IACnG,IAAA,qBAAW,EAAC,EAAE,MAAM,EAAE,mBAAU,CAAC,YAAY,EAAE,WAAW,EAAE,sCAAsC,EAAE,IAAI,EAAE,oBAAa,EAAE,CAAC;IAC1H,IAAA,qBAAW,EAAC,EAAE,MAAM,EAAE,mBAAU,CAAC,SAAS,EAAE,WAAW,EAAE,oCAAoC,EAAE,IAAI,EAAE,oBAAa,EAAE,CAAC;IAEnH,WAAA,IAAA,aAAI,GAAE,CAAA;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;;qCADQ,oCAA6B;;gEA4B5C;yCA/CU,8BAA8B;IAJ1C,IAAA,iBAAO,EAAC,cAAc,CAAC;IACvB,IAAA,uBAAa,GAAE;IACf,IAAA,mBAAU,EAAC,cAAc,CAAC;IAC1B,IAAA,kBAAS,EAAC,qCAAiB,CAAC;qCAGK,yBAAW;QACL,qCAAiB;GAH5C,8BAA8B,CAgD1C","sourcesContent":["import { Body, Controller, HttpStatus, Post, Res, UseGuards } from '@nestjs/common';\nimport {\n ApiBearerAuth,\n ApiBody,\n ApiOkResponse,\n ApiOperation,\n ApiResponse,\n ApiTags,\n} from '@nestjs/swagger';\nimport { Response } from 'express';\nimport { RestUtilities } from '../../Utilities';\nimport {\n ErrorResponse,\n IntrospectAccessiTokenRequest,\n IntrospectAccessiTokenResponse,\n} from '../Dtos';\nimport { PermissionService } from '../Services/PermissionService/PermissionService';\nimport { AuthService } from '../Services/AuthService/AuthService';\nimport { RequireServiceTokenScopes, ServiceTokenGuard } from '../security/serviceTokenGuard';\n\n/**\n * Endpoint service-to-service per validare un access token Accessi.\n *\n * Non e pensato per il browser: richiede un service token con scope\n * `accessi:introspect`. I backend fratelli lo usano per autorizzare richieste\n * senza accedere al database Accessi.\n */\n@ApiTags('ServiceToken')\n@ApiBearerAuth()\n@Controller('accessi/auth')\n@UseGuards(ServiceTokenGuard)\nexport class AccessiIntrospectionController {\n constructor(\n private readonly authService: AuthService,\n private readonly permissionService: PermissionService,\n ) {}\n\n @Post('introspect')\n @RequireServiceTokenScopes('accessi:introspect')\n @ApiOperation({\n summary: 'Verifica un access token Accessi',\n operationId: 'introspectAccessiToken',\n description:\n 'Restituisce active=false per token invalido, scaduto o utente non piu abilitato. Richiede un service token con scope accessi:introspect.',\n })\n @ApiBody({ type: IntrospectAccessiTokenRequest })\n @ApiOkResponse({ description: 'Esito della verifica token.', type: IntrospectAccessiTokenResponse })\n @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Service token mancante o non valido.', type: ErrorResponse })\n @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Scope accessi:introspect mancante.', type: ErrorResponse })\n async introspect(\n @Body() body: IntrospectAccessiTokenRequest,\n @Res() res: Response,\n ): Promise<Response> {\n try {\n if (!body?.token) {\n return RestUtilities.sendErrorMessage(\n res,\n 'Token non fornito.',\n AccessiIntrospectionController.name,\n HttpStatus.BAD_REQUEST,\n );\n }\n\n const introspection = await this.authService.introspectAccessToken(body.token);\n if (!introspection.active || !introspection.codiceUtente) {\n return RestUtilities.sendBaseResponse(res, { active: false });\n }\n\n const user = (introspection.payload as { utente?: unknown } | undefined)?.utente;\n const grants = await this.permissionService.getUserRolesAndGrants(introspection.codiceUtente);\n return RestUtilities.sendBaseResponse(res, {\n active: true,\n ...(user ? { user } : {}),\n grants,\n });\n } catch (error) {\n return RestUtilities.sendErrorMessage(res, error, AccessiIntrospectionController.name);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"AccessiIntrospectionController.js","sourceRoot":"","sources":["../../../src/accessi-module/Controllers/AccessiIntrospectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Body, Controller, HttpStatus, Post, Res, UseGuards } from '@nestjs/common';\nimport {\n ApiBearerAuth,\n ApiBody,\n ApiOkResponse,\n ApiOperation,\n ApiResponse,\n ApiTags,\n} from '@nestjs/swagger';\nimport { Response } from 'express';\nimport { RestUtilities } from '../../Utilities';\nimport {\n ErrorResponse,\n IntrospectAccessiTokenRequest,\n IntrospectAccessiTokenResponse,\n} from '../Dtos';\nimport { PermissionService } from '../Services/PermissionService/PermissionService';\nimport { AuthService } from '../Services/AuthService/AuthService';\nimport { RequireServiceTokenScopes, ServiceTokenGuard } from '../security/serviceTokenGuard';\nimport { recordAccessiDuration, recordAccessiMetric } from '../observability/metrics';\n\n/**\n * Endpoint service-to-service per validare un access token Accessi.\n *\n * Non e pensato per il browser: richiede un service token con scope\n * `accessi:introspect`. I backend fratelli lo usano per autorizzare richieste\n * senza accedere al database Accessi.\n */\n@ApiTags('ServiceToken')\n@ApiBearerAuth()\n@Controller('accessi/auth')\n@UseGuards(ServiceTokenGuard)\nexport class AccessiIntrospectionController {\n constructor(\n private readonly authService: AuthService,\n private readonly permissionService: PermissionService,\n ) {}\n\n @Post('introspect')\n @RequireServiceTokenScopes('accessi:introspect')\n @ApiOperation({\n summary: 'Verifica un access token Accessi',\n operationId: 'introspectAccessiToken',\n description:\n 'Restituisce active=false per token invalido, scaduto o utente non piu abilitato. Richiede un service token con scope accessi:introspect.',\n })\n @ApiBody({ type: IntrospectAccessiTokenRequest })\n @ApiOkResponse({ description: 'Esito della verifica token.', type: IntrospectAccessiTokenResponse })\n @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Service token mancante o non valido.', type: ErrorResponse })\n @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Scope accessi:introspect mancante.', type: ErrorResponse })\n async introspect(\n @Body() body: IntrospectAccessiTokenRequest,\n @Res() res: Response,\n ): Promise<Response> {\n const startedAt = Date.now();\n try {\n if (!body?.token) {\n recordAccessiMetric('accessi_introspection_total', 1, { result: 'bad_request' });\n return RestUtilities.sendErrorMessage(\n res,\n 'Token non fornito.',\n AccessiIntrospectionController.name,\n HttpStatus.BAD_REQUEST,\n );\n }\n\n const introspection = await this.authService.introspectAccessToken(body.token);\n if (!introspection.active || !introspection.codiceUtente) {\n recordAccessiMetric('accessi_introspection_total', 1, { result: 'inactive' });\n return RestUtilities.sendBaseResponse(res, { active: false });\n }\n\n const user = (introspection.payload as { utente?: unknown } | undefined)?.utente;\n const grants = await this.permissionService.getUserRolesAndGrants(introspection.codiceUtente);\n recordAccessiMetric('accessi_introspection_total', 1, { result: 'active' });\n return RestUtilities.sendBaseResponse(res, {\n active: true,\n ...(user ? { user } : {}),\n grants,\n });\n } catch (error) {\n recordAccessiMetric('accessi_introspection_total', 1, { result: 'error' });\n return RestUtilities.sendErrorMessage(res, error, AccessiIntrospectionController.name);\n } finally {\n recordAccessiDuration('accessi_introspection_duration_ms', Date.now() - startedAt);\n }\n }\n}\n"]}
@@ -36,10 +36,24 @@ const jwt_strategy_1 = require("../jwt/jwt.strategy");
36
36
  const accessControl_1 = require("../security/accessControl");
37
37
  const authenticatedToken_1 = require("../security/authenticatedToken");
38
38
  const authCookies_1 = require("../security/authCookies");
39
+ const metrics_1 = require("../observability/metrics");
39
40
  const sessionRevocation_1 = require("../security/sessionRevocation");
40
41
  const accessTokenRevocation_1 = require("../security/accessTokenRevocation");
41
42
  const audit_1 = require("../security/audit");
42
43
  const publicAuthRateLimit_1 = require("../security/publicAuthRateLimit");
44
+ /** Estrae il codice stabile di un errore Nest `{ code, message }`. */
45
+ function authErrorCode(error) {
46
+ if (!error || typeof error !== 'object')
47
+ return undefined;
48
+ const response = typeof error.getResponse === 'function'
49
+ ? error.getResponse()
50
+ : error.response;
51
+ if (response && typeof response === 'object') {
52
+ const code = response.code;
53
+ return typeof code === 'string' ? code : undefined;
54
+ }
55
+ return undefined;
56
+ }
43
57
  let AuthController = AuthController_1 = class AuthController {
44
58
  constructor(authService, adminBootstrapService, options) {
45
59
  this.authService = authService;
@@ -157,13 +171,18 @@ let AuthController = AuthController_1 = class AuthController {
157
171
  }
158
172
  login(request, loginRequest, res) {
159
173
  return __awaiter(this, void 0, void 0, function* () {
174
+ var _a;
175
+ const startedAt = Date.now();
160
176
  try {
161
177
  const rateLimitDecision = yield (0, publicAuthRateLimit_1.checkPublicAuthRateLimit)(this.options, 'login', request, [loginRequest === null || loginRequest === void 0 ? void 0 : loginRequest.email]);
162
178
  if (!rateLimitDecision.allowed) {
179
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_login_total', { result: 'rate_limited' });
163
180
  return (0, publicAuthRateLimit_1.sendPublicAuthRateLimitExceeded)(res, rateLimitDecision.retryAfterSeconds);
164
181
  }
165
182
  const userData = yield this.authService.login(loginRequest);
166
183
  if (!userData) {
184
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_login_total', { result: 'failure' });
185
+ (0, audit_1.auditAccessi)('login_failed', { result: 'failure', ip: request.ip, emailProvided: Boolean(loginRequest === null || loginRequest === void 0 ? void 0 : loginRequest.email) });
167
186
  return Utilities_1.RestUtilities.sendInvalidCredentials(res);
168
187
  }
169
188
  if (!userData.challenge && !userData.passwordRequired) {
@@ -173,10 +192,30 @@ let AuthController = AuthController_1 = class AuthController {
173
192
  userData.refreshToken = tokens.refresh;
174
193
  (0, authCookies_1.applyAccessiAuthCookies)(res, (0, authCookies_1.resolveAccessiAuthCookieOptions)(this.options), userData);
175
194
  }
195
+ const result = userData.challenge
196
+ ? 'challenge'
197
+ : userData.passwordRequired
198
+ ? 'password_required'
199
+ : 'success';
200
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_login_total', { result });
201
+ if (result === 'success') {
202
+ (0, audit_1.auditAccessi)('login_succeeded', {
203
+ codiceUtente: (_a = userData.utente) === null || _a === void 0 ? void 0 : _a.codiceUtente,
204
+ ip: request.ip,
205
+ });
206
+ }
176
207
  return Utilities_1.RestUtilities.sendBaseResponse(res, userData);
177
208
  }
178
209
  catch (error) {
179
- if ((error === null || error === void 0 ? void 0 : error.message) === 'PASSWORD_EXPIRED') {
210
+ const message = error === null || error === void 0 ? void 0 : error.message;
211
+ const result = message === 'PASSWORD_EXPIRED'
212
+ ? 'password_expired'
213
+ : message === AuthService_1.PASSWORD_LOGIN_DISABLED
214
+ ? 'sso_only'
215
+ : 'error';
216
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_login_total', { result });
217
+ (0, audit_1.auditAccessi)('login_failed', { result, ip: request.ip, emailProvided: Boolean(loginRequest === null || loginRequest === void 0 ? void 0 : loginRequest.email) });
218
+ if (message === 'PASSWORD_EXPIRED') {
180
219
  this.logger.warning('Password scaduta, cambiare password ', error);
181
220
  return Utilities_1.RestUtilities.sendPasswordExpired(res);
182
221
  }
@@ -197,10 +236,14 @@ let AuthController = AuthController_1 = class AuthController {
197
236
  }
198
237
  return Utilities_1.RestUtilities.sendInvalidCredentials(res);
199
238
  }
239
+ finally {
240
+ (0, metrics_1.observeAccessiMetric)(this.options, 'auth_login_duration_ms', Date.now() - startedAt);
241
+ }
200
242
  });
201
243
  }
202
244
  refresh(request, body, res) {
203
245
  return __awaiter(this, void 0, void 0, function* () {
246
+ const startedAt = Date.now();
204
247
  try {
205
248
  const cookieConfig = (0, authCookies_1.resolveAccessiAuthCookieOptions)(this.options);
206
249
  const cookieRefreshToken = (cookieConfig === null || cookieConfig === void 0 ? void 0 : cookieConfig.refreshFromCookie)
@@ -214,6 +257,7 @@ let AuthController = AuthController_1 = class AuthController {
214
257
  : undefined;
215
258
  const decision = yield (0, publicAuthRateLimit_1.checkPublicAuthRateLimit)(this.options, 'refresh', request, tokenKey ? [tokenKey] : []);
216
259
  if (!decision.allowed) {
260
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_refresh_total', { result: 'rate_limited' });
217
261
  return (0, publicAuthRateLimit_1.sendPublicAuthRateLimitExceeded)(res, decision.retryAfterSeconds);
218
262
  }
219
263
  if (!refreshToken) {
@@ -225,37 +269,69 @@ let AuthController = AuthController_1 = class AuthController {
225
269
  }
226
270
  const response = Object.assign({ token: result.token }, (result.refreshToken ? { refreshToken: result.refreshToken } : {}));
227
271
  (0, authCookies_1.applyAccessiAuthCookies)(res, cookieConfig, response);
272
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_refresh_total', { result: 'success' });
273
+ (0, audit_1.auditAccessi)('refresh_succeeded', { ip: request.ip });
228
274
  return Utilities_1.RestUtilities.sendBaseResponse(res, response);
229
275
  }
230
276
  catch (error) {
277
+ const code = authErrorCode(error);
278
+ const result = code === 'ACCESSI_REFRESH_TOKEN_REUSE'
279
+ ? 'reuse'
280
+ : code === 'ACCESSI_REFRESH_FAMILY_REVOKED'
281
+ ? 'family_revoked'
282
+ : code === 'ACCESSI_REFRESH_ROTATION_UNAVAILABLE'
283
+ ? 'store_unavailable'
284
+ : 'invalid';
285
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_refresh_total', { result });
286
+ if (result !== 'reuse')
287
+ (0, audit_1.auditAccessi)('refresh_failed', { result, ip: request.ip });
231
288
  return Utilities_1.RestUtilities.sendErrorMessage(res, error, AuthController_1.name, error instanceof common_1.HttpException ? error.getStatus() : common_1.HttpStatus.UNAUTHORIZED);
232
289
  }
290
+ finally {
291
+ (0, metrics_1.observeAccessiMetric)(this.options, 'auth_refresh_duration_ms', Date.now() - startedAt);
292
+ }
233
293
  });
234
294
  }
235
295
  verifyTwoFactor(request, body, res) {
236
296
  return __awaiter(this, void 0, void 0, function* () {
297
+ var _a;
298
+ const startedAt = Date.now();
237
299
  try {
238
300
  const limit = yield (0, publicAuthRateLimit_1.checkPublicAuthRateLimit)(this.options, 'twoFactorVerify', request, [body.challengeId]);
239
- if (!limit.allowed)
301
+ if (!limit.allowed) {
302
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_two_factor_total', { action: 'verify', result: 'rate_limited' });
240
303
  return (0, publicAuthRateLimit_1.sendPublicAuthRateLimitExceeded)(res, limit.retryAfterSeconds);
304
+ }
241
305
  const result = yield this.authService.verifyTwoFactor(body.challengeId, body.code, body.scope);
242
306
  (0, authCookies_1.applyAccessiAuthCookies)(res, (0, authCookies_1.resolveAccessiAuthCookieOptions)(this.options), result);
307
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_two_factor_total', { action: 'verify', result: 'success' });
308
+ (0, audit_1.auditAccessi)('two_factor_succeeded', { codiceUtente: (_a = result.utente) === null || _a === void 0 ? void 0 : _a.codiceUtente, ip: request.ip });
243
309
  return Utilities_1.RestUtilities.sendBaseResponse(res, result);
244
310
  }
245
311
  catch (error) {
312
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_two_factor_total', { action: 'verify', result: 'failure' });
313
+ (0, audit_1.auditAccessi)('two_factor_failed', { ip: request.ip });
246
314
  return Utilities_1.RestUtilities.sendErrorMessage(res, error, AuthController_1.name, error instanceof common_1.HttpException ? error.getStatus() : 500);
247
315
  }
316
+ finally {
317
+ (0, metrics_1.observeAccessiMetric)(this.options, 'auth_two_factor_duration_ms', Date.now() - startedAt, { action: 'verify' });
318
+ }
248
319
  });
249
320
  }
250
321
  resendTwoFactor(request, body, res) {
251
322
  return __awaiter(this, void 0, void 0, function* () {
252
323
  try {
253
324
  const limit = yield (0, publicAuthRateLimit_1.checkPublicAuthRateLimit)(this.options, 'twoFactorResend', request, [body.challengeId]);
254
- if (!limit.allowed)
325
+ if (!limit.allowed) {
326
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_two_factor_total', { action: 'resend', result: 'rate_limited' });
255
327
  return (0, publicAuthRateLimit_1.sendPublicAuthRateLimitExceeded)(res, limit.retryAfterSeconds);
256
- return Utilities_1.RestUtilities.sendBaseResponse(res, yield this.authService.resendTwoFactor(body.challengeId));
328
+ }
329
+ const result = yield this.authService.resendTwoFactor(body.challengeId);
330
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_two_factor_total', { action: 'resend', result: 'success' });
331
+ return Utilities_1.RestUtilities.sendBaseResponse(res, result);
257
332
  }
258
333
  catch (error) {
334
+ (0, metrics_1.countAccessiMetric)(this.options, 'auth_two_factor_total', { action: 'resend', result: 'failure' });
259
335
  return Utilities_1.RestUtilities.sendErrorMessage(res, error, AuthController_1.name, error instanceof common_1.HttpException ? error.getStatus() : 500);
260
336
  }
261
337
  });
@@ -477,12 +553,10 @@ __decorate([
477
553
  ], AuthController.prototype, "adminBootstrapStatus", null);
478
554
  exports.AuthController = AuthController = AuthController_1 = __decorate([
479
555
  (0, swagger_1.ApiTags)('Auth'),
480
- (0, common_1.Controller)('accessi/auth')
481
- /**
556
+ (0, common_1.Controller)('accessi/auth') /**
482
557
  * Endpoint pubblici di autenticazione locale e verifica sessione.
483
558
  * Il login SSO non riceve token esterni qui: il backend validante usa `FederatedAuthService.authenticate`.
484
- */
485
- ,
559
+ */,
486
560
  __param(2, (0, common_1.Inject)('ACCESSI_OPTIONS')),
487
561
  __metadata("design:paramtypes", [AuthService_1.AuthService,
488
562
  AdminBootstrapService_1.AdminBootstrapService, Object])