Wellbeing history was browsable by every staff role - #144
Merged
Conversation
…role Found by walking the Sozialarbeit role in production — the one care role AOZ is now hiring for and the only one never exercised. Three findings, all the same shape: a permission that exists, that the nav honours, and that nothing on the server enforces. 1. SatisfactionHistory on residents/[id] had NO permission check. Every staff role saw a client's full wellbeing check-in history by opening their page. Confirmed earlier this session against a Jobcoach, who holds neither `placements:read` nor `incidents:read` and still got "Zufriedenheits- Check-ins" on any client he opened. This is the complaint the whole refactor started from — staff seeing the smiley scale as a property of the client rather than of an appointment they conducted. Moving CAPTURE into closing an appointment fixed half of it and left READING wide open. Now gated on `placements:read`, matching /placements and /analytics. A role without it is not cut off from someone in trouble: a check-in of 1 or 2 already raises a WELLBEING incident, which carries its own permission. The signal still travels; browsing someone's mood history does not. 2. /maintenance, /maintenance/new and /maintenance/[id] had no guard at all, and all five server actions checked `requireStaffAuth()` — signed in, nothing more. The nav gates Wartung on `maintenance:read`. Verified in production as a Sozialarbeiter*in holding neither maintenance permission: the nav hid Wartung and the URL served her the board, with working "Neue Anfrage", "Zuweisen" and "Abschliessen" buttons. Pages and actions now enforce read/write. Residents are unaffected — /api/portal/report writes MaintenanceRequest via Prisma directly, not through these actions. If other roles SHOULD be able to report a defect they noticed on a visit — plausible in distributed housing — that is a deliberate ROLE_PERMISSIONS change, not an unguarded page. Flagged, not decided. 3. My own gate was too weak to catch #2. `admin-page-guards.test.ts` said in a comment that a page both enforcing a permission AND sitting on the session-is-enough list is a stale entry, then asserted an OR that allowed exactly that. Tightened to exactly-one, which is what the comment claimed. Also corrects "There is no Leitung", asserted as settled fact in CLAUDE.md, role-policy.ts and schema.prisma. AOZ was recruiting a Programmleiter*in and a Teamleiter*in Betreuung for the `Begleitung im regulären Wohnraum` pilot this product is named after; only those three named people have no lead among them. The split already expresses a team lead — BETREUUNG + ALL_DOMAINS + not isSystemAdmin — so the correction must not become a LEITUNG enum value, which would rebuild the bundled role ADMIN was retired for. Pinned by a test. All three fixes mutation-proven. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cd183M6472xBgTKWA2is6h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found by walking the Sozialarbeit role in production — the one care role AOZ is now hiring for, and the only one never exercised.
Three findings, all the same shape: a permission that exists, that the nav honours, and that nothing on the server enforces.
1. A client's wellbeing history was open to every role
SatisfactionHistoryonresidents/[id]had no permission check at all. Any staff role saw a client's full check-in history just by opening their page — confirmed against a Jobcoach, who holds neitherplacements:readnorincidents:read.This is the complaint this whole refactor started from: staff seeing the smiley scale as a property of the client rather than of an appointment they conducted. Moving capture into closing an appointment fixed half of it and left reading open.
Now gated on
placements:read, matching/placementsand/analytics. A role without it is not cut off from someone in trouble — a check-in of 1 or 2 already raises aWELLBEINGincident, which carries its own permission. The signal still travels; browsing someone's mood history does not.2. Maintenance declared permissions it never enforced
The three maintenance pages had no guard, and all five server actions checked
requireStaffAuth()— signed in, nothing more. The nav gates Wartung onmaintenance:read.Verified in production as a Sozialarbeiter*in holding neither maintenance permission: the nav correctly hid Wartung, and the URL served her the whole board with working Neue Anfrage, Zuweisen and Abschliessen buttons.
Pages and actions now enforce read/write. Residents are unaffected —
/api/portal/reportwritesMaintenanceRequestvia Prisma directly, not through these actions.3. My own gate was too weak to catch #2
admin-page-guards.test.tssaid in a comment that a page both enforcing a permission and sitting on the session-is-enough list is a stale entry — then asserted anORthat allowed exactly that. Tightened to exactly-one.Also: "There is no Leitung" was wrong
Asserted as settled fact in
CLAUDE.md,role-policy.tsandschema.prisma. AOZ was recruiting a Programmleiter*in and a Teamleiter*in Betreuung for the «Begleitung im regulären Wohnraum» pilot this product is named after. Only those three named people have no lead among them.The split already expresses a team lead —
BETREUUNG+ALL_DOMAINS+ notisSystemAdmin— so the correction must not become aLEITUNGenum value, which would rebuild the bundled roleADMINwas retired for. Pinned by a test.All three fixes mutation-proven. 199 suites / 3511 tests green.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Cd183M6472xBgTKWA2is6h