forked from Mike/freno-dev
security hardening
This commit is contained in:
133
src/server/api/routers/audit.ts
Normal file
133
src/server/api/routers/audit.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { createTRPCRouter, adminProcedure } from "../utils";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
queryAuditLogs,
|
||||
getUserSecuritySummary,
|
||||
cleanupOldLogs,
|
||||
getFailedLoginAttempts,
|
||||
detectSuspiciousActivity
|
||||
} from "~/server/audit";
|
||||
|
||||
/**
|
||||
* Audit log router - admin-only endpoints for querying security logs
|
||||
*/
|
||||
export const auditRouter = createTRPCRouter({
|
||||
/**
|
||||
* Query audit logs with filters
|
||||
*/
|
||||
getLogs: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.string().optional(),
|
||||
eventType: z.string().optional(),
|
||||
success: z.boolean().optional(),
|
||||
startDate: z.string().optional(),
|
||||
endDate: z.string().optional(),
|
||||
limit: z.number().min(1).max(1000).default(100),
|
||||
offset: z.number().min(0).default(0)
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const logs = await queryAuditLogs({
|
||||
userId: input.userId,
|
||||
eventType: input.eventType as any,
|
||||
success: input.success,
|
||||
startDate: input.startDate,
|
||||
endDate: input.endDate,
|
||||
limit: input.limit,
|
||||
offset: input.offset
|
||||
});
|
||||
|
||||
return {
|
||||
logs,
|
||||
count: logs.length,
|
||||
offset: input.offset,
|
||||
limit: input.limit
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get failed login attempts (last 24 hours by default)
|
||||
*/
|
||||
getFailedLogins: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
hours: z.number().min(1).max(168).default(24),
|
||||
limit: z.number().min(1).max(1000).default(100)
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const attempts = (await getFailedLoginAttempts(
|
||||
input.hours,
|
||||
input.limit
|
||||
)) as Array<any>;
|
||||
|
||||
return {
|
||||
attempts,
|
||||
count: attempts.length,
|
||||
timeWindow: `${input.hours} hours`
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get security summary for a specific user
|
||||
*/
|
||||
getUserSummary: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
days: z.number().min(1).max(90).default(30)
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const summary = await getUserSecuritySummary(input.userId, input.days);
|
||||
|
||||
return {
|
||||
userId: input.userId,
|
||||
summary,
|
||||
timeWindow: `${input.days} days`
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Detect suspicious activity patterns
|
||||
*/
|
||||
getSuspiciousActivity: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
hours: z.number().min(1).max(168).default(24),
|
||||
minFailedAttempts: z.number().min(1).default(5)
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const suspicious = (await detectSuspiciousActivity(
|
||||
input.hours,
|
||||
input.minFailedAttempts
|
||||
)) as Array<any>;
|
||||
|
||||
return {
|
||||
suspicious,
|
||||
count: suspicious.length,
|
||||
timeWindow: `${input.hours} hours`,
|
||||
threshold: input.minFailedAttempts
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Clean up old logs
|
||||
*/
|
||||
cleanupLogs: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
olderThanDays: z.number().min(1).max(365).default(90)
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const deleted = await cleanupOldLogs(input.olderThanDays);
|
||||
|
||||
return {
|
||||
deleted,
|
||||
olderThanDays: input.olderThanDays
|
||||
};
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user