How Workflow Authorization and Workspace Permissions Work in SimStudioAI Sim
SimStudioAI Sim secures every workflow operation by tying workflows to workspaces and validating caller permissions through the @sim/workflow-authz package, enforcing hierarchical locks before allowing mutations.
The simstudioai/sim repository implements robust workflow authorization and workspace permissions through a model that ties every workflow to a workspace and validates user access before allowing any operation. By centralizing access control in the @sim/workflow-authz package and storing permissions in a dedicated database table, the system maintains strict separation between authentication and authorization across API routes, webhook handlers, and Copilot tools.
Workspace Permission Architecture
The authorization system resolves workflow access through two primary steps: active workflow validation and workspace-level permission lookup.
Active Workflow Resolution
Before checking permissions, the system must locate a valid, non-archived workflow record. The getActiveWorkflowContext function in packages/workflow-authz/src/index.ts (lines 18‑31) fetches the workflow together with its workspace ID, verifying that the workflow isn't archived and its parent workspace remains active. If the workflow cannot be found, the authorization routine aborts early with a 404 result.
The Permissions Table
Workspace permissions are stored in the permissions table defined in packages/db/schema.ts. This table stores a row per (user, entity-type, entity-id) combination, where entity-type is workspace and permissionType is an enum with values read, write, and admin. The authorization function queries this table to retrieve the caller’s permission level for the workflow’s specific workspace.
Authorization Logic and Permission Hierarchy
The authorizeWorkflowByWorkspacePermission function serves as the single source of truth for workflow access decisions. Located in packages/workflow-authz/src/index.ts (lines 2‑80), this function receives a workflow ID, user ID, and requested action (read, write, or admin), then returns a WorkflowWorkspaceAuthorizationResult containing allowed, status, message, the workflow record, and the user's workspace permission.
The permission evaluation follows a strict hierarchy:
- read: Always allowed if the user has any permission on the workspace (
read,write, oradmin) - write: Requires
writeoradminpermission - admin: Requires explicit
adminpermission
The function enforces that every workflow must be attached to a workspace—personal workflows are deprecated—returning 403 if the workspace ID is missing. If no permission record exists for the user-workspace pair, the request is denied.
Lock Handling for Concurrent Access
Before mutating a workflow, the system checks resource locks to prevent concurrent edits. The getWorkflowLockStatus function climbs from the workflow to its folder hierarchy, reusing getFolderLockStatus to detect locks at any level.
If a lock is detected, assertWorkflowMutable or assertFolderMutable throw WorkflowLockedError or FolderLockedError, resulting in HTTP 423 (Locked) responses. These utilities in packages/workflow-authz/src/index.ts (lines 41‑73) ensure immutable resources cannot be modified, providing optimistic concurrency control for collaborative environments.
API Integration and Usage Examples
Every API route that touches a workflow invokes the authorization routine, passing the current user ID from the auth session and the appropriate action. For example, apps/sim/app/api/workflows/[id]/state/route.ts (lines 46‑58) demonstrates this pattern for workflow state requests.
Authorizing a Read Request
To check if a user can view a workflow:
import { authorizeWorkflowByWorkspacePermission } from '@sim/workflow-authz'
async function canReadWorkflow(workflowId: string, userId: string) {
const result = await authorizeWorkflowByWorkspacePermission({
workflowId,
userId,
action: 'read',
})
return result.allowed // true → proceed; false → send result.status / result.message
}
Authorizing a Write Request in an API Route
For mutations within Next.js API routes:
import { authorizeWorkflowByWorkspacePermission } from '@sim/workflow-authz'
import { NextResponse } from 'next/server'
export async function POST(request) {
const { workflowId, userId } = await request.json()
const auth = await authorizeWorkflowByWorkspacePermission({
workflowId,
userId,
action: 'write',
})
if (!auth.allowed) {
return NextResponse.json(
{ error: auth.message },
{ status: auth.status }
)
}
// …perform mutable operation (e.g. update workflow)…
}
Checking Lock Status Before Updates
To prevent edits on locked workflows:
import { assertWorkflowMutable } from '@sim/workflow-authz'
async function updateWorkflow(workflowId: string, updates: Partial<Workflow>) {
await assertWorkflowMutable(workflowId) // throws 423 if locked
// …apply updates…
}
Summary
- Workspace-centric security: Every workflow must belong to a workspace; personal workflows are deprecated.
- Hierarchical permissions: The
permissionstable storesread,write, andadminlevels, withauthorizeWorkflowByWorkspacePermissionenforcing thatwriterequireswriteor higher, andadminrequires explicit admin rights. - Active workflow validation:
getActiveWorkflowContextensures workflows exist, aren't archived, and belong to active workspaces before permission checks occur. - Lock-based concurrency:
assertWorkflowMutableandassertFolderMutableprevent modifications to locked resources, returning HTTP 423 when conflicts are detected. - Universal enforcement: All API routes, webhook handlers, and Copilot tools use the same authorization flow from
@sim/workflow-authzto maintain consistent security boundaries.
Frequently Asked Questions
What happens if a workflow isn't attached to a workspace?
The authorization function returns a 403 Forbidden response. According to the source code in packages/workflow-authz/src/index.ts, SimStudioAI Sim has deprecated personal workflows, so every workflow must have a valid workspace ID to pass authorization.
How does the system handle read versus write permissions?
Read requests are allowed if the user has any permission level on the workspace (read, write, or admin). Write requests require explicit write or admin permissions, while admin operations require the admin permission type specifically. This hierarchy is enforced in the authorizeWorkflowByWorkspacePermission function.
What HTTP status code is returned when a workflow is locked?
The system returns HTTP 423 (Locked) when attempting to mutate a locked workflow or folder. The assertWorkflowMutable and assertFolderMutable functions throw WorkflowLockedError or FolderLockedError respectively, which translate to this status code in API responses.
Where are workspace permissions stored in the database?
Permissions are stored in the permissions table defined in packages/db/schema.ts. Each row maps a user to a workspace with a specific permissionType enum value, allowing efficient lookups during the authorization process.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →