fix(demo): lock removing a Steam link on the demo (DM27)
All checks were successful
PR Checks / server-tests (pull_request) Successful in 54s
PR Checks / client-build (pull_request) Successful in 1m5s
PR Checks / frozen-manifest (pull_request) Successful in 1m36s

The step-5 walk found demo-player could remove its stand-in Steam link from
its Rust page, and staff could remove it from Admin -> Users. That link is
what the player tour's Rust pages, rewards and grants hang on.

- module.json demoLocks gains DELETE /player/rust/links/:steamId.
- The staff route, DELETE /admin/users/:id/rust/links/:steamId, serves under
  core's users mount, which a module's demoLocks may not name, so its handler
  refuses with DEMO_LOCKED behind ctx.demoMode before the link is touched.

Off the demo both behave as before. Checked on a real core with the demo
seed: both answer 403 DEMO_LOCKED and the link stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-28 04:44:28 -05:00
parent 5c9cefdbc2
commit fb7511d700
4 changed files with 44 additions and 5 deletions

View File

@@ -1,9 +1,13 @@
// ── The public demo's module-side locks (DEMO_MODE.md §3.4, §3.5) ──────────
//
// Core locks this module's two link routes by `demoLocks` in module.json. What a
// route list cannot express lives here, behind `ctx.demoMode` (MODULE_API
// 1.18.0), and every handler that enforces one of these asks this file:
// Core locks this module's two rig-link routes and the player's Steam unlink by
// `demoLocks` in module.json. What a route list cannot express lives here, behind
// `ctx.demoMode` (MODULE_API 1.18.0), and every handler that enforces one of these
// asks this file:
//
// the staff Steam unlink on Admin → Users (step-5 walk, W4): it serves under
// core's users mount, which a module's demoLocks may not name, so its handler
// refuses instead. The demo player's seeded link stays put either way;
// the console-command deny list (§3.4, DM11): the commands that would break
// the rig for every visitor until the next wipe. Asked at save, through
// core's `demoRefuses` hook (1.19.0, DM18), and again at run;

View File

@@ -8,6 +8,7 @@
const crypto = require('node:crypto')
const core = require('../../core')
const demo = require('../../demo')
const backpacks = require('../../model/backpacks/backpacks.model')
const economics = require('../../model/economics/economics.model')
@@ -54,6 +55,10 @@ async function removeLink(req, res) {
const { steamId } = req.params
const userId = req.params.id
// On the demo the player's own unlink is locked by demoLocks; this route serves
// under core's users mount, which a module may not lock by route, so it asks here.
if (demo.isDemo()) return demo.refuse(res)
try {
const removed = await links.unlinkOwned(steamId, userId)

View File

@@ -447,11 +447,40 @@ test('the seed sets the faction table, saves each profile, and styles each group
// ── The manifest (§3.2) ────────────────────────────────────────────────────
test('module.json locks the two link routes and needs a core that asks demoRefuses', () => {
test('module.json locks the two rig-link routes and the player Steam unlink, and needs a core that asks demoRefuses', () => {
const manifest = require('../../module.json')
assert.deepEqual(manifest.demoLocks, [
{ method: 'PUT', path: '/admin/rust/servers/:id' },
{ method: 'DELETE', path: '/admin/rust/servers/:id' },
{ method: 'DELETE', path: '/player/rust/links/:steamId' },
])
assert.equal(manifest.coreApi, '^1.19.0')
})
// ── The staff Steam unlink (W4) ────────────────────────────────────────────
test('staff cannot sever a Steam link on the demo, and the link is never touched', async () => {
for (const demoMode of [true, false]) {
withCore({ demo: demoMode })
const links = require('../model/links/links.model')
const asked = []
links.unlinkOwned = async (steamId, userId) => {
asked.push([steamId, userId])
return true
}
const res = fakeRes()
await require('../router/admin/usersRust.controller').removeLink(
{ params: { id: '3', steamId: '76561190000000001' }, user: { id: 1 } },
res,
)
if (demoMode) {
assert.equal(res.statusCode, 403)
assert.equal(res.body.code, 'DEMO_LOCKED')
assert.deepEqual(asked, [])
} else {
assert.equal(res.statusCode, 200)
assert.deepEqual(res.body, { unlinked: true })
assert.deepEqual(asked, [['76561190000000001', '3']])
}
}
})