Skip to content

feat: add RBAC middleware and role-aware UI#6

Open
ttupper92618 wants to merge 1 commit into
mainfrom
feat/rbac
Open

feat: add RBAC middleware and role-aware UI#6
ttupper92618 wants to merge 1 commit into
mainfrom
feat/rbac

Conversation

@ttupper92618

Copy link
Copy Markdown
Contributor

Summary

Backend:

  • requireRole() middleware with requireAdmin, requireOperator, requireOwner wrappers
  • RBAC on mutation routes: prompt/model changes need admin, session kill needs operator
  • GET /api/auth/roles returns current user's tenant memberships
  • GET/POST/PUT/DELETE /api/tenants/:tenantId/members for membership management (owner only)
  • tenant_memberships table CRUD helpers in db.ts

Frontend:

  • useRbac() hook: derives canEdit/canOperate from user memberships
  • Prompt editors disabled for viewers
  • Model switching disabled for viewers
  • RBAC wired through App.tsx → FoxMemorySection → FoxMemoryAgentsView / ModelConfigSection

Backward compatible — if no memberships exist, all access is allowed.

RBAC Role Permissions

Action owner admin operator viewer
View all data yes yes yes yes
Change config (prompts, models) yes yes no no
Kill sessions yes yes yes no
Manage members yes no no no

Test plan

  • yarn typecheck:server passes
  • yarn typecheck:web passes (only pre-existing shadows warning)
  • With no memberships, all routes work as before (backward compat)
  • Viewer role can read but not modify prompts/models
  • Admin can modify prompts but not manage members
  • Owner can manage tenant members

🤖 Generated with Claude Code

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread server/index.ts
const memberships = getMembershipsByUserId(user.id);

// If no memberships exist (pre-migration / fresh install), allow access for backward compat
if (memberships.length === 0) return next();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread server/index.ts
// 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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant