feat(permissions): per-desk till guards, jobs in the role composer, permission-scoped live feed; role reassignment applies without re-login
Permissions matrix rethink (wiki/decisions/venue-modules.md §"Permissions matrix", open-questions #16) — the grid stays the enforcement layer: - Move 1: each desk's money is guarded by that desk's own permissions. Manifest tillGuards {read, shift, cash}: booth = shift:read / shift:create / drawer:create (unchanged), carwash = carwash:read / carwash:cash (new). Shift + drawer routes resolve the guard FROM THE TILL (requireTill); a wash role holds no shift:* and cannot touch the booth by construction. Replaces the session:read borrowing (tillPermission). /api/shift/tills lists the role's readable tills with canWork; history/movements without a till filter return the union of readable tills. - Move 2: jobs — manifest permission bundles (booth-operator, booth-supervisor, merchant, wash-operator) as one-click chips in Setup → Roles, with "mixes desks" and "partial job" lints (warnings, never blocks). - Move 3: the live WebSocket admits any watch permission (event/session/device read or a module's feedPermission) and filters every push per role; report:read is the reports screen only. Auth: the token's roleId is only a hint — refreshRole() after every jwtVerify resolves the user's CURRENT role (cached, bumped on role/user writes), so reassigning a user's role applies on the next request and a deleted user's session ends with 401. Tests: till guards + look-only role, feed rules, every job's permissions exist, role reassignment without re-login. 353/353. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
+26
@@ -3003,3 +3003,29 @@ landing, all guards bounce to `/`, `/booth` needs `session:read`. Diagnosed the
|
||||
`session:create`) and lacks `carwash:create/update` — a role problem, not a code one. The
|
||||
WebSocket stays `report:read`-only by design; the desk polls. Recorded on [[shift]] §Tills
|
||||
and [[venue-modules]] §Tills → As-built.
|
||||
|
||||
## [2026-09-05] ingest | Permissions matrix rethink — three moves built
|
||||
|
||||
User: "we opened Pandora's box with this car wash module … rethink the permissions matrix";
|
||||
and "the user should have websocket for live events — this does not mean it can read
|
||||
/reports". Decision recorded on [[venue-modules]] §"Permissions matrix" (open-questions
|
||||
#16), then built: (1) per-desk till guards — manifest `tillGuards`, new `carwash:cash`,
|
||||
`requireTill(kind)` resolves the guard from the till, `tillPermission`/`session:read`
|
||||
borrowing removed; (2) jobs — manifest `jobs[]` (booth-operator, booth-supervisor,
|
||||
merchant, wash-operator) as one-click chips in Setup → Roles with "mixes desks" / "partial
|
||||
job" lints; (3) the live feed admits any WATCH permission (event/session/device read or a
|
||||
module's `feedPermission`) and filters every push per role — `report:read` is the reports
|
||||
screen only. 352/352 server tests; lavazhier (event:read) now shows LIVE. Their dev role
|
||||
still needs `carwash:cash` (+ create/update) and should drop the booth permissions — the
|
||||
"Wash operator" chip is exactly that.
|
||||
|
||||
## [2026-09-05] ingest | Role reassignment now takes effect without re-login
|
||||
|
||||
User: a user moved to a new "Lavazh NEW" role kept getting `403` on `POST /api/carwash/orders`.
|
||||
Cause: the login token pins the `roleId` current at LOGIN; `/api/auth/me` read the user row (new
|
||||
role) while every guard read the token (old role). Editing a role already took effect per
|
||||
request (the permission cache); reassigning one did not. Fix in `auth.ts`: `refreshRole()` after
|
||||
every `jwtVerify` resolves the user's CURRENT role from the DB (cached per user, cleared by
|
||||
`bumpPermsCache()`, which the user update/delete routes now call); a deleted user's session
|
||||
ends with 401 on its next request; the WS cookie path uses the same. Test: moved user creates
|
||||
an order on the next request with the same cookie. Recorded on [[local-jwt-auth]].
|
||||
|
||||
Reference in New Issue
Block a user