fix admin limiters

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-03 13:31:38 +08:00
parent d246c09cdd
commit 32c72f0b39
2 changed files with 77 additions and 5 deletions
+1 -5
View File
@@ -3,11 +3,7 @@ const ctrl = require("../../controllers/admin/courses.controller");
const progressCtrl = require("../../controllers/admin/course_reading_progress.controller"); const progressCtrl = require("../../controllers/admin/course_reading_progress.controller");
const requirementsCtrl = require("../../controllers/admin/completion_requirements.controller"); const requirementsCtrl = require("../../controllers/admin/completion_requirements.controller");
const { authenticate } = require("../../middleware/auth.middleware"); // Guards are applied by admin.routes.js (authenticate → requireAdmin → adminLimiter)
const { requireAdmin } = require("../../middleware/rbac.middleware");
const { adminLimiter } = require("../../middleware/rateLimiter.middleware");
router.use(authenticate, requireAdmin(), adminLimiter);
// ══════════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════════
// COURSES // COURSES
+76
View File
@@ -0,0 +1,76 @@
'use strict';
// courses.routes.js used to re-declare `router.use(authenticate, requireAdmin(), adminLimiter)`
// on top of the guard chain admin.routes.js already applies to every mounted admin sub-router.
// That double-application silently halved the effective adminLimiter budget (each request
// incremented the shared store twice) and surfaced as premature "Too many admin requests"
// errors. This suite locks in the invariant: the guard chain is applied exactly once, in
// admin.routes.js, and never re-declared by the sub-routers it mounts.
process.env.NODE_ENV = 'test';
const path = require('path');
const fs = require('fs');
const { authenticate } = require('../../middleware/auth.middleware');
const { adminLimiter } = require('../../middleware/rateLimiter.middleware');
const adminRoutesDir = path.join(__dirname, '../../routes/admin');
// Every admin sub-router mounted by admin.routes.js, discovered dynamically so newly
// added route files are covered automatically.
const subRouterFiles = fs
.readdirSync(adminRoutesDir)
.filter((f) => f.endsWith('.routes.js') && f !== 'admin.routes.js');
function stackHandles(router) {
return router.stack.map((layer) => layer.handle);
}
describe('admin sub-routers do not re-declare inherited guards', () => {
test.each(subRouterFiles)('%s does not re-apply authenticate or adminLimiter', (file) => {
const router = require(path.join(adminRoutesDir, file));
const handles = stackHandles(router);
expect(handles).not.toContain(authenticate);
expect(handles).not.toContain(adminLimiter);
});
// Explicit named cases for clarity: the one that broke, plus known-good references.
test('courses.routes.js (regression case) has no top-level guard layer', () => {
const router = require(path.join(adminRoutesDir, 'courses.routes.js'));
const handles = stackHandles(router);
expect(handles).not.toContain(authenticate);
expect(handles).not.toContain(adminLimiter);
});
test('units.routes.js (already correct) has no top-level guard layer', () => {
const router = require(path.join(adminRoutesDir, 'units.routes.js'));
const handles = stackHandles(router);
expect(handles).not.toContain(authenticate);
expect(handles).not.toContain(adminLimiter);
});
test('lessons.routes.js (already correct) has no top-level guard layer', () => {
const router = require(path.join(adminRoutesDir, 'lessons.routes.js'));
const handles = stackHandles(router);
expect(handles).not.toContain(authenticate);
expect(handles).not.toContain(adminLimiter);
});
});
describe('admin.routes.js applies the guard chain exactly once', () => {
test('authenticate and adminLimiter each appear exactly one time in the stack', () => {
const adminRouter = require(path.join(adminRoutesDir, 'admin.routes.js'));
const handles = stackHandles(adminRouter);
const authCount = handles.filter((h) => h === authenticate).length;
const limitCount = handles.filter((h) => h === adminLimiter).length;
expect(authCount).toBe(1);
expect(limitCount).toBe(1);
});
});