From 30cfa0df1f61635dde4520b06512e93eaf406d2b Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 22 Jul 2026 02:18:22 -0500 Subject: [PATCH] docs(backend): note the /player/* group is role-agnostic self-service Record that the whole /player/* router sits behind requireAuth only (not requireRole('player')): staff are a superset of players, every handler is self-scoped to the caller, and staff reach the identical handlers under /admin/shard/*. This is why a staff account with linked characters gets its "My characters" and personal notification streams on the mobile client. Matches the code change in RunicGateway/website. Co-Authored-By: Claude --- website/BACKEND_DESIGN.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 8657bff..3956299 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -284,6 +284,16 @@ lets a client (the Android app) manage its own account through one surface witho `/admin` (docs/android/PLAN.md ยง6.4). The older `/player/account/*` + `/admin/account/*` routes stay for web back-compat. +**The `/player/*` group is self-service, not player-only.** Staff are a **superset** of players โ€” every +player ability plus their staff tools on top โ€” so the whole `/player/*` router (game-account linking, +character/vendor/house reads, credential changes, appeals) sits behind `requireAuth` **only**, never +`requireRole('player')`. Every handler is self-scoped to the caller by `req.user.id`, so an admin/editor/ +moderator using it sees only their **own** linked accounts and characters (with the pre-existing +`isAdmin` bypass still letting a genuine admin read *any* character). Staff also reach the identical +self-scoped handlers under `/admin/shard/*` (same controller) for the web admin surface; the two are +interchangeable. This is why a staff account with linked game characters gets its "My characters" and +personal notification streams on the mobile client โ€” the group no longer 403s a non-`player` role. + **Password reset.** Uses the same audited pattern as `user_invites`: an opaque 32-byte token whose **sha256 hash only** is stored in `password_resets`, single-use and short-lived (~1h). It also serves SSO-only accounts (null `password_hash`) as their "set an initial password" path. The