From 8ad18140d0c96347b277a1784462401845e103fd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 21:16:43 -0500 Subject: [PATCH] Re-validate JWT against the DB in isLoggedIn (#12) isLoggedIn trusted id and role straight from the JWT and never re-checked the database, so a demoted admin kept their old role and a deleted user kept a working session until the token expired (up to JWT_EXPIRES_IN). This also undercut the "last admin" guards. isLoggedIn now loads the user from the DB by the token's id on every request: a missing user returns 401 (deleted), and req.user carries the fresh DB row so the current role is always used downstream. Co-Authored-By: Claude Opus 4.8 --- server/src/utils/auth.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 327e291..7d108cf 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -2,6 +2,7 @@ const jwt = require('jsonwebtoken') require('dotenv').config() const log = require('./logger')('auth') +const users = require('../model/users/users.model') const JWT_SECRET = process.env.JWT_SECRET const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '1d' @@ -77,12 +78,22 @@ function getUserFromRequest(req) { return verifyToken(token) } -// Gate middleware for protected (admin) routes. -function isLoggedIn(req, res, next) { - const user = getUserFromRequest(req) - if (!user) return res.status(401).json({ message: 'Unauthorized' }) - req.user = user - return next() +// Gate middleware for protected (admin) routes. Re-validates the token against +// the database on every request so a demoted or deleted user loses access +// immediately, instead of keeping their old role (or a working session) until +// the JWT expires. req.user carries the fresh DB row, not the token payload. +async function isLoggedIn(req, res, next) { + const decoded = getUserFromRequest(req) + if (!decoded) return res.status(401).json({ message: 'Unauthorized' }) + try { + const user = await users.getById(decoded.id) + if (!user) return res.status(401).json({ message: 'Unauthorized' }) // deleted since token issued + req.user = user + return next() + } catch (err) { + log.error('isLoggedIn', err) + return res.status(500).json({ message: 'Internal Server Error' }) + } } module.exports = {