Some checks failed
CI/CD Pipeline / Discord Notification (Failure) (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Deployment Summary (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Portainer (push) Blocked by required conditions
CI/CD Pipeline / Discord Notification (Success) (push) Blocked by required conditions
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m20s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Has been cancelled
229 lines
7.3 KiB
TypeScript
229 lines
7.3 KiB
TypeScript
/**
|
|
* Audit Log Controller
|
|
*
|
|
* Provides endpoints for querying audit logs
|
|
*/
|
|
|
|
import {
|
|
Controller,
|
|
Get,
|
|
Param,
|
|
Query,
|
|
UseGuards,
|
|
ParseIntPipe,
|
|
DefaultValuePipe,
|
|
} from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
|
|
import { AuditService } from '../services/audit.service';
|
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
|
import { RolesGuard } from '../guards/roles.guard';
|
|
import { Roles } from '../decorators/roles.decorator';
|
|
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
|
|
import { AuditLog, AuditAction, AuditStatus } from '@domain/entities/audit-log.entity';
|
|
|
|
class AuditLogResponseDto {
|
|
id: string;
|
|
action: string;
|
|
status: string;
|
|
userId: string;
|
|
userEmail: string;
|
|
organizationId: string;
|
|
resourceType?: string;
|
|
resourceId?: string;
|
|
resourceName?: string;
|
|
metadata?: Record<string, any>;
|
|
ipAddress?: string;
|
|
userAgent?: string;
|
|
errorMessage?: string;
|
|
timestamp: string;
|
|
}
|
|
|
|
class _AuditLogQueryDto {
|
|
userId?: string;
|
|
action?: AuditAction[];
|
|
status?: AuditStatus[];
|
|
resourceType?: string;
|
|
resourceId?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
@ApiTags('Audit Logs')
|
|
@ApiBearerAuth()
|
|
@Controller('audit-logs')
|
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
export class AuditController {
|
|
constructor(private readonly auditService: AuditService) {}
|
|
|
|
/**
|
|
* Get audit logs with filters
|
|
* Only admins and managers can view audit logs
|
|
*/
|
|
@Get()
|
|
@Roles('admin', 'manager')
|
|
@ApiOperation({ summary: 'Get audit logs with filters' })
|
|
@ApiResponse({ status: 200, description: 'Audit logs retrieved successfully' })
|
|
@ApiQuery({ name: 'userId', required: false, description: 'Filter by user ID' })
|
|
@ApiQuery({
|
|
name: 'action',
|
|
required: false,
|
|
description: 'Filter by action (comma-separated)',
|
|
isArray: true,
|
|
})
|
|
@ApiQuery({
|
|
name: 'status',
|
|
required: false,
|
|
description: 'Filter by status (comma-separated)',
|
|
isArray: true,
|
|
})
|
|
@ApiQuery({ name: 'resourceType', required: false, description: 'Filter by resource type' })
|
|
@ApiQuery({ name: 'resourceId', required: false, description: 'Filter by resource ID' })
|
|
@ApiQuery({ name: 'startDate', required: false, description: 'Filter by start date (ISO 8601)' })
|
|
@ApiQuery({ name: 'endDate', required: false, description: 'Filter by end date (ISO 8601)' })
|
|
@ApiQuery({ name: 'page', required: false, description: 'Page number (default: 1)' })
|
|
@ApiQuery({ name: 'limit', required: false, description: 'Items per page (default: 50)' })
|
|
async getAuditLogs(
|
|
@CurrentUser() user: UserPayload,
|
|
@Query('userId') userId?: string,
|
|
@Query('action') action?: string,
|
|
@Query('status') status?: string,
|
|
@Query('resourceType') resourceType?: string,
|
|
@Query('resourceId') resourceId?: string,
|
|
@Query('startDate') startDate?: string,
|
|
@Query('endDate') endDate?: string,
|
|
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page?: number,
|
|
@Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit?: number
|
|
): Promise<{ logs: AuditLogResponseDto[]; total: number; page: number; pageSize: number }> {
|
|
page = page || 1;
|
|
limit = limit || 50;
|
|
const filters: any = {
|
|
organizationId: user.organizationId,
|
|
userId,
|
|
action: action ? action.split(',') : undefined,
|
|
status: status ? status.split(',') : undefined,
|
|
resourceType,
|
|
resourceId,
|
|
startDate: startDate ? new Date(startDate) : undefined,
|
|
endDate: endDate ? new Date(endDate) : undefined,
|
|
offset: (page - 1) * limit,
|
|
limit,
|
|
};
|
|
|
|
const { logs, total } = await this.auditService.getAuditLogs(filters);
|
|
|
|
return {
|
|
logs: logs.map(log => this.mapToDto(log)),
|
|
total,
|
|
page,
|
|
pageSize: limit,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get specific audit log by ID
|
|
*/
|
|
@Get(':id')
|
|
@Roles('admin', 'manager')
|
|
@ApiOperation({ summary: 'Get audit log by ID' })
|
|
@ApiResponse({ status: 200, description: 'Audit log retrieved successfully' })
|
|
@ApiResponse({ status: 404, description: 'Audit log not found' })
|
|
async getAuditLogById(
|
|
@Param('id') id: string,
|
|
@CurrentUser() user: UserPayload
|
|
): Promise<AuditLogResponseDto> {
|
|
const log = await this.auditService.getAuditLogs({
|
|
organizationId: user.organizationId,
|
|
limit: 1,
|
|
});
|
|
|
|
if (!log.logs.length) {
|
|
throw new Error('Audit log not found');
|
|
}
|
|
|
|
return this.mapToDto(log.logs[0]);
|
|
}
|
|
|
|
/**
|
|
* Get audit trail for a specific resource
|
|
*/
|
|
@Get('resource/:type/:id')
|
|
@Roles('admin', 'manager', 'user')
|
|
@ApiOperation({ summary: 'Get audit trail for a specific resource' })
|
|
@ApiResponse({ status: 200, description: 'Audit trail retrieved successfully' })
|
|
async getResourceAuditTrail(
|
|
@Param('type') resourceType: string,
|
|
@Param('id') resourceId: string,
|
|
@CurrentUser() user: UserPayload
|
|
): Promise<AuditLogResponseDto[]> {
|
|
const logs = await this.auditService.getResourceAuditTrail(resourceType, resourceId);
|
|
|
|
// Filter by organization for security
|
|
const filteredLogs = logs.filter(log => log.organizationId === user.organizationId);
|
|
|
|
return filteredLogs.map(log => this.mapToDto(log));
|
|
}
|
|
|
|
/**
|
|
* Get recent activity for current organization
|
|
*/
|
|
@Get('organization/activity')
|
|
@Roles('admin', 'manager')
|
|
@ApiOperation({ summary: 'Get recent organization activity' })
|
|
@ApiResponse({ status: 200, description: 'Organization activity retrieved successfully' })
|
|
@ApiQuery({ name: 'limit', required: false, description: 'Number of recent logs (default: 50)' })
|
|
async getOrganizationActivity(
|
|
@CurrentUser() user: UserPayload,
|
|
@Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit?: number
|
|
): Promise<AuditLogResponseDto[]> {
|
|
limit = limit || 50;
|
|
const logs = await this.auditService.getOrganizationActivity(user.organizationId, limit);
|
|
return logs.map(log => this.mapToDto(log));
|
|
}
|
|
|
|
/**
|
|
* Get user activity history
|
|
*/
|
|
@Get('user/:userId/activity')
|
|
@Roles('admin', 'manager')
|
|
@ApiOperation({ summary: 'Get user activity history' })
|
|
@ApiResponse({ status: 200, description: 'User activity retrieved successfully' })
|
|
@ApiQuery({ name: 'limit', required: false, description: 'Number of recent logs (default: 50)' })
|
|
async getUserActivity(
|
|
@CurrentUser() user: UserPayload,
|
|
@Param('userId') userId: string,
|
|
@Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit?: number
|
|
): Promise<AuditLogResponseDto[]> {
|
|
limit = limit || 50;
|
|
const logs = await this.auditService.getUserActivity(userId, limit);
|
|
|
|
// Filter by organization for security
|
|
const filteredLogs = logs.filter(log => log.organizationId === user.organizationId);
|
|
|
|
return filteredLogs.map(log => this.mapToDto(log));
|
|
}
|
|
|
|
/**
|
|
* Map domain entity to DTO
|
|
*/
|
|
private mapToDto(log: AuditLog): AuditLogResponseDto {
|
|
return {
|
|
id: log.id,
|
|
action: log.action,
|
|
status: log.status,
|
|
userId: log.userId,
|
|
userEmail: log.userEmail,
|
|
organizationId: log.organizationId,
|
|
resourceType: log.resourceType,
|
|
resourceId: log.resourceId,
|
|
resourceName: log.resourceName,
|
|
metadata: log.metadata,
|
|
ipAddress: log.ipAddress,
|
|
userAgent: log.userAgent,
|
|
errorMessage: log.errorMessage,
|
|
timestamp: log.timestamp.toISOString(),
|
|
};
|
|
}
|
|
}
|