Enforce role-based authorization on admin-only routes (fixes #10) #15
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/role-authorization"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes #10.
isLoggedInonly verified that the JWT was valid — no route ever checkedreq.user.role. Any authenticated editor could therefore call every admin endpoint: create/promote/delete users, flip the site in/out of maintenance, change settings, delete wiki pages, etc. This is a privilege-escalation hole.This PR adds a role gate and applies it to the admin-only routes.
What changed
server/src/utils/auth.js— newrequireRole(...roles)middleware factory. It assumesisLoggedInhas already run (soreq.useris set) and returns403 Forbiddenwhenreq.user.roleis not in the allowed list.server/src/router/v1/admin/admin.routes.js— definesconst adminOnly = requireRole('admin')and applies it to the sensitive routes:PUT /site-modeGET /settings,PUT /settings/users/*— gated once withadminRouter.use('/users', adminOnly)placed before the user route definitions, so list/create/update/delete are all covered by a single line.Access model (per-route decision)
The two roles map to a content-vs-administration split:
PUT /site-modeGET/PUT /settings/users/*(manage accounts)Rationale: an
editoris a content role, so posts and wiki stay open to them. Anything that can change who has access or how the whole site behaves (users, site mode, settings) is restricted toadmin.Why this approach
if (req.user.role !== 'admin')branches in each controller. It matches the suggested fix in the issue and composes with the existingisLoggedInchain.adminRouter.use('/users', adminOnly)— gating the subtree in one place is harder to get wrong than annotating four separate user routes, and any future/users/*route is protected by default.auth.jsalongsideisLoggedInso all auth/authorization primitives live together and are exported from one module.Testing
node -csyntax check on both changed files.Notes
Purely additive/backend. No DB or client changes. Independent of the companion PR for #12 (stale-JWT revalidation); the two reinforce each other — once #12 lands,
req.user.roleis always the fresh DB role, which is exactly what this gate reads.It works and is properly gated