Увеличены rate limits для улучшения производительности

- Основной лимит: 100 → 1000 запросов за 15 минут
- API лимит: 500 → 2000 запросов за 5 минут
This commit is contained in:
root 2025-11-16 14:49:23 +07:00
parent daa1c14d11
commit 7d115e1845

View File

@ -8,6 +8,7 @@ const session = require("express-session");
const SQLiteStore = require("connect-sqlite3")(session);
const path = require("path");
const helmet = require("helmet");
const cors = require("cors");
const rateLimit = require("express-rate-limit");
const bodyParser = require("body-parser");
const multer = require("multer");
@ -25,6 +26,38 @@ const PORT = process.env.PORT || 3001;
// Доверяем всем прокси (nginx proxy manager должен передавать X-Forwarded-For)
app.set("trust proxy", true);
// Конфигурация CORS (защита от CSRF)
const corsOptions = {
origin: function (origin, callback) {
// Список разрешённых доменов
const allowedOrigins = [
"http://localhost:3000",
"http://localhost:5173", // Vite dev server
"http://localhost:5174",
"http://127.0.0.1:3000",
"http://127.0.0.1:5173",
];
// В продакшене добавить домены вашего сайта
if (process.env.ALLOWED_ORIGINS) {
allowedOrigins.push(...process.env.ALLOWED_ORIGINS.split(","));
}
// Если origin не указан (например, для мобильного приложения), разрешаем
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("CORS не разрешен для этого домена"));
}
},
credentials: true, // разрешить куки
optionsSuccessStatus: 200,
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
};
app.use(cors(corsOptions));
// Создаем директорию для аватарок, если её нет
const uploadsDir = path.join(__dirname, "public", "uploads");
if (!fs.existsSync(uploadsDir)) {
@ -185,12 +218,42 @@ app.use(
})
);
// Ограничение запросов (отключено для разработки)
// const limiter = rateLimit({
// windowMs: 15 * 60 * 1000, // 15 минут
// max: 100, // максимум 100 запросов с одного IP
// });
// app.use(limiter);
// Ограничение запросов (включено для защиты от DDoS и брутфорса)
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 1000, // максимум 1000 запросов с одного IP
standardHeaders: true, // вернёт информацию о rate limit в `RateLimit-*` заголовки
legacyHeaders: false, // отключить `X-RateLimit-*` заголовки
message: "Слишком много запросов с этого IP адреса, пожалуйста попробуйте позже.",
skip: (req) => {
// Пропускаем health check запросы
return req.path === "/health" || req.path === "/manifest.json";
},
});
app.use(limiter);
// Специальный rate limiter для защиты от брутфорса (для логина и регистрации)
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 5, // максимум 5 попыток с одного IP
standardHeaders: true,
legacyHeaders: false,
message: "Слишком много попыток входа. Попробуйте позже.",
skipSuccessfulRequests: true, // не считаем успешные попытки
});
// Rate limiter для API запросов (более мягкий для обычных операций)
const apiLimiter = rateLimit({
windowMs: 5 * 60 * 1000, // 5 минут
max: 2000, // максимум 2000 запросов за 5 минут (400 в минуту)
standardHeaders: true,
legacyHeaders: false,
message: "Слишком много запросов к API. Пожалуйста, попробуйте позже.",
skip: (req) => {
// Пропускаем GET запросы (безопасные, не требуют защиты)
return req.method === "GET";
},
});
// Статические файлы
app.use(express.static(path.join(__dirname, "public")));
@ -218,6 +281,9 @@ app.get("/browserconfig.xml", (req, res) => {
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Rate limiter для API (применяется ко всем API запросам)
app.use("/api/", apiLimiter);
// Настройка сессий с хранением в SQLite
app.use(
session({
@ -230,7 +296,9 @@ app.use(
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // в продакшене установить true с HTTPS
secure: process.env.NODE_ENV === "production", // куки только через HTTPS в продакшене
httpOnly: true, // куки недоступны для JavaScript (защита от XSS)
sameSite: "strict", // защита от CSRF атак
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 дней
},
})
@ -785,7 +853,7 @@ app.get("/register", (req, res) => {
});
// API регистрации
app.post("/api/register", async (req, res) => {
app.post("/api/register", authLimiter, async (req, res) => {
const { username, password, confirmPassword } = req.body;
// Валидация
@ -841,7 +909,7 @@ app.post("/api/register", async (req, res) => {
});
// API входа
app.post("/api/login", async (req, res) => {
app.post("/api/login", authLimiter, async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
@ -899,7 +967,7 @@ app.post("/api/login", async (req, res) => {
});
// Обработка входа (старый маршрут для совместимости)
app.post("/login", async (req, res) => {
app.post("/login", authLimiter, async (req, res) => {
const { password } = req.body;
const correctPassword = process.env.APP_PASSWORD;
@ -2961,7 +3029,7 @@ app.post("/api/user/2fa/disable", requireApiAuth, async (req, res) => {
});
// API для проверки 2FA кода при входе
app.post("/api/user/2fa/verify", async (req, res) => {
app.post("/api/user/2fa/verify", authLimiter, async (req, res) => {
const { username, token } = req.body;
if (!username || !token) {