Permissions
A simple RBAC (Role-Based Access Control) system with a twist: roles live in the database, while permissions are defined in config. This hybrid approach keeps role assignments flexible (database) while keeping permission definitions simple and version-controlled (config).
How it works
- Users are assigned roles (stored in
user_rolesdatabase table) - Roles map to permissions (defined in
packages/config/index.ts)
Configuration
Section titled “Configuration”Define roles and their permissions in packages/config/index.ts:
const config = { permissions: { roleToPermissions: { premium: { viewPremiumContent: true, }, elite: { viewPremiumContent: true, viewEliteContent: true, }, }, },}All functions are exported from @/permissions.
| Function | Purpose |
|---|---|
requireUserPermissions(db, userId, permissions) | Throws 403 if user lacks any of the specified permissions |
requireUserRole(db, userId, roleName) | Throws 403 if user doesn’t have the role |
hasUserRole(db, userId, roleName) | Returns boolean - use when you need to check without throwing |
grantUserRole(db, userId, roleName) | Assigns a role to user |
revokeUserRole(db, userId, roleName) | Removes a role from user |
Protecting Pages
Section titled “Protecting Pages”Use requireUserPermissions in your loader to restrict access by permission:
import { authContext } from "@/auth/context"import { dbContext } from "@/db/context"import { requireUserId } from "@/auth"import { requireUserPermissions } from "@/permissions"
export async function loader({ request, context }: Route.LoaderArgs) { const db = context.get(dbContext) const { session } = context.get(authContext) const userId = await requireUserId(request, session) await requireUserPermissions(db, userId, ["viewPremiumContent"]) // User has permission, continue...}Or requireUserRole to restrict access by role:
import { authContext } from "@/auth/context"import { dbContext } from "@/db/context"import { requireUserId } from "@/auth"import { requireUserRole } from "@/permissions"
export async function loader({ request, context }: Route.LoaderArgs) { const db = context.get(dbContext) const { session } = context.get(authContext) const userId = await requireUserId(request, session) await requireUserRole(db, userId, "elite") // User has role, continue...}For conditional checks without throwing:
import { hasUserRole } from "@/permissions"
const isElite = await hasUserRole(db, userId, "elite")Granting & Revoking Access
Section titled “Granting & Revoking Access”Assign or remove roles from users:
import { grantUserRole, revokeUserRole } from "@/permissions"
// After a purchaseawait grantUserRole(db, userId, "premium")
// When subscription endsawait revokeUserRole(db, userId, "premium")