feat(perm): block API endpoints for globally-disabled features (B-lite)

Adds backend/src/middleware/features.js with requireFeature(name)

that returns 404 when app_settings.feature_<name>_enabled='0'.

Wired on 8 routes:

- /api/pet            (pet)

- /api/collection     (collection)

- /api/red-book       (red_book)

- /api/flashcards     (flashcards)

- /api/knowledge-map  (knowledge_map)

- /api/biochem        (biochem)

- /api/games/hangman/*   (hangman, per-route inside games router)

- /api/games/crossword/* (crossword, per-route)

Scope: GLOBAL only. Per-class disable (classes.features JSON) and the

free_student role overlay remain UI-gated. Add user-aware merge later

if needed (extract logic from /api/features endpoint into shared helper).

Not gated (intentional, core teacher tools): board, classroom, live_quiz.

Smoke: pet disabled → 404; enabled → 401 (auth-required passthrough).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-17 14:35:29 +03:00
parent 3e187a94c0
commit 19c16bdfe8
3 changed files with 56 additions and 10 deletions
+8 -4
View File
@@ -1,10 +1,14 @@
const router = require('express').Router();
const { authMiddleware } = require('../middleware/auth');
const { requireFeature } = require('../middleware/features');
const c = require('../controllers/gamesController');
router.get('/hangman/word', authMiddleware, c.hangmanWord);
router.post('/hangman/complete', authMiddleware, c.hangmanComplete);
router.get('/crossword/generate', authMiddleware, c.crosswordGenerate);
router.post('/crossword/complete', authMiddleware, c.crosswordComplete);
const hangman = requireFeature('hangman');
const crossword = requireFeature('crossword');
router.get('/hangman/word', hangman, authMiddleware, c.hangmanWord);
router.post('/hangman/complete', hangman, authMiddleware, c.hangmanComplete);
router.get('/crossword/generate', crossword, authMiddleware, c.crosswordGenerate);
router.post('/crossword/complete', crossword, authMiddleware, c.crosswordComplete);
module.exports = router;