diff --git a/package.json b/package.json index 41e713e..92603a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.4", + "version": "0.5.5", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/src/api/routes/entries.js b/src/api/routes/entries.js index b6f62cf..b8b7346 100644 --- a/src/api/routes/entries.js +++ b/src/api/routes/entries.js @@ -70,12 +70,13 @@ function createEntriesRouter({ q }) { // GET /api/v1/entries/export — агрегированный список CIDR (text/plain) // Доступен всем аутентифицированным пользователям. - // Admin: все компании или ?company=. User: только своя компания. + // Admin: все компании или ?company=. User: только своя компания или ?client_id=. router.get('/export', async (req, res) => { try { let companyId = null; if (!req.user.isAdmin) { - const company = await q.getOrCreateCompany(req.user.clientId, req.user.companyName); + const effectiveClientId = req.query.client_id || req.user.clientId; + const company = await q.getOrCreateCompany(effectiveClientId, req.user.companyName); companyId = company.id; } else if (req.query.company) { const id = parseInt(req.query.company, 10); diff --git a/tests/api.js b/tests/api.js index 143c75f..62edc53 100644 --- a/tests/api.js +++ b/tests/api.js @@ -880,6 +880,121 @@ async function cleanup() { } } + // ══════════════════════════════════════════════════════════════════════════════ + // O. Multi-company — токен с clientId через запятую + // ══════════════════════════════════════════════════════════════════════════════ + console.log('\n── O. Multi-company ──'); + + const uMulti = MOCK_USERS.find(u => u.id === 'multi'); + const tokenMulti = uMulti + ? auth.issueMockToken({ clientId: uMulti.clientId, companyId: uMulti.companyId, companyName: uMulti.companyName, email: uMulti.email }) + : null; + + if (!tokenMulti) { + log(false, 'O0: не найден multi-пользователь в MOCK_USERS'); + } else { + log(!!tokenMulti, 'O0: выпущен токен multi (WZ01325, WZ02001)'); + + // O1: GET /entries без ?client_id= → видит записи первой компании (WZ01325) + // Сначала добавим запись в обе компании для проверки + let multiEntry1, multiEntry2; + + // O1a: создаём запись в первой компании (WZ01325) через multi-токен + { + const r = await req.post('/api/v1/entries').set(bearer(tokenMulti)) + .send({ value: '4.4.4.1', comment: 'multi-company-1' }); + log(r.status === 201, 'O1a: multi POST → 201 (первая компания WZ01325)', `status=${r.status}`); + multiEntry1 = json(r).entry?.id; + } + + // O1b: создаём запись во ВТОРОЙ компании (WZ02001) через ?client_id= + { + const r = await req.post('/api/v1/entries?client_id=WZ02001').set(bearer(tokenMulti)) + .send({ value: '4.4.4.2', comment: 'multi-company-2' }); + log(r.status === 201, 'O1b: multi POST ?client_id=WZ02001 → 201', `status=${r.status}`); + multiEntry2 = json(r).entry?.id; + } + + // O2: GET без ?client_id= → только записи WZ01325 (не видит WZ02001) + { + const rDefault = await req.get('/api/v1/entries').set(bearer(tokenMulti)); + const entries = json(rDefault).entries || []; + const hasE1 = entries.some(e => e.id === multiEntry1); + const hasE2 = entries.some(e => e.id === multiEntry2); + log(hasE1, 'O2a: GET default содержит запись WZ01325'); + log(!hasE2, 'O2b: GET default НЕ содержит запись WZ02001'); + } + + // O3: GET ?client_id=WZ02001 → только записи WZ02001 + { + const rSwitched = await req.get('/api/v1/entries?client_id=WZ02001').set(bearer(tokenMulti)); + const entries = json(rSwitched).entries || []; + const hasE1 = entries.some(e => e.id === multiEntry1); + const hasE2 = entries.some(e => e.id === multiEntry2); + log(!hasE1, 'O3a: GET ?client_id=WZ02001 НЕ содержит запись WZ01325'); + log(hasE2, 'O3b: GET ?client_id=WZ02001 содержит запись WZ02001'); + } + + // O4: PATCH через ?client_id= работает изолированно + { + const r = await req.patch('/api/v1/entries/' + multiEntry2 + '?client_id=WZ02001') + .set(bearer(tokenMulti)) + .send({ value: '4.4.4.3', comment: 'patched-in-WZ02001' }); + log(r.status === 200, 'O4: multi PATCH ?client_id=WZ02001 → 200', `status=${r.status}`); + } + + // O5: DELETE через ?client_id= + { + const r = await req.delete('/api/v1/entries/' + multiEntry2 + '?client_id=WZ02001') + .set(bearer(tokenMulti)); + log([200, 204].includes(r.status), 'O5: multi DELETE ?client_id=WZ02001 → 200/204', `status=${r.status}`); + } + + // O6: экспорт с ?client_id=WZ02001 не содержит запись WZ01325 + { + // Создаём запись в WZ02001 для проверки экспорта + const cr = await req.post('/api/v1/entries?client_id=WZ02001').set(bearer(tokenMulti)) + .send({ value: '4.4.5.1', comment: 'export-multi' }); + const exportEntryId = json(cr).entry?.id; + + const rWZ02 = await req.get('/api/v1/entries/export?client_id=WZ02001').set(bearer(tokenMulti)); + log(rWZ02.status === 200, 'O6a: export ?client_id=WZ02001 → 200', `status=${rWZ02.status}`); + log(rWZ02.text.includes('4.4.5.1'), 'O6b: export содержит CIDR WZ02001'); + log(!rWZ02.text.includes('4.4.4.1'), 'O6c: export НЕ содержит CIDR WZ01325'); + + // O6d: экспорт без ?client_id= → только WZ01325 + const rDefault = await req.get('/api/v1/entries/export').set(bearer(tokenMulti)); + log(rDefault.text.includes('4.4.4.1'), 'O6d: export default содержит WZ01325'); + log(!rDefault.text.includes('4.4.5.1'), 'O6e: export default НЕ содержит WZ02001'); + + // Чистка + if (exportEntryId) { + await req.delete('/api/v1/entries/' + exportEntryId + '?client_id=WZ02001').set(bearer(tokenMulti)); + } + } + + // O7: несуществующий ?client_id= → upsert-ит новую компанию (безопасно) + { + const r = await req.get('/api/v1/entries?client_id=NONEXISTENT').set(bearer(tokenMulti)); + log(r.status === 200, 'O7: GET ?client_id=NONEXISTENT → 200 (создаёт пустую компанию)', `status=${r.status}`); + log(json(r).entries?.length === 0, 'O7b: новая компания без записей'); + } + + // O8: admin передаёт ?client_id= → использует свою компанию (игнор) + { + const r = await req.get('/api/v1/entries?client_id=WZ02001').set(bearer(tokenAdmin)); + log(r.status === 200, 'O8: admin GET ?client_id= → 200 (игнорирует)', `status=${r.status}`); + } + + // Чистка записей multi + if (multiEntry1) { + await req.delete('/api/v1/entries/' + multiEntry1).set(bearer(tokenMulti)); + } + if (multiEntry2) { + await req.delete('/api/v1/entries/' + multiEntry2 + '?client_id=WZ02001').set(bearer(tokenMulti)); + } + } + // ══════════════════════════════════════════════════════════════════════════════ // Cleanup // ══════════════════════════════════════════════════════════════════════════════