release: v1.2.0 — каталог, email заказа, SEO, админ CSV
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
const buckets = new Map();
|
||||
|
||||
function rateLimit({ windowMs = 15 * 60 * 1000, max = 20, keyPrefix = '' }) {
|
||||
return (req, res, next) => {
|
||||
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
||||
const key = `${keyPrefix}:${ip}`;
|
||||
const now = Date.now();
|
||||
let entry = buckets.get(key);
|
||||
if (!entry || now > entry.resetAt) {
|
||||
entry = { count: 0, resetAt: now + windowMs };
|
||||
buckets.set(key, entry);
|
||||
}
|
||||
entry.count += 1;
|
||||
if (entry.count > max) {
|
||||
return res.status(429).render('error', {
|
||||
title: 'Слишком много запросов',
|
||||
message: 'Подождите несколько минут и попробуйте снова.',
|
||||
code: 429,
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { rateLimit };
|
||||
@@ -0,0 +1,9 @@
|
||||
function securityHeaders(_req, res, next) {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = { securityHeaders };
|
||||
Reference in New Issue
Block a user