feat: security and auditing

This commit is contained in:
2026-02-04 16:48:08 -08:00
parent 11be34cd21
commit 0a654f423a
42 changed files with 2195 additions and 160 deletions
+103
View File
@@ -0,0 +1,103 @@
import type { FastifyRequest } from "fastify";
import { prisma } from "../lib/prisma";
import type { AuditAction, AuditCategory, AuditLogFilters } from "@library/shared-types";
interface AuditLogData {
action: AuditAction;
category: AuditCategory;
userId?: string;
targetUserId?: string;
resourceType?: string;
resourceId?: string;
details?: string;
success?: boolean;
}
export const AuditService = {
async log(data: AuditLogData, request?: FastifyRequest) {
const userAgent = request?.headers["user-agent"] ?? undefined;
return prisma.auditLog.create({
data: {
action: data.action,
category: data.category,
userId: data.userId,
targetUserId: data.targetUserId,
resourceType: data.resourceType,
resourceId: data.resourceId,
details: data.details,
userAgent,
success: data.success ?? true,
},
});
},
async logFromRequest(
request: FastifyRequest,
data: Omit<AuditLogData, "userId">
) {
const userId = (request.user as { id?: string } | undefined)?.id;
return this.log(
{
...data,
userId,
},
request
);
},
async getLogs(filters: AuditLogFilters = {}) {
const { action, category, userId, success, startDate, endDate, page = 1, limit = 50 } = filters;
const where: Record<string, unknown> = {};
if (action) {
where.action = action;
}
if (category) {
where.category = category;
}
if (userId) {
where.userId = userId;
}
if (success !== undefined) {
where.success = success;
}
if (startDate || endDate) {
where.createdAt = {};
if (startDate) {
(where.createdAt as Record<string, Date>).gte = startDate;
}
if (endDate) {
(where.createdAt as Record<string, Date>).lte = endDate;
}
}
const [logs, total] = await Promise.all([
prisma.auditLog.findMany({
where,
orderBy: { createdAt: "desc" },
skip: (page - 1) * limit,
take: limit,
}),
prisma.auditLog.count({ where }),
]);
return {
logs,
total,
page,
limit,
totalPages: Math.ceil(total / limit),
};
},
async getLogsByUser(userId: string, page = 1, limit = 50) {
return this.getLogs({ userId, page, limit });
},
async getSecurityLogs(page = 1, limit = 50) {
return this.getLogs({ category: "SECURITY" as AuditCategory, page, limit });
},
};