From 6bbd0fe1e2789b64757313cba806b707f74326e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Tue, 2 Jun 2026 18:12:35 +0300 Subject: [PATCH] =?UTF-8?q?v0.5.11:=20/export=20=D0=BF=D1=83=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=BD=D1=8B=D0=B9=20(=D0=B2=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=BE,=20=D1=81=20=D0=B7=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=BE=D0=BC=20=D0=BD=D0=B0=20Keycloak=20SSO)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменения: - server.js: публичный GET /export до всей авторизации 🔮 Для Keycloak SSO: раскомментировать auth.bearerMiddleware - Удалён ошибочно созданный src/routes/exp.js - Обновлены тесты: /export доступен без авторизации --- server.js | 19 +++++++++++++++++++ tests/integration.js | 15 ++++++++------- tests/tz-compliance.js | 14 ++++++++------ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/server.js b/server.js index 23cb23c..2d941bd 100644 --- a/server.js +++ b/server.js @@ -76,6 +76,25 @@ async function start() { // JWKS endpoint для валидации токенов внешними сервисами (только в mock-режиме) if (auth.jwksHandler) app.get('/.well-known/jwks.json', auth.jwksHandler); + // ── /export — публичный (ВРЕМЕННО без авторизации) ─────────────────────── + // 🔮 КОГДА Keycloak SSO: заменить на app.use('/export', auth.bearerMiddleware, ...) + app.get('/export', + /* 🔮 auth.bearerMiddleware, */ + async (req, res) => { + try { + const cidrs = await q.getExportCIDRs(); + const text = cidrs.join('\n') + (cidrs.length ? '\n' : ''); + res.set('Content-Type', 'text/plain; charset=utf-8'); + const fname = req.query.filename || 'white-list.txt'; + const disp = req.query.view === '1' ? 'inline' : 'attachment'; + res.set('Content-Disposition', disp + '; filename="' + fname + '"'); + res.send(text); + } catch (e) { + console.error('[export] Error:', e); + res.status(500).send('Ошибка экспорта'); + } + } + ); // ── REST API v1 (Bearer JWT, без сессий/CSRF) ───────────────────────────── const { createApiRouter } = require('./src/api/index'); diff --git a/tests/integration.js b/tests/integration.js index 2211b5c..2227209 100644 --- a/tests/integration.js +++ b/tests/integration.js @@ -299,18 +299,19 @@ async function cleanup() { log(r.headers['content-disposition']?.includes('custom.txt'), '4g: имя файла custom.txt', `disp=${r.headers['content-disposition']}`); } - // 4h: GET /export без сессии → редирект + // 4h: GET /export без сессии → 200 (ПУБЛИЧНЫЙ, ВРЕМЕННО) { const r = await supertest(app).get('/export'); - log(r.status === 302, '4h: GET /export без сессии → 302', `status=${r.status}`); + log(r.status === 200, '4h: GET /export без авторизации → 200 (публичный)', `status=${r.status}`); + log(r.headers['content-type']?.includes('text/plain'), '4i: Content-Type text/plain'); } - // 4i: GET /export?view=1&filename=test.txt + // 4j: GET /export?view=1&filename=test.txt { - const r = await agentExp.get('/export?view=1&filename=test.txt'); - log(r.status === 200, '4i: GET /export?view=1&filename=test.txt → 200', `status=${r.status}`); - log(r.headers['content-disposition']?.includes('inline'), '4j: inline (view=1 приоритетнее filename)'); - log(r.headers['content-disposition']?.includes('test.txt'), '4k: имя файла test.txt'); + const r = await supertest(app).get('/export?view=1&filename=test.txt'); + log(r.status === 200, '4j: GET /export?view=1&filename=test.txt → 200', `status=${r.status}`); + log(r.headers['content-disposition']?.includes('inline'), '4k: inline (view=1 приоритетнее filename)'); + log(r.headers['content-disposition']?.includes('test.txt'), '4l: имя файла test.txt'); } // ════════════════════════════════════════════════════════════════════════════ diff --git a/tests/tz-compliance.js b/tests/tz-compliance.js index b7752d3..79e1214 100644 --- a/tests/tz-compliance.js +++ b/tests/tz-compliance.js @@ -240,13 +240,15 @@ function cidr(ip) { return ip.includes('/') ? ip : ip + '/32'; } // ══════════════════════════════════════════════════ console.log('── ТЗ 4.7: Export ──'); { - // 4.7a: /api/v1/entries/export (требует Bearer token) - const cr = await req.post('/api/v1/entries').set(bearer(tokUser)).send({ value: C.A, comment: 'export-test' }); - - const r = await req.get('/api/v1/entries/export').set(bearer(tokUser)); - log(r.status === 200, '4.7a: GET /api/v1/entries/export → 200', `status=${r.status}`); + // 4.7a: /export — публичный, без авторизации (ВРЕМЕННО) + const r = await req.get('/export'); + log(r.status === 200, '4.7a: GET /export → 200 (без авторизации)', `status=${r.status}`); log((r.headers['content-type'] || '').includes('text/plain'), '4.7b: Content-Type text/plain'); - log(r.text.includes(cidr(C.A)), '4.7c: export содержит добавленный CIDR'); + + // 4.7c: /export содержит добавленный CIDR + const cr = await req.post('/api/v1/entries').set(bearer(tokUser)).send({ value: C.A, comment: 'export-test' }); + const r2 = await req.get('/export'); + log(r2.text.includes(cidr(C.A)), '4.7c: /export содержит добавленный CIDR'); } // ══════════════════════════════════════════════════