Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name: CI Tests

on:
# Runs tests whenever someone opens or updates a Pull Request targeting 'main' or
# 'dev' — the integration branch that publishes development images.
# Runs tests whenever someone opens or updates a Pull Request, whatever branch it targets.
#
# This used to be limited to 'main' and 'dev', so a long-running feature branch that takes
# its pieces as PRs of its own (feature/system-builder, with sb/* pieces merged into it)
# was never tested until the final merge to main. Every PR is a change about to land
# somewhere, so every PR gets the suites.
pull_request:
branches:
- main
- dev

# Runs tests when code is merged or pushed directly to 'main'.
#
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Security

- **Players can no longer use the GM's tools.** A player's own login worked as a key to the GM's
side of the server: with the right request, a signed-in player could delete buildings, edit the
map, read GM notes, approve accounts, reset passwords, set anyone's bank balance, give themselves
editor rights, or post in chat as anyone. Only the GM, and players the GM has granted editing
rights, can now do those things. Nothing a player normally does changes: their sheet, portrait,
chat, shops and bank all work as before, and granting, revoking and giving back editor rights
work as before.

- **Editor rights go only to the player receiving them.** Granting editor rights, or approving an
edit request, used to send the new editor's key to every connected player, and any of them
could copy it. It now reaches only the player being promoted. Approving, denying and ending an
edit request were also open to anyone: a player could approve their own request. Those buttons
now work only for the GM and granted editors, as they appear in the admin panel.

### Under the hood

- **The first piece of the system builder.** An engine that works out a sheet's derived
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ CITY_NET/
│ ├── net/
│ │ └── outbound.js # Every request to a host we do not own goes through here. A named destination (exact hostname, never a suffix test), HTTPS, a deadline covering the body as well as the connection, a byte cap, and no redirect following — none of which a caller can opt out of. Two callers, one auditable surface
│ ├── middleware/
│ │ ├── auth.js # JWT verify middleware (admin + elevated users)
│ │ ├── auth.js # Who a token belongs to, in one place. Every token is signed with the same secret, so a valid signature is not enough: `authenticate` admits the GM (role admin) or a granted editor, `authenticatePlayer` also admits a player's own login for their own sheet and portrait, `optionalAuthenticate` treats players as anonymous; `isMainAdmin` is what every admin socket event checks
│ │ ├── uploadConstraints.js # What an upload may be and how to say so when it is not. One message shape naming the file, what was wrong and what would have worked — plus a handler for multer's own failures, since an oversized file previously reached Express's HTML error page and the client reported a JSON syntax error to the user
│ │ ├── uploadHeaders.js # What a browser may do with a file somebody uploaded. `/uploads` is served with no auth, so a sandbox CSP puts anything opened from it in an opaque origin and nosniff stops it being re-read as HTML — which is what lets the upload allowlists stay as wide as the file pickers
│ │ └── rateLimit.js # A sliding per-caller ceiling, for the one open route that spends our outbound requests on an anonymous caller's say-so. Bounded in memory, since the key is whoever is asking; evicts the least recently seen, so it forgives rather than blocks
Expand Down Expand Up @@ -415,6 +415,7 @@ CITY_NET/
│ │ └── testDb.js # In-memory SQLite factory for isolated test DBs
│ ├── admin.test.js # Admin endpoints (auth, settings, undo access); update routes — 409 with a reason rather than a false success, unauthenticated status, boot id on /version; check-update against a stubbed registry — upgrades only, dev tags per channel, and a prerelease not hiding a stable release
│ ├── large_deletes.test.js # A map-sized city (40,000 buildings, past SQLite's bound-value limit) deleted, purged and undone; a failed delete rolling back whole; the history capped, oversized entries marked too large to undo, and a failed history write logged rather than crashing
│ ├── gm_route_auth.test.js # Walks a player's real login token against every route behind the GM check (all refused), and holds what must keep working: GM login, granted editors (grant, use, revoke, surrender), a player's own sheet and portrait, chat, and a player unable to become a socket admin, grant rights, approve their own edit request or set a bank balance; editor grants reach only the player promoted
│ ├── cpr_stats.test.js # CP:R stat rolls — BODY rollable, MOVE and LUCK not, and every roll button in the template backed by a server-side roll
│ ├── shop_checkout_sockets.test.js # The cart's checkout over the socket: totals in each direction, every way a line fails taking the whole checkout down with it, a changed total, overdraft asked once, and the payer from the socket
│ ├── nginx_config.test.js # The assumptions the app makes about the proxy every request arrives through, which no other test here touches — body ceiling at least the largest upload limit, X-Forwarded-For present, the socket able to upgrade, and every mounted path actually proxied. Two faults in one release lived exactly in that gap
Expand Down
Loading
Loading