3 Commits

Author SHA1 Message Date
6b04aa72c1 Merge pull request 'Re-validate JWT against the DB in isLoggedIn (fixes #12)' (#16) from fix/stale-jwt-revalidation into main
Reviewed-on: UOM/website#16
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-03 02:32:07 +00:00
81318ae264 Merge branch 'main' into fix/stale-jwt-revalidation 2026-07-03 02:31:59 +00:00
8ad18140d0 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 <noreply@anthropic.com>
2026-07-02 21:16:43 -05:00

View File

@@ -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' })
}
}
// Gate middleware factory: allow only the listed roles. Assumes isLoggedIn ran