|
| 1 | +# Cal.com Permission Documentation |
| 2 | + |
| 3 | +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`. |
| 4 | + |
| 5 | +## Permissions by Resource |
| 6 | + |
| 7 | +### Team Permissions |
| 8 | + |
| 9 | +| Permission String | Description | File Path | Line | |
| 10 | +|------------------|-------------|-----------|------| |
| 11 | +| team.create | Create teams | [packages/trpc/server/routers/viewer/teams/create.handler.ts](packages/trpc/server/routers/viewer/teams/create.handler.ts) | 55-57 | |
| 12 | +| team.update | Update team settings | [packages/trpc/server/routers/viewer/teams/update.handler.ts](packages/trpc/server/routers/viewer/teams/update.handler.ts) | 18 | |
| 13 | +| team.changeMemberRole | Change role of team members | [packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts](packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts) | 18-21 | |
| 14 | +| team.remove | Remove members from team | [packages/trpc/server/routers/viewer/teams/removeMember.handler.ts](packages/trpc/server/routers/viewer/teams/removeMember.handler.ts) | 35-36, 51-55 | |
| 15 | +| team.invite | Invite members to team | [packages/trpc/server/routers/viewer/teams/invite.handler.ts](packages/trpc/server/routers/viewer/teams/invite.handler.ts) | - | |
| 16 | + |
| 17 | +### Event Type Permissions |
| 18 | + |
| 19 | +| Permission String | Description | File Path | Line | |
| 20 | +|------------------|-------------|-----------|------| |
| 21 | +| eventType.create | Create event types | [packages/trpc/server/routers/viewer/eventTypes/create.handler.ts](packages/trpc/server/routers/viewer/eventTypes/create.handler.ts) | - | |
| 22 | +| eventType.update | Update event types | [packages/trpc/server/routers/viewer/eventTypes/update.handler.ts](packages/trpc/server/routers/viewer/eventTypes/update.handler.ts) | 162-164 | |
| 23 | +| eventType.delete | Delete event types | [packages/trpc/server/routers/viewer/eventTypes/delete.handler.ts](packages/trpc/server/routers/viewer/eventTypes/delete.handler.ts) | 13-31 | |
| 24 | + |
| 25 | +### Booking Permissions |
| 26 | + |
| 27 | +| Permission String | Description | File Path | Line | |
| 28 | +|------------------|-------------|-----------|------| |
| 29 | +| booking.read | Read booking details | [packages/trpc/server/routers/viewer/bookings/get.handler.ts](packages/trpc/server/routers/viewer/bookings/get.handler.ts) | 95-107, 132-134 | |
| 30 | +| booking.readTeamBookings | Read team bookings | [packages/trpc/server/routers/viewer/bookings/get.handler.ts](packages/trpc/server/routers/viewer/bookings/get.handler.ts) | 240-252 | |
| 31 | +| booking.readOrgBookings | Read organization bookings | [packages/trpc/server/routers/viewer/bookings/get.handler.ts](packages/trpc/server/routers/viewer/bookings/get.handler.ts) | 240-252 | |
| 32 | + |
| 33 | +### Organization Permissions |
| 34 | + |
| 35 | +| Permission String | Description | File Path | Line | |
| 36 | +|------------------|-------------|-----------|------| |
| 37 | +| organization.read | Read organization details | [packages/trpc/server/routers/viewer/organizations/get.handler.ts](packages/trpc/server/routers/viewer/organizations/get.handler.ts) | - | |
| 38 | +| organization.listMembers | List organization members | [packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts](packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts) | 68-76 | |
| 39 | +| organization.create | Create organization | [packages/trpc/server/routers/viewer/organizations/create.handler.ts](packages/trpc/server/routers/viewer/organizations/create.handler.ts) | - | |
| 40 | + |
| 41 | +### API Key Permissions |
| 42 | + |
| 43 | +| Permission String | Description | File Path | Line | |
| 44 | +|------------------|-------------|-----------|------| |
| 45 | +| apiKey.create | Create API keys | [packages/trpc/server/routers/viewer/apiKeys/create.handler.ts](packages/trpc/server/routers/viewer/apiKeys/create.handler.ts) | 25-26 | |
| 46 | +| apiKey.findKeyOfType | Find API keys by type | [packages/trpc/server/routers/viewer/apiKeys/findKeyOfType.handler.ts](packages/trpc/server/routers/viewer/apiKeys/findKeyOfType.handler.ts) | 18-19 | |
| 47 | + |
| 48 | +## Helper Functions and Utilities |
| 49 | + |
| 50 | +The following helper functions are commonly used for permission checks: |
| 51 | + |
| 52 | +| Function | Description | File Path | |
| 53 | +|----------|-------------|-----------| |
| 54 | +| isTeamAdmin | Checks if user is admin or owner of a team | [packages/lib/server/queries/teams/index.ts](packages/lib/server/queries/teams/index.ts#L385-L405) | |
| 55 | +| isTeamOwner | Checks if user is owner of a team | [packages/lib/server/queries/teams/index.ts](packages/lib/server/queries/teams/index.ts#L407-L416) | |
| 56 | +| isTeamMember | Checks if user is a member of a team | [packages/lib/server/queries/teams/index.ts](packages/lib/server/queries/teams/index.ts#L418-L426) | |
| 57 | +| canEditEntity | Checks if user can edit an entity | [packages/lib/entityPermissionUtils.ts](packages/lib/entityPermissionUtils.ts#L13-L22) | |
| 58 | +| canAccessEntity | Checks if user can access an entity | [packages/lib/entityPermissionUtils.ts](packages/lib/entityPermissionUtils.ts#L24-L34) | |
| 59 | +| getEntityPermissionLevel | Gets permission level for an entity | [packages/lib/entityPermissionUtils.ts](packages/lib/entityPermissionUtils.ts#L36-L73) | |
| 60 | +| canCreateEntity | Checks if user can create an entity | [packages/lib/entityPermissionUtils.ts](packages/lib/entityPermissionUtils.ts#L102-L115) | |
| 61 | +| withRoleCanCreateEntity | Checks if role allows entity creation | [packages/lib/entityPermissionUtils.ts](packages/lib/entityPermissionUtils.ts#L119-L121) | |
| 62 | + |
| 63 | +## Permission Level Enums |
| 64 | + |
| 65 | +The codebase uses several enums to define permission levels: |
| 66 | + |
| 67 | +### MembershipRole Enum |
| 68 | + |
| 69 | +```typescript |
| 70 | +enum MembershipRole { |
| 71 | + OWNER = "OWNER", |
| 72 | + ADMIN = "ADMIN", |
| 73 | + MEMBER = "MEMBER" |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +### Entity Permission Level Enum |
| 78 | + |
| 79 | +```typescript |
| 80 | +enum ENTITY_PERMISSION_LEVEL { |
| 81 | + NONE, |
| 82 | + USER_ONLY_WRITE, |
| 83 | + TEAM_READ_ONLY, |
| 84 | + TEAM_WRITE |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +## Common Permission Patterns |
| 89 | + |
| 90 | +1. **Team Admin/Owner Check**: Many operations require the user to be a team admin or owner. |
| 91 | + ```typescript |
| 92 | + if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" }); |
| 93 | + ``` |
| 94 | + |
| 95 | +2. **Team Owner Check**: Some operations (like changing an owner's role) require the user to be a team owner. |
| 96 | + ```typescript |
| 97 | + if (input.role === MembershipRole.OWNER && !(await isTeamOwner(ctx.user?.id, input.teamId))) |
| 98 | + throw new TRPCError({ code: "UNAUTHORIZED" }); |
| 99 | + ``` |
| 100 | + |
| 101 | +3. **Organization Admin Check**: Operations within an organization require the user to be an organization admin. |
| 102 | + ```typescript |
| 103 | + if (user.profile?.organizationId && !user.organization.isOrgAdmin) { |
| 104 | + throw new TRPCError({ code: "FORBIDDEN", message: "org_admins_can_create_new_teams" }); |
| 105 | + } |
| 106 | + ``` |
| 107 | + |
| 108 | +4. **Entity Permission Check**: Entity operations use permission level checks. |
| 109 | + ```typescript |
| 110 | + const permissionLevel = await getEntityPermissionLevel(entity, userId); |
| 111 | + return permissionLevel === ENTITY_PERMISSION_LEVEL.TEAM_WRITE || |
| 112 | + permissionLevel === ENTITY_PERMISSION_LEVEL.USER_ONLY_WRITE; |
| 113 | + ``` |
| 114 | + |
| 115 | +5. **Booking Access Control**: Complex permission checks for retrieving bookings based on user roles and team/organization membership. |
| 116 | + ```typescript |
| 117 | + const membershipIdsWhereUserIsAdminOwner = ( |
| 118 | + await prisma.membership.findMany({ |
| 119 | + where: { |
| 120 | + userId: user.id, |
| 121 | + role: { |
| 122 | + in: ["ADMIN", "OWNER"], |
| 123 | + }, |
| 124 | + }, |
| 125 | + select: { |
| 126 | + id: true, |
| 127 | + }, |
| 128 | + }) |
| 129 | + ).map((membership) => membership.id); |
| 130 | + ``` |
| 131 | + |
| 132 | +## Migration to PBAC |
| 133 | + |
| 134 | +When migrating to the new PBAC system, these existing role-based checks should be replaced with permission string checks using the `permissionMatches` function: |
| 135 | + |
| 136 | +```typescript |
| 137 | +import { permissionMatches } from "@calcom/features/pbac/types/permission-registry"; |
| 138 | + |
| 139 | +// Instead of: |
| 140 | +if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" }); |
| 141 | + |
| 142 | +// Use: |
| 143 | +if (!permissionMatches("team.update", userPermissions)) throw new TRPCError({ code: "UNAUTHORIZED" }); |
| 144 | +``` |
| 145 | + |
| 146 | +## Permission String Alternatives for Helper Functions |
| 147 | + |
| 148 | +The following table provides permission string alternatives for common helper functions: |
| 149 | + |
| 150 | +| Helper Function | Permission String Alternative | Description | |
| 151 | +|-----------------|-------------------------------|-------------| |
| 152 | +| isTeamAdmin | `team.*` | Grants all team permissions | |
| 153 | +| isTeamAdmin | `team.update` | Update team settings | |
| 154 | +| isTeamAdmin | `team.invite` | Invite team members | |
| 155 | +| isTeamAdmin | `team.remove` | Remove team members | |
| 156 | +| isTeamOwner | `team.changeMemberRole` | Change role of team members | |
| 157 | +| isTeamOwner | `team.delete` | Delete team | |
| 158 | +| isTeamMember | `team.read` | Read-only access to team | |
| 159 | + |
| 160 | +## Entity Permission Functions and Resources |
| 161 | + |
| 162 | +The following functions are used to check permissions for entities with userId and teamId properties: |
| 163 | + |
| 164 | +| Function | Description | Permission String Alternative | |
| 165 | +|----------|-------------|-------------------------------| |
| 166 | +| canEditEntity | Checks if user can edit an entity | `{resource}.update` | |
| 167 | +| canAccessEntity | Checks if user can access an entity | `{resource}.read` | |
| 168 | +| getEntityPermissionLevel | Gets permission level for an entity | N/A - Implementation detail | |
| 169 | +| canCreateEntity | Checks if user can create an entity | `{resource}.create` | |
| 170 | + |
| 171 | +### Resources Used with Entity Permission Functions |
| 172 | + |
| 173 | +| Resource | canEditEntity Usage | canAccessEntity Usage | Permission String | |
| 174 | +|----------|-------------------|---------------------|------------------| |
| 175 | +| routingForm | [packages/app-store/routing-forms/trpc/forms.handler.ts](packages/app-store/routing-forms/trpc/forms.handler.ts#L72) | [packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts](packages/app-store/routing-forms/trpc/getResponseWithFormFields.handler.ts#L78) | `routingForm.update`, `routingForm.read` | |
| 176 | +| routingForm | [packages/app-store/routing-forms/trpc/formMutation.handler.ts](packages/app-store/routing-forms/trpc/formMutation.handler.ts#L315) | | `routingForm.update` | |
| 177 | +| routingForm | [packages/app-store/routing-forms/api/responses/[formId].ts](packages/app-store/routing-forms/api/responses/[formId].ts#L96) | | `routingForm.update` | |
| 178 | + |
| 179 | +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: |
| 180 | + |
| 181 | +```typescript |
| 182 | +// Instead of: |
| 183 | +if (!(await canEditEntity(form, user.id))) throw new TRPCError({ code: "UNAUTHORIZED" }); |
| 184 | + |
| 185 | +// Use: |
| 186 | +if (!permissionMatches("routingForm.update", userPermissions)) throw new TRPCError({ code: "UNAUTHORIZED" }); |
| 187 | +``` |
| 188 | + |
| 189 | +This allows for more granular permission control and the creation of custom roles with specific permissions. |
0 commit comments