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
This commit is contained in:
2025-07-12 04:44:50 +02:00
parent 7a3eabccd9
commit e1abedb148
56 changed files with 6881 additions and 7040 deletions

View File

@ -16,8 +16,9 @@ import {
// GET /api/admin/audit-logs/retention - Get retention statistics and policy status
export async function GET(request: NextRequest) {
const session = await getServerSession(authOptions);
try {
const session = await getServerSession(authOptions);
const ip = extractClientIP(request);
const userAgent = request.headers.get("user-agent") || undefined;
@ -127,8 +128,9 @@ export async function GET(request: NextRequest) {
// POST /api/admin/audit-logs/retention - Execute retention policies manually
export async function POST(request: NextRequest) {
const session = await getServerSession(authOptions);
try {
const session = await getServerSession(authOptions);
const ip = extractClientIP(request);
const userAgent = request.headers.get("user-agent") || undefined;

View File

@ -13,7 +13,7 @@ import {
* Validates user authorization for audit logs access
*/
async function validateAuditLogAccess(
session: { user?: { id: string; companyId: string; role: string } } | null,
session: { user?: { id?: string; companyId?: string; role?: string } } | null,
ip: string,
userAgent?: string
) {
@ -33,17 +33,17 @@ async function validateAuditLogAccess(
return { valid: false, status: 401, error: "Unauthorized" };
}
if (session.user.role !== "ADMIN") {
if (session?.user?.role !== "ADMIN") {
await securityAuditLogger.logAuthorization(
"audit_logs_insufficient_permissions",
AuditOutcome.BLOCKED,
{
userId: session.user.id,
companyId: session.user.companyId,
userId: session?.user?.id,
companyId: session?.user?.companyId,
ipAddress: ip,
userAgent,
metadata: createAuditMetadata({
userRole: session.user.role,
userRole: session?.user?.role,
requiredRole: "ADMIN",
}),
},
@ -121,8 +121,9 @@ function buildAuditLogWhereClause(
}
export async function GET(request: NextRequest) {
const session = await getServerSession(authOptions);
try {
const session = await getServerSession(authOptions);
const ip = extractClientIP(request);
const userAgent = request.headers.get("user-agent") || undefined;
@ -137,11 +138,23 @@ export async function GET(request: NextRequest) {
const url = new URL(request.url);
const filters = parseAuditLogFilters(url);
const { page, limit } = filters;
const {
page,
limit,
eventType,
outcome,
severity,
userId,
startDate,
endDate,
} = filters;
const skip = (page - 1) * limit;
// Build filter conditions
const where = buildAuditLogWhereClause(session.user.companyId, filters);
const where = buildAuditLogWhereClause(
session?.user?.companyId || "",
filters
);
// Get audit logs with pagination
const [auditLogs, totalCount] = await Promise.all([
@ -177,8 +190,8 @@ export async function GET(request: NextRequest) {
"audit_logs_accessed",
AuditOutcome.SUCCESS,
{
userId: session.user.id,
companyId: session.user.companyId,
userId: session?.user?.id,
companyId: session?.user?.companyId,
ipAddress: ip,
userAgent,
metadata: createAuditMetadata({

230
app/api/admin/cache/invalidate/route.ts vendored Normal file
View File

@ -0,0 +1,230 @@
/**
* Cache Invalidation API Endpoint
*
* Allows administrators to manually invalidate cache entries or patterns
* for troubleshooting and cache management.
*/
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { z } from "zod";
import { authOptions } from "../../../../../lib/auth";
import { invalidateCompanyCache } from "../../../../../lib/batchProcessorOptimized";
import { Cache } from "../../../../../lib/cache";
import {
AuditOutcome,
AuditSeverity,
createAuditMetadata,
SecurityEventType,
} from "../../../../../lib/securityAuditLogger";
import { enhancedSecurityLog } from "../../../../../lib/securityMonitoring";
const invalidationSchema = z.object({
type: z.enum(["key", "pattern", "company", "user", "all"]),
value: z.string().optional(),
});
async function validateCacheAccess(
session: { user?: { id?: string; companyId?: string; role?: string } } | null
) {
if (!session?.user) {
await enhancedSecurityLog(
SecurityEventType.AUTHORIZATION,
"cache_invalidation_access_denied",
AuditOutcome.BLOCKED,
{
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/invalidate",
reason: "not_authenticated",
}),
},
AuditSeverity.MEDIUM,
"Unauthenticated access attempt to cache invalidation endpoint"
);
return { valid: false, status: 401, error: "Authentication required" };
}
if (session.user.role !== "ADMIN") {
await enhancedSecurityLog(
SecurityEventType.AUTHORIZATION,
"cache_invalidation_access_denied",
AuditOutcome.BLOCKED,
{
userId: session.user.id,
companyId: session.user.companyId,
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/invalidate",
userRole: session.user.role,
reason: "insufficient_privileges",
}),
},
AuditSeverity.HIGH,
"Non-admin user attempted to access cache invalidation"
);
return { valid: false, status: 403, error: "Admin access required" };
}
return { valid: true };
}
async function performCacheInvalidation(type: string, value?: string) {
let deletedCount = 0;
let operation = "";
switch (type) {
case "key": {
if (!value) {
return {
error: "Key value required for key invalidation",
status: 400,
};
}
const deleted = await Cache.delete(value);
deletedCount = deleted ? 1 : 0;
operation = `key: ${value}`;
break;
}
case "pattern": {
if (!value) {
return {
error: "Pattern value required for pattern invalidation",
status: 400,
};
}
deletedCount = await Cache.invalidatePattern(value);
operation = `pattern: ${value}`;
break;
}
case "company": {
if (!value) {
return {
error: "Company ID required for company invalidation",
status: 400,
};
}
deletedCount = await Cache.invalidateCompany(value);
await invalidateCompanyCache();
operation = `company: ${value}`;
break;
}
case "user": {
if (!value) {
return { error: "User ID required for user invalidation", status: 400 };
}
await Cache.invalidateUser(value);
await Cache.invalidatePattern("user:email:*");
deletedCount = 1;
operation = `user: ${value}`;
break;
}
case "all": {
await Promise.all([
Cache.invalidatePattern("user:*"),
Cache.invalidatePattern("company:*"),
Cache.invalidatePattern("session:*"),
Cache.invalidatePattern("*"),
invalidateCompanyCache(),
]);
deletedCount = 1;
operation = "all caches";
break;
}
default:
return { error: "Invalid invalidation type", status: 400 };
}
return { success: true, deletedCount, operation };
}
export async function POST(request: Request) {
try {
const session = await getServerSession(authOptions);
const authResult = await validateCacheAccess(session);
if (!authResult.valid) {
return NextResponse.json(
{ success: false, error: authResult.error },
{ status: authResult.status }
);
}
const body = await request.json();
const validation = invalidationSchema.safeParse(body);
if (!validation.success) {
return NextResponse.json(
{
success: false,
error: "Invalid request format",
details: validation.error.issues,
},
{ status: 400 }
);
}
const { type, value } = validation.data;
const result = await performCacheInvalidation(type, value);
if (!result.success) {
return NextResponse.json(
{ success: false, error: result.error },
{ status: result.status }
);
}
const response = {
success: true,
data: {
type,
value,
deletedCount: result.deletedCount,
operation: result.operation,
timestamp: new Date().toISOString(),
},
};
await enhancedSecurityLog(
SecurityEventType.PLATFORM_ADMIN,
"cache_invalidation_executed",
AuditOutcome.SUCCESS,
{
userId: session?.user?.id,
companyId: session?.user?.companyId,
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/invalidate",
invalidationType: type,
invalidationValue: value,
deletedCount: result.deletedCount,
}),
},
AuditSeverity.MEDIUM,
`Cache invalidation executed: ${result.operation}`
);
return NextResponse.json(response);
} catch (error) {
console.error("[Cache Invalidation API] Error:", error);
await enhancedSecurityLog(
SecurityEventType.API_SECURITY,
"cache_invalidation_error",
AuditOutcome.FAILURE,
{
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/invalidate",
error: error instanceof Error ? error.message : "Unknown error",
}),
},
AuditSeverity.HIGH,
"Cache invalidation API encountered an error"
);
return NextResponse.json(
{
success: false,
error: "Internal server error",
},
{ status: 500 }
);
}
}

157
app/api/admin/cache/stats/route.ts vendored Normal file
View File

@ -0,0 +1,157 @@
/**
* Cache Statistics API Endpoint
*
* Provides comprehensive cache performance metrics and health status
* for monitoring Redis + in-memory cache performance.
*/
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "../../../../../lib/auth";
import { Cache } from "../../../../../lib/cache";
import {
AuditOutcome,
AuditSeverity,
createAuditMetadata,
SecurityEventType,
} from "../../../../../lib/securityAuditLogger";
import { enhancedSecurityLog } from "../../../../../lib/securityMonitoring";
export async function GET() {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
await enhancedSecurityLog(
SecurityEventType.AUTHORIZATION,
"cache_stats_access_denied",
AuditOutcome.BLOCKED,
{
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/stats",
reason: "not_authenticated",
}),
},
AuditSeverity.MEDIUM,
"Unauthenticated access attempt to cache stats endpoint"
);
return NextResponse.json(
{ success: false, error: "Authentication required" },
{ status: 401 }
);
}
if (session.user.role !== "ADMIN") {
await enhancedSecurityLog(
SecurityEventType.AUTHORIZATION,
"cache_stats_access_denied",
AuditOutcome.BLOCKED,
{
userId: session.user.id,
companyId: session.user.companyId,
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/stats",
userRole: session.user.role,
reason: "insufficient_privileges",
}),
},
AuditSeverity.HIGH,
"Non-admin user attempted to access cache stats"
);
return NextResponse.json(
{ success: false, error: "Admin access required" },
{ status: 403 }
);
}
// Get cache statistics and health information
const [stats, healthCheck] = await Promise.all([
Cache.getStats(),
Cache.healthCheck(),
]);
const response = {
success: true,
data: {
performance: {
hits: stats.hits,
misses: stats.misses,
sets: stats.sets,
deletes: stats.deletes,
errors: stats.errors,
hitRate: Number((stats.hitRate * 100).toFixed(2)), // Convert to percentage
redisHits: stats.redisHits,
memoryHits: stats.memoryHits,
},
health: {
redis: {
connected: healthCheck.redis.connected,
latency: healthCheck.redis.latency,
error: healthCheck.redis.error,
},
memory: {
available: healthCheck.memory.available,
size: healthCheck.memory.size,
valid: healthCheck.memory.valid,
expired: healthCheck.memory.expired,
},
overall: {
available: healthCheck.overall.available,
fallbackMode: healthCheck.overall.fallbackMode,
},
},
configuration: {
redisAvailable: stats.redisAvailable,
fallbackActive: !stats.redisAvailable,
},
timestamp: new Date().toISOString(),
},
};
// Log successful access
await enhancedSecurityLog(
SecurityEventType.PLATFORM_ADMIN,
"cache_stats_accessed",
AuditOutcome.SUCCESS,
{
userId: session.user.id,
companyId: session.user.companyId,
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/stats",
hitRate: response.data.performance.hitRate,
redisConnected: response.data.health.redis.connected,
}),
},
AuditSeverity.INFO,
"Cache statistics accessed by admin"
);
return NextResponse.json(response);
} catch (error) {
console.error("[Cache Stats API] Error:", error);
await enhancedSecurityLog(
SecurityEventType.API_SECURITY,
"cache_stats_error",
AuditOutcome.FAILURE,
{
metadata: createAuditMetadata({
endpoint: "/api/admin/cache/stats",
error: error instanceof Error ? error.message : "Unknown error",
}),
},
AuditSeverity.HIGH,
"Cache stats API encountered an error"
);
return NextResponse.json(
{
success: false,
error: "Internal server error",
},
{ status: 500 }
);
}
}

View File

@ -66,11 +66,12 @@ export async function GET(request: NextRequest) {
await securityAuditLogger.logPlatformAdmin(
"security_alerts_access",
AuditOutcome.SUCCESS,
context,
undefined,
{
alertCount: alerts.length,
filters: query,
...context,
metadata: {
alertCount: alerts.length,
filters: query,
},
}
);
@ -85,7 +86,7 @@ export async function GET(request: NextRequest) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid query parameters", details: error.errors },
{ error: "Invalid query parameters", details: error.issues },
{ status: 400 }
);
}
@ -101,7 +102,7 @@ export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user || !session.user.isPlatformUser) {
if (!session?.user || !session.user.isPlatformUser || !session.user.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
@ -123,9 +124,10 @@ export async function POST(request: NextRequest) {
await securityAuditLogger.logPlatformAdmin(
"security_alert_acknowledged",
AuditOutcome.SUCCESS,
context,
undefined,
{ alertId }
{
...context,
metadata: { alertId },
}
);
return NextResponse.json({ success: true });
@ -137,7 +139,7 @@ export async function POST(request: NextRequest) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid request", details: error.errors },
{ error: "Invalid request", details: error.issues },
{ status: 400 }
);
}

View File

@ -55,13 +55,14 @@ export async function GET(request: NextRequest) {
await securityAuditLogger.logPlatformAdmin(
"security_data_export",
AuditOutcome.SUCCESS,
context,
undefined,
{
exportType: query.type,
format: query.format,
timeRange,
dataSize: data.length,
...context,
metadata: {
exportType: query.type,
format: query.format,
timeRange,
dataSize: data.length,
},
}
);
@ -77,7 +78,7 @@ export async function GET(request: NextRequest) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid query parameters", details: error.errors },
{ error: "Invalid query parameters", details: error.issues },
{ status: 400 }
);
}

View File

@ -8,10 +8,19 @@ import {
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(),
@ -34,9 +43,7 @@ const configUpdateSchema = z.object({
alerting: z
.object({
enabled: z.boolean().optional(),
channels: z
.array(z.enum(["EMAIL", "WEBHOOK", "SLACK", "DISCORD", "PAGERDUTY"]))
.optional(),
channels: z.array(z.nativeEnum(AlertChannel)).optional(),
suppressDuplicateMinutes: z.number().min(1).max(1440).optional(),
escalationTimeoutMinutes: z.number().min(5).max(1440).optional(),
})
@ -107,7 +114,7 @@ export async function GET(request: NextRequest) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid query parameters", details: error.errors },
{ error: "Invalid query parameters", details: error.issues },
{ status: 400 }
);
}
@ -132,19 +139,35 @@ export async function POST(request: NextRequest) {
}
const body = await request.json();
const config = configUpdateSchema.parse(body);
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(config);
securityMonitoring.updateConfig(configUpdate);
// Log configuration change
await securityAuditLogger.logPlatformAdmin(
"security_monitoring_config_update",
AuditOutcome.SUCCESS,
context,
undefined,
{ configChanges: config }
{
...context,
metadata: { configChanges: validatedConfig },
}
);
return NextResponse.json({
@ -156,7 +179,7 @@ export async function POST(request: NextRequest) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid configuration", details: error.errors },
{ error: "Invalid configuration", details: error.issues },
{ status: 400 }
);
}

View File

@ -11,10 +11,11 @@ import {
type AlertType,
type SecurityMetrics,
securityMonitoring,
type ThreatLevel,
} from "@/lib/securityMonitoring";
const threatAnalysisSchema = z.object({
ipAddress: z.string().ip().optional(),
ipAddress: z.string().optional(),
userId: z.string().uuid().optional(),
timeRange: z
.object({
@ -39,9 +40,10 @@ export async function POST(request: NextRequest) {
interface ThreatAnalysisResults {
ipThreatAnalysis?: {
ipAddress: string;
threatLevel: number;
threatLevel: ThreatLevel;
isBlacklisted: boolean;
riskFactors: string[];
recommendations: string[];
};
timeRangeAnalysis?: {
timeRange: { start: Date; end: Date };
@ -111,11 +113,12 @@ export async function POST(request: NextRequest) {
await securityAuditLogger.logPlatformAdmin(
"threat_analysis_performed",
AuditOutcome.SUCCESS,
context,
undefined,
{
analysisType: Object.keys(analysis),
threatLevel: results.overallThreatLandscape?.currentThreatLevel,
...context,
metadata: {
analysisType: Object.keys(analysis),
threatLevel: results.overallThreatLandscape?.currentThreatLevel,
},
}
);
@ -125,7 +128,7 @@ export async function POST(request: NextRequest) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid request", details: error.errors },
{ error: "Invalid request", details: error.issues },
{ status: 400 }
);
}