feat: add RBAC middleware and role-aware UI#6
Conversation
Backend: - server/db.ts: adds tenant_memberships CRUD helpers - server/index.ts: adds requireRole() middleware with convenience wrappers (requireAdmin, requireOperator, requireOwner); applies RBAC to mutation routes (prompt/model changes need admin, session kill needs operator); adds GET /api/auth/roles and tenant membership management endpoints Frontend: - useRbac() hook: derives canEdit/canOperate from user's memberships - FoxMemoryAgentsView: disables prompt editing for viewers - ModelConfigSection: disables model switching for viewers - App.tsx: wires RBAC and passes canEdit to components Backward compatible — if no tenant_memberships rows exist, all access is allowed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c901ccc0dc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const memberships = getMembershipsByUserId(user.id); | ||
|
|
||
| // If no memberships exist (pre-migration / fresh install), allow access for backward compat | ||
| if (memberships.length === 0) return next(); |
There was a problem hiding this comment.
Deny role-gated routes for users with no memberships
The RBAC middleware currently grants access when the current user has zero membership rows, which turns every unassigned account into an implicit owner/admin/operator. This check is intended for backward compatibility when RBAC is globally uninitialized, but as written it bypasses authorization per-user, so in any deployment where some users are assigned roles and others are not, unassigned users can still call protected mutation endpoints.
Useful? React with 👍 / 👎.
| // If no memberships exist (pre-migration / fresh install), allow access for backward compat | ||
| if (memberships.length === 0) return next(); | ||
|
|
||
| const hasRole = memberships.some((m) => roles.includes(m.role)); |
There was a problem hiding this comment.
Scope role checks to requested tenant before allowing access
Authorization is computed from memberships.some(...) across all tenant memberships, with no tenant scoping, so any user who is owner in one tenant also satisfies requireOwner for every /:tenantId/... route. In multi-tenant setups, this lets an owner/admin/operator from tenant A perform protected actions against tenant B by passing B’s tenantId in the URL.
Useful? React with 👍 / 👎.
Summary
Backend:
requireRole()middleware withrequireAdmin,requireOperator,requireOwnerwrappersadmin, session kill needsoperatorGET /api/auth/rolesreturns current user's tenant membershipsGET/POST/PUT/DELETE /api/tenants/:tenantId/membersfor membership management (owner only)tenant_membershipstable CRUD helpers in db.tsFrontend:
useRbac()hook: derivescanEdit/canOperatefrom user membershipsBackward compatible — if no memberships exist, all access is allowed.
RBAC Role Permissions
Test plan
yarn typecheck:serverpassesyarn typecheck:webpasses (only pre-existingshadowswarning)🤖 Generated with Claude Code