// Admin · Users — user management, MFA recovery, and a user's shard footprint. // // Mounted at /api/v1/admin/users by admin/index.js, which already applied // `noindex, isLoggedIn, staffOnly`. The whole capability is admin-only: editors // and moderators manage content and reports, never accounts. // // Handlers live in admin.controller.js. The shard footprint that used to be // wired here is now an EXTENSION SLOT (MODULE_SYSTEM.md §1.9) — see the bottom of // this file. const express = require('express') const { body, param } = require('express-validator') const ctrl = require('./admin.controller') const registries = require('../../../modules/registries') const { requireRole } = require('../../../utils/auth') const validate = require('../../../middleware/validate') const usersRouter = express.Router() const adminOnly = requireRole('admin') usersRouter.use(adminOnly) usersRouter.get( '/', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'List users (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.responses[200] = { description: 'Users', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/User" } } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ ctrl.listUsers, ) usersRouter.post( '/', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Create a user (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/UserCreateRequest" } } } } */ /* #swagger.responses[201] = { description: 'Created user', content: { "application/json": { schema: { $ref: "#/components/schemas/User" } } } } */ /* #swagger.responses[400] = { description: 'Validation error', content: { "application/json": { schema: { $ref: "#/components/schemas/ValidationError" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[409] = { description: 'Username already taken', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ body('username').isString().trim().isLength({ min: 3, max: 32 }), body('password').isString().isLength({ min: 8, max: 64 }), body('role').optional().isIn(['admin', 'editor', 'moderator', 'player']), body('status').optional().isIn(['active', 'disabled', 'banned', 'pending']), body('email').optional({ values: 'falsy' }).isEmail().isLength({ max: 255 }), validate, ctrl.createUser, ) usersRouter.put( '/:id', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Update a user (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.requestBody = { content: { "application/json": { schema: { $ref: "#/components/schemas/UserCreateRequest" } } } } */ /* #swagger.responses[200] = { description: 'Updated user', content: { "application/json": { schema: { $ref: "#/components/schemas/User" } } } } */ /* #swagger.responses[400] = { description: 'Validation error, or cannot demote the last admin', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[409] = { description: 'Username already taken', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ 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', 'moderator', 'player']), body('status').optional().isIn(['active', 'disabled', 'banned', 'pending']), body('email').optional({ values: 'null' }).isEmail().isLength({ max: 255 }), validate, ctrl.updateUser, ) usersRouter.delete( '/:id', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Delete a user (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Deleted (echoes the id)', content: { "application/json": { schema: { $ref: "#/components/schemas/DeletedId" } } } } */ /* #swagger.responses[400] = { description: 'Cannot delete your own account or the last admin', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.deleteUser, ) // ── A user's trusted devices & MFA (admin only) ─────────────────────── usersRouter.get( '/:id/trusted-devices', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'List a user’s trusted devices (admin only)' // #swagger.description = 'Active (unrevoked, unexpired) trusted devices for the target user — the browsers/apps allowed to skip that user’s TOTP step. Never returns tokens.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Trusted devices', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/TrustedDevice" } } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.listUserTrustedDevices, ) usersRouter.delete( '/:id/trusted-devices', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Revoke all of a user’s trusted devices (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'Revoked count', content: { "application/json": { schema: { type: "object", properties: { revoked: { type: "integer" } } } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.revokeAllUserTrustedDevices, ) usersRouter.delete( '/:id/trusted-devices/:deviceId', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Revoke one of a user’s trusted devices (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } // #swagger.parameters['deviceId'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'Trusted-device id.' } /* #swagger.responses[200] = { description: 'Revoked (idempotent)', content: { "application/json": { schema: { type: "object", properties: { revoked: { type: "boolean" } } } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), param('deviceId').isInt({ min: 1 }), validate, ctrl.revokeUserTrustedDevice, ) usersRouter.post( '/:id/mfa/reset', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Reset a user’s MFA (admin only)' // #swagger.description = 'Recovers a locked-out user: turns TOTP off, revokes every trusted device, and clears their recovery codes. The user can then sign in with their password alone and re-enroll.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'MFA reset', content: { "application/json": { schema: { $ref: "#/components/schemas/OkFlag" } } } } */ /* #swagger.responses[401] = { description: 'Not authenticated', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.resetUserMfa, ) usersRouter.get( '/:id', // #swagger.tags = ['Admin · Users'] // #swagger.summary = 'Get a single user (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'integer' }, description: 'User id.' } /* #swagger.responses[200] = { description: 'The user', content: { "application/json": { schema: { $ref: "#/components/schemas/User" } } } } */ /* #swagger.responses[404] = { description: 'Not found', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ param('id').isInt(), validate, ctrl.getUser, ) // ── The `admin.users.detail` extension slot (MODULE_SYSTEM.md §1.9) ──────── // // A module may hang routes off this core resource. Core DECLARES the slot; only // core may, and a module may only fill one (MODULE_API.md §2.4). What fills it // today is core's own usersShard.router.js, registered in registries.js's // registerCore() — the shard footprint that used to be wired inline right here. // Phase 3 changes the registrant, not this line. // // LAST, deliberately: every core route on the resource is already declared, so // first-match-wins means core owns any path conflict. The router is created at // declare time and filled later, because this file is required while app.js is // still being built — long before a module has been scanned. usersRouter.use('/:id', registries.declareSlot('admin.users.detail')) module.exports = usersRouter