test: IPv6, boundary CIDR, race condition, rate-limiter (49/49 pass)
This commit is contained in:
+140
-15
@@ -543,22 +543,8 @@ async function cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// 17. Rate-limiter — превысить mutationLimiter (30 POST/min)
|
// 17. Rate-limiter — перенесён в конец, после всех тестов использующих userAgent
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
console.log('\n── 17. Rate-limiter ──');
|
|
||||||
|
|
||||||
{
|
|
||||||
// Отправляем 35 POST /add подряд с заведомо невалидным CIDR (быстро)
|
|
||||||
// Rate-limiter (30/min) должен сработать и вернуть 429
|
|
||||||
let got429 = false;
|
|
||||||
for (let i = 0; i < 35; i++) {
|
|
||||||
const csrf = await getCsrfFromIndex(userAgent);
|
|
||||||
const res = await userAgent.post('/add').type('form')
|
|
||||||
.send({ value: 'not-an-ip', comment: `rate ${i}`, _csrf: csrf });
|
|
||||||
if (res.status === 429) { got429 = true; break; }
|
|
||||||
}
|
|
||||||
log(got429, 'rate-limiter: POST /add → 429 после 30 запросов');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// 18. IDOR — user пытается добавить запись в чужую компанию через company_id
|
// 18. IDOR — user пытается добавить запись в чужую компанию через company_id
|
||||||
@@ -637,6 +623,145 @@ async function cleanup() {
|
|||||||
log(res.status === 200, 'admin: GET /audit → 200', `status=${res.status}`);
|
log(res.status === 200, 'admin: GET /audit → 200', `status=${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 21. IPv6 адреса — валидация
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
console.log('\n── 21. IPv6 ──');
|
||||||
|
|
||||||
|
{
|
||||||
|
const csrf = await getCsrfFromIndex(userAgent);
|
||||||
|
const res = await userAgent.post('/add').type('form')
|
||||||
|
.send({ value: '2001:db8::1', comment: 'ipv6 test', _csrf: csrf });
|
||||||
|
const err = getRedirectParam(res, 'error');
|
||||||
|
const msg = getRedirectParam(res, 'message');
|
||||||
|
// Либо принимает IPv6, либо отклоняет как несуппортируемый формат
|
||||||
|
log(res.status === 302, 'user: IPv6 → redirect (не падает 500)', `status=${res.status} msg=${msg} err=${err}`);
|
||||||
|
|
||||||
|
// Чистим если добавился
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE whitelist_entries SET deleted_at = NOW(), deleted_by = 'test-cleanup'
|
||||||
|
WHERE value_cidr LIKE '%2001:db8%' AND deleted_at IS NULL`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 22. Граничные CIDR — /0 и /32
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
console.log('\n── 22. Граничные CIDR ──');
|
||||||
|
|
||||||
|
{
|
||||||
|
// /0 — весь интернет, должно быть отклонено или принято
|
||||||
|
const csrf = await getCsrfFromIndex(userAgent);
|
||||||
|
const res = await userAgent.post('/add').type('form')
|
||||||
|
.send({ value: '0.0.0.0/0', comment: 'весь интернет', _csrf: csrf });
|
||||||
|
const err = getRedirectParam(res, 'error');
|
||||||
|
log(res.status === 302, 'user: 0.0.0.0/0 → redirect (не 500)', `err=${err}`);
|
||||||
|
// Чистим
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE whitelist_entries SET deleted_at = NOW(), deleted_by = 'test-cleanup'
|
||||||
|
WHERE value_cidr = '0.0.0.0/0' AND deleted_at IS NULL`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// /32 с host-битами — нормализация не нужна, должен добавиться как есть
|
||||||
|
const csrf = await getCsrfFromIndex(userAgent);
|
||||||
|
const res = await userAgent.post('/add').type('form')
|
||||||
|
.send({ value: '5.5.5.5/32', comment: 'slash32', _csrf: csrf });
|
||||||
|
const msg = getRedirectParam(res, 'message');
|
||||||
|
const err = getRedirectParam(res, 'error');
|
||||||
|
log(!err && res.status === 302, 'user: 5.5.5.5/32 → добавлено', `msg=${msg} err=${err}`);
|
||||||
|
// Чистим
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE whitelist_entries SET deleted_at = NOW(), deleted_by = 'test-cleanup'
|
||||||
|
WHERE value_cidr = '5.5.5.5/32' AND deleted_at IS NULL`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 24. Race condition — одновременные запросы на /add при лимите
|
||||||
|
// (используем свежий агент, не отравленный rate-limit)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
console.log('\n── 24. Race condition: лимит ──');
|
||||||
|
|
||||||
|
{
|
||||||
|
const { MOCK_USERS } = require('../src/config');
|
||||||
|
const testUser = MOCK_USERS.find(u => u.id === 'test');
|
||||||
|
const uRes = await pool.query(
|
||||||
|
`SELECT id FROM companies WHERE client_id = $1 LIMIT 1`, [testUser?.clientId]
|
||||||
|
);
|
||||||
|
const userCompId = uRes.rows[0]?.id;
|
||||||
|
|
||||||
|
if (userCompId) {
|
||||||
|
// Свежий агент — не отравлен rate-limiter
|
||||||
|
const raceAgent = await loginAs(app, 'test');
|
||||||
|
|
||||||
|
const cntRes = await pool.query(
|
||||||
|
`SELECT COUNT(*)::int AS c FROM whitelist_entries WHERE company_id = $1 AND deleted_at IS NULL`,
|
||||||
|
[userCompId]
|
||||||
|
);
|
||||||
|
const count = cntRes.rows[0].c;
|
||||||
|
const raceLimit = count + 1;
|
||||||
|
|
||||||
|
const csrf = await getCsrfFromIndex(adminAgent);
|
||||||
|
await adminAgent.post(`/admin/limit/${userCompId}`).type('form')
|
||||||
|
.send({ limit: String(raceLimit), _csrf: csrf });
|
||||||
|
|
||||||
|
// 5 параллельных запросов — только 1 должен пройти
|
||||||
|
const raceCidrs = ['11.11.11.1', '11.11.11.2', '11.11.11.3', '11.11.11.4', '11.11.11.5'];
|
||||||
|
const results = await Promise.all(raceCidrs.map(async (cidr) => {
|
||||||
|
const c = await getCsrfFromIndex(raceAgent);
|
||||||
|
const r = await raceAgent.post('/add').type('form')
|
||||||
|
.send({ value: cidr, comment: 'race', _csrf: c });
|
||||||
|
const st = r.status;
|
||||||
|
return { cidr, status: st, err: getRedirectParam(r, 'error'), msg: getRedirectParam(r, 'message') };
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Считаем фактически добавленные в БД
|
||||||
|
const dbRes = await pool.query(
|
||||||
|
`SELECT COUNT(*)::int AS c FROM whitelist_entries
|
||||||
|
WHERE company_id = $1 AND value_cidr IN ('11.11.11.1/32','11.11.11.2/32','11.11.11.3/32','11.11.11.4/32','11.11.11.5/32')
|
||||||
|
AND deleted_at IS NULL`, [userCompId]
|
||||||
|
);
|
||||||
|
const addedInDb = dbRes.rows[0].c;
|
||||||
|
log(addedInDb <= 1, 'race: в БД не более 1 записи (FOR UPDATE работает)',
|
||||||
|
`added=${addedInDb} results=${JSON.stringify(results.map(r => ({cidr: r.cidr, err: r.err?.slice(0,20)})))}`);
|
||||||
|
|
||||||
|
// Сброс лимита
|
||||||
|
const csrf2 = await getCsrfFromIndex(adminAgent);
|
||||||
|
await adminAgent.post(`/admin/limit/${userCompId}`).type('form')
|
||||||
|
.send({ limit: '', _csrf: csrf2 });
|
||||||
|
|
||||||
|
// Чистим
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE whitelist_entries SET deleted_at = NOW(), deleted_by = 'test-cleanup'
|
||||||
|
WHERE value_cidr IN ('11.11.11.1/32','11.11.11.2/32','11.11.11.3/32','11.11.11.4/32','11.11.11.5/32')
|
||||||
|
AND deleted_at IS NULL`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log(false, 'race: нет компании user');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 25. Rate-limiter — превысить mutationLimiter (30 POST/min)
|
||||||
|
// Этот тест ПОСЛЕДНИЙ — исчерпывает rate-limit для userAgent
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
console.log('\n── 25. Rate-limiter ──');
|
||||||
|
|
||||||
|
{
|
||||||
|
// Свежий агент — чтобы не зависеть от состояния userAgent
|
||||||
|
const rateAgent = await loginAs(app, 'test');
|
||||||
|
let got429 = false;
|
||||||
|
for (let i = 0; i < 35; i++) {
|
||||||
|
const csrf = await getCsrfFromIndex(rateAgent);
|
||||||
|
const res = await rateAgent.post('/add').type('form')
|
||||||
|
.send({ value: 'not-an-ip', comment: `rate ${i}`, _csrf: csrf });
|
||||||
|
if (res.status === 429) { got429 = true; break; }
|
||||||
|
}
|
||||||
|
log(got429, 'rate-limiter: POST /add → 429 после 30 запросов');
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// Cleanup
|
// Cleanup
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user