Skip to content

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

ActionOwnerAdminEditorViewer
View
Edit
Upload
Delete
Share
Manage Team
Transfer

Problems with Current Approach

  1. No Team Entity: Adding the same 5 people to every project requires 5 individual additions per project
  2. No Audit Trail: No record of who added/removed whom, or when roles changed
  3. 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
  4. No Bulk Operations: Can't "add the mastering team to all projects in this space"
  5. Shared Links = Implicit Role: sharedLinkRole field 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

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:

ModeAuth RequiredPasswordUse Case
PublicNoNoShare on social media
PasswordNoYesSend to A&R, label contacts
RegisteredYesNoCollaborator/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

  1. Phase 1 (now): Keep existing collaborators[] — improve UX for add/remove
  2. Phase 2 (soon): Add shared link access control (password, auth modes)
  3. Phase 3 (premium): Introduce Teams for labels/studios
  4. 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?)

Ctrl-Audio Platform Documentation