ContextForge implements a comprehensive multi-tenant architecture that provides secure isolation, flexible resource sharing, and granular access control. This document describes the complete multi-tenancy design, user lifecycle, team management, and resource scoping mechanisms.
The multi-tenancy system is built around teams as the primary organizational unit, with users belonging to one or more teams, and all resources scoped to teams with configurable visibility levels.
- Team-Centric: Teams are the fundamental organizational unit for resource ownership and access control
- User Flexibility: Users can belong to multiple teams with different roles in each team
- Resource Isolation: Resources are scoped to teams with explicit sharing controls
- Invitation-Based: Team membership is controlled through invitation workflows
- Role-Based Access: Users have roles (Owner, Member) within teams that determine their capabilities
- Platform Administration: Separate platform-level administration for system management
sequenceDiagram
participant U as User
participant G as Gateway
participant SSO as SSO Provider
participant DB as Database
participant E as Email Service
alt Email Authentication
U->>G: POST /auth/email/login
G->>DB: Validate email/password
DB-->>G: User record
G-->>U: JWT token + session
else SSO Authentication
U->>G: GET /auth/sso/login/github
G->>SSO: OAuth redirect
U->>SSO: Authorize application
SSO->>G: OAuth callback with code
G->>SSO: Exchange code for token
SSO-->>G: User profile data
G->>DB: Create/update user
G->>DB: Create personal team
G-->>U: JWT token + session
end
Note over G,DB: Personal team auto-created for new users
When personal teams are enabled (default), each user gets an automatically created Personal Team upon registration:
flowchart TD
A[New User Registration] --> B{Authentication Method}
B -->|Email| C[Email Registration]
B -->|SSO| D[SSO Registration]
C --> E[Create EmailUser Record]
D --> F[Create SSO User Record]
E --> G[Create Personal Team<br/>if enabled]
F --> G
G --> H[Set User as Team Owner]
H --> I[User Can Access System]
subgraph "Personal Team Properties"
J[Name: Display Name's Team<br/>full name or email prefix]
K[is_personal: true]
L[Owner: User]
M[Members: User only]
N[Visibility: private]
end
G --> J
G --> K
G --> L
G --> M
G --> N
style G fill:#e1f5fe
style J fill:#f3e5f5
style K fill:#f3e5f5
style L fill:#f3e5f5
style M fill:#f3e5f5
style N fill:#f3e5f5
erDiagram
EmailTeam ||--o{ EmailTeamMember : has
EmailUser ||--o{ EmailTeamMember : belongs_to
EmailTeam ||--o{ EmailTeamInvitation : has_pending
EmailUser ||--o{ EmailTeamInvitation : invited_by
EmailTeam ||--o{ EmailTeamJoinRequest : join_requests
EmailUser ||--o{ EmailTeamJoinRequest : requested_by
EmailTeam {
string id PK
string name
string slug
string description
string created_by FK
boolean is_personal
string visibility
int max_members
boolean is_active
timestamp created_at
timestamp updated_at
}
EmailUser {
string email PK
string full_name
boolean is_admin
boolean is_active
string auth_provider
timestamp created_at
timestamp updated_at
}
EmailTeamMember {
string id PK
string team_id FK
string user_email FK
string role
string invited_by FK
timestamp joined_at
boolean is_active
}
EmailTeamInvitation {
string id PK
string team_id FK
string email
string role
string invited_by FK
timestamp invited_at
timestamp expires_at
string token
boolean is_active
}
EmailTeamJoinRequest {
string id PK
string team_id FK
string user_email FK
string status
timestamp requested_at
timestamp expires_at
timestamp reviewed_at
string reviewed_by FK
}
flowchart TB
subgraph "Team Visibility Types"
T1["Private Team
Not discoverable; invite-only"]
T2["Public Team
Discoverable; membership by invite/request"]
end
subgraph "Team Roles"
R1["Owner
- Full team control
- Invite/remove members
- Manage resources
- Delete team"]
R2["Member
- Access team resources
- Create resources
- No member management"]
end
subgraph "Team Membership Flow"
A[User Exists] --> B{Team Type}
B -->|Private| C[Requires Invitation]
B -->|Public| D[Discover and Request Join]
C --> E[Owner Sends Invite]
E --> F[Pending Invitation]
F --> G[User Accepts/Declines]
D --> J[Join Request Pending]
J --> K[Owner Approves]
K --> H[User Joins Team]
G -->|Accept| H
H --> I[Team Member]
end
style T1 fill:#ffebee
style T2 fill:#e8f5e8
style R1 fill:#fff3e0
style R2 fill:#f3e5f5
Note: These are team membership levels, separate from RBAC roles. A user can have both a membership level and RBAC role assignments within the same team.
-
Owner (Team Membership Level):
- Manage team settings (name, description, visibility) and lifecycle (cannot delete personal teams).
- Manage membership (invite, accept, change roles, remove members).
- Full control over team resources (create/update/delete), subject to platform policies.
-
Member (Team Membership Level):
- Access and use team resources; can create resources by default unless policies restrict it.
- Cannot manage team membership or team‑level settings.
Platform Admin is a global RBAC role (not a team membership level) with system‑wide oversight.
sequenceDiagram
participant O as Team Owner
participant G as Gateway
participant DB as Database
participant I as Invited User
Note over O,I: Invitation Process
O->>G: POST /teams/{team_id}/invitations
Note right of O: {email, role, expires_in}
G->>DB: Check team ownership
DB-->>G: Owner confirmed
G->>DB: Create invitation record
DB-->>G: Invitation token generated
G-->>O: Invitation created (token)
O-->>I: Share invite link (out of band)
Note over I,G: Acceptance Process
I->>G: POST /teams/invitations/{token}/accept
G->>DB: Validate token + user
G->>DB: Create team membership
G->>DB: Deactivate invitation (is_active=false)
G-->>I: Welcome to team
This section clarifies what Private and Public mean for teams, and what Private/Team/Public mean for resources across the system.
-
Private:
- Discoverability: Not listed to non‑members; only visible to members/owner.
- Membership: By invitation from a team owner (request‑to‑join is not exposed to non‑members).
- API/UI: Team shows up only in the current user's teams list; direct deep links require membership.
-
Public:
- Discoverability: Listed in public team discovery views for all authenticated users.
- Membership: Still requires an invitation or explicit approval of a join request.
- API/UI: Limited metadata may be visible without membership; all management and resource operations still require membership.
Note: Platform Admin is a global role and is not a team role. Admins can view/manage teams for operational purposes irrespective of team visibility.
Applies to Tools, Servers, Resources, Prompts, and A2A Agents. All resources are owned by a team (team_id) and created by a user (owner_email).
-
Private:
- Who sees it: Only the resource owner (owner_email), and only when their token is not a public-only token (token_teams must be non-empty).
- Team members cannot see or use it unless they are the owner.
- Public-only tokens (token_teams=[]) cannot access private resources even as the owner.
- Mutations: Owner and Platform Admin can update/delete; team owners may be allowed by policy (see Enhancements).
-
Team:
- Who sees it: All members of the owning team (owners and members).
- Mutations: Owner can update/delete; team owners can administratively manage; Platform Admin can override.
-
Public:
- Who sees it: All authenticated users across the platform (cross‑team visibility).
- Mutations: Only the resource owner, team owners, or Platform Admins can modify/delete.
Enforcement summary:
- Listing queries include resources where (a) visibility == public, (b) team_id ∈ token_teams with visibility ∈ {team, public}, or (c) owner_email == user.email with visibility == private.
- Owner-based access applies only to private resources. Owners cannot use ownership to bypass team scoping for team-visibility resources outside their token scope.
- Public-only tokens (token_teams=[]) see only public resources — owner and team access are both suppressed.
- Read follows the same rules as list; write operations require ownership or delegated/team administrative rights.
Token scoping is the mechanism that determines which resources a user can see based on the teams claim in their JWT token.
Key functions in mcpgateway/auth.py:
normalize_token_teams()— The single source of truth for interpreting JWT team claims into a canonical form. Used directly by API tokens and legacy tokens.resolve_session_teams()— The single policy point for session-token team resolution. Teams are always resolved from the database first so that revoked memberships take effect immediately. If the JWT carries a non-emptyteamsclaim, the result is narrowed to the intersection of DB teams and JWT teams — letting callers scope a session to a subset of their memberships without risking stale grants. If the intersection is empty (e.g. all JWT-claimed teams have been revoked), an empty list is returned, which demotes the session to public-only scope — the user can still access public resources and their own private resources, but loses access to all team-scoped resources. An explicitteams: [](empty list) in the JWT is treated as "no restriction requested" and returns the full DB membership — this intentionally differs fromnormalize_token_teamswhere[] → public-only. IfemailisNoneor empty, returns[](public-only) — an identity-less session token never receives admin bypass._narrow_by_jwt_teams()— Shared intersection logic used byresolve_session_teams.- API tokens and legacy tokens always use
normalize_token_teams()directly.
The token scoping system follows a secure-first design principle: when in doubt, access is restricted.
These use normalize_token_teams() directly — the JWT teams claim is the sole authority:
JWT teams State |
is_admin |
Result | Access Level |
|---|---|---|---|
| Key MISSING | any | [] |
PUBLIC-ONLY (secure default) |
Key null |
true |
None |
ADMIN BYPASS (unrestricted) |
Key null |
false |
[] |
PUBLIC-ONLY (no bypass) |
Key [] (empty) |
any | [] |
PUBLIC-ONLY |
Key ["t1", "t2"] |
any | ["t1", "t2"] |
TEAM-SCOPED |
Session tokens use resolve_session_teams() — the database is the authority, and the JWT teams claim only narrows (never broadens) the result:
JWT teams State |
DB Teams | Result | Access Level |
|---|---|---|---|
| Key MISSING | ["t1", "t2"] |
["t1", "t2"] |
Full DB membership |
Key null |
["t1", "t2"] |
["t1", "t2"] |
Full DB membership |
Key [] (empty) |
["t1", "t2"] |
["t1", "t2"] |
Full DB membership (no restriction requested) |
Key ["t1"] |
["t1", "t2"] |
["t1"] |
Narrowed to intersection |
Key ["revoked"] |
["t1", "t2"] |
[] |
Empty intersection — public-only (fail-closed) |
| any | None (admin) |
None |
ADMIN BYPASS (DB authority) |
!!! warning "Critical Security Behavior"
A missing teams key always results in public-only access for API/legacy tokens, even for admin users. Admin bypass requires explicit teams: null combined with is_admin: true.
!!! note "Session Token Membership Staleness"
Session tokens skip the _check_team_membership re-validation in the token scoping middleware because resolve_session_teams() already resolved membership from the database. Membership staleness is bounded by the auth_cache TTL. The cache stores the full DB membership (not the per-session narrowed intersection) so that multiple sessions for the same user narrow independently.
When a token contains multiple teams (teams: ["team-1", "team-2"]):
- Listing queries: Return resources visible to ANY of the user's teams
request.state.team_id: Set to the first team in the list- Resource creation: Uses
request.state.team_idas default if not explicitly specified
This means the first team in the token's teams array has special significance for default resource ownership.
The normalize_token_teams() and resolve_session_teams() functions return:
| Return Value | Meaning | Query Behavior |
|---|---|---|
None |
Admin bypass | Skip ALL team filtering |
[] (empty list) |
Public-only | Filter to visibility='public' ONLY |
["t1", "t2"] |
Team-scoped | Filter to team resources + public |
ContextForge implements two distinct security layers that work together:
Purpose: Controls what resources a user CAN SEE
- Enforced by
TokenScopingMiddlewareand service layer queries - Based on the
teamsclaim in the JWT token - Filters database queries based on visibility rules
- Applied before RBAC checks
Purpose: Controls what actions a user CAN DO
- Enforced by
@require_permissiondecorators - Based on role assignments and permission sets
- Checks if user has permission for the requested action
- Applied after token scoping passes
Request: POST /tools/{id}/execute
│
▼
┌─────────────────────────────┐
│ Token Scoping (Layer 1) │ ← Can user ACCESS this tool?
│ - Check visibility │ (public/team/private)
│ - Check team membership │
└─────────────────────────────┘
│ (allowed)
▼
┌─────────────────────────────┐
│ RBAC Permission (Layer 2) │ ← Can user EXECUTE tools?
│ @require_permission( │ (tools.execute permission)
│ "tools.execute") │
└─────────────────────────────┘
│ (allowed)
▼
┌─────────────────────────────┐
│ Execute Operation │
└─────────────────────────────┘
| Location | Token Scoping | RBAC | Notes |
|---|---|---|---|
| Token Scoping Middleware | ✅ | N/A | Request-level data filtering |
| REST API Endpoints | ✅ | ✅ | @require_permission decorators |
RPC Handler (/rpc) |
✅ | Varies | Method-specific checks |
| Admin UI | ✅ | ✅ | Permission-based rendering |
| Service Layer | ✅ | N/A | Database query filtering |
| WebSocket | ✅ | ✅ | Forwards auth to /rpc |
| MCP Transport | ✅ | N/A | Streamable HTTP filtering + per-server OAuth enforcement |
All resources in ContextForge are scoped to teams with three visibility levels:
flowchart TD
subgraph "Resource Types"
A[MCP Servers]
B[Virtual Servers]
C[Tools]
D[Resources]
E[Prompts]
F[A2A Agents]
end
subgraph "Team Scoping"
G[team_id: UUID]
H[owner_email: string]
I[visibility: enum]
end
subgraph "Visibility Levels"
J["Private
Owner only"]
K["Team
Team members"]
L["Public
All users"]
end
A --> G
B --> G
C --> G
D --> G
E --> G
F --> G
G --> I
H --> I
I --> J
I --> K
I --> L
style J fill:#ffebee
style K fill:#e3f2fd
style L fill:#e8f5e8
flowchart LR
subgraph "User Access to Resources"
U1["User A
Team 1 Member
Team 2 Owner"]
U2["User B
Team 1 Owner
Team 3 Member"]
U3["User C
No team membership"]
end
subgraph "Resource Visibility"
R1["Resource 1
Team 1, Private
Owner: User B"]
R2["Resource 2
Team 1, Team
Owner: User A"]
R3["Resource 3
Team 2, Public
Owner: User A"]
R4["Resource 4
Team 3, Team
Owner: User B"]
end
U1 -.->|❌ No Access| R1
U1 -->|✅ Team Member| R2
U1 -->|✅ Owner & Public| R3
U1 -.->|❌ Not Team Member| R4
U2 -->|✅ Owner & Private| R1
U2 -->|✅ Team Member| R2
U2 -->|✅ Public| R3
U2 -->|✅ Team Member| R4
U3 -.->|❌ No Access| R1
U3 -.->|❌ No Access| R2
U3 -->|✅ Public| R3
U3 -.->|❌ No Access| R4
style U1 fill:#e1f5fe
style U2 fill:#f3e5f5
style U3 fill:#fff3e0
flowchart TD
A[User requests resource access] --> B{Resource visibility}
B -->|Private| C{User owns resource?}
B -->|Team| D{User in resource team?}
B -->|Public| E[✅ Allow access]
C -->|Yes| F[✅ Allow access]
C -->|No| G[❌ Deny access]
D -->|Yes| H[✅ Allow access]
D -->|No| I[❌ Deny access]
style F fill:#e8f5e8
style H fill:#e8f5e8
style E fill:#e8f5e8
style G fill:#ffebee
style I fill:#ffebee
ContextForge implements a comprehensive RBAC system with four built-in roles that are automatically created during system bootstrap. These roles provide granular permission management across different scopes.
The following roles are created automatically when the system starts:
- Permissions:
*(all permissions) - Scope: Global
- Description: Platform administrator with all system-wide permissions
- Use Case: System administrators who manage the entire platform
-
Permissions:
teams.read- View team informationteams.update- Modify team settingsteams.join- Join public teamsteams.manage_members- Add/remove team memberstools.read- View toolstools.execute- Execute toolsresources.read- View resourcesprompts.read- View prompts
-
Scope: Team
-
Description: Team administrator with team management permissions
-
Use Case: Team leaders who manage team membership and resources
-
Permissions:
teams.join- Join public teamstools.read- View toolstools.execute- Execute toolsresources.read- View resourcesprompts.read- View prompts
-
Scope: Team
-
Description: Developer with tool and resource access
-
Use Case: Team members who need to use tools and access resources
-
Permissions:
teams.join- Join public teamstools.read- View toolsresources.read- View resourcesprompts.read- View prompts
-
Scope: Team
-
Description: Read-only access to resources
-
Use Case: Team members who only need to view resources without executing them
The RBAC system defines permissions across multiple resource categories:
users.create,users.read,users.update,users.delete,users.invite
teams.create,teams.read,teams.update,teams.delete,teams.manage_members
tools.create,tools.read,tools.update,tools.delete,tools.execute
resources.create,resources.read,resources.update,resources.delete,resources.share
prompts.create,prompts.read,prompts.update,prompts.delete,prompts.execute
servers.create,servers.read,servers.update,servers.delete,servers.manage
tokens.create,tokens.read,tokens.update,tokens.revoke
admin.system_config,admin.user_management,admin.security_audit
Roles are assigned to users within specific scopes:
- Global Scope: Platform-wide permissions (platform_admin only)
- Team Scope: Team-specific permissions (team_admin, developer, viewer)
- Personal Scope: Individual user permissions (future use)
flowchart TD
subgraph "RBAC Roles"
A["Platform Admin
- All permissions (*)
- Global scope
- System management"]
B["Team Admin
- Team management
- Member control
- Resource access"]
C["Developer
- Tool execution
- Resource access
- No team management"]
D["Viewer
- Read-only access
- No execution
- No management"]
end
subgraph "Domain Restrictions"
E["Admin Domain Whitelist
SSO_AUTO_ADMIN_DOMAINS"]
F["Trusted Domains
SSO_TRUSTED_DOMAINS"]
G["Admin Approval Gate
SSO_REQUIRE_ADMIN_APPROVAL"]
end
A --> E
A --> G
B --> F
subgraph "Access Hierarchy"
H[Platform Admin] --> I[All Teams & Resources]
J[Team Admin] --> K[Team Resources & Members]
L[Developer] --> M[Team Resources Only]
N[Viewer] --> O[Read-Only Access]
end
style A fill:#ff8a80
style B fill:#ffb74d
style C fill:#81c784
style D fill:#90caf9
sequenceDiagram
participant U as New User
participant G as Gateway
participant SSO as SSO Provider
participant DB as Database
participant A as Platform Admin
Note over U,A: SSO Registration with Domain + Approval Checks
U->>G: SSO Login (user@company.com)
G->>SSO: OAuth flow
SSO-->>G: User profile
G->>G: Check trusted domains (if configured)
alt Domain not trusted
G-->>U: Access denied
else Domain trusted
G->>G: Require admin approval?
alt Approval required (SSO_REQUIRE_ADMIN_APPROVAL=true)
G->>DB: Create PendingUserApproval
G-->>A: Approval required
A->>G: Approve user (admin UI)
Note over U,G: User logs in again after approval
U->>G: SSO Login (retry)
else Approval not required
Note right of G: Continue auto-create flow
end
G->>G: Evaluate admin criteria
Note right of G: Auto-admin domains / provider admin groups
G->>DB: Create user (is_admin true/false)
G-->>U: JWT token issued
end
ContextForge now supports:
- Self-service forgot-password/reset flow:
POST /auth/email/forgot-passwordGET /auth/email/reset-password/{token}POST /auth/email/reset-password/{token}
- Admin unlock flow:
POST /auth/email/admin/users/{email}/unlock
- Admin password reset via UI and API (
PUT /auth/email/admin/users/{email})
Operational guidance, Kubernetes recovery commands, emergency SQL procedures, and SMTP/reset configuration are documented in:
- Argon2id hashing for stored credentials
- One-time reset tokens (hashed in DB, configurable expiry)
- Password reset request rate limiting
- Failed login tracking and account lockout controls
- Audit events for login/reset/lockout/unlock actions
The user interface adapts based on the user's assigned roles:
- Full System Access: Can view and manage all teams, users, and resources across the platform
- Global Configuration: Access to system-wide settings, SSO configuration, and platform management
- Cross-Team Management: Can manage resources in any team regardless of membership
- User Management: Can create, modify, and delete user accounts and role assignments
- Team Management: Can modify team settings, manage team membership (invite/remove members)
- Resource Control: Full access to create, modify, and delete team resources
- Member Oversight: Can view and manage all team members and their access
- Limited to Assigned Teams: Only sees teams where they have the team_admin role
- Tool Access: Can view and execute tools within their teams
- Resource Usage: Can access and use team resources and prompts
- No Management Rights: Cannot manage team membership or team settings
- Create Resources: Can create new tools, resources, and prompts within their teams
- Read-Only Access: Can view tools, resources, and prompts but cannot execute or modify them
- No Creation Rights: Cannot create new resources or tools
- No Management Access: Cannot manage team membership or settings
- Limited Interaction: Primarily for reviewing and consuming existing resources
-
Default on create: New resources (including MCP Servers, Tools, Resources, Prompts, and A2A Agents) default to
visibility="private"unless a different value is explicitly provided by an allowed actor. For servers created via the UI, the visibility is enforced toprivateby default. -
Team assignment: When a user creates a server and does not specify
team_id, the server is automatically assigned to the user's personal team. -
Sharing workflow:
- Private → Team: Make the resource visible to the owning team by setting
visibility="team". - Private/Team → Public: Make the resource visible to all authenticated users by setting
visibility="public". - Cross-team: To have a resource under a different team, create it in that team or move/clone it per policy; cross-team "share" is by visibility, not multi-team ownership.
- Private → Team: Make the resource visible to the owning team by setting
sequenceDiagram
participant U as User
participant G as Gateway
participant Auth as Authentication
participant Team as Team Service
participant Res as Resource Service
participant DB as Database
Note over U,DB: Complete Access Flow
U->>G: Request resource list
G->>Auth: Validate JWT token
Auth-->>G: User identity confirmed
G->>Team: Get user teams
Team->>DB: Query team memberships
DB-->>Team: User team list
Team-->>G: Teams with roles
G->>Res: List resources for user
Res->>DB: Query with team filtering
Note right of Res: WHERE owner_email = user OR team_id IN user_teams AND visibility IN team,public OR visibility = public
DB-->>Res: Filtered resource list
Res-->>G: User-accessible resources
G-->>U: Resource list response
Note over U,DB: Resource Creation
U->>G: Create new resource
G->>Auth: Validate permissions
G->>Team: Verify team membership
Team-->>G: Team access confirmed
G->>Res: Create resource
Res->>DB: INSERT with team_id, owner_email, visibility
DB-->>Res: Resource created
Res-->>G: Creation confirmed
G-->>U: Resource created successfully
flowchart TD
A[User Request] --> B[Extract User Identity]
B --> C[Get User Team Memberships]
C --> D[Build Filter Criteria]
D --> E{Resource Query}
E --> F["Owner-Owned Resources
owner_email = user.email"]
E --> G["Team Resources
team_id IN user.teams
AND visibility IN team,public"]
E --> H["Public Resources
visibility = public"]
F --> I[Combine Results]
G --> I
H --> I
I --> J[Apply Additional Filters]
J --> K[Return Filtered Resources]
subgraph "Filter Logic"
L[Personal: User owns directly]
M[Team: User is team member]
N[Public: Available to all]
end
style F fill:#e1f5fe
style G fill:#e3f2fd
style H fill:#e8f5e8
erDiagram
%% User Management
EmailUser ||--o{ EmailTeamMember : belongs_to
EmailUser ||--o{ EmailTeamInvitation : invites
EmailUser ||--o{ EmailTeamJoinRequest : requests
EmailUser ||--o{ EmailTeam : creates
%% Team Management
EmailTeam ||--o{ EmailTeamMember : has
EmailTeam ||--o{ EmailTeamInvitation : has_pending
EmailTeam ||--o{ EmailTeamJoinRequest : has_requests
EmailTeam ||--o{ Tool : owns
EmailTeam ||--o{ Server : owns
EmailTeam ||--o{ Resource : owns
EmailTeam ||--o{ Prompt : owns
EmailTeam ||--o{ A2AAgent : owns
EmailUser {
string email PK
string full_name
boolean is_admin
boolean is_active
string auth_provider
timestamp created_at
timestamp updated_at
}
EmailTeam {
string id PK
string name
string slug
text description
string created_by FK
boolean is_personal
string visibility
int max_members
boolean is_active
timestamp created_at
timestamp updated_at
}
EmailTeamMember {
string id PK
string team_id FK
string user_email FK
string role
string invited_by FK
timestamp joined_at
boolean is_active
}
EmailTeamInvitation {
string id PK
string team_id FK
string email
string role
string invited_by FK
timestamp invited_at
string token
timestamp expires_at
boolean is_active
}
EmailTeamJoinRequest {
string id PK
string team_id FK
string user_email FK
string status
timestamp requested_at
timestamp expires_at
timestamp reviewed_at
string reviewed_by FK
string notes
}
Tool {
string id PK
string original_name
string display_name
string team_id FK
string owner_email
string visibility
timestamp created_at
timestamp updated_at
}
Resource {
string id PK
string uri
string name
string team_id FK
string owner_email
string visibility
timestamp created_at
timestamp updated_at
}
Prompt {
string id PK
string name
string original_name
string team_id FK
string owner_email
string visibility
timestamp created_at
timestamp updated_at
}
A2AAgent {
string id PK
string name
string slug
string team_id FK
string owner_email
string visibility
timestamp created_at
timestamp updated_at
}
Server {
string id PK
string name
string team_id FK
string owner_email
string visibility
timestamp created_at
timestamp updated_at
}
All resource endpoints follow consistent team-scoping patterns:
flowchart TD
subgraph "API Endpoint Patterns"
A["GET /tools?team_id=uuid&visibility=team"]
B["POST /tools
name, team_id (optional), visibility"]
C["GET /tools/{tool_id}"]
D["PUT /tools/{tool_id}
visibility, name, etc."]
E["DELETE /tools/{tool_id}"]
end
subgraph "Request Processing"
F[Extract User Identity] --> G[Validate Team Access]
G --> H[Apply Team Filters]
H --> I[Execute Query]
I --> J[Return Results]
end
subgraph "Access Control Checks"
K[User Team Membership]
L[Resource Ownership]
M[Visibility Level]
N[Operation Permissions]
end
A --> F
B --> F
C --> F
D --> F
E --> F
G --> K
G --> L
G --> M
G --> N
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#fff3e0
style D fill:#e8f5e8
style E fill:#ffebee
sequenceDiagram
participant C as Client
participant G as Gateway
participant A as Auth Middleware
participant R as Resource Service
participant DB as Database
C->>G: POST /tools
Note right of C: {name, team_id, visibility}
G->>A: Validate request
A->>A: Extract user from JWT
A->>A: Enforce permissions + token scope
A-->>G: User authorized
G->>R: Create resource
R->>R: Validate payload + defaults
R->>DB: INSERT resource
Note right of R: team_id, owner_email, visibility
DB-->>R: Resource created
R-->>G: Creation response
G-->>C: 201 Created
#####################################
# Multi-Tenancy Configuration
#####################################
# Team Settings
AUTO_CREATE_PERSONAL_TEAMS=true
# PERSONAL_TEAM_PREFIX=personal # optional: set to get collision-safe email-based slugs
MAX_TEAMS_PER_USER=50
MAX_MEMBERS_PER_TEAM=100 # platform admins are exempt from this cap
# Team Invitation Settings
INVITATION_EXPIRY_DAYS=7
REQUIRE_EMAIL_VERIFICATION_FOR_INVITES=true
# Team Governance
ALLOW_TEAM_CREATION=true
ALLOW_TEAM_JOIN_REQUESTS=true
ALLOW_TEAM_INVITATIONS=true
# Visibility
# NOTE: Resources default to 'private' (not configurable via env today)
# Allowed visibility values: private | team | public
# Platform Administration
PLATFORM_ADMIN_EMAIL=admin@company.com
PLATFORM_ADMIN_PASSWORD=changeme
PLATFORM_ADMIN_FULL_NAME="Platform Administrator"
# SSO (enable + trust and admin mapping)
SSO_ENABLED=true
SSO_TRUSTED_DOMAINS=["company.com","trusted-partner.com"]
SSO_AUTO_ADMIN_DOMAINS=["company.com"]
SSO_GITHUB_ADMIN_ORGS=["your-org"]
SSO_GOOGLE_ADMIN_DOMAINS=["your-google-workspace-domain.com"]
SSO_REQUIRE_ADMIN_APPROVAL=false
# Public team self-join flows are planned; no env toggles yetflowchart TD
subgraph "Security Layers"
A["Authentication Layer
- JWT validation
- Session management"]
B["Authorization Layer
- Team membership
- Resource ownership
- Visibility checks"]
C["Data Isolation Layer
- Team-scoped queries
- Owner validation
- Access logging"]
end
subgraph "Security Controls"
D["Input Validation
- Team ID validation
- Email format
- Role validation"]
E["Rate Limiting
- Per-user limits
- Per-team limits
- API quotas"]
F["Audit Logging
- Access attempts
- Resource changes
- Team modifications"]
end
subgraph "Attack Prevention"
G["Team Enumeration
- UUID team IDs
- Access validation"]
H["Resource Access
- Ownership checks
- Visibility enforcement"]
I["Privilege Escalation
- Role validation
- Permission boundaries"]
end
A --> B --> C
D --> E --> F
G --> H --> I
style A fill:#ffcdd2
style B fill:#f8bbd9
style C fill:#e1bee7
style D fill:#c8e6c9
style E fill:#dcedc8
style F fill:#f0f4c3
| RBAC Role | Scope | Team Access | Resource Creation | Member Management | Team Settings | Platform Admin |
|---|---|---|---|---|---|---|
| Platform Admin | Global | All teams | All resources | All teams | All settings | Full access |
| Team Admin | Team | Assigned teams | Team resources | Team members | Team settings | No access |
| Developer | Team | Member teams | Team resources | No access | No access | No access |
| Viewer | Team | Member teams | No access | No access | No access | No access |
Note: Team Owner/Member roles from the team management system work alongside RBAC roles. A user can have both team membership status (Owner/Member) and RBAC role assignments (Team Admin/Developer/Viewer) within the same team.
These behaviors are enforced consistently across all access paths:
normalize_token_teams()is the canonical interpreter of JWT team claims;resolve_session_teams()is the single policy point for session tokens (always DB-resolved)- For API/legacy tokens: missing
teamskey always returns[](public-only, secure default); emptyteams: []also returns[]. For session tokens: missing, null, or emptyteamsreturns the full DB membership (no narrowing requested) - Admin bypass for API/legacy tokens requires BOTH
teams: nullANDis_admin: true; for session tokens, admin bypass is DB-derived (is_adminflag). In both cases the service layer requirestoken_teams=NoneANDuser_email=Nonefor unrestricted queries - All list endpoints pass
token_teamsto the service layer - Service layer applies visibility filtering based on
token_teamsviaBaseService._apply_access_control() - Public-only tokens can ONLY access
visibility='public'resources — owner and team access are both suppressed - Owner-based access (
owner_email) grants visibility only forvisibility='private'resources — it does not bypass team scoping for team-visibility resources
For detailed information on RBAC configuration, token scoping, and permission management, see RBAC Configuration.
- User Authentication: Email and SSO authentication implemented
- Personal Teams: Auto-created for every user
- Team Roles: Owner and Member roles (platform Admin is global)
- Team Visibility: Private and Public team types
- Resource Scoping: All resources scoped to teams with visibility controls
- Invitation System: Email-based invitations with token management
- Platform Administration: Separate admin role with domain restrictions
- Access Control: Team-based filtering for all resources
- Database Design: Complete multi-tenant schema
- API Patterns: Consistent team-scoped endpoints
- Team ID Validation: Every resource operation must validate team membership
- Visibility Enforcement: Resource visibility (private/team/public) strictly enforced; team visibility (private/public) per design
- Owner Permissions: Only team owners can manage members and settings
- Personal Team Protection: Personal teams cannot be deleted or transferred
- Invitation Security: Invitation tokens with expiration and single-use
- Platform Admin Isolation: Platform admin access separate from team access
- Cross-Team Access: Public resources accessible across team boundaries
- Audit Trail: Permission checks and auth events audited; extended operation audit planned
- Team roles: Owner and Member only (platform Admin is global) — consistent across ERD, APIs, and UI.
- Team visibility: Private and Public.
- Resource visibility:
private|team|public— enforced as designed. - Public team discovery/join: Join‑request/self‑join flows to be implemented.
- Default resource visibility: Defaults to "private"; not configurable via env.
- SSO admin mapping: Domain/org lists supported; provider‑specific org checks may require provider API calls in production.
-
Public Team Discovery & Join Requests:
- Add endpoints and UI to request membership on public teams; owner approval workflow; optional auto‑approve policy.
- Admin toggles/policies to restrict who can create public teams and who can approve joins.
-
Unified Operation Audit:
- System‑wide audit log for create/update/delete across teams, tools, servers, resources, prompts, agents with export/reporting.
-
Role Automation:
- Auto‑assign default RBAC roles on resource creation (e.g., owner gets manager role in team scope; members get viewer).
- Optional per‑team policies defining who may create public resources.
-
ABAC for Virtual Servers:
- Attribute‑based conditions layered on top of RBAC (tenant tags, data classifications, environment, time windows, client IP).
-
Team/Resource Quotas and Policies:
- Per‑team limits (tools/servers/resources/agents); per‑team defaults for resource visibility and creation rights.
-
Public Resource Access Controls:
- Fine‑grained cross‑tenant rate limits and opt‑in masking for metadata shown to non‑members.
This architecture provides a robust, secure, and scalable multi-tenant system that supports complex organizational structures while maintaining strict data isolation and flexible resource sharing capabilities.