refactor: fix biome linting issues and update project documentation

- Fix 36+ biome linting issues reducing errors/warnings from 227 to 191
- Replace explicit 'any' types with proper TypeScript interfaces
- Fix React hooks dependencies and useCallback patterns
- Resolve unused variables and parameter assignment issues
- Improve accessibility with proper label associations
- Add comprehensive API documentation for admin and security features
- Update README.md with accurate PostgreSQL setup and current tech stack
- Create complete documentation for audit logging, CSP monitoring, and batch processing
- Fix outdated project information and missing developer workflows
This commit is contained in:
2025-07-11 21:50:53 +02:00
committed by Kaj Kowalski
parent 3e9e75e854
commit 1eea2cc3e4
121 changed files with 28687 additions and 4895 deletions

View File

@ -0,0 +1,142 @@
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 {
type AlertSeverity,
securityMonitoring,
} from "@/lib/securityMonitoring";
const alertQuerySchema = z.object({
severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]).optional(),
acknowledged: z.enum(["true", "false"]).optional(),
limit: z
.string()
.transform((val) => Number.parseInt(val, 10))
.optional(),
offset: z
.string()
.transform((val) => Number.parseInt(val, 10))
.optional(),
});
const acknowledgeAlertSchema = z.object({
alertId: z.string().uuid(),
action: z.literal("acknowledge"),
});
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !session.user.isPlatformUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const url = new URL(request.url);
const params = Object.fromEntries(url.searchParams.entries());
const query = alertQuerySchema.parse(params);
const context = await createAuditContext(request, session);
// Get alerts based on filters
const alerts = securityMonitoring.getActiveAlerts(
query.severity as AlertSeverity
);
// Apply pagination
const limit = query.limit || 50;
const offset = query.offset || 0;
const paginatedAlerts = alerts.slice(offset, offset + limit);
// Log alert access
await securityAuditLogger.logPlatformAdmin(
"security_alerts_access",
AuditOutcome.SUCCESS,
context,
undefined,
{
alertCount: alerts.length,
filters: query,
}
);
return NextResponse.json({
alerts: paginatedAlerts,
total: alerts.length,
limit,
offset,
});
} catch (error) {
console.error("Security alerts API error:", error);
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid query parameters", details: error.errors },
{ 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 || !session.user.isPlatformUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { alertId, action } = acknowledgeAlertSchema.parse(body);
const context = await createAuditContext(request, session);
if (action === "acknowledge") {
const success = await securityMonitoring.acknowledgeAlert(
alertId,
session.user.id
);
if (!success) {
return NextResponse.json({ error: "Alert not found" }, { status: 404 });
}
// Log alert acknowledgment
await securityAuditLogger.logPlatformAdmin(
"security_alert_acknowledged",
AuditOutcome.SUCCESS,
context,
undefined,
{ alertId }
);
return NextResponse.json({ success: true });
}
return NextResponse.json({ error: "Invalid action" }, { status: 400 });
} catch (error) {
console.error("Security alert action error:", error);
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid request", details: error.errors },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}

View File

@ -0,0 +1,90 @@
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 { securityMonitoring } from "@/lib/securityMonitoring";
const exportQuerySchema = z.object({
format: z.enum(["json", "csv"]).default("json"),
startDate: z.string().datetime(),
endDate: z.string().datetime(),
type: z.enum(["alerts", "metrics"]).default("alerts"),
});
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !session.user.isPlatformUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const url = new URL(request.url);
const params = Object.fromEntries(url.searchParams.entries());
const query = exportQuerySchema.parse(params);
const context = await createAuditContext(request, session);
const timeRange = {
start: new Date(query.startDate),
end: new Date(query.endDate),
};
let data: string;
let filename: string;
let contentType: string;
if (query.type === "alerts") {
data = securityMonitoring.exportSecurityData(query.format, timeRange);
filename = `security-alerts-${query.startDate.split("T")[0]}-to-${query.endDate.split("T")[0]}.${query.format}`;
contentType = query.format === "csv" ? "text/csv" : "application/json";
} else {
// Export metrics
const metrics = await securityMonitoring.getSecurityMetrics(timeRange);
data = JSON.stringify(metrics, null, 2);
filename = `security-metrics-${query.startDate.split("T")[0]}-to-${query.endDate.split("T")[0]}.json`;
contentType = "application/json";
}
// Log data export
await securityAuditLogger.logPlatformAdmin(
"security_data_export",
AuditOutcome.SUCCESS,
context,
undefined,
{
exportType: query.type,
format: query.format,
timeRange,
dataSize: data.length,
}
);
const headers = new Headers({
"Content-Type": contentType,
"Content-Disposition": `attachment; filename="${filename}"`,
"Content-Length": data.length.toString(),
});
return new NextResponse(data, { headers });
} catch (error) {
console.error("Security data export error:", error);
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid query parameters", details: error.errors },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}

View File

@ -0,0 +1,169 @@
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 {
type AlertSeverity,
securityMonitoring,
} from "@/lib/securityMonitoring";
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.enum(["EMAIL", "WEBHOOK", "SLACK", "DISCORD", "PAGERDUTY"]))
.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.errors },
{ 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 config = configUpdateSchema.parse(body);
const context = await createAuditContext(request, session);
// Update monitoring configuration
securityMonitoring.updateConfig(config);
// Log configuration change
await securityAuditLogger.logPlatformAdmin(
"security_monitoring_config_update",
AuditOutcome.SUCCESS,
context,
undefined,
{ configChanges: config }
);
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.errors },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}

View File

@ -0,0 +1,191 @@
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 { securityMonitoring, type SecurityMetrics, type AlertType } from "@/lib/securityMonitoring";
const threatAnalysisSchema = z.object({
ipAddress: z.string().ip().optional(),
userId: z.string().uuid().optional(),
timeRange: z
.object({
start: z.string().datetime(),
end: z.string().datetime(),
})
.optional(),
});
export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !session.user.isPlatformUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const analysis = threatAnalysisSchema.parse(body);
const context = await createAuditContext(request, session);
interface ThreatAnalysisResults {
ipThreatAnalysis?: {
ipAddress: string;
threatLevel: number;
isBlacklisted: boolean;
riskFactors: string[];
};
timeRangeAnalysis?: {
timeRange: { start: Date; end: Date };
securityScore: number;
threatLevel: string;
topThreats: Array<{ type: AlertType; count: number }>;
geoDistribution: Record<string, number>;
riskUsers: Array<{ userId: string; email: string; riskScore: number }>;
};
overallThreatLandscape?: {
currentThreatLevel: string;
securityScore: number;
activeAlerts: number;
criticalEvents: number;
recommendations: string[];
};
}
const results: ThreatAnalysisResults = {};
// IP threat analysis
if (analysis.ipAddress) {
const ipThreat = await securityMonitoring.calculateIPThreatLevel(
analysis.ipAddress
);
results.ipThreatAnalysis = {
ipAddress: analysis.ipAddress,
...ipThreat,
};
}
// Time-based analysis
if (analysis.timeRange) {
const timeRange = {
start: new Date(analysis.timeRange.start),
end: new Date(analysis.timeRange.end),
};
const metrics = await securityMonitoring.getSecurityMetrics(timeRange);
results.timeRangeAnalysis = {
timeRange,
securityScore: metrics.securityScore,
threatLevel: metrics.threatLevel,
topThreats: metrics.topThreats,
geoDistribution: metrics.geoDistribution,
riskUsers: metrics.userRiskScores.slice(0, 5),
};
}
// General threat landscape
const defaultTimeRange = {
start: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
end: new Date(),
};
const overallMetrics =
await securityMonitoring.getSecurityMetrics(defaultTimeRange);
results.overallThreatLandscape = {
currentThreatLevel: overallMetrics.threatLevel,
securityScore: overallMetrics.securityScore,
activeAlerts: overallMetrics.activeAlerts,
criticalEvents: overallMetrics.criticalEvents,
recommendations: generateThreatRecommendations(overallMetrics),
};
// Log threat analysis request
await securityAuditLogger.logPlatformAdmin(
"threat_analysis_performed",
AuditOutcome.SUCCESS,
context,
undefined,
{
analysisType: Object.keys(analysis),
threatLevel: results.overallThreatLandscape?.currentThreatLevel,
}
);
return NextResponse.json(results);
} catch (error) {
console.error("Threat analysis error:", error);
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid request", details: error.errors },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
function generateThreatRecommendations(metrics: SecurityMetrics): string[] {
const recommendations: string[] = [];
if (metrics.securityScore < 70) {
recommendations.push(
"Security score is below acceptable threshold - immediate action required"
);
}
if (metrics.activeAlerts > 5) {
recommendations.push(
"High number of active alerts - prioritize alert resolution"
);
}
if (metrics.criticalEvents > 0) {
recommendations.push(
"Critical security events detected - investigate immediately"
);
}
const highRiskUsers = metrics.userRiskScores.filter(
(user) => user.riskScore > 50
);
if (highRiskUsers.length > 0) {
recommendations.push(
`${highRiskUsers.length} users have elevated risk scores - review accounts`
);
}
// Check for geographic anomalies
const countries = Object.keys(metrics.geoDistribution);
if (countries.length > 10) {
recommendations.push(
"High geographic diversity detected - review for suspicious activity"
);
}
// Check for common attack patterns
const bruteForceAlerts = metrics.topThreats.filter(
(threat) => threat.type === "BRUTE_FORCE_ATTACK"
);
if (bruteForceAlerts.length > 0) {
recommendations.push(
"Brute force attacks detected - strengthen authentication controls"
);
}
if (recommendations.length === 0) {
recommendations.push(
"Security posture appears stable - continue monitoring"
);
}
return recommendations;
}