UserTenantAssociation Model Documentation
Overview​
The UserTenantAssociation model enables multi-tenant user support in CODITECT, allowing users to be associated with multiple tenants while maintaining different roles and permissions in each. This supports complex organizational scenarios like contractors working across companies, auditors reviewing multiple organizations, or employees transitioning between subsidiaries.
Model Structure​
Core Fields​
| Field | Type | Description | Constraints |
|---|---|---|---|
id | UUID | Unique association identifier | Primary key, auto-generated |
user_id | UUID | User being associated | Foreign key to User |
tenant_id | UUID | Target tenant | Foreign key to Tenant |
role | UserRole | Role within tenant | Required (Admin, Manager, Developer, User) |
permissions | Vec | Additional permissions | Beyond role defaults |
association_type | AssociationType (Enum) | Nature of relationship | Required |
valid_from | DateTime | Association start date | Required, defaults to now |
valid_until | DateTime | Association end date | For time-bound access |
created_by | UUID | Admin who created association | Foreign key to User |
created_at | DateTime | Creation timestamp | Auto-set |
updated_at | DateTime | Last modification | Auto-updated |
is_active | bool | Active status | Required, default true |
notes | String (Optional) | Association reason/context | For audit purposes |
AssociationType Enum​
enum AssociationType {
Primary, // User's home tenant
Employee, // Regular employment
Contractor, // Time-bound contract work
Auditor, // External audit access
Support, // Technical support access
Guest, // Limited guest access
Custom(String) // Custom relationship type
}
Default Permissions by Type​
| Association Type | Default Permissions | Write Access | Time-Bound |
|---|---|---|---|
| Primary | read, write, delete | Yes | No |
| Employee | read, write | Yes | No |
| Contractor | read, write:assigned | Yes | Yes |
| Auditor | read, audit:view, report:generate | No | Yes |
| Support | read, support:troubleshoot, logs:view | No | No |
| Guest | read:limited | No | Yes |
| Custom | None (must specify) | Depends | Depends |
Example Associations​
Primary Employee​
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"tenant_id": "456e7890-e89b-12d3-a456-426614174000",
"role": "Developer",
"permissions": ["read", "write", "delete"],
"association_type": "Primary",
"valid_from": "2024-01-15T00:00:00Z",
"valid_until": null,
"created_by": "789e0123-e89b-12d3-a456-426614174000",
"created_at": "2024-01-15T00:00:00Z",
"updated_at": "2025-08-29T10:30:00Z",
"is_active": true,
"notes": "Founding team member"
}
External Contractor​
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"user_id": "234e5678-e89b-12d3-a456-426614174000",
"tenant_id": "567e8901-e89b-12d3-a456-426614174000",
"role": "Developer",
"permissions": ["read", "write:assigned", "comment"],
"association_type": "Contractor",
"valid_from": "2025-08-01T00:00:00Z",
"valid_until": "2025-12-31T23:59:59Z",
"created_by": "890e1234-e89b-12d3-a456-426614174000",
"created_at": "2025-07-28T14:30:00Z",
"updated_at": "2025-08-29T09:15:00Z",
"is_active": true,
"notes": "6-month contract for Project Phoenix"
}
Security Auditor​
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"user_id": "345e6789-e89b-12d3-a456-426614174000",
"tenant_id": "678e9012-e89b-12d3-a456-426614174000",
"role": "User",
"permissions": ["read", "audit:view", "report:generate", "compliance:check"],
"association_type": "Auditor",
"valid_from": "2025-09-01T00:00:00Z",
"valid_until": "2025-09-07T23:59:59Z",
"created_by": "901e2345-e89b-12d3-a456-426614174000",
"created_at": "2025-08-25T16:00:00Z",
"updated_at": "2025-08-29T11:45:00Z",
"is_active": true,
"notes": "Annual SOC2 compliance audit - PwC"
}
Supporting Models​
UserTenantSummary​
Provides overview of user's multi-tenant access:
| Field | Type | Description |
|---|---|---|
user_id | UUID | User identifier |
primary_tenant_id | UUID | Primary/home tenant |
total_associations | usize | Total tenant count |
active_associations | usize | Currently valid count |
association_details | Vec | Detailed list |
TenantAssociationInfo​
Summary information for each association:
| Field | Type | Description |
|---|---|---|
tenant_id | UUID | Tenant identifier |
tenant_name | String | Display name |
role | UserRole | User's role |
association_type | AssociationType | Relationship type |
is_active | bool | Current status |
valid_until | DateTime | Expiration date |
Database Schema​
Primary Storage Pattern​
Key: /{tenant_id}/user_tenant_associations/{association_id}
Value: JSON serialized UserTenantAssociation
Secondary Indexes​
# User's associations across all tenants
/global/user_associations/{user_id} -> [association_ids]
# Tenant's user associations
/{tenant_id}/tenant_user_associations -> [association_ids]
# Active associations by type
/{tenant_id}/associations_by_type/{type} -> [association_ids]
# Expiring associations
/global/expiring_associations/{YYYY-MM-DD} -> [association_ids]
# Primary tenant lookup
/global/user_primary_tenant/{user_id} -> tenant_id
Business Logic​
Association Validation​
impl UserTenantAssociation {
pub fn is_valid(&self) -> bool {
// Must be active
if !self.is_active {
return false;
}
// Check time bounds
let now = Utc::now();
if now < self.valid_from {
return false; // Future dated
}
if let Some(valid_until) = self.valid_until {
if now > valid_until {
return false; // Expired
}
}
true
}
}
Permission Resolution​
// Effective permissions = Role permissions + Additional permissions
pub fn effective_permissions(&self) -> Vec<String> {
let mut perms = self.role.permissions(); // Base role permissions
perms.extend(self.permissions.clone()); // Additional permissions
perms.sort();
perms.dedup(); // Remove duplicates
perms
}
API Endpoints​
Association Management​
- POST
/api/users/{user_id}/associations- Create association - GET
/api/users/{user_id}/associations- List user's associations - GET
/api/associations/{association_id}- Get association details - PUT
/api/associations/{association_id}- Update association - DELETE
/api/associations/{association_id}- Remove association
Tenant User Management​
- GET
/api/tenants/{tenant_id}/users- List tenant users - POST
/api/tenants/{tenant_id}/invite- Invite user to tenant - PUT
/api/tenants/{tenant_id}/users/{user_id}/role- Change role
Validation and Switching​
- GET
/api/users/me/tenants- Get accessible tenants - POST
/api/auth/switch-tenant- Switch active tenant - GET
/api/associations/validate- Check association validity
Use Cases​
Multi-Company Employee​
// User works for parent company and subsidiary
let parent_assoc = UserTenantAssociation::new(
user_id,
parent_tenant_id,
UserRole::Manager,
AssociationType::Primary,
admin_id
);
let subsidiary_assoc = UserTenantAssociation::new(
user_id,
subsidiary_tenant_id,
UserRole::Developer,
AssociationType::Employee,
admin_id
);
Time-Limited Contractor​
// Contractor with 3-month project access
let mut contractor_assoc = UserTenantAssociation::new(
contractor_id,
client_tenant_id,
UserRole::Developer,
AssociationType::Contractor,
manager_id
);
contractor_assoc.valid_until = Some(Utc::now() + Duration::days(90));
contractor_assoc.permissions = vec![
"project:read:project-123".to_string(),
"project:write:project-123".to_string(),
"task:*:project-123".to_string()
];
contractor_assoc.notes = Some("React developer for mobile app project".to_string());
Compliance Auditor​
// Annual audit with read-only access
let auditor_assoc = UserTenantAssociation::new(
auditor_id,
tenant_id,
UserRole::User, // Minimal role
AssociationType::Auditor,
compliance_officer_id
);
// Override with specific audit permissions
auditor_assoc.permissions = vec![
"audit:logs:read".to_string(),
"compliance:reports:generate".to_string(),
"financial:records:view".to_string(),
"security:events:analyze".to_string()
];
Security Patterns​
Tenant Switching​
// Validate user can access requested tenant
async fn switch_tenant(user_id: Uuid, target_tenant_id: Uuid) -> Result<()> {
let association = get_user_tenant_association(user_id, target_tenant_id).await?;
if !association.is_valid() {
return Err(Unauthorized("Invalid or expired association"));
}
// Update session with new tenant context
update_session_tenant(user_id, target_tenant_id).await?;
Ok(())
}
Permission Checking​
// Check permission in tenant context
async fn check_permission(
user_id: Uuid,
tenant_id: Uuid,
required_permission: &str
) -> Result<bool> {
let association = get_user_tenant_association(user_id, tenant_id).await?;
if !association.is_valid() {
return Ok(false);
}
let permissions = association.effective_permissions();
Ok(permissions.contains(&required_permission.to_string()))
}
Workflows​
Invitation Flow​
1. Admin initiates invitation
2. System creates pending association
3. Email sent to invitee
4. User accepts invitation
5. Association activated
6. User granted access
Expiration Handling​
1. Daily job checks expiring associations
2. Warning sent 7 days before expiry
3. Final notice 1 day before
4. Association deactivated on expiry
5. Access automatically revoked
6. Audit log entry created
Role Change Process​
1. Admin requests role change
2. Validate admin has permission
3. Check business rules
4. Update association record
5. Refresh user permissions
6. Log role change event
Performance Considerations​
Caching Strategy​
- Cache user's active associations
- Cache primary tenant lookup
- Invalidate on any association change
- TTL: 5 minutes for active sessions
Query Optimization​
- Index on user_id for fast lookup
- Composite index on (tenant_id, is_active)
- Separate index for expiring associations
- Denormalize primary tenant for speed
Compliance & Audit​
Tracking Requirements​
- Log all association changes
- Record who approved access
- Track permission usage
- Monitor unusual patterns
- Regular access reviews
Data Retention​
- Active associations: Indefinite
- Expired associations: 7 years
- Audit logs: 7 years
- Permission usage: 90 days
Integration Points​
With User Model​
- User deletion cascades to associations
- Primary tenant stored on user
- Profile syncs across tenants
With Tenant Model​
- Tenant deletion removes associations
- Tenant settings affect permissions
- Billing based on active users
With RBAC Model​
- Role permissions baseline
- Additional permissions overlay
- Dynamic permission calculation
Best Practices​
Association Management​
- Always set expiration for contractors
- Document reason in notes field
- Review associations quarterly
- Automate contractor offboarding
- Use least privilege principle
Security Guidelines​
- Require approval for cross-tenant access
- Log all tenant switches
- Alert on unusual access patterns
- Regular permission audits
- Enforce separation of duties
Future Enhancements​
Advanced Features​
- Delegation Support: Temporary permission delegation
- Approval Workflows: Multi-step association approval
- Access Reviews: Automated periodic reviews
- Smart Expiration: ML-based duration suggestions
Integration Improvements​
- SSO Integration: Federated identity support
- SCIM Support: Automated provisioning
- Directory Sync: LDAP/AD integration
- OAuth Scopes: Fine-grained API access
Compliance Features​
- Access Certification: Periodic attestation
- Segregation of Duties: Rule enforcement
- Privacy Controls: Cross-tenant data isolation
- Audit Reports: Compliance reporting
Last Updated: 2025-08-29 Version: 1.0