Skip to content

Commit 6f469a7

Browse files
improvement(permissions): added ability to auto-add new org members to existing permission group, disallow disabling of start block (#2836)
* improvement(permissions): added ability to auto-add new org members to existing permission group, disallow disabling of start block * ran migrations * add deploy modal tabs config to perm groups * fix ordering of access control listings * prep staging merge * regen migrations --------- Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
1 parent a35f6ec commit 6f469a7

File tree

12 files changed

+10563
-35
lines changed

12 files changed

+10563
-35
lines changed

apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
invitation,
55
member,
66
organization,
7+
permissionGroup,
8+
permissionGroupMember,
79
permissions,
810
subscription as subscriptionTable,
911
user,
@@ -17,6 +19,7 @@ import { type NextRequest, NextResponse } from 'next/server'
1719
import { z } from 'zod'
1820
import { getEmailSubject, renderInvitationEmail } from '@/components/emails'
1921
import { getSession } from '@/lib/auth'
22+
import { hasAccessControlAccess } from '@/lib/billing'
2023
import { requireStripeClient } from '@/lib/billing/stripe-client'
2124
import { getBaseUrl } from '@/lib/core/utils/urls'
2225
import { sendEmail } from '@/lib/messaging/email/mailer'
@@ -382,6 +385,47 @@ export async function PUT(
382385
// Don't fail the whole invitation acceptance due to this
383386
}
384387

388+
// Auto-assign to permission group if one has autoAddNewMembers enabled
389+
try {
390+
const hasAccessControl = await hasAccessControlAccess(session.user.id)
391+
if (hasAccessControl) {
392+
const [autoAddGroup] = await tx
393+
.select({ id: permissionGroup.id, name: permissionGroup.name })
394+
.from(permissionGroup)
395+
.where(
396+
and(
397+
eq(permissionGroup.organizationId, organizationId),
398+
eq(permissionGroup.autoAddNewMembers, true)
399+
)
400+
)
401+
.limit(1)
402+
403+
if (autoAddGroup) {
404+
await tx.insert(permissionGroupMember).values({
405+
id: randomUUID(),
406+
permissionGroupId: autoAddGroup.id,
407+
userId: session.user.id,
408+
assignedBy: null,
409+
assignedAt: new Date(),
410+
})
411+
412+
logger.info('Auto-assigned new member to permission group', {
413+
userId: session.user.id,
414+
organizationId,
415+
permissionGroupId: autoAddGroup.id,
416+
permissionGroupName: autoAddGroup.name,
417+
})
418+
}
419+
}
420+
} catch (error) {
421+
logger.error('Failed to auto-assign user to permission group', {
422+
userId: session.user.id,
423+
organizationId,
424+
error,
425+
})
426+
// Don't fail the whole invitation acceptance due to this
427+
}
428+
385429
const linkedWorkspaceInvitations = await tx
386430
.select()
387431
.from(workspaceInvitation)

apps/sim/app/api/permission-groups/[id]/route.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@ const configSchema = z.object({
2525
disableMcpTools: z.boolean().optional(),
2626
disableCustomTools: z.boolean().optional(),
2727
hideTemplates: z.boolean().optional(),
28+
disableInvitations: z.boolean().optional(),
29+
hideDeployApi: z.boolean().optional(),
30+
hideDeployMcp: z.boolean().optional(),
31+
hideDeployA2a: z.boolean().optional(),
32+
hideDeployChatbot: z.boolean().optional(),
33+
hideDeployTemplate: z.boolean().optional(),
2834
})
2935

3036
const updateSchema = z.object({
3137
name: z.string().trim().min(1).max(100).optional(),
3238
description: z.string().max(500).nullable().optional(),
3339
config: configSchema.optional(),
40+
autoAddNewMembers: z.boolean().optional(),
3441
})
3542

3643
async function getPermissionGroupWithAccess(groupId: string, userId: string) {
@@ -44,6 +51,7 @@ async function getPermissionGroupWithAccess(groupId: string, userId: string) {
4451
createdBy: permissionGroup.createdBy,
4552
createdAt: permissionGroup.createdAt,
4653
updatedAt: permissionGroup.updatedAt,
54+
autoAddNewMembers: permissionGroup.autoAddNewMembers,
4755
})
4856
.from(permissionGroup)
4957
.where(eq(permissionGroup.id, groupId))
@@ -140,11 +148,27 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
140148
? { ...currentConfig, ...updates.config }
141149
: currentConfig
142150

151+
// If setting autoAddNewMembers to true, unset it on other groups in the org first
152+
if (updates.autoAddNewMembers === true) {
153+
await db
154+
.update(permissionGroup)
155+
.set({ autoAddNewMembers: false, updatedAt: new Date() })
156+
.where(
157+
and(
158+
eq(permissionGroup.organizationId, result.group.organizationId),
159+
eq(permissionGroup.autoAddNewMembers, true)
160+
)
161+
)
162+
}
163+
143164
await db
144165
.update(permissionGroup)
145166
.set({
146167
...(updates.name !== undefined && { name: updates.name }),
147168
...(updates.description !== undefined && { description: updates.description }),
169+
...(updates.autoAddNewMembers !== undefined && {
170+
autoAddNewMembers: updates.autoAddNewMembers,
171+
}),
148172
config: newConfig,
149173
updatedAt: new Date(),
150174
})

apps/sim/app/api/permission-groups/route.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@ const configSchema = z.object({
2626
disableMcpTools: z.boolean().optional(),
2727
disableCustomTools: z.boolean().optional(),
2828
hideTemplates: z.boolean().optional(),
29+
disableInvitations: z.boolean().optional(),
30+
hideDeployApi: z.boolean().optional(),
31+
hideDeployMcp: z.boolean().optional(),
32+
hideDeployA2a: z.boolean().optional(),
33+
hideDeployChatbot: z.boolean().optional(),
34+
hideDeployTemplate: z.boolean().optional(),
2935
})
3036

3137
const createSchema = z.object({
3238
organizationId: z.string().min(1),
3339
name: z.string().trim().min(1).max(100),
3440
description: z.string().max(500).optional(),
3541
config: configSchema.optional(),
42+
autoAddNewMembers: z.boolean().optional(),
3643
})
3744

3845
export async function GET(req: Request) {
@@ -68,6 +75,7 @@ export async function GET(req: Request) {
6875
createdBy: permissionGroup.createdBy,
6976
createdAt: permissionGroup.createdAt,
7077
updatedAt: permissionGroup.updatedAt,
78+
autoAddNewMembers: permissionGroup.autoAddNewMembers,
7179
creatorName: user.name,
7280
creatorEmail: user.email,
7381
})
@@ -111,7 +119,8 @@ export async function POST(req: Request) {
111119
}
112120

113121
const body = await req.json()
114-
const { organizationId, name, description, config } = createSchema.parse(body)
122+
const { organizationId, name, description, config, autoAddNewMembers } =
123+
createSchema.parse(body)
115124

116125
const membership = await db
117126
.select({ id: member.id, role: member.role })
@@ -154,6 +163,19 @@ export async function POST(req: Request) {
154163
...config,
155164
}
156165

166+
// If autoAddNewMembers is true, unset it on any existing groups first
167+
if (autoAddNewMembers) {
168+
await db
169+
.update(permissionGroup)
170+
.set({ autoAddNewMembers: false, updatedAt: new Date() })
171+
.where(
172+
and(
173+
eq(permissionGroup.organizationId, organizationId),
174+
eq(permissionGroup.autoAddNewMembers, true)
175+
)
176+
)
177+
}
178+
157179
const now = new Date()
158180
const newGroup = {
159181
id: crypto.randomUUID(),
@@ -164,6 +186,7 @@ export async function POST(req: Request) {
164186
createdBy: session.user.id,
165187
createdAt: now,
166188
updatedAt: now,
189+
autoAddNewMembers: autoAddNewMembers || false,
167190
}
168191

169192
await db.insert(permissionGroup).values(newGroup)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { CreateApiKeyModal } from '@/app/workspace/[workspaceId]/w/components/si
2323
import { startsWithUuid } from '@/executor/constants'
2424
import { useApiKeys } from '@/hooks/queries/api-keys'
2525
import { useWorkspaceSettings } from '@/hooks/queries/workspace'
26+
import { usePermissionConfig } from '@/hooks/use-permission-config'
2627
import { useSettingsModalStore } from '@/stores/modals/settings/store'
2728
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
2829
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -113,16 +114,12 @@ export function DeployModal({
113114
const [existingChat, setExistingChat] = useState<ExistingChat | null>(null)
114115
const [isLoadingChat, setIsLoadingChat] = useState(false)
115116

116-
const [formSubmitting, setFormSubmitting] = useState(false)
117-
const [formExists, setFormExists] = useState(false)
118-
const [isFormValid, setIsFormValid] = useState(false)
119-
120117
const [chatSuccess, setChatSuccess] = useState(false)
121-
const [formSuccess, setFormSuccess] = useState(false)
122118

123119
const [isCreateKeyModalOpen, setIsCreateKeyModalOpen] = useState(false)
124120
const userPermissions = useUserPermissionsContext()
125121
const canManageWorkspaceKeys = userPermissions.canAdmin
122+
const { config: permissionConfig } = usePermissionConfig()
126123
const { data: apiKeysData, isLoading: isLoadingKeys } = useApiKeys(workflowWorkspaceId || '')
127124
const { data: workspaceSettingsData, isLoading: isLoadingSettings } = useWorkspaceSettings(
128125
workflowWorkspaceId || ''
@@ -518,12 +515,6 @@ export function DeployModal({
518515
setTimeout(() => setChatSuccess(false), 2000)
519516
}
520517

521-
const handleFormDeployed = async () => {
522-
await handlePostDeploymentUpdate()
523-
setFormSuccess(true)
524-
setTimeout(() => setFormSuccess(false), 2000)
525-
}
526-
527518
const handlePostDeploymentUpdate = async () => {
528519
if (!workflowId) return
529520

@@ -632,17 +623,6 @@ export function DeployModal({
632623
deleteTrigger?.click()
633624
}, [])
634625

635-
const handleFormFormSubmit = useCallback(() => {
636-
const form = document.getElementById('form-deploy-form') as HTMLFormElement
637-
form?.requestSubmit()
638-
}, [])
639-
640-
const handleFormDelete = useCallback(() => {
641-
const form = document.getElementById('form-deploy-form')
642-
const deleteTrigger = form?.querySelector('[data-delete-trigger]') as HTMLButtonElement
643-
deleteTrigger?.click()
644-
}, [])
645-
646626
return (
647627
<>
648628
<Modal open={open} onOpenChange={handleCloseModal}>
@@ -656,12 +636,22 @@ export function DeployModal({
656636
>
657637
<ModalTabsList activeValue={activeTab}>
658638
<ModalTabsTrigger value='general'>General</ModalTabsTrigger>
659-
<ModalTabsTrigger value='api'>API</ModalTabsTrigger>
660-
<ModalTabsTrigger value='mcp'>MCP</ModalTabsTrigger>
661-
<ModalTabsTrigger value='a2a'>A2A</ModalTabsTrigger>
662-
<ModalTabsTrigger value='chat'>Chat</ModalTabsTrigger>
639+
{!permissionConfig.hideDeployApi && (
640+
<ModalTabsTrigger value='api'>API</ModalTabsTrigger>
641+
)}
642+
{!permissionConfig.hideDeployMcp && (
643+
<ModalTabsTrigger value='mcp'>MCP</ModalTabsTrigger>
644+
)}
645+
{!permissionConfig.hideDeployA2a && (
646+
<ModalTabsTrigger value='a2a'>A2A</ModalTabsTrigger>
647+
)}
648+
{!permissionConfig.hideDeployChatbot && (
649+
<ModalTabsTrigger value='chat'>Chat</ModalTabsTrigger>
650+
)}
663651
{/* <ModalTabsTrigger value='form'>Form</ModalTabsTrigger> */}
664-
<ModalTabsTrigger value='template'>Template</ModalTabsTrigger>
652+
{!permissionConfig.hideDeployTemplate && (
653+
<ModalTabsTrigger value='template'>Template</ModalTabsTrigger>
654+
)}
665655
</ModalTabsList>
666656

667657
<ModalBody className='min-h-0 flex-1'>

0 commit comments

Comments
 (0)