- Docker: Multi-stage Dockerfile with security hardening, docker-compose for production and development environments - Environment: Comprehensive .env.example with all config options, lib/config/env.ts for typed environment validation - Logging: Structured JSON logging with request/response middleware - Monitoring: Prometheus metrics endpoint, Grafana dashboard, health checks (liveness/readiness probes) - Security: Security headers, rate limiting, CORS middleware - CI/CD: GitHub Actions workflows for CI, production deploy, and preview deployments - Error tracking: Sentry integration foundation Files created: - Docker: Dockerfile, docker-compose.yml, docker-compose.dev.yml, .dockerignore - Config: lib/config/env.ts, lib/config/index.ts - Logging: lib/logging/logger.ts, lib/logging/middleware.ts - Monitoring: lib/monitoring/sentry.ts, lib/monitoring/metrics.ts, lib/monitoring/health.ts - Security: lib/security/headers.ts, lib/security/rateLimit.ts, lib/security/cors.ts - API: pages/api/health/*, pages/api/metrics.ts - Infra: infra/prometheus/prometheus.yml, infra/grafana/*
135 lines
3.3 KiB
TypeScript
135 lines
3.3 KiB
TypeScript
/**
|
|
* Security Headers Middleware
|
|
* Agent 4: Production Deployment
|
|
*
|
|
* Adds security headers to all responses to protect against common attacks.
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse, NextApiHandler } from 'next';
|
|
import { env } from '../config';
|
|
|
|
interface SecurityHeadersConfig {
|
|
contentSecurityPolicy?: string;
|
|
reportUri?: string;
|
|
enableHSTS?: boolean;
|
|
hstsMaxAge?: number;
|
|
frameOptions?: 'DENY' | 'SAMEORIGIN';
|
|
contentTypeOptions?: boolean;
|
|
xssProtection?: boolean;
|
|
referrerPolicy?: string;
|
|
permissionsPolicy?: string;
|
|
}
|
|
|
|
const DEFAULT_CONFIG: SecurityHeadersConfig = {
|
|
enableHSTS: true,
|
|
hstsMaxAge: 31536000, // 1 year
|
|
frameOptions: 'DENY',
|
|
contentTypeOptions: true,
|
|
xssProtection: true,
|
|
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
};
|
|
|
|
/**
|
|
* Generate Content Security Policy header
|
|
*/
|
|
function generateCSP(reportUri?: string): string {
|
|
const directives = [
|
|
"default-src 'self'",
|
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Needed for Next.js
|
|
"style-src 'self' 'unsafe-inline'",
|
|
"img-src 'self' data: blob: https:",
|
|
"font-src 'self' data:",
|
|
"connect-src 'self' https: wss:",
|
|
"media-src 'self'",
|
|
"object-src 'none'",
|
|
"frame-ancestors 'none'",
|
|
"base-uri 'self'",
|
|
"form-action 'self'",
|
|
"upgrade-insecure-requests",
|
|
];
|
|
|
|
if (reportUri) {
|
|
directives.push(`report-uri ${reportUri}`);
|
|
}
|
|
|
|
return directives.join('; ');
|
|
}
|
|
|
|
/**
|
|
* Generate Permissions Policy header
|
|
*/
|
|
function generatePermissionsPolicy(): string {
|
|
const policies = [
|
|
'accelerometer=()',
|
|
'camera=()',
|
|
'geolocation=(self)',
|
|
'gyroscope=()',
|
|
'magnetometer=()',
|
|
'microphone=()',
|
|
'payment=()',
|
|
'usb=()',
|
|
];
|
|
|
|
return policies.join(', ');
|
|
}
|
|
|
|
/**
|
|
* Apply security headers to response
|
|
*/
|
|
export function applySecurityHeaders(
|
|
res: NextApiResponse,
|
|
config: SecurityHeadersConfig = DEFAULT_CONFIG
|
|
): void {
|
|
// Content Security Policy
|
|
const csp = config.contentSecurityPolicy || generateCSP(config.reportUri || env.cspReportUri);
|
|
res.setHeader('Content-Security-Policy', csp);
|
|
|
|
// Strict Transport Security (HTTPS)
|
|
if (config.enableHSTS && env.isProduction) {
|
|
res.setHeader(
|
|
'Strict-Transport-Security',
|
|
`max-age=${config.hstsMaxAge}; includeSubDomains; preload`
|
|
);
|
|
}
|
|
|
|
// Prevent clickjacking
|
|
res.setHeader('X-Frame-Options', config.frameOptions || 'DENY');
|
|
|
|
// Prevent MIME type sniffing
|
|
if (config.contentTypeOptions) {
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
}
|
|
|
|
// XSS Protection (legacy, but still useful)
|
|
if (config.xssProtection) {
|
|
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
}
|
|
|
|
// Referrer Policy
|
|
res.setHeader('Referrer-Policy', config.referrerPolicy || 'strict-origin-when-cross-origin');
|
|
|
|
// Permissions Policy
|
|
res.setHeader(
|
|
'Permissions-Policy',
|
|
config.permissionsPolicy || generatePermissionsPolicy()
|
|
);
|
|
|
|
// Remove unnecessary headers
|
|
res.removeHeader('X-Powered-By');
|
|
}
|
|
|
|
/**
|
|
* Security headers middleware for API routes
|
|
*/
|
|
export function withSecurityHeaders(
|
|
handler: NextApiHandler,
|
|
config?: SecurityHeadersConfig
|
|
): NextApiHandler {
|
|
return async (req: NextApiRequest, res: NextApiResponse) => {
|
|
applySecurityHeaders(res, config);
|
|
return handler(req, res);
|
|
};
|
|
}
|
|
|
|
// Export types
|
|
export type { SecurityHeadersConfig };
|