Files
sim/apps/sim/executor/utils/permission-check.ts
Vikhyath Mondreti 78e4ca9d45 improvement(serializer): canonical subblock, serialization cleanups, schedules/webhooks are deployment version friendly (#2848)
* hide form deployment tab from docs

* progress

* fix resolution

* cleanup code

* fix positioning

* cleanup dead sockets adv mode ops

* address greptile comments

* fix tests plus more simplification

* fix cleanup

* bring back advanced mode with specific definition

* revert feature flags

* improvement(subblock): ui

* resolver change to make all var references optional chaining

* fix(webhooks/schedules): deployment version friendly

* fix tests

* fix credential sets with new lifecycle

* prep merge

* add back migration

* fix display check for adv fields

* fix trigger vs block scoping

---------

Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
2026-01-16 15:23:43 -08:00

230 lines
5.7 KiB
TypeScript

import { db } from '@sim/db'
import { member, permissionGroup, permissionGroupMember } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { isOrganizationOnEnterprisePlan } from '@/lib/billing'
import { isAccessControlEnabled, isHosted } from '@/lib/core/config/feature-flags'
import {
type PermissionGroupConfig,
parsePermissionGroupConfig,
} from '@/lib/permission-groups/types'
import type { ExecutionContext } from '@/executor/types'
import { getProviderFromModel } from '@/providers/utils'
const logger = createLogger('PermissionCheck')
export class ProviderNotAllowedError extends Error {
constructor(providerId: string, model: string) {
super(
`Provider "${providerId}" is not allowed for model "${model}" based on your permission group settings`
)
this.name = 'ProviderNotAllowedError'
}
}
export class IntegrationNotAllowedError extends Error {
constructor(blockType: string) {
super(`Integration "${blockType}" is not allowed based on your permission group settings`)
this.name = 'IntegrationNotAllowedError'
}
}
export class McpToolsNotAllowedError extends Error {
constructor() {
super('MCP tools are not allowed based on your permission group settings')
this.name = 'McpToolsNotAllowedError'
}
}
export class CustomToolsNotAllowedError extends Error {
constructor() {
super('Custom tools are not allowed based on your permission group settings')
this.name = 'CustomToolsNotAllowedError'
}
}
export class InvitationsNotAllowedError extends Error {
constructor() {
super('Invitations are not allowed based on your permission group settings')
this.name = 'InvitationsNotAllowedError'
}
}
export async function getUserPermissionConfig(
userId: string
): Promise<PermissionGroupConfig | null> {
if (!isHosted && !isAccessControlEnabled) {
return null
}
const [membership] = await db
.select({ organizationId: member.organizationId })
.from(member)
.where(eq(member.userId, userId))
.limit(1)
if (!membership) {
return null
}
const isEnterprise = await isOrganizationOnEnterprisePlan(membership.organizationId)
if (!isEnterprise) {
return null
}
const [groupMembership] = await db
.select({ config: permissionGroup.config })
.from(permissionGroupMember)
.innerJoin(permissionGroup, eq(permissionGroupMember.permissionGroupId, permissionGroup.id))
.where(
and(
eq(permissionGroupMember.userId, userId),
eq(permissionGroup.organizationId, membership.organizationId)
)
)
.limit(1)
if (!groupMembership) {
return null
}
return parsePermissionGroupConfig(groupMembership.config)
}
export async function getPermissionConfig(
userId: string | undefined,
ctx?: ExecutionContext
): Promise<PermissionGroupConfig | null> {
if (!userId) {
return null
}
if (ctx) {
if (ctx.permissionConfigLoaded) {
return ctx.permissionConfig ?? null
}
const config = await getUserPermissionConfig(userId)
ctx.permissionConfig = config
ctx.permissionConfigLoaded = true
return config
}
return getUserPermissionConfig(userId)
}
export async function validateModelProvider(
userId: string | undefined,
model: string,
ctx?: ExecutionContext
): Promise<void> {
if (!userId) {
return
}
const config = await getPermissionConfig(userId, ctx)
if (!config || config.allowedModelProviders === null) {
return
}
const providerId = getProviderFromModel(model)
if (!config.allowedModelProviders.includes(providerId)) {
logger.warn('Model provider blocked by permission group', { userId, model, providerId })
throw new ProviderNotAllowedError(providerId, model)
}
}
export async function validateBlockType(
userId: string | undefined,
blockType: string,
ctx?: ExecutionContext
): Promise<void> {
if (blockType === 'start_trigger') {
return
}
if (!userId) {
return
}
const config = await getPermissionConfig(userId, ctx)
if (!config || config.allowedIntegrations === null) {
return
}
if (!config.allowedIntegrations.includes(blockType)) {
logger.warn('Integration blocked by permission group', { userId, blockType })
throw new IntegrationNotAllowedError(blockType)
}
}
export async function validateMcpToolsAllowed(
userId: string | undefined,
ctx?: ExecutionContext
): Promise<void> {
if (!userId) {
return
}
const config = await getPermissionConfig(userId, ctx)
if (!config) {
return
}
if (config.disableMcpTools) {
logger.warn('MCP tools blocked by permission group', { userId })
throw new McpToolsNotAllowedError()
}
}
export async function validateCustomToolsAllowed(
userId: string | undefined,
ctx?: ExecutionContext
): Promise<void> {
if (!userId) {
return
}
const config = await getPermissionConfig(userId, ctx)
if (!config) {
return
}
if (config.disableCustomTools) {
logger.warn('Custom tools blocked by permission group', { userId })
throw new CustomToolsNotAllowedError()
}
}
/**
* Validates if the user is allowed to send invitations.
* Also checks the global feature flag.
*/
export async function validateInvitationsAllowed(userId: string | undefined): Promise<void> {
const { isInvitationsDisabled } = await import('@/lib/core/config/feature-flags')
if (isInvitationsDisabled) {
logger.warn('Invitations blocked by feature flag')
throw new InvitationsNotAllowedError()
}
if (!userId) {
return
}
const config = await getUserPermissionConfig(userId)
if (!config) {
return
}
if (config.disableInvitations) {
logger.warn('Invitations blocked by permission group', { userId })
throw new InvitationsNotAllowedError()
}
}