RBAC & Team Management — Design Planning
Status: 📝 Planning (not yet implemented) Phase: Mid-term (after navigation, Tailwind stabilization, and test coverage) Pricing: Basic collaborators = freemium; Teams = premium feature
Current State
The platform uses per-entity collaborator arrays for access control:
typescript
// Each entity has its own collaborators
collaborators: [{
userId: ObjectId, // Reference to User
role: 'owner' | 'admin' | 'editor' | 'viewer'
}]Entities with collaborators
- ArtistSpace — top-level container
- Project — inherits from parent ArtistSpace (implicit)
- Track — inherits from parent Project (implicit)
Current Role Hierarchy
| Action | Owner | Admin | Editor | Viewer |
|---|---|---|---|---|
| View | ✅ | ✅ | ✅ | ✅ |
| Edit | ✅ | ✅ | ✅ | ❌ |
| Upload | ✅ | ✅ | ✅ | ❌ |
| Delete | ✅ | ✅ | ❌ | ❌ |
| Share | ✅ | ✅ | ❌ | ❌ |
| Manage Team | ✅ | ✅ | ❌ | ❌ |
| Transfer | ✅ | ❌ | ❌ | ❌ |
Problems with Current Approach
- No Team Entity: Adding the same 5 people to every project requires 5 individual additions per project
- No Audit Trail: No record of who added/removed whom, or when roles changed
- Inconsistent Inheritance: If a collaborator is added at ArtistSpace level, they don't automatically appear on new Projects under that space — it's checked at query time
- No Bulk Operations: Can't "add the mastering team to all projects in this space"
- Shared Links = Implicit Role:
sharedLinkRolefield exists but isn't well-defined in terms of how it interacts with explicit collaborators
Proposed Design
Phase 1: Basic Collaborators (Freemium)
Keep and improve the existing per-entity collaborator model:
- Add/remove collaborators on ArtistSpace, Project, Track
- Roles: owner, admin, editor, viewer
- No Team abstraction yet — simple and sufficient for indie artists
Phase 2: Shared Access Links (Freemium)
Redesign the current shared link system with proper access control:
typescript
// SharedLink
{
_id: ObjectId,
entityType: 'artistSpace' | 'project' | 'track',
entityId: ObjectId,
createdBy: ObjectId,
// Access control
accessType: 'public' | 'password' | 'registered-only',
password?: string, // Hashed, for password-protected links
requireAuth: boolean, // Must be logged in?
allowAnonymous: boolean, // Can view without account?
role: 'viewer' | 'commenter',
// Lifecycle
expiresAt?: Date,
maxUses?: number,
usageCount: number,
isActive: boolean,
createdAt: Date
}Access modes:
| Mode | Auth Required | Password | Use Case |
|---|---|---|---|
| Public | No | No | Share on social media |
| Password | No | Yes | Send to A&R, label contacts |
| Registered | Yes | No | Collaborator/client review |
Phase 3: Teams (Premium)
Teams are both global and scoped:
┌─────────────────────┐
│ Music Label (Org) │
│ ─────────────── │
│ Team: "Mastering" │──→ assigned to ArtistSpace A, B
│ Team: "A&R" │──→ assigned to ArtistSpace C
│ Team: "Marketing" │──→ assigned to all projects
└─────────────────────┘
┌─────────────────────┐
│ Indie Artist │
│ ─────────────── │
│ (no teams needed) │
│ Uses basic collab │
└─────────────────────┘Team model:
typescript
// Team (global — exists independent of any entity)
{
_id: ObjectId,
name: string, // "Mastering Team", "Label A&R"
owner: Ref → User, // Team creator
members: [{
userId: Ref → User,
teamRole: 'admin' | 'member', // Within the team itself
entityRole?: 'editor' | 'viewer', // Optional per-member override
addedAt: Date
}],
createdAt: Date
}
// Entity assignment (on ArtistSpace/Project/Track)
teams: [{
teamId: Ref → Team,
entityRole: 'admin' | 'editor' | 'viewer' // Default role for team members
}]Scope flexibility:
- Teams are created globally (belong to the user, not to an entity)
- Teams can be assigned to any combination of spaces/projects/tracks
- A music label creates teams once, then assigns them across artists
- An indie artist just uses basic collaborator (Phase 1) — no overhead
Phase 4: Permission Policies & Audit (Future)
- Centralized permission check service
canUserPerform(userId, entityId, action)→ boolean- Resolution: explicit user role > team member override > team default role > shared link role
- Audit log for all permission changes
Migration Strategy
- Phase 1 (now): Keep existing
collaborators[]— improve UX for add/remove - Phase 2 (soon): Add shared link access control (password, auth modes)
- Phase 3 (premium): Introduce Teams for labels/studios
- Phase 4 (future): Centralized permissions + audit log
Each phase is non-breaking — new features layer on top.
Open Questions
- How should per-member role overrides work in a team context? (e.g., team is "editor" but one member is "viewer-only")
- Should shared links support expiry by default, or is that a premium feature?
- What analytics/metrics should be tracked for shared link usage?
- Password-protected links: should the password be set per-link or per-entity?
- How do we handle the case where a user is both a direct collaborator AND in a team? (highest role wins?)