Files
deeplx/src/lib/security.ts
T
xixu-me 8afa94767e Initial repository setup and core files added
Add configuration, documentation, code of conduct, contributing guide, license, and README files. Set up .gitignore, coverage config, GitHub issue templates, pull request template, and initial source code, scripts, and tests for DeepLX optimized for Cloudflare Workers.
2025-07-27 02:09:25 +08:00

123 lines
3.2 KiB
TypeScript

/**
* Security middleware for DeepLX API
* Provides security headers, CORS configuration, and request sanitization
*/
/**
* Security headers configuration
*/
const SECURITY_HEADERS = {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "geolocation=(), microphone=(), camera=()",
"Content-Security-Policy":
"default-src 'self'; script-src 'none'; object-src 'none';",
};
/**
* CORS configuration
*/
const CORS_CONFIG = {
"Access-Control-Allow-Origin": "*", // Consider restricting in production
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
};
/**
* Add security headers to response
* @param response The response object to modify
* @returns Modified response with security headers
*/
export function addSecurityHeaders(response: any): any {
const newHeaders = new Map(response.headers);
// Add security headers
Object.entries(SECURITY_HEADERS).forEach(([key, value]) => {
newHeaders.set(key, value);
});
// Add CORS headers for API endpoints
Object.entries(CORS_CONFIG).forEach(([key, value]) => {
newHeaders.set(key, value);
});
return {
...response,
headers: newHeaders,
};
}
/**
* Handle CORS preflight requests
* @param c Hono context
* @returns CORS preflight response
*/
export function handleCORSPreflight(c: any) {
return c.text("", 200, CORS_CONFIG);
}
/**
* Validate language codes
* @param langCode The language code to validate
* @returns Original language code if valid, or null if invalid
*/
export function validateLanguageCode(langCode: string): string | null {
if (typeof langCode !== "string") {
return null;
}
// Check if language code matches expected pattern (alphanumeric and hyphens only)
const normalized = langCode.toLowerCase().trim();
if (normalized.length < 2 || normalized.length > 5) {
return null;
}
// Only allow valid language code characters
if (!/^[a-z0-9-]+$/.test(normalized)) {
return null;
}
return normalized;
}
/**
* Rate limit by IP with enhanced security
* @param request The incoming request
* @returns Validated client IP or null if suspicious
*/
export function getSecureClientIP(request: any): string | null {
// Get IP from Cloudflare headers first (most trusted)
const cfIP = request.headers.get("CF-Connecting-IP");
if (cfIP && isValidIP(cfIP)) {
return cfIP;
}
// Fallback to X-Forwarded-For (less trusted)
const forwardedFor = request.headers.get("X-Forwarded-For");
if (forwardedFor) {
const firstIP = forwardedFor.split(",")[0]?.trim();
if (firstIP && isValidIP(firstIP)) {
return firstIP;
}
}
return null; // Return null for suspicious requests
}
/**
* Basic IP address validation
* @param ip The IP address to validate
* @returns True if IP appears valid
*/
function isValidIP(ip: string): boolean {
// Basic IPv4 and IPv6 validation
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
}