From 43db5092939116c7869e2acc93f3f0cde1fdcd20 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 21:38:16 -0500 Subject: [PATCH] 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 --- server/src/router/v1/admin/admin.controller.js | 7 +++++++ server/src/router/v1/admin/admin.routes.js | 1 + 2 files changed, 8 insertions(+) diff --git a/server/src/router/v1/admin/admin.controller.js b/server/src/router/v1/admin/admin.controller.js index bba3c83..7d24996 100644 --- a/server/src/router/v1/admin/admin.controller.js +++ b/server/src/router/v1/admin/admin.controller.js @@ -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) { diff --git a/server/src/router/v1/admin/admin.routes.js b/server/src/router/v1/admin/admin.routes.js index 5e774d6..83de1d6 100644 --- a/server/src/router/v1/admin/admin.routes.js +++ b/server/src/router/v1/admin/admin.routes.js @@ -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,