Files
sim/apps/sim/socket/middleware/permissions.ts
Vikhyath Mondreti b0fbf3648d improvment(sockets): migrate to redis (#3072)
* improvment(sockets): migrate to redis

* remove random error code

* improve typing

* use native api

* fix bugbot comments

* bugbot comment

* fix more bugbot cleanup comments

* null cursor

* fix

* cleanup code

* fix bugbot comments
2026-01-30 09:47:15 -08:00

145 lines
4.3 KiB
TypeScript

import { db } from '@sim/db'
import { workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
import {
BLOCK_OPERATIONS,
BLOCKS_OPERATIONS,
EDGE_OPERATIONS,
EDGES_OPERATIONS,
SUBFLOW_OPERATIONS,
WORKFLOW_OPERATIONS,
} from '@/socket/constants'
const logger = createLogger('SocketPermissions')
// All write operations (admin and write roles have same permissions)
const WRITE_OPERATIONS: string[] = [
// Block operations
BLOCK_OPERATIONS.UPDATE_POSITION,
BLOCK_OPERATIONS.UPDATE_NAME,
BLOCK_OPERATIONS.TOGGLE_ENABLED,
BLOCK_OPERATIONS.UPDATE_PARENT,
BLOCK_OPERATIONS.UPDATE_ADVANCED_MODE,
BLOCK_OPERATIONS.UPDATE_CANONICAL_MODE,
BLOCK_OPERATIONS.TOGGLE_HANDLES,
// Batch block operations
BLOCKS_OPERATIONS.BATCH_UPDATE_POSITIONS,
BLOCKS_OPERATIONS.BATCH_ADD_BLOCKS,
BLOCKS_OPERATIONS.BATCH_REMOVE_BLOCKS,
BLOCKS_OPERATIONS.BATCH_TOGGLE_ENABLED,
BLOCKS_OPERATIONS.BATCH_TOGGLE_HANDLES,
BLOCKS_OPERATIONS.BATCH_UPDATE_PARENT,
// Edge operations
EDGE_OPERATIONS.ADD,
EDGE_OPERATIONS.REMOVE,
// Batch edge operations
EDGES_OPERATIONS.BATCH_ADD_EDGES,
EDGES_OPERATIONS.BATCH_REMOVE_EDGES,
// Subflow operations
SUBFLOW_OPERATIONS.UPDATE,
// Workflow operations
WORKFLOW_OPERATIONS.REPLACE_STATE,
]
// Read role can only update positions (for cursor sync, etc.)
const READ_OPERATIONS: string[] = [
BLOCK_OPERATIONS.UPDATE_POSITION,
BLOCKS_OPERATIONS.BATCH_UPDATE_POSITIONS,
]
// Define operation permissions based on role
const ROLE_PERMISSIONS: Record<string, string[]> = {
admin: WRITE_OPERATIONS,
write: WRITE_OPERATIONS,
read: READ_OPERATIONS,
}
// Check if a role allows a specific operation (no DB query, pure logic)
export function checkRolePermission(
role: string,
operation: string
): { allowed: boolean; reason?: string } {
const allowedOperations = ROLE_PERMISSIONS[role] || []
if (!allowedOperations.includes(operation)) {
return {
allowed: false,
reason: `Role '${role}' not permitted to perform '${operation}'`,
}
}
return { allowed: true }
}
async function verifyWorkspaceMembership(
userId: string,
workspaceId: string
): Promise<string | null> {
try {
const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId)
return permission
} catch (error) {
logger.error(`Error verifying workspace permissions for ${userId} in ${workspaceId}:`, error)
return null
}
}
export async function verifyWorkflowAccess(
userId: string,
workflowId: string
): Promise<{ hasAccess: boolean; role?: string; workspaceId?: string }> {
try {
const workflowData = await db
.select({
userId: workflow.userId,
workspaceId: workflow.workspaceId,
name: workflow.name,
})
.from(workflow)
.where(eq(workflow.id, workflowId))
.limit(1)
if (!workflowData.length) {
logger.warn(`Workflow ${workflowId} not found`)
return { hasAccess: false }
}
const { userId: workflowUserId, workspaceId, name: workflowName } = workflowData[0]
// Check if user owns the workflow - treat as admin
if (workflowUserId === userId) {
logger.debug(
`User ${userId} has admin access to workflow ${workflowId} (${workflowName}) as owner`
)
return { hasAccess: true, role: 'admin', workspaceId: workspaceId || undefined }
}
// Check workspace membership if workflow belongs to a workspace
if (workspaceId) {
const userRole = await verifyWorkspaceMembership(userId, workspaceId)
if (userRole) {
logger.debug(
`User ${userId} has ${userRole} access to workflow ${workflowId} via workspace ${workspaceId}`
)
return { hasAccess: true, role: userRole, workspaceId }
}
logger.warn(
`User ${userId} is not a member of workspace ${workspaceId} for workflow ${workflowId}`
)
return { hasAccess: false }
}
// Workflow doesn't belong to a workspace and user doesn't own it
logger.warn(`User ${userId} has no access to workflow ${workflowId} (no workspace, not owner)`)
return { hasAccess: false }
} catch (error) {
logger.error(
`Error verifying workflow access for user ${userId}, workflow ${workflowId}:`,
error
)
return { hasAccess: false }
}
}