Skip to main content

Role-Based Access Control (RBAC)

Switchyard uses a comprehensive RBAC system built on Supabase, providing granular control over user permissions.

Database Schema

The RBAC system consists of five core tables:

roles

Stores role definitions.
CREATE TABLE public.roles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(50) NOT NULL UNIQUE,
  description TEXT,
  is_system BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

permissions

Stores permission definitions following the resource.action pattern.
CREATE TABLE public.permissions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(100) NOT NULL UNIQUE,
  description TEXT,
  resource VARCHAR(50) NOT NULL,
  action VARCHAR(20) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

role_permissions

Junction table linking roles to permissions.

user_roles

Junction table linking Supabase users to roles.

Default Roles

RoleDescriptionSystem?
superadminFull system access, bypasses all permission checksYes
managerStore operations, inventory, orders, productsYes
pickerWarehouse picking operations, inventory scanningYes
driverDelivery operations, order viewingYes
robotAutomated system operationsYes

Permissions

Permissions follow the resource.action naming convention:
PermissionResourceActionDescription
orders.readordersreadView orders
orders.writeorderswriteCreate and update orders
orders.deleteordersdeleteCancel orders
inventory.readinventoryreadView inventory
inventory.writeinventorywriteUpdate inventory levels
inventory.scaninventoryscanScan and process inventory
products.readproductsreadView products
products.writeproductswriteCreate and update products
scanner.usescanneruseUse scanner functionality
roles.adminrolesadminManage roles and permissions

Default Permission Assignments

RolePermissions
superadminAll permissions
managerorders., inventory., products., customers., settings.read
pickerinventory.*, orders.read, products.read, scanner.use
driverorders.read, customers.read, scanner.use
robotinventory.*, products.read, scanner.use

Helper Functions

The RBAC schema includes SQL helper functions:

Check Permission

SELECT public.user_has_permission('user-uuid', 'orders.read');
-- Returns: true/false

Get User Roles

SELECT * FROM public.get_user_roles('user-uuid');
-- Returns: table of role_name

Get User Permissions

SELECT * FROM public.get_user_permissions('user-uuid');
-- Returns: table of permission_name

Check Any Permission

SELECT public.user_has_any_permission(
  'user-uuid',
  ARRAY['orders.read', 'orders.write']
);
-- Returns: true/false

Using Authorization in Routes

Middleware Configuration

import { defineMiddlewares, authenticate } from "@switchyard/framework/http"
import { authorize } from "../middlewares/authorize-middleware"

export default defineMiddlewares({
  routes: [
    {
      method: ["GET", "POST"],
      matcher: "/admin/orders",
      middlewares: [
        authenticate("user", ["session", "bearer", "api-key"]),
      ],
    },
    {
      method: ["DELETE"],
      matcher: "/admin/orders/:id",
      middlewares: [
        authenticate("user", ["session", "bearer", "api-key"]),
        authorize("orders.delete"),
      ],
    },
  ],
})

Checking Permissions in Code

const authContext = req.auth_context
const permissions = authContext?.auth_identity?.user_metadata?.permissions || []

if (!permissions.includes("inventory.write")) {
  return res.status(403).json({ message: "Permission denied" })
}

Assigning Roles to Users

-- Assign role to user
INSERT INTO public.user_roles (user_id, role_id)
SELECT 
  'supabase-user-uuid',
  id 
FROM public.roles 
WHERE name = 'manager';