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.
- package/dist/accessi/client/generated/admin.d.ts +21 -1
- package/dist/accessi/client/generated/admin.js +35 -1
- package/dist/accessi/client/generated/admin.js.map +1 -1
- package/dist/accessi/resource-server/token.js +2 -0
- package/dist/accessi/resource-server/token.js.map +1 -1
- package/dist/accessi/server/standalone.js +2 -1
- package/dist/accessi/server/standalone.js.map +1 -1
- package/dist/accessi-module/AccessiModule.d.ts +6 -0
- package/dist/accessi-module/AccessiModule.js +4 -0
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/console.js +2 -0
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.d.ts +21 -1
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.js +35 -1
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/exportAuditOrder.d.ts +11 -0
- package/dist/accessi-module/Console/generated/model/exportAuditOrder.js +15 -0
- package/dist/accessi-module/Console/generated/model/exportAuditOrder.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/exportAuditParams.d.ts +37 -0
- package/dist/accessi-module/Console/generated/model/exportAuditParams.js +3 -0
- package/dist/accessi-module/Console/generated/model/exportAuditParams.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/index.d.ts +4 -0
- package/dist/accessi-module/Console/generated/model/index.js +4 -0
- package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/queryAuditOrder.d.ts +11 -0
- package/dist/accessi-module/Console/generated/model/queryAuditOrder.js +15 -0
- package/dist/accessi-module/Console/generated/model/queryAuditOrder.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/queryAuditParams.d.ts +37 -0
- package/dist/accessi-module/Console/generated/model/queryAuditParams.js +3 -0
- package/dist/accessi-module/Console/generated/model/queryAuditParams.js.map +1 -0
- package/dist/accessi-module/Console/wiki.js +2 -0
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/Controllers/AccessiAuditController.d.ts +16 -0
- package/dist/accessi-module/Controllers/AccessiAuditController.js +155 -0
- package/dist/accessi-module/Controllers/AccessiAuditController.js.map +1 -0
- package/dist/accessi-module/Controllers/AccessiIntrospectionController.js +9 -0
- package/dist/accessi-module/Controllers/AccessiIntrospectionController.js.map +1 -1
- package/dist/accessi-module/Controllers/AuthController.js +82 -8
- package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
- package/dist/accessi-module/Controllers/ConsoleEntryController.js +15 -0
- package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
- package/dist/accessi-module/Dtos/AuditDtos.d.ts +10 -0
- package/dist/accessi-module/Dtos/AuditDtos.js +68 -0
- package/dist/accessi-module/Dtos/AuditDtos.js.map +1 -0
- package/dist/accessi-module/Dtos/index.d.ts +1 -0
- package/dist/accessi-module/Dtos/index.js +1 -0
- package/dist/accessi-module/Dtos/index.js.map +1 -1
- package/dist/accessi-module/Services/AuthService/AuthService.js +8 -2
- package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
- package/dist/accessi-module/index.d.ts +9 -1
- package/dist/accessi-module/index.js +27 -2
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/accessi-module/observability/metrics.d.ts +79 -0
- package/dist/accessi-module/observability/metrics.js +216 -0
- package/dist/accessi-module/observability/metrics.js.map +1 -0
- package/dist/accessi-module/observability/redisMetrics.d.ts +12 -0
- package/dist/accessi-module/observability/redisMetrics.js +44 -0
- package/dist/accessi-module/observability/redisMetrics.js.map +1 -0
- package/dist/accessi-module/security/accessTokenRevocation.js +5 -3
- package/dist/accessi-module/security/accessTokenRevocation.js.map +1 -1
- package/dist/accessi-module/security/audit.d.ts +68 -3
- package/dist/accessi-module/security/audit.js +177 -4
- package/dist/accessi-module/security/audit.js.map +1 -1
- package/dist/accessi-module/security/auditSinks.d.ts +55 -0
- package/dist/accessi-module/security/auditSinks.js +228 -0
- package/dist/accessi-module/security/auditSinks.js.map +1 -0
- package/dist/accessi-module/security/consoleEntry.js +5 -3
- package/dist/accessi-module/security/consoleEntry.js.map +1 -1
- package/dist/accessi-module/security/edgeRateLimit.js +3 -0
- package/dist/accessi-module/security/edgeRateLimit.js.map +1 -1
- package/dist/accessi-module/security/jwks.js +3 -0
- package/dist/accessi-module/security/jwks.js.map +1 -1
- package/dist/accessi-module/security/jwtTokenOptions.js +4 -0
- package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
- package/dist/accessi-module/security/publicAuthRateLimit.js +10 -0
- package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
- package/dist/accessi-module/security/rateLimitStore.js +4 -2
- package/dist/accessi-module/security/rateLimitStore.js.map +1 -1
- package/dist/accessi-module/security/refreshTokenRotation.js +7 -5
- package/dist/accessi-module/security/refreshTokenRotation.js.map +1 -1
- package/dist/accessi-module/security/serviceTokenRateLimit.js +3 -0
- package/dist/accessi-module/security/serviceTokenRateLimit.js.map +1 -1
- package/dist/accessi-module/security/sessionRevocation.js +7 -3
- package/dist/accessi-module/security/sessionRevocation.js.map +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.79.tgz +0 -0
- package/docs/audit.md +136 -0
- package/docs/metrics.md +153 -0
- package/docs/redis-load-testing.md +88 -0
- package/package.json +4 -3
- 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;
|
|
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
|
-
|
|
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
|
-
|
|
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])
|