diff --git a/package.json b/package.json index f4e2700..9dd87f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.173", + "version": "0.5.174", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/server.js b/server.js index 55161c3..8df0035 100644 --- a/server.js +++ b/server.js @@ -83,6 +83,7 @@ async function start() { if (auth.jwksHandler) app.get('/.well-known/jwks.json', auth.jwksHandler); // ── OIDC-роуты (Keycloak) ─────────────────────────────────────────────── + app.use('/login', authLimiter); // защита от брутфорса const { createRouter: createOidcRouter } = require('./src/routes/oidc'); app.use('/', createOidcRouter({ auth })); diff --git a/v2/server.js b/v2/server.js index 09b1536..2ce17fe 100644 --- a/v2/server.js +++ b/v2/server.js @@ -31,6 +31,7 @@ const { createUserRouter } = require('./src/user'); const { createAdminRouter } = require('./src/admin'); const { pool } = require('./src/db'); const { ensureSchema } = require('./src/db/schema'); +const { mutationLimiter, exportLimiter } = require('../../src/middleware/rateLimit'); function createV2Router({ generateCsrfToken, doubleCsrfProtection } = {}) { // Автосоздание таблиц v2_companies, v2_entries, v2_audit при старте @@ -102,10 +103,10 @@ function createV2Router({ generateCsrfToken, doubleCsrfProtection } = {}) { }); // ── /v2/app — пользовательский CRUD (только с сессией) ────────────── - router.use('/app', enhanceImpersonation, resolveContext, doubleCsrfProtection || ((req,res,next)=>next()), createUserRouter({ generateCsrfToken })); + router.use('/app', mutationLimiter, enhanceImpersonation, resolveContext, doubleCsrfProtection || ((req,res,next)=>next()), createUserRouter({ generateCsrfToken })); // ── /v2/admin — админка: лимиты + аудит ────────────────────────── - router.use('/admin', enhanceImpersonation, resolveContext, doubleCsrfProtection || ((req,res,next)=>next()), createAdminRouter({ generateCsrfToken })); + router.use('/admin', mutationLimiter, enhanceImpersonation, resolveContext, doubleCsrfProtection || ((req,res,next)=>next()), createAdminRouter({ generateCsrfToken })); // ── GET /v2/ — редирект на /v2/app ────────────────────────────────── router.get('/', (req, res) => res.redirect('/v2/app')); @@ -121,7 +122,7 @@ function createV2Router({ generateCsrfToken, doubleCsrfProtection } = {}) { // ── /v2/export — выгрузка CIDR (только для админов) ───────────── const { createExportRouter } = require('./src/export'); - router.use('/export', resolveContext, (req, res, next) => { + router.use('/export', exportLimiter, resolveContext, (req, res, next) => { if (!req.canAdmin) return res.status(403).send('Доступ запрещён — только для администраторов'); next(); }, createExportRouter()); @@ -172,7 +173,7 @@ async function iamFetch(token, url) { const https = require('https'); const u = new URL(url); return new Promise((resolve, reject) => { - https.get({ + const req = https.get({ hostname: u.hostname, port: 443, path: u.pathname, headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' }, }, res => { @@ -182,7 +183,9 @@ async function iamFetch(token, url) { if (res.statusCode !== 200) return reject(new Error(res.statusCode + ': ' + data.slice(0, 200))); try { resolve(JSON.parse(data)); } catch (e) { reject(e); } }); - }).on('error', reject); + }); + req.on('error', reject); + req.setTimeout(5000, () => { req.destroy(); reject(new Error('timeout')); }); }); } diff --git a/v2/src/config/index.js b/v2/src/config/index.js index c8285cb..82b0293 100644 --- a/v2/src/config/index.js +++ b/v2/src/config/index.js @@ -8,7 +8,7 @@ // ═══════════════════════════════════════════════════════════════════════════════ module.exports = { - version: '0.5.173', + version: '0.5.174', // ── IAM ────────────────────────────────────────────────────────────────── iamUrl: process.env.V2_IAM_URL || 'https://auth-api.ngcloud.ru/api/v1/auth/user', diff --git a/v2/src/router/index.js b/v2/src/router/index.js index 6df03ba..27f90a9 100644 --- a/v2/src/router/index.js +++ b/v2/src/router/index.js @@ -36,7 +36,7 @@ async function checkImpersonation(token, req, u) { const url = config.iamApiBase + '/auth/user'; const uObj = new URL(url); const data = await new Promise((resolve, reject) => { - https.get({ + const req = https.get({ hostname: uObj.hostname, port: 443, path: uObj.pathname, headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' }, }, res => { @@ -46,7 +46,9 @@ async function checkImpersonation(token, req, u) { if (res.statusCode !== 200) return reject(new Error(res.statusCode + ': ' + d.slice(0, 200))); try { resolve(JSON.parse(d)); } catch (e) { reject(e); } }); - }).on('error', reject); + }); + req.on('error', reject); + req.setTimeout(5000, () => { req.destroy(); reject(new Error('timeout')); }); }); const imp = data.impersonation || {};