50 lines
1.7 KiB
JavaScript
50 lines
1.7 KiB
JavaScript
const LOCAL_HEALTH_ADDRESSES = new Set([
|
||
'127.0.0.1',
|
||
'::1',
|
||
'::ffff:127.0.0.1'
|
||
]);
|
||
|
||
function applySecurityHeaders(req, res, options = {}) {
|
||
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||
res.setHeader('Permissions-Policy', 'camera=(), geolocation=(), microphone=()');
|
||
res.setHeader(
|
||
'Content-Security-Policy',
|
||
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';"
|
||
);
|
||
|
||
if (req?.secure || (!req && options.secureByDefault)) {
|
||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||
}
|
||
res.removeHeader('X-Powered-By');
|
||
}
|
||
|
||
function securityHeadersMiddleware(options = {}) {
|
||
return (req, res, next) => {
|
||
applySecurityHeaders(req, res, options);
|
||
next();
|
||
};
|
||
}
|
||
|
||
function createHttpsEnforcementMiddleware({ enabled = false } = {}) {
|
||
return (req, res, next) => {
|
||
const remoteAddress = req.socket?.remoteAddress || '';
|
||
const isLocalHealthCheck = req.path === '/api/health' && LOCAL_HEALTH_ADDRESSES.has(remoteAddress);
|
||
if (!enabled || isLocalHealthCheck || req.secure) return next();
|
||
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '仅支持HTTPS访问,请使用HTTPS',
|
||
requestId: req.id
|
||
});
|
||
};
|
||
}
|
||
|
||
module.exports = {
|
||
applySecurityHeaders,
|
||
createHttpsEnforcementMiddleware,
|
||
securityHeadersMiddleware
|
||
};
|