mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 22:12:08 +01:00
feat: implement comprehensive CSRF protection
This commit is contained in:
156
tests/integration/security-headers-basic.test.ts
Normal file
156
tests/integration/security-headers-basic.test.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
describe("Security Headers Configuration", () => {
|
||||
describe("Next.js Config Validation", () => {
|
||||
it("should have valid security headers configuration", async () => {
|
||||
// Import the Next.js config
|
||||
const nextConfig = await import("../../next.config.js");
|
||||
|
||||
expect(nextConfig.default).toBeDefined();
|
||||
expect(nextConfig.default.headers).toBeDefined();
|
||||
expect(typeof nextConfig.default.headers).toBe("function");
|
||||
});
|
||||
|
||||
it("should generate expected headers structure", async () => {
|
||||
const nextConfig = await import("../../next.config.js");
|
||||
const headers = await nextConfig.default.headers();
|
||||
|
||||
expect(Array.isArray(headers)).toBe(true);
|
||||
expect(headers.length).toBeGreaterThan(0);
|
||||
|
||||
// Find the main security headers configuration
|
||||
const securityConfig = headers.find(h => h.source === "/(.*)" && h.headers.length > 1);
|
||||
expect(securityConfig).toBeDefined();
|
||||
|
||||
if (securityConfig) {
|
||||
const headerNames = securityConfig.headers.map(h => h.key);
|
||||
|
||||
// Check required security headers are present
|
||||
expect(headerNames).toContain("X-Content-Type-Options");
|
||||
expect(headerNames).toContain("X-Frame-Options");
|
||||
expect(headerNames).toContain("X-XSS-Protection");
|
||||
expect(headerNames).toContain("Referrer-Policy");
|
||||
expect(headerNames).toContain("Content-Security-Policy");
|
||||
expect(headerNames).toContain("Permissions-Policy");
|
||||
expect(headerNames).toContain("X-DNS-Prefetch-Control");
|
||||
}
|
||||
});
|
||||
|
||||
it("should have correct security header values", async () => {
|
||||
const nextConfig = await import("../../next.config.js");
|
||||
const headers = await nextConfig.default.headers();
|
||||
|
||||
const securityConfig = headers.find(h => h.source === "/(.*)" && h.headers.length > 1);
|
||||
|
||||
if (securityConfig) {
|
||||
const headerMap = new Map(securityConfig.headers.map(h => [h.key, h.value]));
|
||||
|
||||
expect(headerMap.get("X-Content-Type-Options")).toBe("nosniff");
|
||||
expect(headerMap.get("X-Frame-Options")).toBe("DENY");
|
||||
expect(headerMap.get("X-XSS-Protection")).toBe("1; mode=block");
|
||||
expect(headerMap.get("Referrer-Policy")).toBe("strict-origin-when-cross-origin");
|
||||
expect(headerMap.get("X-DNS-Prefetch-Control")).toBe("off");
|
||||
|
||||
// CSP should contain essential directives
|
||||
const csp = headerMap.get("Content-Security-Policy");
|
||||
expect(csp).toContain("default-src 'self'");
|
||||
expect(csp).toContain("frame-ancestors 'none'");
|
||||
expect(csp).toContain("object-src 'none'");
|
||||
|
||||
// Permissions Policy should restrict dangerous features
|
||||
const permissions = headerMap.get("Permissions-Policy");
|
||||
expect(permissions).toContain("camera=()");
|
||||
expect(permissions).toContain("microphone=()");
|
||||
expect(permissions).toContain("geolocation=()");
|
||||
}
|
||||
});
|
||||
|
||||
it("should handle HSTS header based on environment", async () => {
|
||||
const nextConfig = await import("../../next.config.js");
|
||||
|
||||
// Test production environment
|
||||
const originalEnv = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
const prodHeaders = await nextConfig.default.headers();
|
||||
const hstsConfig = prodHeaders.find(h =>
|
||||
h.headers.some(header => header.key === "Strict-Transport-Security")
|
||||
);
|
||||
|
||||
if (hstsConfig) {
|
||||
const hstsHeader = hstsConfig.headers.find(h => h.key === "Strict-Transport-Security");
|
||||
expect(hstsHeader?.value).toBe("max-age=31536000; includeSubDomains; preload");
|
||||
}
|
||||
|
||||
// Test development environment
|
||||
process.env.NODE_ENV = "development";
|
||||
|
||||
const devHeaders = await nextConfig.default.headers();
|
||||
const devHstsConfig = devHeaders.find(h =>
|
||||
h.headers.some(header => header.key === "Strict-Transport-Security")
|
||||
);
|
||||
|
||||
// In development, HSTS header array should be empty
|
||||
if (devHstsConfig) {
|
||||
expect(devHstsConfig.headers.length).toBe(0);
|
||||
}
|
||||
|
||||
// Restore original environment
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
});
|
||||
});
|
||||
|
||||
describe("CSP Directive Validation", () => {
|
||||
it("should have comprehensive CSP directives", async () => {
|
||||
const nextConfig = await import("../../next.config.js");
|
||||
const headers = await nextConfig.default.headers();
|
||||
|
||||
const securityConfig = headers.find(h => h.source === "/(.*)" && h.headers.length > 1);
|
||||
const cspHeader = securityConfig?.headers.find(h => h.key === "Content-Security-Policy");
|
||||
|
||||
expect(cspHeader).toBeDefined();
|
||||
|
||||
if (cspHeader) {
|
||||
const csp = cspHeader.value;
|
||||
|
||||
// Essential security directives
|
||||
expect(csp).toContain("default-src 'self'");
|
||||
expect(csp).toContain("object-src 'none'");
|
||||
expect(csp).toContain("base-uri 'self'");
|
||||
expect(csp).toContain("form-action 'self'");
|
||||
expect(csp).toContain("frame-ancestors 'none'");
|
||||
expect(csp).toContain("upgrade-insecure-requests");
|
||||
|
||||
// Next.js compatibility directives
|
||||
expect(csp).toContain("script-src 'self' 'unsafe-eval' 'unsafe-inline'");
|
||||
expect(csp).toContain("style-src 'self' 'unsafe-inline'");
|
||||
expect(csp).toContain("img-src 'self' data: https:");
|
||||
expect(csp).toContain("font-src 'self' data:");
|
||||
expect(csp).toContain("connect-src 'self' https:");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Permissions Policy Validation", () => {
|
||||
it("should restrict dangerous browser features", async () => {
|
||||
const nextConfig = await import("../../next.config.js");
|
||||
const headers = await nextConfig.default.headers();
|
||||
|
||||
const securityConfig = headers.find(h => h.source === "/(.*)" && h.headers.length > 1);
|
||||
const permissionsHeader = securityConfig?.headers.find(h => h.key === "Permissions-Policy");
|
||||
|
||||
expect(permissionsHeader).toBeDefined();
|
||||
|
||||
if (permissionsHeader) {
|
||||
const permissions = permissionsHeader.value;
|
||||
|
||||
// Should disable privacy-sensitive features
|
||||
expect(permissions).toContain("camera=()");
|
||||
expect(permissions).toContain("microphone=()");
|
||||
expect(permissions).toContain("geolocation=()");
|
||||
expect(permissions).toContain("interest-cohort=()");
|
||||
expect(permissions).toContain("browsing-topics=()");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user