Teams & Roles

Multi-tenancy, team management, and invitation flows

Teams & Roles

Build multi-tenant applications with team management and role-based permissions.

Features

  • Team creation and management
  • Member invitations
  • Role-based access control
  • Team switching
  • Resource isolation

Team Structure

interface Team {
  id: string
  name: string
  slug: string
  ownerId: string
  createdAt: Date
}

interface TeamMember {
  id: string
  teamId: string
  userId: string
  role: 'owner' | 'admin' | 'member'
  joinedAt: Date
}

Creating Teams

// Create a new team
const team = await $fetch('/api/teams', {
  method: 'POST',
  body: {
    name: 'Acme Inc',
    slug: 'acme-inc'
  }
})

Inviting Members

// Send team invitation
await $fetch(`/api/teams/${teamId}/invitations`, {
  method: 'POST',
  body: {
    email: '[email protected]',
    role: 'member'
  }
})

Accepting Invitations

// Accept invitation
await $fetch(`/api/teams/invitations/${token}/accept`, {
  method: 'POST'
})

Team Permissions

// Check team permission
function hasPermission(user: User, team: Team, permission: string) {
  const member = team.members.find(m => m.userId === user.id)
  
  if (!member) return false
  
  const permissions = {
    owner: ['*'],
    admin: ['read', 'write', 'invite', 'remove_member'],
    member: ['read']
  }
  
  const rolePermissions = permissions[member.role]
  return rolePermissions.includes('*') || rolePermissions.includes(permission)
}

Role Middleware

// server/middleware/team-role.ts
export default defineEventHandler(async (event) => {
  const teamId = getRouterParam(event, 'teamId')
  const user = event.context.user
  
  const member = await getTeamMember(teamId, user.id)
  
  if (!member || !hasRequiredRole(member.role, 'admin')) {
    throw createError({
      statusCode: 403,
      message: 'Insufficient permissions'
    })
  }
})

Team Switching

<script setup>
const currentTeam = ref(null)

async function switchTeam(teamId: string) {
  await $fetch('/api/teams/switch', {
    method: 'POST',
    body: { teamId }
  })
  
  currentTeam.value = teamId
  await navigateTo('/dashboard')
}
</script>

Resource Isolation

Ensure resources are scoped to teams:

// Always filter by team
const projects = await db
  .select()
  .from(projects)
  .where(eq(projects.teamId, currentTeamId))

Best Practices

  1. Always validate team membership
  2. Implement proper role checks
  3. Log team actions for audit
  4. Limit team size if needed
  5. Handle team deletion carefully

Next Steps