Skip to content

RBAC Sprint Plan — Cross-Functional War Room Output

Context: 75 issues from User Management & RBAC Deep-Dive Generated: 2026-02-25 | Last Updated: 2026-02-25 | Method: Cross-Functional War Room (PM + Engineer + Designer) Participants: PM (business impact), Engineer (effort/risk), Designer (UX impact) Related: Attack Scenarios | Deep-Dive Post-Update Note: Backend refactored Feb 18-22 (middleware.ts now active, new permissionMiddleware.ts v2, auth.ts restructured). All 6 attack vectors re-verified STILL VULNERABLE. Sprint items updated after comparative analysis + devil's advocate challenge.


Comparative Analysis Scorecard (Feb 25)

Scored every sprint item against the current codebase after the Feb 18-22 refactoring:

Phase Items Resolved Still Needed Modified Notes
Ship Blockers 17 0 17 0 None fixed by refactoring. +3 v2 middleware prerequisites (PRE-½/3) added after expert panel.
Sprint 1 14 0 9 3 S1-2, S1-3 need architectural rethink; S1-8 reduced scope
Sprint 2 20 1 19 0 S2-18 (api.md) resolved by separate doc rewrite
Backlog 24 0 24 0 +6 new items from post-scan
Total 72 1 66 3 The refactoring focused on auth restructuring, NOT security fixes

Triage Summary

Bucket Criteria Count Effort
Ship Blocker Cannot deploy. Security exploit, data loss, or core feature broken. 17 ~65 min
Sprint 1 RBAC doesn't work for non-super_admin users. Architectural unification. 14 ~7 hrs
Sprint 2 Data model cleanup, seed robustness, documentation, testing. 20 ~12 hrs
Backlog Low-impact cosmetics, dead code, minor inconsistencies. 18 Ongoing

Ship Blockers (Pre-Release Gate)

Timeline: ~60 minutes, 1 developer Exit criteria: All 6 attack vectors blocked. Admin can create users, change passwords, manage invites.

Order Issue Description File Change Effort
1 #5b Strip role/roleId from updateProfile user.service.ts:533 Remove user.roleId = payload.role \|\| user.roleId; 5 min
1b #5b Strip role from profile validation schema user.validation.ts:19-24 Create separate updateProfileValidation without role field 10 min
2 #4 Uncomment createRole permission check role.service.ts:150 Uncomment await checkPermissionAndThrow("role.create", "create a role"); 1 min
3 #5 Add permission check to grantPermissionToRole permission.service.ts:106 Add await checkPermissionAndThrow("permission.grant", "grant permissions to role"); 2 min
4 #23 Add permission check to getAllPermissions permission.service.ts:461 Add await checkPermissionAndThrow("permission.view", "view all permissions"); 1 min
5 #15 Add permission check to getUserSetting/getSettingById user_setting.service.ts:261,288 Add await checkPermissionAndThrow("user-setting.view", "view user settings"); 3 min
6 #16 Set twoFactorSecret to select: false UserSetting.ts:46 Change select: trueselect: false (also tempTwoFactorSecret) 1 min
7 #6 Fix updateProfile return res on password change user.controller.ts:59-61 Replace return res with proper sendResponse() call 5 min
8 #1 Fix create-user mutation users-page.jsx handleFormSubmit Change updateUser.mutate(userData)createUser.mutate(userData) for create mode; import useCreateUser 5 min
9 #10 Fix HTTP status code for permission denial checkPermissionAndThrow.ts:19 Change httpStatus.BAD_REQUESThttpStatus.FORBIDDEN 1 min
10 #28 Seed missing permission strings seed/data/permission.ts Add invite.* (6), users.assign.roles, users.update.role, payment.view, user-setting.view 10 min
11 #17 Remove TOTP secret from 2FA setup response user_setting.service.ts:173,186 Remove secret: secret.base32 from return objects 2 min
12 #35 Remove backup code plaintext log user_setting.service.ts:72 Delete console.log(code) 1 min
13 #12, #13 Remove debug console.logs user.service.ts:490, hasPermission.ts:68 Delete console.log({ payload }) and console.log({cachedResult}) 2 min
14 New Add user status check in contextMiddleware contextMiddleware.ts (after loading fullUser) Add: if (fullUser.status !== 'active') return next(new ApiError(403, "Account not active")); 10 min
15 #76 (PRE-1) Add allPermissionAssignToAdmin() to seed pipeline seed.ts (after permissionSeed(), before seedSuperAdmin()) Add: await PermissionService.allPermissionAssignToAdmin(); — without this, super_admin has ZERO permissions in role_permission table, v2 middleware blocks super_admin 1 min
16 #76 (PRE-2) Add super_admin bypass to v2 permission() middleware permissionMiddleware.ts (before has() check) Add: if (req.user.role === 'super_admin') return next(); — belt-and-suspenders for new permissions not yet in role_permission 2 min
17 #76 (PRE-3) Add null-safety for req.user.permissions in v2 middleware permissionMiddleware.ts (before has() check) Add: if (!req.user?.permissions) throw new ApiError(401, "Unauthorized"); — prevents TypeError when contextMiddleware fails 1 min

Verification Checklist

After completing all 14 ship blockers:

  • PATCH /users/profile { role: 3 } does NOT change the user's role
  • POST /roles without role.create permission returns 403
  • PUT /permissions/grant without permission.grant permission returns 403
  • GET /permissions/all without permission.view permission returns 403
  • GET /user-settings without user-setting.view permission returns 403
  • Suspended user (status: inactive) gets 403 on all endpoints
  • Admin can create a new user via the Users page (modal → success toast)
  • Password change via profile completes with proper response (not hanging)
  • yarn seed creates all invite/payment/setting permissions
  • No console.log output for profile updates, permission checks, or backup codes

Sprint 1: Make RBAC Actually Work (Week 1)

Timeline: ~7 hours, 1 developer North Star: A non-super_admin user with appropriate permissions can perform all CRUD operations. Exit criteria: Log in as "admin" role (not super_admin), verify all Users/Roles/Permissions/Invites pages work.

# Issue(s) Description File(s) Effort
S1-1 #8, #9 Unify permission system. Delete middleware v1 (hasPermission.ts), keep v2 (permissionMiddleware.ts). Move GET /payments check to permission("payment.view") using v2, then delete v1. Fix hasAnyPermission bug (#69). middlewares/hasPermission.ts (DELETE), permissionMiddleware.ts (FIX), payment.route.ts 2 hrs
S1-2 #37 CORRECTED: Restructure contextMiddleware pipeline. The global contextMiddleware in middleware.ts is a no-op for JWT-auth (runs before auth(), req.user is undefined → short-circuits). The per-route registrations are the ONLY effective invocation — do NOT remove them. Instead: either (a) remove the useless global registration from middleware.ts:77, or (b) move auth() into middleware.ts before contextMiddleware so global registration works, then remove per-route duplicates. Option (b) is cleaner but higher risk. middleware.ts, route files 1.5 hr
S1-3 #18 CORRECTED: Depends on S1-2 decision. If S1-2 option (b) is chosen (global auth+context), then per-route auth(), contextMiddleware on individual permission routes become truly redundant and should be removed. If S1-2 option (a), these stay because they're the only effective invocation. The router.use(auth(), contextMiddleware) at line 9 already handles routes after it, but individual routes re-apply it — those ARE redundant even with option (a). permission.routes.ts 30 min
S1-4 #22 Delete permissionNormalizer. Remove file + remove imports from helper/hasPermission.ts. Simplify checkPermissionInRoles to direct string match. permissionNormalizer.ts (DELETE), helper/hasPermission.ts 15 min
S1-5 #7 Delete UserRole entity. Remove file + dead import from seed.ts. UserRoles.ts (DELETE), seed.ts 5 min
S1-6 #14 Fix duplicate userRoles validation. Remove the unconditional second validation block (lines 352-363) in updateUser. user.service.ts:352-363 15 min
S1-7 #3 REDUCED PRIORITY (Low). Fix permission route ordering — move GET /user-permissions before GET /:permissionId. Downgraded: frontend never calls this endpoint (permissions come from GET /users/profile). Dead backend code. Consider moving to backlog. permission.routes.ts:11-13 5 min
S1-8 #2 REDUCED SCOPE: Remove 3 broken hooks + buttons (not 5). Users bulk-permanent-delete button is already commented out (dead code). Resend button hidden by case mismatch #75 (dormant). Active bugs: roles, permissions, invited-users bulk-permanent-delete → 404 with error toast. Also fix case mismatch #75 ("pending" vs "PENDING") to surface the resend issue. api/roles/index.js, api/permissions/index.js, api/invited-users/index.js, page components 45 min
S1-9 #30 Fix permissions page gate (Medium — downgraded after challenge). Change permission="role.view" to permission="permission.view". Edge case — both permissions typically assigned together. Only affects rare "Permission Auditor" role. Still a 5-min fix, keep in Sprint 1. permissions-page.jsx 5 min
S1-10 #29 Remove duplicate ConfirmationModal. permissions-page.jsx (near end of file) 5 min
S1-11 #31 Remove debug console.log in roles API hook. api/roles/index.js:71 1 min
S1-12 #58 Add permission gate to useGetUsers (upgraded to Medium). Add useCheckPermission(["user.view"]) and enabled: hasPermission. Unnecessary failed API call visible in dev tools. api/users/index.js 5 min
S1-13 #24 Fix upsertRole for seed. Create createRoleInternal() without permission check, called by upsertRole and seed. Keep createRole() with permission check for API. role.service.ts 30 min
S1-14 #19 Fix mixed soft-delete. Replace isDeleted: false queries with TypeORM default deletedAt IS NULL filtering. user.service.ts:215,418,493 1 hr

Sprint 1 Acceptance Test

1. yarn seed                           → Seeds roles, permissions, super_admin
2. Login as super_admin                → Create "admin" role with user.*, role.*, permission.*, invite.* permissions
3. Create user with "admin" role       → User created successfully
4. Logout, login as new admin user     → Dashboard loads, sidebar shows all admin sections
5. Navigate to Users page              → User list loads, CRUD works
6. Navigate to Roles page              → Role list loads, can edit permissions
7. Navigate to Permissions page        → Permission list loads, can create/delete
8. Navigate to Invited Users page      → Invite list loads, can create invites
9. All trash/restore/permanent-delete  → Works without 404 errors
10. No console errors in browser       → Clean

Sprint 2: Clean Foundation (Week 2-3)

Timeline: ~12 hours, 1-2 developers North Star: Clean data model, robust seeding, documentation matches code, first integration test. Exit criteria: isDeleted removed. Seed produces working instance. Integration test passes. Docs updated.

Data Model (4 hrs)

# Issue(s) Description Effort
S2-1 #26 Remove isDeleted from User, Role, Permission entities. Create DB migration. Update all remaining queries. 2 hrs
S2-2 #25 Remove duplicate /invite-users route registration. Keep /user-invites only. 10 min
S2-3 #27 Add displayName to role update validation schema. 5 min
S2-4 #39 Remove redundant DELETE /permissions/delete endpoint. Keep PATCH /bulk-delete. 10 min
S2-5 #34 Add displayName to seeded roles (admin → "Administrator", user → "User", super_admin → "Super Administrator"). 5 min
S2-6 #38 Extract 'super_admin' to a constant. Use across requestContext, contextMiddleware, seed. 15 min

Security Hardening (2.5 hrs)

# Issue(s) Description Effort
S2-7 #20 Fix stale JWT role in contextMiddleware — use DB-loaded fullUser.role.name instead of req.user.role. 30 min
S2-8 #21 Clean up permission cache: remove variant matching, use direct dot-notation match only. 30 min
S2-9 #11 Fix backup code storage (downgraded to Medium) — use JSON array or delimiter-separated hashes instead of concatenation. SHA-512 collision risk is theoretical (~2^-512) but replace() removal is fragile. 1 hr
S2-10 #36 Fix contextMiddleware silent failure — reject request on DB error instead of continuing without context. 30 min

Seed & Config (1.5 hrs)

# Issue(s) Description Effort
S2-11 #44 Make permission seed incremental (upsert missing, don't skip all if any exist). 1 hr
S2-12 #42, #43 Clean up seed data: clarify readonly semantics, decide on read vs view convention. 30 min

Frontend (1 hr)

# Issue(s) Description Effort
S2-13 #32 Remove permissions-group-page.jsx and its route (80% commented out, non-functional). 15 min
S2-14 #57 Uncomment handleUpdatePermission in permission-form OR remove edit button. 15 min
S2-15 #33 Standardize bulk delete payload key — change users to { ids } (match roles/permissions/invites). 30 min

Backend Cleanup (30 min)

# Issue(s) Description Effort
S2-16 #41 Fix createRole cache invalidation on restore path — add deleteCacheByPrefix("roles:"). 5 min
S2-17 #40 Either export startEmail2FASetup and wire verification flow, OR delete dead code. 30 min

Documentation (1.25 hrs)

# Issue(s) Description Effort
S2-18 #45 Update api.md with undocumented endpoints. RESOLVEDapi.md was completely rewritten in a separate review (94 findings, all resolved). Now documents ~210 endpoints across 30 modules. 1 hr 0
S2-19 #46 Fix database.md UserRoles entity docs (wrong class name, missing relations). 15 min

Testing (2 hrs)

# Description Effort
S2-20 Set up test framework (Jest + ts-jest for backend). Create first integration test: non-super_admin user CRUD lifecycle through User/Role/Permission modules. This is the single most important preventive measure. 2 hrs

Backlog (Ongoing)

Fix opportunistically when touching adjacent files. No dedicated sprint time needed.

Issue(s) Description Fix When
#47 Typo: UserSerivceUserService Touching user.service.ts
#48 Typo: checkIviteUserExistscheckInviteUserExists Touching invite controller
#49 Typo: roleDiplayNameExistsroleDisplayNameExists Touching role.service.ts
#50 Remove unnecessary index on twoFactorSecret During DB migration
#51 Delete empty Role.repository.ts Anytime
#52 Remove dead UserRole import in seed.ts Done in Sprint 1 (S1-5)
#53 Remove dead { get } from "http" import Touching user_setting.service.ts
#54 Fix "Sass Boilerplate" → "SaaS Boilerplate" in 2FA setup Touching UserSetting
#55 Remove dead required_error on optional validation field Touching user.validation.ts
#56 Fix duplicate demo ID in roles-badge-group.jsx Touching component
#59 Remove or gate dev sandbox permissions/page.jsx Anytime
#60 Type RequestUser.role properly Touching requestContext.ts
#61 Remove excessive userId validation in permissionCache Touching permissionCache.ts
#62 Remove misleading timestamp field in cache Touching permissionCache.ts
#63 Fix multi-segment permission path loss in normalizer Deleted in Sprint 1 (S1-4)
#64 Remove redundant deletedAt IS NULL in QueryBuilder Touching QueryBuilder.ts
#65 Add as const to EVENT_TYPES Touching eventTypes.ts
#66 Fix "registration email" error message in invite handler Touching inviteUserCreate.handler.ts
#67 Add proper error tracking for failed invite emails Touching inviteUserCreate.handler.ts
#68 Consider seeding RBAC configuration settings Future feature planning

Key Decisions Log

Decision PM Engineer Designer Outcome
Fix order for ship blockers Security first, then broken features Agrees — security is 1-line fixes Password hang most visible to users Security → broken features → UX
Unify permission system when Sprint 1 — root cause of "nothing works" Sprint 1 — unblocks everything else Sprint 1 — admins test roles first Sprint 1
Remove isDeleted when Sprint 2 — needs DB migration Sprint 2 — needs careful testing Invisible to users Sprint 2
Write integration test when Sprint 2 — after architecture stabilizes Sprint 2 — needs test framework Ideally Sprint 1, realistically Sprint 2 Sprint 2
Update 23 undocumented endpoints Sprint 2 — devs need accurate docs Sprint 2 — update as routes are fixed Low priority for users Sprint 2
Permissions-group-page: fix or remove Remove unless user demand Remove — 80% commented out Broken UI hurts trust. Remove. Remove
Permission-form update: fix or remove Ask designer Either way, 15 min If edit button exists, it must work Fix

Sprint Board Visualization

┌─────────────────────────────────────────────────────────────────┐
│                    SHIP BLOCKERS (pre-release)                  │
│                    17 items · ~65 min · 1 developer             │
│                    STATUS: 0/17 resolved by recent refactoring  │
│                                                                 │
│  [SEC] #5b(delayed) #4 #5 #23 #15 #16 #17 #35 #12 #13 +stat  │
│  [BUG] #1 #6 #10 #28                                          │
│  [PRE] #76: PRE-1(seed) PRE-2(bypass) PRE-3(null-safety)      │
│                                                                 │
│  EXIT: All attack vectors blocked. Core features work.          │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    SPRINT 1 — "RBAC Works" (week 1)            │
│                    14 items · ~7 hrs · 1 developer              │
│                    STATUS: 0/14 resolved. 3 items MODIFIED.     │
│                                                                 │
│  [ARCH] #8 #9 #22 #7(low) #37(corrected) #18(depends) #14 #24 │
│  [FE]   #2(reduced) #30(med) #29 #31 #58                      │
│  [FIX]  #19 #75(new)                                            │
│                                                                 │
│  EXIT: Non-super_admin admin can CRUD all management pages.     │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    SPRINT 2 — "Clean Foundation" (week 2-3)    │
│                    19 items · ~11 hrs · 1-2 developers          │
│                    STATUS: 1/20 resolved (S2-18 api.md done)    │
│                                                                 │
│  [DATA]  #26 #25 #27 #39 #34 #38                              │
│  [SEC]   #20 #21 #11(med) #36 #41                             │
│  [SEED]  #44 #42 #43                                           │
│  [FE]    #32 #57 #33                                           │
│  [DOCS]  ~~#45~~ ✓ #46                                         │
│  [TEST]  Integration test ★ (most important item)              │
│  [BE]    #40                                                    │
│                                                                 │
│  EXIT: isDeleted gone. Seed works. Test passes. Docs match.    │
└──────────────────────────┬──────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                    BACKLOG (ongoing)                             │
│                    24 items · fix when touching adjacent files   │
│                                                                 │
│  #47-74: Typos, dead code, cosmetics, new middleware bugs       │
└─────────────────────────────────────────────────────────────────┘

Total Effort Estimate

Phase Items Effort Developers Timeline
Ship Blockers 14 ~1 hr 1 Day 1 (before any deployment)
Sprint 1 14 ~7 hrs 1 Week 1
Sprint 2 20 ~12 hrs 1-2 Week 2-3
Backlog 18 ~3 hrs 1 Ongoing
Total 74 ~23 hrs ~3 weeks

1 item resolved (S2-18). 3 items modified (S1-2, S1-3, S1-8). 4 new items added (#75, PRE-½/3). Net: 72 → 74 actionable.

4 issues resolved by Sprint 1 deletions (#52 via S1-5, #63 via S1-4) — not counted separately.


Generated by BMAD Document Project workflow v1.2.0 — Cross-Functional War Room, 2026-02-25 Cross-reference: Deep-Dive: User Management & RBAC — Section 21