Assist_Design/apps/bff/src/common/audit/audit.service.ts

134 lines
3.4 KiB
TypeScript
Raw Normal View History

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
// Define audit actions to match Prisma schema
export enum AuditAction {
LOGIN_SUCCESS = 'LOGIN_SUCCESS',
LOGIN_FAILED = 'LOGIN_FAILED',
LOGOUT = 'LOGOUT',
SIGNUP = 'SIGNUP',
PASSWORD_RESET = 'PASSWORD_RESET',
PASSWORD_CHANGE = 'PASSWORD_CHANGE',
ACCOUNT_LOCKED = 'ACCOUNT_LOCKED',
ACCOUNT_UNLOCKED = 'ACCOUNT_UNLOCKED',
PROFILE_UPDATE = 'PROFILE_UPDATE',
MFA_ENABLED = 'MFA_ENABLED',
MFA_DISABLED = 'MFA_DISABLED',
API_ACCESS = 'API_ACCESS'
}
export interface AuditLogData {
userId?: string;
action: AuditAction;
resource?: string;
details?: any;
ipAddress?: string;
userAgent?: string;
success?: boolean;
error?: string;
}
@Injectable()
export class AuditService {
constructor(private readonly prisma: PrismaService) {}
// Expose prisma for admin operations
get prismaClient() {
return this.prisma;
}
async log(data: AuditLogData): Promise<void> {
try {
await this.prisma.auditLog.create({
data: {
userId: data.userId,
action: data.action,
resource: data.resource,
details: data.details ? JSON.parse(JSON.stringify(data.details)) : null,
ipAddress: data.ipAddress,
userAgent: data.userAgent,
success: data.success ?? true,
error: data.error,
},
});
} catch (error) {
// Don't fail the original operation if audit logging fails
// Use a simple console.error here since we can't use this.logger (circular dependency risk)
console.error('Failed to create audit log:', error);
}
}
async logAuthEvent(
action: AuditAction,
userId?: string,
details?: any,
request?: any,
success: boolean = true,
error?: string
): Promise<void> {
const ipAddress = this.extractIpAddress(request);
const userAgent = request?.headers?.['user-agent'];
await this.log({
userId,
action,
resource: 'auth',
details,
ipAddress,
userAgent,
success,
error,
});
}
private extractIpAddress(request?: any): string | undefined {
if (!request) return undefined;
return (
request.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
request.headers['x-real-ip'] ||
request.connection?.remoteAddress ||
request.socket?.remoteAddress ||
request.ip
);
}
// Cleanup old audit logs (run as a scheduled job)
async cleanupOldLogs(daysToKeep: number = 90): Promise<number> {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const result = await this.prisma.auditLog.deleteMany({
where: {
createdAt: {
lt: cutoffDate,
},
},
});
// Log cleanup result - use console.log for maintenance operations
console.log(`Cleaned up ${result.count} audit logs older than ${daysToKeep} days`);
return result.count;
}
// Get user's recent auth events
async getUserAuthHistory(userId: string, limit: number = 10) {
return this.prisma.auditLog.findMany({
where: {
userId,
resource: 'auth',
},
orderBy: {
createdAt: 'desc',
},
take: limit,
select: {
action: true,
success: true,
ipAddress: true,
createdAt: true,
},
});
}
}