@@ -1,10 +1,15 @@
// ── Player self-service (role: 'player') ────────── ─────────────────────────
// ── Player self-service (any authenticated account) ─────────────────────────
//
//
// The player-gated surface. Every route here requires an authenticated session
// The player self-service surface: linked game accounts, character/vendor/house
// whose fresh DB role is 'player' (staff use /admin/account for the same self-
// reads, and account-credential changes, all self-scoped to the caller by
// service). Handlers are shared with the admin account view (account.controller)
// req.user.id. Staff are a *superset* of players — they have every player ability
// — the same TOTP / identity logic, plus the net-new self-scoped credential
// plus their staff tools on top — so this group is open to any authenticated
// changes. Futur e player-only endpoints (profile, etc.) hang off this group.
// account, not just rol e ' player'. Staff also reach the identical self-scoped
// handlers under /admin/shard (they are the same controller); this group lets a
// staff account use the player surface directly. Handlers are shared with the
// admin account view (account.controller) — the same TOTP / identity logic, plus
// the net-new self-scoped credential changes. Future self-service endpoints hang
// off this group.
const express = require ( 'express' )
const express = require ( 'express' )
const { body , param } = require ( 'express-validator' )
const { body , param } = require ( 'express-validator' )
@@ -12,17 +17,18 @@ const { body, param } = require('express-validator')
const account = require ( '../admin/account.controller' )
const account = require ( '../admin/account.controller' )
const shard = require ( './shard.controller' )
const shard = require ( './shard.controller' )
const appeals = require ( './appeals.controller' )
const appeals = require ( './appeals.controller' )
const { requireAuth , requireRole } = require ( '../../../auth/session.middleware' )
const { requireAuth } = require ( '../../../auth/session.middleware' )
const noindex = require ( '../../../middleware/noindex' )
const noindex = require ( '../../../middleware/noindex' )
const validate = require ( '../../../middleware/validate' )
const validate = require ( '../../../middleware/validate' )
const { accountChangeLimiter } = require ( '../../../middleware/rateLimit' )
const { accountChangeLimiter } = require ( '../../../middleware/rateLimit' )
const playerRouter = express . Router ( )
const playerRouter = express . Router ( )
// Group gate: authenticated + fresh role must be ' player', and keep it out of
// Group gate: authenticated only (no role restriction) — players and staff alike
// search indexes. requireAuth also enforces the account status check (a
// u se this self-service surface; every read/write is scoped to the caller. Keep it
// disabled/banned player is rejected here with 403 before any handler runs).
// out of search indexes. requireAuth also enforces the account status check (a
playerRouter . use ( noindex , requireAuth , requireRole ( 'player' ) )
// disabled/banned account is rejected here with 403 before any handler runs).
playerRouter . use ( noindex , requireAuth )
playerRouter . get (
playerRouter . get (
'/account' ,
'/account' ,
@@ -31,7 +37,7 @@ playerRouter.get(
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
/* #swagger.responses[200] = { description: 'The player account', content: { "application/json": { schema: { $ref: "#/components/schemas/PlayerAccount" } } } } */
/* #swagger.responses[200] = { description: 'The player account', content: { "application/json": { schema: { $ref: "#/components/schemas/PlayerAccount" } } } } */
/* #swagger.responses[401] = { description: 'Not authenticated', 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: 'Player role required, or account not active ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[403] = { description: 'Account not active (disabled/banned) ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
account . getAccount ,
account . getAccount ,
)
)
@@ -43,7 +49,7 @@ playerRouter.patch(
/* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/ChangeUsernameRequest" } } } } */
/* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/ChangeUsernameRequest" } } } } */
/* #swagger.responses[200] = { description: 'Updated username (session cookie re-issued)', content: { "application/json": { schema: { type: "object", properties: { username: { type: "string" } } } } } } */
/* #swagger.responses[200] = { description: 'Updated username (session cookie re-issued)', content: { "application/json": { schema: { type: "object", properties: { username: { type: "string" } } } } } } */
/* #swagger.responses[400] = { description: 'Validation error or unavailable username', content: { "application/json": { schema: { $ref: "#/components/schemas/ValidationError" } } } } */
/* #swagger.responses[400] = { description: 'Validation error or unavailable username', content: { "application/json": { schema: { $ref: "#/components/schemas/ValidationError" } } } } */
/* #swagger.responses[403] = { description: 'Player role required, or account not active ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[403] = { description: 'Account not active (disabled/banned) ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[409] = { description: 'Username already taken', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[409] = { description: 'Username already taken', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[429] = { description: 'Too many changes (rate limited)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[429] = { description: 'Too many changes (rate limited)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
accountChangeLimiter ,
accountChangeLimiter ,
@@ -61,7 +67,7 @@ playerRouter.patch(
/* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/ChangePasswordRequest" } } } } */
/* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/ChangePasswordRequest" } } } } */
/* #swagger.responses[200] = { description: 'Password changed', content: { "application/json": { schema: { $ref: "#/components/schemas/OkFlag" } } } } */
/* #swagger.responses[200] = { description: 'Password changed', content: { "application/json": { schema: { $ref: "#/components/schemas/OkFlag" } } } } */
/* #swagger.responses[400] = { description: 'Validation error or wrong current password', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[400] = { description: 'Validation error or wrong current password', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[403] = { description: 'Player role required, or account not active ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[403] = { description: 'Account not active (disabled/banned) ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[429] = { description: 'Too many changes (rate limited)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[429] = { description: 'Too many changes (rate limited)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
accountChangeLimiter ,
accountChangeLimiter ,
body ( 'newPassword' ) . isString ( ) . isLength ( { min : 8 , max : 64 } ) ,
body ( 'newPassword' ) . isString ( ) . isLength ( { min : 8 , max : 64 } ) ,
@@ -242,7 +248,7 @@ playerRouter.get(
// #swagger.summary = 'List the caller’ s moderation appeals'
// #swagger.summary = 'List the caller’ s moderation appeals'
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
/* #swagger.responses[200] = { description: 'The caller’ s appeals', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Appeal" } } } } } */
/* #swagger.responses[200] = { description: 'The caller’ s appeals', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Appeal" } } } } } */
/* #swagger.responses[403] = { description: 'Player role required, or account not active ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[403] = { description: 'Account not active (disabled/banned) ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
appeals . listMine ,
appeals . listMine ,
)
)
playerRouter . get (
playerRouter . get (
@@ -252,7 +258,7 @@ playerRouter.get(
// #swagger.description = 'The caller’ s ban/mute mod_actions that have no active appeal. Returns an empty array when the caller has no linked Discord account (the UI shows a “link Discord” hint).'
// #swagger.description = 'The caller’ s ban/mute mod_actions that have no active appeal. Returns an empty array when the caller has no linked Discord account (the UI shows a “link Discord” hint).'
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
// #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }]
/* #swagger.responses[200] = { description: 'Appealable actions', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/AppealEligibleAction" } } } } } */
/* #swagger.responses[200] = { description: 'Appealable actions', content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/AppealEligibleAction" } } } } } */
/* #swagger.responses[403] = { description: 'Player role required, or account not active ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
/* #swagger.responses[403] = { description: 'Account not active (disabled/banned) ', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */
appeals . listEligible ,
appeals . listEligible ,
)
)
playerRouter . post (
playerRouter . post (