feat: auditoría (audit_log en SQLite, endpoint /api/audit y pantalla /admin/auditoria)

Registra sesiones, creaciones y modificaciones en SAP (solo nombres de campos), denegados y cambios de permisos.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-16 12:35:46 -05:00
1 parent 4e9d6a757d
commit fe8c080471
26 files changed
+993 -28

No files matched your search

@@ -1,5 +1,6 @@
import { Body, Controller, Delete, Get, HttpCode, Param, Put, Req, UseGuards } from '@nestjs/common';
import { z } from 'zod';
import { AuditService } from '../audit/audit.service.js';
import type { AuthenticatedRequest } from '../auth/auth.types.js';
import { ZodValidationPipe } from '../common/zod-validation.pipe.js';
import { AdminGuard } from './admin.guard.js';
@@ -29,7 +30,10 @@ export class UserMenuController {
@Controller('permissions')
@UseGuards(AdminGuard)
export class PermissionsController {
constructor(private readonly permissions: PermissionsService) {}
constructor(
private readonly permissions: PermissionsService,
private readonly audit: AuditService,
) {}
@Get()
list() {
@@ -42,16 +46,30 @@ export class PermissionsController {
}
@Put(':webUser')
set(
async set(
@Req() req: AuthenticatedRequest,
@Param('webUser', new ZodValidationPipe(webUserSchema)) webUser: string,
@Body(new ZodValidationPipe(setPathsSchema)) body: SetPathsDto,
) {
return this.permissions.setPaths(webUser, body.paths);
try {
const result = await this.permissions.setPaths(webUser, body.paths);
await this.audit.record({ action: 'permissions_set', webUser: req.user.webUser, ip: req.ip, resource: webUser, detail: { paths: body.paths } });
return result;
} catch (e) {
await this.audit.record({ action: 'permissions_set', webUser: req.user.webUser, ip: req.ip, resource: webUser, outcome: 'error', detail: { paths: body.paths, error: e instanceof Error ? e.message : String(e) } });
throw e;
}
}
@Delete(':webUser')
@HttpCode(204)
remove(@Param('webUser', new ZodValidationPipe(webUserSchema)) webUser: string) {
return this.permissions.remove(webUser);
async remove(@Req() req: AuthenticatedRequest, @Param('webUser', new ZodValidationPipe(webUserSchema)) webUser: string) {
try {
await this.permissions.remove(webUser);
await this.audit.record({ action: 'permissions_remove', webUser: req.user.webUser, ip: req.ip, resource: webUser });
} catch (e) {
await this.audit.record({ action: 'permissions_remove', webUser: req.user.webUser, ip: req.ip, resource: webUser, outcome: 'error', detail: { error: e instanceof Error ? e.message : String(e) } });
throw e;
}
}
}
@@ -1,12 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuditModule } from '../audit/audit.module.js';
import { AdminGuard } from './admin.guard.js';
import { MenuPermission } from './menu-permission.entity.js';
import { PermissionsController, UserMenuController } from './permissions.controller.js';
import { PermissionsService } from './permissions.service.js';
@Module({
imports: [TypeOrmModule.forFeature([MenuPermission])],
imports: [TypeOrmModule.forFeature([MenuPermission]), AuditModule],
controllers: [UserMenuController, PermissionsController],
providers: [PermissionsService, AdminGuard],
exports: [PermissionsService],