Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
invitation,
member,
organization,
permissionGroup,
permissionGroupMember,
permissions,
subscription as subscriptionTable,
user,
Expand All @@ -17,6 +19,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getEmailSubject, renderInvitationEmail } from '@/components/emails'
import { getSession } from '@/lib/auth'
import { hasAccessControlAccess } from '@/lib/billing'
import { requireStripeClient } from '@/lib/billing/stripe-client'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { sendEmail } from '@/lib/messaging/email/mailer'
Expand Down Expand Up @@ -382,6 +385,47 @@ export async function PUT(
// Don't fail the whole invitation acceptance due to this
}

// Auto-assign to permission group if one has autoAddNewMembers enabled
try {
const hasAccessControl = await hasAccessControlAccess(session.user.id)
if (hasAccessControl) {
const [autoAddGroup] = await tx
.select({ id: permissionGroup.id, name: permissionGroup.name })
.from(permissionGroup)
.where(
and(
eq(permissionGroup.organizationId, organizationId),
eq(permissionGroup.autoAddNewMembers, true)
)
)
.limit(1)

if (autoAddGroup) {
await tx.insert(permissionGroupMember).values({
id: randomUUID(),
permissionGroupId: autoAddGroup.id,
userId: session.user.id,
assignedBy: null,
assignedAt: new Date(),
})

logger.info('Auto-assigned new member to permission group', {
userId: session.user.id,
organizationId,
permissionGroupId: autoAddGroup.id,
permissionGroupName: autoAddGroup.name,
})
}
}
} catch (error) {
logger.error('Failed to auto-assign user to permission group', {
userId: session.user.id,
organizationId,
error,
})
// Don't fail the whole invitation acceptance due to this
}

const linkedWorkspaceInvitations = await tx
.select()
.from(workspaceInvitation)
Expand Down
24 changes: 24 additions & 0 deletions apps/sim/app/api/permission-groups/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ const configSchema = z.object({
disableMcpTools: z.boolean().optional(),
disableCustomTools: z.boolean().optional(),
hideTemplates: z.boolean().optional(),
disableInvitations: z.boolean().optional(),
hideDeployApi: z.boolean().optional(),
hideDeployMcp: z.boolean().optional(),
hideDeployA2a: z.boolean().optional(),
hideDeployChatbot: z.boolean().optional(),
hideDeployTemplate: z.boolean().optional(),
})

const updateSchema = z.object({
name: z.string().trim().min(1).max(100).optional(),
description: z.string().max(500).nullable().optional(),
config: configSchema.optional(),
autoAddNewMembers: z.boolean().optional(),
})

async function getPermissionGroupWithAccess(groupId: string, userId: string) {
Expand All @@ -44,6 +51,7 @@ async function getPermissionGroupWithAccess(groupId: string, userId: string) {
createdBy: permissionGroup.createdBy,
createdAt: permissionGroup.createdAt,
updatedAt: permissionGroup.updatedAt,
autoAddNewMembers: permissionGroup.autoAddNewMembers,
})
.from(permissionGroup)
.where(eq(permissionGroup.id, groupId))
Expand Down Expand Up @@ -140,11 +148,27 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
? { ...currentConfig, ...updates.config }
: currentConfig

// If setting autoAddNewMembers to true, unset it on other groups in the org first
if (updates.autoAddNewMembers === true) {
await db
.update(permissionGroup)
.set({ autoAddNewMembers: false, updatedAt: new Date() })
.where(
and(
eq(permissionGroup.organizationId, result.group.organizationId),
eq(permissionGroup.autoAddNewMembers, true)
)
)
}

await db
.update(permissionGroup)
.set({
...(updates.name !== undefined && { name: updates.name }),
...(updates.description !== undefined && { description: updates.description }),
...(updates.autoAddNewMembers !== undefined && {
autoAddNewMembers: updates.autoAddNewMembers,
}),
config: newConfig,
updatedAt: new Date(),
})
Expand Down
25 changes: 24 additions & 1 deletion apps/sim/app/api/permission-groups/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ const configSchema = z.object({
disableMcpTools: z.boolean().optional(),
disableCustomTools: z.boolean().optional(),
hideTemplates: z.boolean().optional(),
disableInvitations: z.boolean().optional(),
hideDeployApi: z.boolean().optional(),
hideDeployMcp: z.boolean().optional(),
hideDeployA2a: z.boolean().optional(),
hideDeployChatbot: z.boolean().optional(),
hideDeployTemplate: z.boolean().optional(),
})

const createSchema = z.object({
organizationId: z.string().min(1),
name: z.string().trim().min(1).max(100),
description: z.string().max(500).optional(),
config: configSchema.optional(),
autoAddNewMembers: z.boolean().optional(),
})

export async function GET(req: Request) {
Expand Down Expand Up @@ -68,6 +75,7 @@ export async function GET(req: Request) {
createdBy: permissionGroup.createdBy,
createdAt: permissionGroup.createdAt,
updatedAt: permissionGroup.updatedAt,
autoAddNewMembers: permissionGroup.autoAddNewMembers,
creatorName: user.name,
creatorEmail: user.email,
})
Expand Down Expand Up @@ -111,7 +119,8 @@ export async function POST(req: Request) {
}

const body = await req.json()
const { organizationId, name, description, config } = createSchema.parse(body)
const { organizationId, name, description, config, autoAddNewMembers } =
createSchema.parse(body)

const membership = await db
.select({ id: member.id, role: member.role })
Expand Down Expand Up @@ -154,6 +163,19 @@ export async function POST(req: Request) {
...config,
}

// If autoAddNewMembers is true, unset it on any existing groups first
if (autoAddNewMembers) {
await db
.update(permissionGroup)
.set({ autoAddNewMembers: false, updatedAt: new Date() })
.where(
and(
eq(permissionGroup.organizationId, organizationId),
eq(permissionGroup.autoAddNewMembers, true)
)
)
}

const now = new Date()
const newGroup = {
id: crypto.randomUUID(),
Expand All @@ -164,6 +186,7 @@ export async function POST(req: Request) {
createdBy: session.user.id,
createdAt: now,
updatedAt: now,
autoAddNewMembers: autoAddNewMembers || false,
}

await db.insert(permissionGroup).values(newGroup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { CreateApiKeyModal } from '@/app/workspace/[workspaceId]/w/components/si
import { startsWithUuid } from '@/executor/constants'
import { useApiKeys } from '@/hooks/queries/api-keys'
import { useWorkspaceSettings } from '@/hooks/queries/workspace'
import { usePermissionConfig } from '@/hooks/use-permission-config'
import { useSettingsModalStore } from '@/stores/modals/settings/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
Expand Down Expand Up @@ -113,16 +114,12 @@ export function DeployModal({
const [existingChat, setExistingChat] = useState<ExistingChat | null>(null)
const [isLoadingChat, setIsLoadingChat] = useState(false)

const [formSubmitting, setFormSubmitting] = useState(false)
const [formExists, setFormExists] = useState(false)
const [isFormValid, setIsFormValid] = useState(false)

const [chatSuccess, setChatSuccess] = useState(false)
const [formSuccess, setFormSuccess] = useState(false)

const [isCreateKeyModalOpen, setIsCreateKeyModalOpen] = useState(false)
const userPermissions = useUserPermissionsContext()
const canManageWorkspaceKeys = userPermissions.canAdmin
const { config: permissionConfig } = usePermissionConfig()
const { data: apiKeysData, isLoading: isLoadingKeys } = useApiKeys(workflowWorkspaceId || '')
const { data: workspaceSettingsData, isLoading: isLoadingSettings } = useWorkspaceSettings(
workflowWorkspaceId || ''
Expand Down Expand Up @@ -518,12 +515,6 @@ export function DeployModal({
setTimeout(() => setChatSuccess(false), 2000)
}

const handleFormDeployed = async () => {
await handlePostDeploymentUpdate()
setFormSuccess(true)
setTimeout(() => setFormSuccess(false), 2000)
}

const handlePostDeploymentUpdate = async () => {
if (!workflowId) return

Expand Down Expand Up @@ -632,17 +623,6 @@ export function DeployModal({
deleteTrigger?.click()
}, [])

const handleFormFormSubmit = useCallback(() => {
const form = document.getElementById('form-deploy-form') as HTMLFormElement
form?.requestSubmit()
}, [])

const handleFormDelete = useCallback(() => {
const form = document.getElementById('form-deploy-form')
const deleteTrigger = form?.querySelector('[data-delete-trigger]') as HTMLButtonElement
deleteTrigger?.click()
}, [])

return (
<>
<Modal open={open} onOpenChange={handleCloseModal}>
Expand All @@ -656,12 +636,22 @@ export function DeployModal({
>
<ModalTabsList activeValue={activeTab}>
<ModalTabsTrigger value='general'>General</ModalTabsTrigger>
<ModalTabsTrigger value='api'>API</ModalTabsTrigger>
<ModalTabsTrigger value='mcp'>MCP</ModalTabsTrigger>
<ModalTabsTrigger value='a2a'>A2A</ModalTabsTrigger>
<ModalTabsTrigger value='chat'>Chat</ModalTabsTrigger>
{!permissionConfig.hideDeployApi && (
<ModalTabsTrigger value='api'>API</ModalTabsTrigger>
)}
{!permissionConfig.hideDeployMcp && (
<ModalTabsTrigger value='mcp'>MCP</ModalTabsTrigger>
)}
{!permissionConfig.hideDeployA2a && (
<ModalTabsTrigger value='a2a'>A2A</ModalTabsTrigger>
)}
{!permissionConfig.hideDeployChatbot && (
<ModalTabsTrigger value='chat'>Chat</ModalTabsTrigger>
)}
{/* <ModalTabsTrigger value='form'>Form</ModalTabsTrigger> */}
<ModalTabsTrigger value='template'>Template</ModalTabsTrigger>
{!permissionConfig.hideDeployTemplate && (
<ModalTabsTrigger value='template'>Template</ModalTabsTrigger>
)}
</ModalTabsList>

<ModalBody className='min-h-0 flex-1'>
Expand Down
Loading