Files
3dtours/backend/middlewares/securityMiddleware.js

93 lines
3.5 KiB
JavaScript

const { URL } = require('url');
/**
* Anti-hotlinking middleware. Ensures that requests for assets originate
* from the official app domain (SYSTEM_HOST). Blocks direct URL access.
*/
const verifyReferer = (req, res, next) => {
// Cho phép các Bot của mạng xã hội truy cập để lấy ảnh thumbnail
const userAgent = req.headers['user-agent'] || '';
const isSocialBot = /facebookexternalhit|Facebot|ZaloBot|Twitterbot|Slackbot|LinkedInBot|Embedly/i.test(userAgent);
if (isSocialBot) {
return next();
}
const referer = req.headers.referer;
const origin = req.headers.origin;
// Prepare allowed origins for Referer/Origin check
const primarySystemHost = process.env.SYSTEM_HOST || 'http://localhost:5000';
let configuredAllowedOrigins = [];
// Add primary SYSTEM_HOST
try {
configuredAllowedOrigins.push(new URL(primarySystemHost).origin);
} catch (e) {
console.warn(`[Security Config Warning] Malformed SYSTEM_HOST: ${primarySystemHost}. Using as-is.`);
configuredAllowedOrigins.push(primarySystemHost);
}
// Add additional allowed origins from environment variable (comma-separated)
if (process.env.ADDITIONAL_ALLOWED_ORIGINS) {
process.env.ADDITIONAL_ALLOWED_ORIGINS.split(',').forEach(originStr => {
try {
configuredAllowedOrigins.push(new URL(originStr.trim()).origin);
} catch (e) {
console.warn(`[Security Config Warning] Malformed origin in ADDITIONAL_ALLOWED_ORIGINS: ${originStr.trim()}. Skipping.`);
}
});
}
const isMatch = (headerValue) => {
if (!headerValue) return false;
try {
const urlObj = new URL(headerValue);
const incomingOrigin = urlObj.origin;
// Cho phép nếu khớp với bất kỳ origin nào trong danh sách cấu hình
if (configuredAllowedOrigins.includes(incomingOrigin)) return true;
// Trong môi trường development, cho phép localhost với bất kỳ port nào
const isLocal = incomingOrigin.includes('localhost') || incomingOrigin.includes('127.0.0.1') || incomingOrigin.includes('::1');
if (process.env.NODE_ENV !== 'production' && isLocal) return true;
return false;
} catch (e) {
console.warn(`[Security] Invalid URL in header value: ${headerValue}`);
return false;
}
};
const hasValidReferer = isMatch(referer);
const hasValidOrigin = isMatch(origin);
// Block request if both referer and origin are missing or do not match SYSTEM_HOST
if (!hasValidReferer && !hasValidOrigin) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[Security Blocked] Referer: ${referer || 'N/A'}, Origin: ${origin || 'N/A'}, Configured: ${configuredAllowedOrigins.join(', ')}`);
}
return res.status(403).json({
message: 'Access denied: Hotlinking detected or direct file access is prohibited.'
});
}
next();
};
/**
* Cache prevention middleware. Ensures that sensitive image assets are never
* cached by client browsers or intermediate proxies.
*/
const setNoCacheHeaders = (req, res, next) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
next();
};
module.exports = {
verifyReferer,
setNoCacheHeaders
};