Files
livedash-node/app/api/admin/cache/invalidate/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

231 lines
6.2 KiB
TypeScript

/**
* 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 }
);
}
}