feat(admin): sidecar REST routes for the write plane
Phase 1 (sidecar side): POST /admin/{kick,ban,unban,broadcast} forward to the
shard, correlated on a fresh reqId, with an admin-specific status mapping —
unknown target -> 404, protected target / plane-disabled -> 403, missing actor
/ bad body -> 400. actor is required and checked up front. Documents the
endpoints and the admin.audit event in INTEGRATION.md.
Verified end-to-end (real sidecar + booted shard): 200 on success, 403 on the
Owner floor, 404 unknown target, 400 missing actor, 401 no token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
This commit is contained in:
@@ -178,6 +178,7 @@ Every event has `t` (epoch ms) and `kind`. A nested actor object looks like `{"s
|
||||
| `cheat.fastwalk` | `who`, `ip` | The shard's own speed-hack detector fired. |
|
||||
| `audit.set` | `staff`, `prop`, `target`, `targetSerial`, `old`, `new` | A staff member used `[set` to change a property. `staff` may be null. |
|
||||
| `audit.command` | `staff`, `command`, `args` | A staff command was invoked. |
|
||||
| `admin.audit` | `origin`, `action`, `actor`, `target`, `reason`, plus action-specific (`durationSec`, `sessions`, `hue`, `text`) | A moderation action was applied. `origin` is `"web"` (from the site, `actor:"web:<user>"`) or `"in-game"` (a staff member in the game client). Broadcast to every dashboard so your moderation log stays complete regardless of who acted. Emitted alongside the `admin.ok` reply for web actions; see §6. |
|
||||
|
||||
#### Account linking
|
||||
| kind | fields | notes |
|
||||
@@ -301,6 +302,55 @@ DELETE /towncrier/{id}
|
||||
|
||||
Caps apply (line count/length, active entries, duration); an over-cap post returns `towncrier.error`.
|
||||
|
||||
### Staff moderation — the write plane
|
||||
|
||||
Account and session moderation against the live shard. **These are privileged.** The sidecar does
|
||||
not model per-user roles — **your site must authenticate the staff user and check their permission
|
||||
before calling.** The shard trusts the loopback socket and applies each command with CoOwner-level
|
||||
authority, with one hard floor it enforces itself: any target at or above CoOwner (e.g. the Owner
|
||||
account) is refused (**403**). The whole plane is **opt-in on the shard** (`AdminWriteEnabled` in
|
||||
`Bridge.cfg`); when it's off, every call returns **403** `"admin write plane disabled"`.
|
||||
|
||||
Every request requires an **`actor`** — the website username/id of the staff member taking the
|
||||
action. It is recorded in the shard console log, the ban's `BanDealer` tag, and the `admin.audit`
|
||||
event, so actions are always attributable. A missing `actor` is **400**.
|
||||
|
||||
```
|
||||
POST /admin/kick { "actor":"jane", "account":"griefer42" } # or "serial":"0x2E0"
|
||||
POST /admin/ban { "actor":"jane", "account":"griefer42", "durationSec":604800, "reason":"harassment" }
|
||||
POST /admin/unban { "actor":"jane", "account":"griefer42" }
|
||||
POST /admin/broadcast { "actor":"jane", "text":"Server restart in 5 minutes", "hue":53 }
|
||||
```
|
||||
|
||||
- **kick** — disconnects every live session of the target account (including one parked at
|
||||
character-select). Target by `account` or `serial`. Reply carries `sessions` (how many were cut).
|
||||
- **ban** — bans the account (works offline) and disconnects any live sessions. `durationSec > 0`
|
||||
is a timed ban that auto-expires; `0`/absent is indefinite. Clamped to the shard's
|
||||
`AdminBanMaxDurationSec`.
|
||||
- **unban** — clears the ban.
|
||||
- **broadcast** — a system message to everyone online. `hue` optional (default `53`, staff green).
|
||||
Length-capped by the shard.
|
||||
|
||||
Success → **200** with an `admin.ok`:
|
||||
|
||||
```json
|
||||
{ "kind":"admin.ok", "reqId":"r-2", "action":"ban", "target":"griefer42", "durationSec":604800, "sessions":1 }
|
||||
```
|
||||
|
||||
Failure → an `admin.error` with a mapped status:
|
||||
|
||||
| Status | When |
|
||||
|--------|------|
|
||||
| 400 | missing `actor`, malformed body, or bad parameter |
|
||||
| 401 | missing/invalid auth token |
|
||||
| 403 | target is protected (at/above the floor), or the write plane is disabled on the shard |
|
||||
| 404 | unknown or accountless target |
|
||||
| 503 / 504 | shard not connected / didn't reply in time |
|
||||
|
||||
Each applied action also emits an unsolicited **`admin.audit`** frame on the WebSocket (§4) with
|
||||
`origin:"web"`, so every connected dashboard — not just the caller — sees it. In-game moderation
|
||||
by staff in the game client surfaces the same way with `origin:"in-game"`.
|
||||
|
||||
### History (from the sidecar's database)
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user