con un clic
migrate-permissions
// Guide for moving inline auth checks out of GraphQL resolvers and into permissions.ts using graphql-shield rules. Use when adding, reviewing, or migrating authorization logic for mutations or queries.
// Guide for moving inline auth checks out of GraphQL resolvers and into permissions.ts using graphql-shield rules. Use when adding, reviewing, or migrating authorization logic for mutations or queries.
Reference conventions for React client components — Tailwind CSS, UI primitives (Dialog, Tooltip, Menu), constants, HTML sanitization, and component design patterns. Use when building or reviewing UI components in packages/client.
Conventions for the SDL-first GraphQL server — payload types, codegen mappers, dataLoaders, TypeScript safety, database patterns, and migration best practices. Use when writing or reviewing GraphQL mutations, queries, or resolvers.
Steps to set up the Stripe CLI and forward webhook events to a local dev server for testing Stripe integrations.
| name | migrate-permissions |
| description | Guide for moving inline auth checks out of GraphQL resolvers and into permissions.ts using graphql-shield rules. Use when adding, reviewing, or migrating authorization logic for mutations or queries. |
Authorization lives in two places: graphql/public/permissions.ts (preferred) and inline in resolver functions (legacy). The goal is to move all auth into permissions.ts.
permissions.ts exports a permissionMap that maps GraphQL types/fields to shield rules. composeResolvers.ts wraps each resolver with its rule as a higher-order function — avoiding the overhead of graphql-middleware.
'*' wildcard sets a default rule for all fields on a type (e.g., all Mutations default to isAuthenticated)and(), or(), not() from graphql-shieldgraphql/public/rules/| Rule | What it checks |
|---|---|
isAuthenticated | viewer has a valid auth token |
isSuperUser | viewer has su role |
isTeamMember | authToken.tms includes the teamId (fast, no DB) |
isViewerTeamLead | viewer has isLead on the team member record (DB) |
isViewerOnTeam | viewer has an active (non-removed) team member record (DB) |
isTeamMemberOfMeeting | viewer's authToken.tms includes the meeting's teamId (fast) |
isMeetingMember | viewer has an actual meetingMembers record (DB, stricter) |
isViewerBillingLeader | viewer has BILLING_LEADER or ORG_ADMIN org role |
hasOrgRole | viewer has a specific org role (ORG_ADMIN, etc.) |
isViewerOnOrg | viewer is a member of the org |
isOrgTier | org is on a specific tier (enterprise, etc.) |
hasPageAccess | viewer has viewer or owner access to a page |
hasProviderAccess | viewer can access an OAuth provider |
isNull | an arg is null (used for conditional or() rules) |
isEnvVarTrue | an env var is set to true |
rateLimit | rate limits by perMinute/perHour |
Key distinction: isTeamMemberOfMeeting (used for createReflection) only checks team membership and is more permissive. isMeetingMember checks for an actual meeting member record and is stricter. Use isMeetingMember when you need to confirm the viewer has joined the meeting.
Rules that take a dotPath accept strings like:
'args.teamId' — top-level arg'args.input.meetingId' — nested arg'args.newAgendaItem.teamId' — nested arg'source.id' — field on the parent resolver's source object'source.orgId' — field on the parent resolver's source objectWhen extracting inline auth:
// AUTH)and()/or()permissionMap in permissions.ts (keep fields alphabetical, '*' first)scope === 'global' | 'org' | 'team')