Files
livedash-node/app/api/admin/security-monitoring/route.ts
Kaj Kowalski e1abedb148 feat: implement cache layer, CSP improvements, and database performance optimizations
- Add Redis cache implementation with LRU eviction
- Enhance Content Security Policy with nonce generation
- Optimize database queries with connection pooling
- Add cache invalidation API endpoints
- Improve security monitoring performance
2025-07-13 11:52:49 +02:00

193 lines
5.4 KiB
TypeScript

import { type NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { z } from "zod";
import { authOptions } from "@/lib/auth";
import {
AuditOutcome,
createAuditContext,
securityAuditLogger,
} from "@/lib/securityAuditLogger";
import {
AlertChannel,
type AlertSeverity,
type MonitoringConfig,
securityMonitoring,
} from "@/lib/securityMonitoring";
// Type for partial config updates that allows optional nested properties
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type ConfigUpdate = DeepPartial<MonitoringConfig>;
const metricsQuerySchema = z.object({
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
companyId: z.string().uuid().optional(),
severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]).optional(),
});
const configUpdateSchema = z.object({
thresholds: z
.object({
failedLoginsPerMinute: z.number().min(1).max(100).optional(),
failedLoginsPerHour: z.number().min(1).max(1000).optional(),
rateLimitViolationsPerMinute: z.number().min(1).max(100).optional(),
cspViolationsPerMinute: z.number().min(1).max(100).optional(),
adminActionsPerHour: z.number().min(1).max(100).optional(),
massDataAccessThreshold: z.number().min(10).max(10000).optional(),
suspiciousIPThreshold: z.number().min(1).max(100).optional(),
})
.optional(),
alerting: z
.object({
enabled: z.boolean().optional(),
channels: z.array(z.nativeEnum(AlertChannel)).optional(),
suppressDuplicateMinutes: z.number().min(1).max(1440).optional(),
escalationTimeoutMinutes: z.number().min(5).max(1440).optional(),
})
.optional(),
retention: z
.object({
alertRetentionDays: z.number().min(1).max(3650).optional(),
metricsRetentionDays: z.number().min(1).max(3650).optional(),
})
.optional(),
});
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Only platform admins can access security monitoring
if (!session.user.isPlatformUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const url = new URL(request.url);
const params = Object.fromEntries(url.searchParams.entries());
const query = metricsQuerySchema.parse(params);
const context = await createAuditContext(request, session);
const timeRange = {
start: query.startDate
? new Date(query.startDate)
: new Date(Date.now() - 24 * 60 * 60 * 1000),
end: query.endDate ? new Date(query.endDate) : new Date(),
};
// Get security metrics
const metrics = await securityMonitoring.getSecurityMetrics(
timeRange,
query.companyId
);
// Get active alerts
const alerts = securityMonitoring.getActiveAlerts(
query.severity as AlertSeverity
);
// Get monitoring configuration
const config = securityMonitoring.getConfig();
// Log access to security monitoring
await securityAuditLogger.logPlatformAdmin(
"security_monitoring_access",
AuditOutcome.SUCCESS,
context
);
return NextResponse.json({
metrics,
alerts,
config,
timeRange,
});
} catch (error) {
console.error("Security monitoring API error:", error);
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid query parameters", details: error.issues },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!session.user.isPlatformUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await request.json();
const validatedConfig = configUpdateSchema.parse(body);
const context = await createAuditContext(request, session);
// Build the config update object with proper type safety
const configUpdate: ConfigUpdate = {};
if (validatedConfig.thresholds) {
configUpdate.thresholds = validatedConfig.thresholds;
}
if (validatedConfig.alerting) {
configUpdate.alerting = validatedConfig.alerting;
}
if (validatedConfig.retention) {
configUpdate.retention = validatedConfig.retention;
}
// Update monitoring configuration
securityMonitoring.updateConfig(configUpdate);
// Log configuration change
await securityAuditLogger.logPlatformAdmin(
"security_monitoring_config_update",
AuditOutcome.SUCCESS,
{
...context,
metadata: { configChanges: validatedConfig },
}
);
return NextResponse.json({
success: true,
config: securityMonitoring.getConfig(),
});
} catch (error) {
console.error("Security monitoring config update error:", error);
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid configuration", details: error.issues },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}