This document maps existing role-based permission checks to the new PBAC (Permission-Based Access Control) system's permission strings in the format resource.action.
| Permission String | Description | File Path | Line |
|---|---|---|---|
| team.create | Create teams | packages/trpc/server/routers/viewer/teams/create.handler.ts | 55-57 |
| team.update | Update team settings | packages/trpc/server/routers/viewer/teams/update.handler.ts | 18 |
| team.changeMemberRole | Change role of team members | packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts | 18-21 |
| team.remove | Remove members from team | packages/trpc/server/routers/viewer/teams/removeMember.handler.ts | 35-36, 51-55 |
| team.invite | Invite members to team | packages/trpc/server/routers/viewer/teams/invite.handler.ts | - |
| Permission String | Description | File Path | Line |
|---|---|---|---|
| eventType.create | Create event types | packages/trpc/server/routers/viewer/eventTypes/create.handler.ts | - |
| eventType.update | Update event types | packages/trpc/server/routers/viewer/eventTypes/update.handler.ts | 162-164 |
| eventType.delete | Delete event types | packages/trpc/server/routers/viewer/eventTypes/delete.handler.ts | 13-31 |
| Permission String | Description | File Path | Line |
|---|---|---|---|
| booking.read | Read booking details | packages/trpc/server/routers/viewer/bookings/get.handler.ts | 95-107, 132-134 |
| booking.readTeamBookings | Read team bookings | packages/trpc/server/routers/viewer/bookings/get.handler.ts | 240-252 |
| booking.readOrgBookings | Read organization bookings | packages/trpc/server/routers/viewer/bookings/get.handler.ts | 240-252 |
| Permission String | Description | File Path | Line |
|---|---|---|---|
| organization.read | Read organization details | packages/trpc/server/routers/viewer/organizations/get.handler.ts | - |
| organization.listMembers | List organization members | packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts | 68-76 |
| organization.create | Create organization | packages/trpc/server/routers/viewer/organizations/create.handler.ts | - |
| Permission String | Description | File Path | Line |
|---|---|---|---|
| apiKey.create | Create API keys | packages/trpc/server/routers/viewer/apiKeys/create.handler.ts | 25-26 |
| apiKey.findKeyOfType | Find API keys by type | packages/trpc/server/routers/viewer/apiKeys/findKeyOfType.handler.ts | 18-19 |
The following helper functions are commonly used for permission checks:
| Function | Description | File Path |
|---|---|---|
| isTeamAdmin | Checks if user is admin or owner of a team | packages/lib/server/queries/teams/index.ts |
| isTeamOwner | Checks if user is owner of a team | packages/lib/server/queries/teams/index.ts |
| isTeamMember | Checks if user is a member of a team | packages/lib/server/queries/teams/index.ts |
| canEditEntity | Checks if user can edit an entity | packages/lib/entityPermissionUtils.ts |
| canAccessEntity | Checks if user can access an entity | packages/lib/entityPermissionUtils.ts |
| getEntityPermissionLevel | Gets permission level for an entity | packages/lib/entityPermissionUtils.ts |
| canCreateEntity | Checks if user can create an entity | packages/lib/entityPermissionUtils.ts |
| withRoleCanCreateEntity | Checks if role allows entity creation | packages/lib/entityPermissionUtils.ts |
The codebase uses several enums to define permission levels:
enum MembershipRole {
OWNER = "OWNER",
ADMIN = "ADMIN",
MEMBER = "MEMBER"
}enum ENTITY_PERMISSION_LEVEL {
NONE,
USER_ONLY_WRITE,
TEAM_READ_ONLY,
TEAM_WRITE
}-
Team Admin/Owner Check: Many operations require the user to be a team admin or owner.
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
-
Team Owner Check: Some operations (like changing an owner's role) require the user to be a team owner.
if (input.role === MembershipRole.OWNER && !(await isTeamOwner(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
-
Organization Admin Check: Operations within an organization require the user to be an organization admin.
if (user.profile?.organizationId && !user.organization.isOrgAdmin) { throw new TRPCError({ code: "FORBIDDEN", message: "org_admins_can_create_new_teams" }); }
-
Entity Permission Check: Entity operations use permission level checks.
const permissionLevel = await getEntityPermissionLevel(entity, userId); return permissionLevel === ENTITY_PERMISSION_LEVEL.TEAM_WRITE || permissionLevel === ENTITY_PERMISSION_LEVEL.USER_ONLY_WRITE;
-
Booking Access Control: Complex permission checks for retrieving bookings based on user roles and team/organization membership.
const membershipIdsWhereUserIsAdminOwner = ( await prisma.membership.findMany({ where: { userId: user.id, role: { in: ["ADMIN", "OWNER"], }, }, select: { id: true, }, }) ).map((membership) => membership.id);
When migrating to the new PBAC system, these existing role-based checks should be replaced with permission string checks using the permissionMatches function:
import { permissionMatches } from "@calcom/features/pbac/types/permission-registry";
// Instead of:
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
// Use:
if (!permissionMatches("team.update", userPermissions)) throw new TRPCError({ code: "UNAUTHORIZED" });The following table provides permission string alternatives for common helper functions:
| Helper Function | Permission String Alternative | Description |
|---|---|---|
| isTeamAdmin | team.* |
Grants all team permissions |
| isTeamAdmin | team.update |
Update team settings |
| isTeamAdmin | team.invite |
Invite team members |
| isTeamAdmin | team.remove |
Remove team members |
| isTeamOwner | team.changeMemberRole |
Change role of team members |
| isTeamOwner | team.delete |
Delete team |
| isTeamMember | team.read |
Read-only access to team |
The following functions are used to check permissions for entities with userId and teamId properties:
| Function | Description | Permission String Alternative |
|---|---|---|
| canEditEntity | Checks if user can edit an entity | {resource}.update |
| canAccessEntity | Checks if user can access an entity | {resource}.read |
| getEntityPermissionLevel | Gets permission level for an entity | N/A - Implementation detail |
| canCreateEntity | Checks if user can create an entity | {resource}.create |
| Resource | canEditEntity Usage | canAccessEntity Usage | Permission String |
|---|---|---|---|
| routingForm | packages/app-store/routing-forms/trpc/forms.handler.ts | packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts | routingForm.update, routingForm.read |
| routingForm | packages/app-store/routing-forms/trpc/formMutation.handler.ts | routingForm.update |
|
| routingForm | packages/app-store/routing-forms/api/responses/[formId].ts | routingForm.update |
These functions can be used with any entity that has userId and teamId properties. When migrating to PBAC, replace these checks with appropriate permission string checks:
// Instead of:
if (!(await canEditEntity(form, user.id))) throw new TRPCError({ code: "UNAUTHORIZED" });
// Use:
if (!permissionMatches("routingForm.update", userPermissions)) throw new TRPCError({ code: "UNAUTHORIZED" });This allows for more granular permission control and the creation of custom roles with specific permissions.