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.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
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 dashboardACCESS_BILLING- View/manage billing
User Management
INVITE_MEMBERS- Invite new membersREMOVE_MEMBERS- Remove team membersCHANGE_MEMBER_ROLES- Modify member roles
Team Management
EDIT_TEAM_SETTINGS- Modify team settingsDELETE_TEAM- Delete team
Role Management
CREATE_ROLES- Create custom rolesEDIT_ROLES- Modify rolesDELETE_ROLES- Delete rolesASSIGN_ROLES- Assign roles to members
Content Management
CREATE_COMPONENTS- Create workout components (exercises, scaling groups, etc.)EDIT_COMPONENTS- Modify workout componentsDELETE_COMPONENTS- Delete workout components
Programming
MANAGE_PROGRAMMING- Manage programming tracksMANAGE_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")
}
// Check permission
const canEdit = await hasTeamPermission(
input.teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS,
)
if (!canEdit) {
throw new ZSAError("FORBIDDEN", "Cannot edit components")
}
// Perform action...
})
In Server Functions (src/server/)
export async function myServerFunction({ teamId, ... }) {
const db = getDb()
// Verify team ownership/access
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")
// Must match permission in calling action
await requireTeamPermission(teamId, TEAM_PERMISSIONS.EDIT_COMPONENTS)
if (resource.teamId !== teamId) throw new Error("Forbidden")
}
// Perform operation...
}
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
// BEFORE: Mismatch
// Action (scaling-actions.ts)
const canEdit = await hasTeamPermission(
input.teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS, // ❌ Different
)
// Server function (scaling-levels.ts)
await requireTeamPermission(
teamId,
TEAM_PERMISSIONS.EDIT_TEAM_SETTINGS, // ❌ Different
)
// AFTER: Aligned
// Action
const canEdit = await hasTeamPermission(
input.teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS, // ✅ Match
)
// Server function
await requireTeamPermission(
teamId,
TEAM_PERMISSIONS.EDIT_COMPONENTS, // ✅ Match
)
Permission Utilities
Located in src/utils/team-auth.ts:
Check Functions (return boolean)
hasTeamPermission(teamId, permission)- Check if user has permissionhasTeamRole(teamId, roleId, isSystemRole)- Check if user has roleisTeamMember(teamId)- Check if user is member
Require Functions (throw ZSAError if unauthorized)
requireTeamPermission(teamId, permission)- Require permission or throwrequireTeamRole(teamId, roleId, isSystemRole)- Require role or throwrequireTeamMembership(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:
- Action and server function use the same permission constant
- Permission is appropriate for the operation's granularity
-
teamIdis validated and passed through call chain - Both client and server handle unauthorized cases
- Related operations (CRUD siblings) use consistent permission family
- Type checking passes after changes
- Consider if new feature needs a new permission constant
Adding New Permissions
- Add to
TEAM_PERMISSIONSinsrc/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
More by wodsmith
View allImplement drag and drop using @atlaskit/pragmatic-drag-and-drop. Use when implementing sortable lists, reorderable items, kanban boards, or any drag-drop interactions. Covers draggable setup, drop targets, edge detection, drag previews, and critical state management patterns to avoid performance issues.
Handle local database schema changes with Drizzle and PlanetScale. Use when making schema changes to src/db/schema.ts, adding/modifying database tables or columns, or when asked about database migrations. Covers the push-based local development workflow and when to generate migrations.
Fix type assertions and improve TypeScript type safety. Use when encountering 'as unknown as' casts, manual type definitions that duplicate schema types, or unclear type errors in database queries, especially with Drizzle ORM relations. Also use when verifying types
Documentation guidance for competition athletes and volunteers in WODsmith. Use when writing, reviewing, or improving athlete-facing documentation including registration, scheduling, workout viewing, leaderboards, check-in, and volunteer coordination.
