Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14 — QA Checklist (RBAC Module)

Module-specific QA on top of 00-shared/10. Every scenario below traces to a server behaviour (rbac.service.ts, rbac.guard.ts, role.schema.ts, organization-member.schema.ts) or a wire-contract rule. Cross-ref: PLAN.md rows 1.5 (PLAN.md:17), 10.1-10.5 (PLAN.md:106-110), 12.x (PLAN.md:127-132), 14.x (PLAN.md:146-148), 19.x (PLAN.md:189-191).


1. Permission matrix — edge cases

#ScenarioExpectedSource
M1Catalog has exactly 95 perms; matrix renders all 26 groups; count badge = 95no truncation, scrollablepermissions.constants.ts:1-97
M2Toggle all in group → group checkbox checked; toggle one → indeterminatetri-state correct06 §3
M3Search "pay" → only payments/receipts group + perms; 0-match groups collapsefilter correct; liveRegion count07 §1-2
M4Save with empty permissions → warning shown, POST succeeds (role like student, role.schema.ts:63)empty allowed08 §1
M5Permission string not in catalog (client injection attempt) → blocked client-sidewhitelist enforced in UIserver @IsArray only, create-role.dto.ts:36
M6Role with 95 perms (clone org_admin) → save round-trip < 1.5 s p95single PATCHrbac.service.ts:96
M7Rapid toggling while saving → anti-double-submit; state not corrupteddisabled during save10 §4
M895-perm matrix at text scale 2× → no clipping; chips wrap00-shared/09 §4
M9Matrix in dark mode — chip contrast ≥ 4.5:1token check11 §6

2. Roles CRUD & custom-role persistence

#ScenarioExpectedSource
R1Create role → appears in Roles list under Custom section; persists across app restart (server fetch)persistencerbac.service.ts:83-89
R2Create with existing slug → 409 inline, no state loss (form preserved)error mappingrbac.service.ts:86-87
R3Create without forcing isSystem:false (bug path) → server would lock; UI always sends falseregression guard (OQ-R2)role.schema.ts:78-79
R4Edit custom role perms → PATCH 200 → detail refreshed; holders see change ≤ 5 min (cache)propagation bannerrbac.service.ts:68,96
R5System role edit/delete → UI blocks (no editor, no delete); API 400 fallback shown if reachedlockedrbac.service.ts:94-95,104-105
R6Delete custom role → soft-deleted; gone from list; members keep stale slug chip (render-only)rbac.service.ts:106; 09 §2
R7Role slug immutability communicated; PATCH slug would orphan member arrays — UI treats as immutable08 §1
R8Duplicate slug on update → 409 handled identically to createrbac.service.ts:86-87 (create path only — update path 409 via unique index; OQ-R11)

3. Members & 403 behaviour

#ScenarioExpectedSource
B1Non-org_admin (e.g. teacher JWT) calls /rbac/roles → 403 PERMISSION_DENIEDenvelope; no datarbac.controller.ts:21, rbac.guard.ts:38-42
B2Client routes hidden for teacher; direct deep link → 403 screen with cause copy + requestIdPLAN.md:17 (1.5)04 §3, 06 §S6
B3Member with perm rbac.member.read but not org_admin → today 403 (OQ-R1) — UI must not promise accessflagged, not silentrbac.controller.ts:21
B4Add duplicate member → client pre-check blocks; if server 500 slips through → conflict banner + refresh (OQ-R5)no silent errororganization-member.schema.ts:48
B5Remove member → row gone; re-fetch confirms isDeleted filtered (base.repository.ts:20-30)rbac.service.ts:138-140
B6Update member roles → PATCH 200; chips update; userId sent but ignored (DTO compat)rbac.controller.ts:71-72
B7Remove own org_admin → typed confirm; after removal JWT still grants access until expiry — banner explains re-login09 §4

4. Tenant isolation

#ScenarioExpectedSource
T1Tenant A admin lists roles/members → zero Tenant B docs (repo scoping)base.repository.ts:20-30; PLAN.md:106 (10.1)
T2Tenant B user uses Tenant A JWT → 401/403 by token claimsjwt-auth.guard.ts:40-55; PLAN.md:107 (10.2)
T3Role slug collision across tenants → both create successfully (unique index is {tenantId, slug})role.schema.ts:89
T4Member {tenantId,userId} unique per tenant — same user in 2 tenants = 2 memberships (supported model, COLLECTIONS.md:554-570)organization-member.schema.ts:48
T5Permission cache keys namespaced sl:{tenantId}:perm:{userId} — no cross-tenant cache leakrbac.service.ts:48; PLAN.md:109 (10.4)
T6platform_admin reads across tenants via repo bypass — UI must not render tenant RBAC data cross-tenant without an org_admin tokenbase.repository.ts:21; 02 §4
T7seedDefaults(org.slug) vs seedDefaults(user.tenantId) inconsistency — verify tenantId == slug after org-created flow (OQ-R6)organizations.service.ts:58, auth.service.ts:93

5. Privilege-escalation scenarios (security focus)

#Attack / scenarioDefenceSource
P1Client sets permissions: ['organization.delete'] on own role via PATCHServer: org_admin-role gate only; once perms are enforced (Phase-5 planned) the guard rejects. Client: matrix whitelist. Today: any org_admin can craft any body — inherent to role-gated admin; documented.rbac.guard.ts:43-49, docs/IMPLEMENTATION_PLAN.md:241
P2Client sets isSystem:true on create to lock role (no privilege gain — lock is restrictive)no vector; still forced false in UIcreate-role.dto.ts:27, OQ-R2
P3Create role with slug org_admin → 409 (unique) — cannot shadow system rolerole.schema.ts:89
P4Crafted perm string '*' or unknown 'a.b.c' in roles → server stores it; guard every() fails for real endpoints (no grant) — matrix whitelist prevents UI pathwhitelistrbac.guard.ts:48
P5Member with removed role still calls role-checked endpoint → JWT claim valid until token expiry (≤ 15 m access) — document in security review (OQ-R4)token TTLenv.ts (15 m access), auth.service.ts:192-196
P6platform_admin tries /rbac/* → 403 (not org_admin) — no tenant cross-read via RBAC UIrbac.controller.ts:21
P7Audit: RBAC writes must appear in audit-logs — verify action strings recorded for role/member CRUD (exact strings OQ-R8)PLAN.md:189-191 (19.x)

6. Wire-contract checks

#Check
W1Success envelope: {success:true, message:"OK", data, meta?, timestamp, requestId} — arrays without meta for roles/members; with meta for audit
W2Error codes: 400 VALIDATION_ERROR (field details), 403 PERMISSION_DENIED, 404 RESOURCE_NOT_FOUND, 409 DUPLICATE_RESOURCE, 429 RATE_LIMITED, 5xx INTERNAL_SERVER_ERROR
W3requestId echoed; client-generated x-request-id honored
W4CastError (bad :id) → 400 "Invalid resource identifier."
W5429 on RBAC in production only; countdown copy; no auto-retry

7. Performance & a11y (from 00-shared/10, RBAC-specific)

  • Matrix first frame < 300 ms (cached catalog), scroll 60 fps on mid-range device; RepaintBoundary per group (15 §2).
  • TalkBack: matrix chip toggle announces group + checked state; audit table headers announced; typed self-removal confirm readable.
  • Text scale 2× on matrix/roles/members; dynamic type no clipping (M8).
  • Dark/light golden per screen (S1–S6).

8. Regression hooks (test plan linkage)

  • Unit: PermissionMirror TTL/invalidate; matrix tri-state logic; slug validator.
  • Widget: S1–S6 × {loading, error, empty, content}; 403 screen.
  • Integration: J1 (create role), J3 (add member), J4 (accountant/HR custom role), J5 (guard-denied) — mapped to PLAN.md:17 (1.5), PLAN.md:107-110 (10.x).
  • E2E: cross-tenant isolation (T1–T6) with two seeded tenants.