mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 18:32:10 +01:00
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:
545
tests/integration/security-monitoring-api.test.ts
Normal file
545
tests/integration/security-monitoring-api.test.ts
Normal file
@ -0,0 +1,545 @@
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { GET, POST } from "@/app/api/admin/security-monitoring/route";
|
||||
import {
|
||||
GET as AlertsGET,
|
||||
POST as AlertsPOST,
|
||||
} from "@/app/api/admin/security-monitoring/alerts/route";
|
||||
import { GET as ExportGET } from "@/app/api/admin/security-monitoring/export/route";
|
||||
import { POST as ThreatAnalysisPOST } from "@/app/api/admin/security-monitoring/threat-analysis/route";
|
||||
|
||||
// Mock next-auth
|
||||
vi.mock("next-auth", () => ({
|
||||
getServerSession: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock security monitoring
|
||||
vi.mock("@/lib/securityMonitoring", () => ({
|
||||
securityMonitoring: {
|
||||
getSecurityMetrics: vi.fn(),
|
||||
getActiveAlerts: vi.fn(),
|
||||
getConfig: vi.fn(),
|
||||
updateConfig: vi.fn(),
|
||||
acknowledgeAlert: vi.fn(),
|
||||
exportSecurityData: vi.fn(),
|
||||
calculateIPThreatLevel: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock security audit logger
|
||||
vi.mock("@/lib/securityAuditLogger", () => ({
|
||||
createAuditContext: vi.fn(),
|
||||
securityAuditLogger: {
|
||||
logPlatformAdmin: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const { securityMonitoring } = await import("@/lib/securityMonitoring");
|
||||
const { createAuditContext, securityAuditLogger } = await import(
|
||||
"@/lib/securityAuditLogger"
|
||||
);
|
||||
|
||||
const mockPlatformUserSession = {
|
||||
user: {
|
||||
id: "platform-user-1",
|
||||
email: "admin@platform.com",
|
||||
isPlatformUser: true,
|
||||
platformRole: "ADMIN",
|
||||
},
|
||||
};
|
||||
|
||||
const mockRegularUserSession = {
|
||||
user: {
|
||||
id: "user-1",
|
||||
email: "user@company.com",
|
||||
isPlatformUser: false,
|
||||
companyId: "company-1",
|
||||
},
|
||||
};
|
||||
|
||||
describe("Security Monitoring API", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(createAuditContext).mockResolvedValue({
|
||||
userId: "platform-user-1",
|
||||
requestId: "test-request-123",
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/admin/security-monitoring", () => {
|
||||
it("should return security metrics for platform admin", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const mockMetrics = {
|
||||
totalEvents: 100,
|
||||
criticalEvents: 5,
|
||||
activeAlerts: 3,
|
||||
resolvedAlerts: 10,
|
||||
securityScore: 85,
|
||||
threatLevel: "MODERATE",
|
||||
eventsByType: { AUTHENTICATION: 50, RATE_LIMITING: 30 },
|
||||
alertsByType: { BRUTE_FORCE_ATTACK: 2, RATE_LIMIT_BREACH: 1 },
|
||||
topThreats: [{ type: "BRUTE_FORCE_ATTACK", count: 2 }],
|
||||
geoDistribution: { USA: 60, GBR: 40 },
|
||||
timeDistribution: Array.from({ length: 24 }, (_, i) => ({
|
||||
hour: i,
|
||||
count: Math.floor(Math.random() * 10),
|
||||
})),
|
||||
userRiskScores: [
|
||||
{ userId: "user-1", email: "test@test.com", riskScore: 75 },
|
||||
],
|
||||
};
|
||||
|
||||
const mockConfig = {
|
||||
thresholds: {
|
||||
failedLoginsPerMinute: 5,
|
||||
failedLoginsPerHour: 20,
|
||||
rateLimitViolationsPerMinute: 10,
|
||||
cspViolationsPerMinute: 15,
|
||||
adminActionsPerHour: 25,
|
||||
massDataAccessThreshold: 100,
|
||||
suspiciousIPThreshold: 10,
|
||||
},
|
||||
alerting: {
|
||||
enabled: true,
|
||||
channels: ["EMAIL"],
|
||||
suppressDuplicateMinutes: 10,
|
||||
escalationTimeoutMinutes: 60,
|
||||
},
|
||||
retention: {
|
||||
alertRetentionDays: 90,
|
||||
metricsRetentionDays: 365,
|
||||
},
|
||||
};
|
||||
|
||||
const mockAlerts = [
|
||||
{
|
||||
id: "alert-1",
|
||||
timestamp: new Date(),
|
||||
severity: "HIGH",
|
||||
type: "BRUTE_FORCE_ATTACK",
|
||||
title: "Brute Force Attack Detected",
|
||||
description: "Multiple failed login attempts",
|
||||
eventType: "AUTHENTICATION",
|
||||
context: { ipAddress: "192.168.1.100" },
|
||||
metadata: {},
|
||||
acknowledged: false,
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(securityMonitoring.getSecurityMetrics).mockResolvedValue(
|
||||
mockMetrics
|
||||
);
|
||||
vi.mocked(securityMonitoring.getConfig).mockReturnValue(mockConfig);
|
||||
vi.mocked(securityMonitoring.getActiveAlerts).mockReturnValue(mockAlerts);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring"
|
||||
);
|
||||
const response = await GET(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data).toMatchObject({
|
||||
metrics: mockMetrics,
|
||||
alerts: mockAlerts,
|
||||
config: mockConfig,
|
||||
timeRange: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(securityAuditLogger.logPlatformAdmin).toHaveBeenCalledWith(
|
||||
"security_monitoring_access",
|
||||
"SUCCESS",
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("should reject non-platform users", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockRegularUserSession);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring"
|
||||
);
|
||||
const response = await GET(request);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe("Forbidden");
|
||||
});
|
||||
|
||||
it("should reject unauthenticated requests", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(null);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring"
|
||||
);
|
||||
const response = await GET(request);
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe("Unauthorized");
|
||||
});
|
||||
|
||||
it("should handle query parameters correctly", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
vi.mocked(securityMonitoring.getSecurityMetrics).mockResolvedValue(
|
||||
{} as any
|
||||
);
|
||||
vi.mocked(securityMonitoring.getConfig).mockReturnValue({} as any);
|
||||
vi.mocked(securityMonitoring.getActiveAlerts).mockReturnValue([]);
|
||||
|
||||
const url =
|
||||
"http://localhost:3000/api/admin/security-monitoring?startDate=2024-01-01T00:00:00Z&endDate=2024-01-02T00:00:00Z&companyId=company-1&severity=HIGH";
|
||||
const request = new NextRequest(url);
|
||||
const response = await GET(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(securityMonitoring.getSecurityMetrics).toHaveBeenCalledWith(
|
||||
{
|
||||
start: new Date("2024-01-01T00:00:00Z"),
|
||||
end: new Date("2024-01-02T00:00:00Z"),
|
||||
},
|
||||
"company-1"
|
||||
);
|
||||
expect(securityMonitoring.getActiveAlerts).toHaveBeenCalledWith("HIGH");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/admin/security-monitoring", () => {
|
||||
it("should update security configuration", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const newConfig = {
|
||||
thresholds: {
|
||||
failedLoginsPerMinute: 3,
|
||||
failedLoginsPerHour: 15,
|
||||
},
|
||||
alerting: {
|
||||
enabled: false,
|
||||
channels: ["EMAIL", "SLACK"],
|
||||
},
|
||||
};
|
||||
|
||||
const updatedConfig = {
|
||||
thresholds: {
|
||||
failedLoginsPerMinute: 3,
|
||||
failedLoginsPerHour: 15,
|
||||
rateLimitViolationsPerMinute: 10,
|
||||
cspViolationsPerMinute: 15,
|
||||
adminActionsPerHour: 25,
|
||||
massDataAccessThreshold: 100,
|
||||
suspiciousIPThreshold: 10,
|
||||
},
|
||||
alerting: {
|
||||
enabled: false,
|
||||
channels: ["EMAIL", "SLACK"],
|
||||
suppressDuplicateMinutes: 10,
|
||||
escalationTimeoutMinutes: 60,
|
||||
},
|
||||
retention: {
|
||||
alertRetentionDays: 90,
|
||||
metricsRetentionDays: 365,
|
||||
},
|
||||
};
|
||||
|
||||
vi.mocked(securityMonitoring.getConfig).mockReturnValue(updatedConfig);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(newConfig),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
const response = await POST(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.config).toEqual(updatedConfig);
|
||||
|
||||
expect(securityMonitoring.updateConfig).toHaveBeenCalledWith(newConfig);
|
||||
expect(securityAuditLogger.logPlatformAdmin).toHaveBeenCalledWith(
|
||||
"security_monitoring_config_update",
|
||||
"SUCCESS",
|
||||
expect.any(Object),
|
||||
undefined,
|
||||
{ configChanges: newConfig }
|
||||
);
|
||||
});
|
||||
|
||||
it("should validate configuration input", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const invalidConfig = {
|
||||
thresholds: {
|
||||
failedLoginsPerMinute: -1, // Invalid: negative number
|
||||
failedLoginsPerHour: 2000, // Invalid: too large
|
||||
},
|
||||
};
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(invalidConfig),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
const response = await POST(request);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe("Invalid configuration");
|
||||
expect(data.details).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/admin/security-monitoring/alerts", () => {
|
||||
it("should return filtered alerts", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const mockAlerts = [
|
||||
{
|
||||
id: "alert-1",
|
||||
timestamp: new Date().toISOString(),
|
||||
severity: "HIGH",
|
||||
type: "BRUTE_FORCE_ATTACK",
|
||||
title: "Brute Force Attack",
|
||||
description: "Multiple failed logins",
|
||||
eventType: "AUTHENTICATION",
|
||||
context: {},
|
||||
metadata: {},
|
||||
acknowledged: false,
|
||||
},
|
||||
{
|
||||
id: "alert-2",
|
||||
timestamp: new Date().toISOString(),
|
||||
severity: "MEDIUM",
|
||||
type: "RATE_LIMIT_BREACH",
|
||||
title: "Rate Limit Exceeded",
|
||||
description: "Too many requests",
|
||||
eventType: "RATE_LIMITING",
|
||||
context: {},
|
||||
metadata: {},
|
||||
acknowledged: false,
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(securityMonitoring.getActiveAlerts).mockReturnValue(mockAlerts);
|
||||
|
||||
const url =
|
||||
"http://localhost:3000/api/admin/security-monitoring/alerts?severity=HIGH&limit=10&offset=0";
|
||||
const request = new NextRequest(url);
|
||||
const response = await AlertsGET(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.alerts).toEqual(mockAlerts);
|
||||
expect(data.total).toBe(2);
|
||||
expect(data.limit).toBe(10);
|
||||
expect(data.offset).toBe(0);
|
||||
|
||||
expect(securityMonitoring.getActiveAlerts).toHaveBeenCalledWith("HIGH");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/admin/security-monitoring/alerts", () => {
|
||||
it("should acknowledge alert", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
vi.mocked(securityMonitoring.acknowledgeAlert).mockResolvedValue(true);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring/alerts",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
alertId: "alert-123",
|
||||
action: "acknowledge",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
const response = await AlertsPOST(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.success).toBe(true);
|
||||
|
||||
expect(securityMonitoring.acknowledgeAlert).toHaveBeenCalledWith(
|
||||
"alert-123",
|
||||
"platform-user-1"
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle non-existent alert", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
vi.mocked(securityMonitoring.acknowledgeAlert).mockResolvedValue(false);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring/alerts",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
alertId: "non-existent",
|
||||
action: "acknowledge",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
const response = await AlertsPOST(request);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe("Alert not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/admin/security-monitoring/export", () => {
|
||||
it("should export security data as JSON", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const mockExportData = JSON.stringify([
|
||||
{
|
||||
id: "alert-1",
|
||||
timestamp: "2024-01-01T00:00:00.000Z",
|
||||
severity: "HIGH",
|
||||
type: "BRUTE_FORCE_ATTACK",
|
||||
title: "Test Alert",
|
||||
description: "Test Description",
|
||||
},
|
||||
]);
|
||||
|
||||
vi.mocked(securityMonitoring.exportSecurityData).mockReturnValue(
|
||||
mockExportData
|
||||
);
|
||||
|
||||
const url =
|
||||
"http://localhost:3000/api/admin/security-monitoring/export?format=json&type=alerts&startDate=2024-01-01T00:00:00Z&endDate=2024-01-02T00:00:00Z";
|
||||
const request = new NextRequest(url);
|
||||
const response = await ExportGET(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("Content-Type")).toBe("application/json");
|
||||
expect(response.headers.get("Content-Disposition")).toContain(
|
||||
"attachment"
|
||||
);
|
||||
|
||||
const data = await response.text();
|
||||
expect(data).toBe(mockExportData);
|
||||
});
|
||||
|
||||
it("should export security data as CSV", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const mockCsvData =
|
||||
"timestamp,severity,type,title\n2024-01-01T00:00:00.000Z,HIGH,BRUTE_FORCE_ATTACK,Test Alert";
|
||||
vi.mocked(securityMonitoring.exportSecurityData).mockReturnValue(
|
||||
mockCsvData
|
||||
);
|
||||
|
||||
const url =
|
||||
"http://localhost:3000/api/admin/security-monitoring/export?format=csv&type=alerts&startDate=2024-01-01T00:00:00Z&endDate=2024-01-02T00:00:00Z";
|
||||
const request = new NextRequest(url);
|
||||
const response = await ExportGET(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("Content-Type")).toBe("text/csv");
|
||||
|
||||
const data = await response.text();
|
||||
expect(data).toBe(mockCsvData);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/admin/security-monitoring/threat-analysis", () => {
|
||||
it("should perform IP threat analysis", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const mockThreatAnalysis = {
|
||||
threatLevel: "HIGH",
|
||||
riskFactors: ["Multiple failed logins", "Rate limit violations"],
|
||||
recommendations: ["Block IP address", "Investigate source"],
|
||||
};
|
||||
|
||||
const mockMetrics = {
|
||||
securityScore: 65,
|
||||
threatLevel: "HIGH",
|
||||
activeAlerts: 5,
|
||||
criticalEvents: 2,
|
||||
topThreats: [],
|
||||
geoDistribution: {},
|
||||
userRiskScores: [],
|
||||
};
|
||||
|
||||
vi.mocked(securityMonitoring.calculateIPThreatLevel).mockResolvedValue(
|
||||
mockThreatAnalysis
|
||||
);
|
||||
vi.mocked(securityMonitoring.getSecurityMetrics).mockResolvedValue(
|
||||
mockMetrics
|
||||
);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring/threat-analysis",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
ipAddress: "192.168.1.100",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
const response = await ThreatAnalysisPOST(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.ipThreatAnalysis).toMatchObject({
|
||||
ipAddress: "192.168.1.100",
|
||||
...mockThreatAnalysis,
|
||||
});
|
||||
expect(data.overallThreatLandscape).toBeDefined();
|
||||
|
||||
expect(securityMonitoring.calculateIPThreatLevel).toHaveBeenCalledWith(
|
||||
"192.168.1.100"
|
||||
);
|
||||
});
|
||||
|
||||
it("should validate IP address format", async () => {
|
||||
vi.mocked(getServerSession).mockResolvedValue(mockPlatformUserSession);
|
||||
|
||||
const request = new NextRequest(
|
||||
"http://localhost:3000/api/admin/security-monitoring/threat-analysis",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
ipAddress: "invalid-ip",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
const response = await ThreatAnalysisPOST(request);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe("Invalid request");
|
||||
expect(data.details).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user