Multi-Tenancy¶
Totis uses a workspace-based multi-tenant architecture to provide isolated environments for different organizations or teams.
Architecture Overview¶
User
└── Workspaces (many-to-many)
└── Projects (one-to-many)
└── Builds (one-to-many)
└── Files (one-to-many)
Workspaces¶
Workspaces are the top-level organizational unit. They provide:
- Isolation: Each workspace has its own projects, builds, and storage
- Team Management: Invite members with different roles
- Resource Limits: Storage, users, and project limits based on subscription
- Custom Storage: Optional custom S3 storage configuration
Workspace Structure¶
| Component | Description |
|---|---|
| Name | Display name for the workspace |
| Slug | URL-safe identifier (auto-generated) |
| Members | Users with assigned roles |
| Projects | Applications/components |
| Storage | S3 bucket for build artifacts |
Projects¶
Projects represent individual applications or components within a workspace:
- Each project has a unique slug within its workspace
- Projects contain builds organized by version
- Builds are isolated between projects
Builds¶
Builds are versioned releases of a project:
- Identified by version and build number
- Contain metadata (platform, type, commit info)
- Store one or more build artifact files
- Support public link sharing
Data Access Control¶
All data access is controlled by the DataAccessValidator which:
- Extracts user context from JWT token
- Validates workspace membership
- Checks role-based permissions
- Enforces tenant isolation
// Every request goes through validation
UserContext userContext = dataAccessValidator.getUserContext();
Workspace workspace = dataAccessValidator.validateAndGetWorkspaceWithPermissions(
workspaceSlug,
List.of(Permission.PROJECT_READ)
);
Request Flow¶
- Authentication: JWT token validated by
HttpAuthenticationFilter - User Context: User ID and claims stored in request scope
- Workspace Validation: Slug resolved and membership verified
- Permission Check: Role-based permissions validated
- Data Access: Query scoped to the validated workspace
Tenant Isolation¶
Database Level¶
All queries are scoped by workspace ID:
// Projects are always fetched within a workspace context
List<Project> projects = projectRepository.findByWorkspaceId(workspace.getWorkspaceId());
Storage Level¶
Build files are organized by workspace and project:
API Level¶
All resource paths include workspace identification:
Cross-Tenant Considerations¶
User Membership¶
A user can be a member of multiple workspaces:
- Different roles in each workspace
- Separate permissions per workspace
- Independent access to each workspace's resources
Workspace Switching¶
The JWT token is workspace-agnostic. The workspace context is determined by:
- The
workspaceSlugpath parameter in API requests - Validation that the user is a member of that workspace
- Permission checks based on the user's role in that specific workspace
Data Boundaries¶
- Users cannot access resources outside their workspaces
- Workspace members can only see other members of the same workspace
- Storage quotas are enforced per workspace
Best Practices¶
For API Consumers¶
- Always specify workspace: Include
workspaceSlugin all requests - Handle 403 errors: User may not have access to requested workspace
- Check permissions: Verify required permissions before operations
For Integrations¶
- Store workspace context: Remember which workspace the user is working in
- List workspaces first: Use
/user/workspacesto get available workspaces - Validate before operations: Check user has required permissions
Example: Multi-Workspace User¶
A user belonging to two workspaces sees different data in each:
# Get workspaces
curl -H "Authorization: Bearer $TOKEN" \
https://api.usetotis.com/api/v1/user/workspaces
# Response shows both workspaces
[
{"slug": "company-a", "role": "OWNER", "permissions": ["WORKSPACE_DELETE", ...]},
{"slug": "client-b", "role": "DEVELOPER", "permissions": ["BUILD_CREATE", ...]}
]
# Access company-a's projects (full access)
curl -H "Authorization: Bearer $TOKEN" \
https://api.usetotis.com/api/v1/user/workspaces/company-a/projects
# Access client-b's projects (limited access)
curl -H "Authorization: Bearer $TOKEN" \
https://api.usetotis.com/api/v1/user/workspaces/client-b/projects