Validate and uniqueness-check username on user update (#13)

PUT /admin/users/:id validated password and role but not username, even
though updateUser writes req.body.username. A blank/too-short username
could be saved, and a duplicate hit the DB unique constraint and
surfaced as an opaque 500.

- Route: add the same validator used on create,
  body('username').optional().isString().trim().isLength({min:3,max:32}).
  The trim sanitizer also collapses whitespace-only input so it fails
  the min-length check.
- Controller: when the username is changing, pre-check for another user
  with that name and return 409 instead of letting the DB throw a 500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 21:38:16 -05:00
parent 6b04aa72c1
commit 43db509293
2 changed files with 8 additions and 0 deletions

View File

@@ -475,6 +475,13 @@ async function updateUser(req, res) {
const target = await users.getById(id)
if (!target) return res.status(404).json({ message: 'Not found' })
// If the username is changing, make sure no other user already has it —
// return 409 rather than letting the DB unique constraint throw a 500.
if (req.body.username && req.body.username !== target.username) {
const clash = await users.getRawByUsername(req.body.username)
if (clash) return res.status(409).json({ message: 'Username already taken' })
}
// Don't let the last admin demote themselves out of admin access.
if (target.role === 'admin' && req.body.role && req.body.role !== 'admin') {
if ((await users.countAdmins()) <= 1) {

View File

@@ -160,6 +160,7 @@ adminRouter.post(
adminRouter.put(
'/users/:id',
param('id').isInt(),
body('username').optional().isString().trim().isLength({ min: 3, max: 32 }),
body('password').optional().isString().isLength({ min: 8, max: 64 }),
body('role').optional().isIn(['admin', 'editor']),
validate,