From 15c74f5aa80214d4b36928c00455804216114e60 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 16:40:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(lab-content-engine):=20phase=205=20?= =?UTF-8?q?=E2=80=94=20read-=D1=80=D0=BE=D1=83=D1=82=D1=8B=20auth-only,=20?= =?UTF-8?q?=D0=BC=D1=83=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20inline=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /related и /links возвращали 200 без токена: они были ПОСЛЕ blanket router.use(requireRole('admin')) (хрупкий порядок при повторном mount роутера в тестах). Убрал blanket; каждая мутация (patch/reorder/links POST+DELETE) имеет INLINE requireRole('admin'); read-роуты — auth-only. Также lab-links seed переведён на seedRow() (NOT NULL дрейф схемы). lab-links 18/18, lab-sims 11/11, route-auth: 0 роутов lab.js во флаге. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/routes/lab.js | 9 ++++++--- backend/tests/lab-links.test.js | 12 ++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/backend/src/routes/lab.js b/backend/src/routes/lab.js index 9dfba4f..d3649d8 100644 --- a/backend/src/routes/lab.js +++ b/backend/src/routes/lab.js @@ -69,8 +69,11 @@ router.get('/sims', (_req, res) => { res.json({ module_disabled: readModuleDisabled(), sims }); }); -/* ── admin mutations ───────────────────────────────────────────────────── */ -router.use(requireRole('admin')); +/* ── admin mutations ─────────────────────────────────────────────────────── + ВАЖНО: НЕ используем blanket `router.use(requireRole('admin'))` — он применялся + бы и к ниже определённым READ-роутам Фазы 5 (/related, /links), которые должны + быть доступны любому авторизованному пользователю. Каждая мутация защищена + INLINE requireRole('admin') (так же видит route-auth линтер). */ /* PATCH /api/lab/sims/:id body: { enabled?, featured?, tags?, subject?, grade?, title?, cat? } */ router.patch('/sims/:id', requireRole('admin'), (req, res) => { @@ -125,7 +128,7 @@ router.patch('/sims/:id', requireRole('admin'), (req, res) => { }); /* POST /api/lab/sims/reorder body: { order: [id, id, ...] } */ -router.post('/sims/reorder', (req, res) => { +router.post('/sims/reorder', requireRole('admin'), (req, res) => { const order = (req.body && req.body.order) || []; if (!Array.isArray(order) || !order.length) { return res.status(400).json({ error: 'order должен быть непустым массивом id' }); diff --git a/backend/tests/lab-links.test.js b/backend/tests/lab-links.test.js index 39e281c..42459f5 100644 --- a/backend/tests/lab-links.test.js +++ b/backend/tests/lab-links.test.js @@ -42,15 +42,11 @@ describe('/api/lab curriculum links', () => { before(async () => { adminToken = (await getToken('admin')).token; studentToken = (await getToken('student')).token; - // Seed a textbook + topic to link against. - db.prepare(`INSERT INTO textbooks (slug, title, subject, grade, is_active) - VALUES ('phys-test', 'Физика тест', 'physics', 9, 1) - ON CONFLICT(slug) DO NOTHING`).run(); + // Seed a textbook + topic to link against (schema-robust — fills NOT NULL cols). tbSlug = 'phys-test'; - const subj = db.prepare(`INSERT INTO subjects (name) VALUES ('LinkTest Subj')`).run(); - const tp = db.prepare(`INSERT INTO topics (subject_id, name) VALUES (?, 'Колебания тест')`) - .run(subj.lastInsertRowid); - topicId = tp.lastInsertRowid; + seedRow('textbooks', { slug: tbSlug, title: 'Физика тест', subject: 'physics', grade: 9, is_active: 1 }); + const subjId = seedRow('subjects', { name: 'LinkTest Subj', slug: 'linktest-subj' }); + topicId = seedRow('topics', { subject_id: subjId, name: 'Колебания тест', slug: 'kolebaniya-test' }); }); it('GET /related requires auth (401)', async () => {