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 = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; }; type ConfigUpdate = DeepPartial; 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 } ); } }