// Admin · uo-link — the sidecar connection config, the town crier, and the // staff SSE stream. // // Mounted at /api/v1/admin/uo-link by admin/index.js, which already applied // `noindex, isLoggedIn, staffOnly`. This is where shard integration is // configured: base/ws URL, bearer token, protocol version and the enabled // toggle all live in the DB (uoLinkConfig), never in env. The token is // write-only over this API (SECURITY note in uoLink.controller.js). // // /stream is the ADMIN SSE channel — it carries staff audit, cheat detection // and login attempts on top of the public event kinds. The public/admin // allowlist split in utils/shardIngest.js is a security boundary; the adminOnly // gate below is its other half. // // The routes keep their `Admin · Shard` swagger tag: retagging is a real // OpenAPI diff and does not belong in a route-move PR. // // Admin-only, and kept as a per-route gate rather than a router-level `use` so // the middleware chain each route carries is unchanged by the move. const core = require('../../core') const express = core.express const { body, param } = core.validator const uoLink = require('./uoLink.controller') const gameSignup = require('../../utils/gameSignup') const { requireRole, validate } = core.middleware const uoLinkRouter = express.Router() const adminOnly = requireRole('admin') uoLinkRouter.get( '/config', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Get uo-link config + live status + ingestion stats (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.responses[200] = { description: 'Masked config, health and ingestion stats', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ adminOnly, uoLink.getConfig, ) uoLinkRouter.put( '/config', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Save uo-link connection config (admin only)' // #swagger.description = 'token is write-only — omit/blank it to keep the existing one. Saving (re)starts the WS ingest client.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { type: "object", properties: { baseUrl: { type: "string" }, wsUrl: { type: "string" }, token: { type: "string" }, protocol: { type: "integer" }, enabled: { type: "boolean" } } } } } } */ /* #swagger.responses[200] = { description: 'Updated config + live status', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */ /* #swagger.responses[400] = { description: 'Validation error, or missing token while enabling', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ adminOnly, body('baseUrl').optional({ values: 'falsy' }).isString().trim().isURL({ require_tld: false, protocols: ['http', 'https'] }), body('wsUrl').optional({ values: 'falsy' }).isString().trim().isURL({ require_tld: false, protocols: ['ws', 'wss'] }), body('token').optional({ values: 'falsy' }).isString().trim(), body('protocol').optional().isInt({ min: 1, max: 99 }), body('enabled').optional().isBoolean(), validate, uoLink.saveConfig, ) // ── Game-account signup mode ─────────────────────────────────────────────── // // New in slice 3, and new only in the sense that the field moved: core's Site // Settings has carried `game_account_signup` since long before the extraction, // and its help text has always been about a game server. The setting key and its // stored value are unchanged, so an existing instance keeps its configured mode. uoLinkRouter.get( '/signup-mode', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Get the game-account signup mode (admin only)' // #swagger.description = 'Whether the site offers game-account creation, and in which direction. The shard’s own SignupMode (Bridge.cfg) must agree: website/hybrid accept site-created accounts, game refuses them.' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.responses[200] = { description: 'The configured mode and the legal values', content: { "application/json": { schema: { type: "object", properties: { mode: { type: "string" }, modes: { type: "array", items: { type: "string" } } } } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ adminOnly, uoLink.getSignupMode, ) uoLinkRouter.put( '/signup-mode', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Set the game-account signup mode (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { type: "object", required: ["mode"], properties: { mode: { type: "string", enum: ["disabled","website","hybrid","game"] } } } } } } */ /* #swagger.responses[200] = { description: 'The saved mode', content: { "application/json": { schema: { type: "object", properties: { mode: { type: "string" } } } } } } */ /* #swagger.responses[400] = { description: 'Unknown mode', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[403] = { description: 'Admin role required', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ adminOnly, // Validated here as well as in gameSignup.setMode: the list is the same list, // and the difference is the answer. A rejected value must be a 400 naming the // field, not a 500 from a thrown Error the controller could only guess about. body('mode').isIn(gameSignup.MODES), validate, uoLink.saveSignupMode, ) uoLinkRouter.post( '/towncrier', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Publish / replace a town-crier message (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] /* #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/UoTownCrierRequest" } } } } */ /* #swagger.responses[200] = { description: 'Posted', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */ /* #swagger.responses[400] = { description: 'Rejected (over caps)', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ /* #swagger.responses[503] = { description: 'Shard unavailable', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ adminOnly, body('id').isString().trim().isLength({ min: 1, max: 64 }), body('lines').isArray({ min: 1, max: 8 }), body('lines.*').isString().isLength({ max: 200 }), body('durationSec').optional().isInt({ min: 1, max: 86400 }), validate, uoLink.postTownCrier, ) uoLinkRouter.delete( '/towncrier/:id', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Remove a town-crier message (admin only)' // #swagger.security = [{ "cookieAuth": [] }, { "bearerAuth": [] }] // #swagger.parameters['id'] = { in: 'path', required: true, schema: { type: 'string' }, description: 'Town-crier message id.' } /* #swagger.responses[200] = { description: 'Removed', content: { "application/json": { schema: { type: "object", additionalProperties: true } } } } */ /* #swagger.responses[404] = { description: 'Unknown id', content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } } */ adminOnly, param('id').isString().trim().isLength({ min: 1, max: 64 }), validate, uoLink.deleteTownCrier, ) uoLinkRouter.get( '/stream', // #swagger.tags = ['Admin · Shard'] // #swagger.summary = 'Full live shard event stream incl. audit/cheat (SSE, admin only)' /* #swagger.responses[200] = { description: 'An SSE stream (Content-Type: text/event-stream).' } */ adminOnly, uoLink.stream, ) module.exports = uoLinkRouter