import type { Payload, PayloadRequest } from "payload"; import { loadUserPermissions } from "@/utils/access-control/loadUserPermissions"; import type { Permission } from "@/permissions"; /** * Minimal user shape for permission checks. * Accepts the full Payload User or a stripped-down { id } from server actions. */ interface UserLike { id: number | string; roleDocs?: unknown; } /** * Check whether a user has a specific permission. * * Resolution order: * 1. No user → false. * 2. User has a role with `isSuperuser: true` → true (bypasses all checks). * 3. User has a role whose `permissions` array includes the given permission → true. * 4. Otherwise → false. * * Results are cached per-user for 30s (see `loadUserPermissions`). * * @example * const canCreate = await hasPermission(payload, user, "users:create"); */ export async function hasPermission( payload: Payload, user: UserLike | null | undefined, permission: Permission, ): Promise { if (!user) return false; const { permissions, isSuperuser } = await loadUserPermissions(payload, user); if (isSuperuser) return true; return permissions.has(permission); } /** * Check whether a user has a superuser role (bypasses all permission checks). * * Use this for the legacy `isDeveloper` replacement where the check was * "can do anything" rather than a specific permission. */ export async function isSuperuser( payload: Payload, user: UserLike | null | undefined, ): Promise { if (!user) return false; const { isSuperuser: su } = await loadUserPermissions(payload, user); return su; } /** * Check whether a user has ANY of the given permissions. */ export async function hasAnyPermission( payload: Payload, user: UserLike | null | undefined, ...permissions: Permission[] ): Promise { if (!user) return false; const { permissions: userPerms, isSuperuser: su } = await loadUserPermissions(payload, user); if (su) return true; return permissions.some((p) => userPerms.has(p)); } /** * Check whether a user has ALL of the given permissions. */ export async function hasAllPermissions( payload: Payload, user: UserLike | null | undefined, ...permissions: Permission[] ): Promise { if (!user) return false; const { permissions: userPerms, isSuperuser: su } = await loadUserPermissions(payload, user); if (su) return true; return permissions.every((p) => userPerms.has(p)); } /** * Factory that creates a Payload collection access function requiring a specific * permission. Drop-in replacement for `isDeveloper` / `isAdmin` in collection * `access` blocks. * * @example * access: { * create: requirePermission("users:create"), * update: requirePermission("users:update"), * } */ export function requirePermission(permission: Permission) { return async ({ req }: { req: PayloadRequest }): Promise => { return hasPermission(req.payload, req.user, permission); }; } /** * Factory that creates a Payload collection access function requiring ANY of * the given permissions. * * @example * access: { * update: requireAnyPermission("tickets:update", "tickets:staff"), * } */ export function requireAnyPermission(...permissions: Permission[]) { return async ({ req }: { req: PayloadRequest }): Promise => { return hasAnyPermission(req.payload, req.user, ...permissions); }; }