| name | permissions |
| description | Guide for working with team-based permissions and authorization in the WODsmith codebase. Use when touching TEAM_PERMISSIONS constants, hasTeamPermission/requireTeamPermission functions, adding permission checks to actions or server functions, creating features requiring authorization, or ensuring client-server permission consistency. |
Permissions
Overview
WODsmith uses a team-based permissions system with role-based access control. All authorization checks must be consistent between client-side actions and server-side functions.
Core Principles
Permission Alignment: Client-side actions and server-side functions MUST check the same permission. Mismatches cause authorization failures.
Granular Permissions: Use the most specific permission available. Prefer EDIT_COMPONENTS over EDIT_TEAM_SETTINGS when working with workout components.
Team Context Required: All permission checks require a teamId. The codebase is multi-tenant.
Permission Categories
Available in src/db/schemas/teams.ts as TEAM_PERMISSIONS:
Resource Access
ACCESS_DASHBOARD - View team dashboard
ACCESS_BILLING - View/manage billing
User Management
INVITE_MEMBERS - Invite new members
REMOVE_MEMBERS - Remove team members
CHANGE_MEMBER_ROLES - Modify member roles
Team Management
EDIT_TEAM_SETTINGS - Modify team settings
DELETE_TEAM - Delete team
Role Management
CREATE_ROLES - Create custom roles
EDIT_ROLES - Modify roles
DELETE_ROLES - Delete roles
ASSIGN_ROLES - Assign roles to members
Content Management
CREATE_COMPONENTS - Create workout components (exercises, scaling groups, etc.)
EDIT_COMPONENTS - Modify workout components
DELETE_COMPONENTS - Delete workout components
Programming
MANAGE_PROGRAMMING - Manage programming tracks
MANAGE_SCALING_GROUPS - Manage scaling groups
Permission Checking Patterns
In Server Actions (src/actions/)
export const myAction = createServerAction()
.input(z.object({ teamId: z.string(), ... }))
.handler(async ({ input }) => {
const session = await getSessionFromCookie()
if (!session) {
throw new ZSAError("NOT_AUTHORIZED", "Not authenticated")
}
const canEdit = await hasTeamPermission(
input.teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS,
)
if (!canEdit) {
throw new ZSAError("FORBIDDEN", "Cannot edit components")
}
})
In Server Functions (src/server/)
export async function myServerFunction({ teamId, ... }) {
const db = getDb()
const [resource] = await db
.select()
.from(resourceTable)
.where(eq(resourceTable.id, resourceId))
if (!resource) throw new Error("Not found")
if (resource.teamId) {
if (!teamId) throw new Error("Forbidden")
await requireTeamPermission(teamId, TEAM_PERMISSIONS.EDIT_COMPONENTS)
if (resource.teamId !== teamId) throw new Error("Forbidden")
}
}
Common Patterns
Workflow Components (Scaling, Exercises, etc.)
Use the COMPONENTS family:
- Actions calling create functions:
CREATE_COMPONENTS
- Actions calling update functions:
EDIT_COMPONENTS
- Actions calling delete functions:
DELETE_COMPONENTS
Team Settings
Use EDIT_TEAM_SETTINGS for:
- Default scaling group assignment
- Team profile updates
- Team configuration
Checking Multiple Call Sites
When fixing permission mismatches:
- Identify the mismatch: Action uses Permission A, server function uses Permission B
- Check related operations: Look at sibling actions (create/update/delete) for consistency
- Determine correct permission: Match the granularity of the operation
- Update the wrong side: Usually update server functions to match actions
- Verify call sites: Search for all calls to ensure compatibility
Example Fix
const canEdit = await hasTeamPermission(
input.teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS,
)
await requireTeamPermission(
teamId,
TEAM_PERMISSIONS.EDIT_TEAM_SETTINGS,
)
const canEdit = await hasTeamPermission(
input.teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS,
)
await requireTeamPermission(
teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS,
)
Permission Utilities
Located in src/utils/team-auth.ts:
Check Functions (return boolean)
hasTeamPermission(teamId, permission) - Check if user has permission
hasTeamRole(teamId, roleId, isSystemRole) - Check if user has role
isTeamMember(teamId) - Check if user is member
Require Functions (throw ZSAError if unauthorized)
requireTeamPermission(teamId, permission) - Require permission or throw
requireTeamRole(teamId, roleId, isSystemRole) - Require role or throw
requireTeamMembership(teamId) - Require membership or throw
Use check functions in actions for manual error handling.
Use require functions in server functions for automatic error throwing.
Validation Checklist
When working with permissions:
Adding New Permissions
- Add to
TEAM_PERMISSIONS in src/db/schemas/teams.ts
- Update default role permissions if needed
- Use consistently across actions and server functions
- Document purpose in this skill's references
References
See references/permissions-reference.md for:
- Complete list of all permissions with descriptions
- System roles and their default permissions
- Permission hierarchy and inheritance