mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
* 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
145 lines
4.3 KiB
TypeScript
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 }
|
|
}
|
|
}
|