diff --git a/apps/sim/app/(auth)/sso/page.tsx b/apps/sim/app/(auth)/sso/page.tsx
index 18ff14f90..49bf30f1c 100644
--- a/apps/sim/app/(auth)/sso/page.tsx
+++ b/apps/sim/app/(auth)/sso/page.tsx
@@ -1,6 +1,6 @@
import { redirect } from 'next/navigation'
import { getEnv, isTruthy } from '@/lib/core/config/env'
-import SSOForm from '@/app/(auth)/sso/sso-form'
+import SSOForm from '@/ee/sso/components/sso-form'
export const dynamic = 'force-dynamic'
diff --git a/apps/sim/app/api/organizations/[id]/invitations/route.ts b/apps/sim/app/api/organizations/[id]/invitations/route.ts
index 124d70957..905628696 100644
--- a/apps/sim/app/api/organizations/[id]/invitations/route.ts
+++ b/apps/sim/app/api/organizations/[id]/invitations/route.ts
@@ -29,7 +29,7 @@ import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils'
import {
InvitationsNotAllowedError,
validateInvitationsAllowed,
-} from '@/executor/utils/permission-check'
+} from '@/ee/access-control/utils/permission-check'
const logger = createLogger('OrganizationInvitations')
diff --git a/apps/sim/app/api/workspaces/invitations/route.test.ts b/apps/sim/app/api/workspaces/invitations/route.test.ts
index 202559142..ac3545885 100644
--- a/apps/sim/app/api/workspaces/invitations/route.test.ts
+++ b/apps/sim/app/api/workspaces/invitations/route.test.ts
@@ -102,7 +102,7 @@ describe('Workspace Invitations API Route', () => {
inArray: vi.fn().mockImplementation((field, values) => ({ type: 'inArray', field, values })),
}))
- vi.doMock('@/executor/utils/permission-check', () => ({
+ vi.doMock('@/ee/access-control/utils/permission-check', () => ({
validateInvitationsAllowed: vi.fn().mockResolvedValue(undefined),
InvitationsNotAllowedError: class InvitationsNotAllowedError extends Error {
constructor() {
diff --git a/apps/sim/app/api/workspaces/invitations/route.ts b/apps/sim/app/api/workspaces/invitations/route.ts
index bd70b9dc9..e6116d840 100644
--- a/apps/sim/app/api/workspaces/invitations/route.ts
+++ b/apps/sim/app/api/workspaces/invitations/route.ts
@@ -21,7 +21,7 @@ import { getFromEmailAddress } from '@/lib/messaging/email/utils'
import {
InvitationsNotAllowedError,
validateInvitationsAllowed,
-} from '@/executor/utils/permission-check'
+} from '@/ee/access-control/utils/permission-check'
export const dynamic = 'force-dynamic'
@@ -38,7 +38,6 @@ export async function GET(req: NextRequest) {
}
try {
- // Get all workspaces where the user has permissions
const userWorkspaces = await db
.select({ id: workspace.id })
.from(workspace)
@@ -55,10 +54,8 @@ export async function GET(req: NextRequest) {
return NextResponse.json({ invitations: [] })
}
- // Get all workspaceIds where the user is a member
const workspaceIds = userWorkspaces.map((w) => w.id)
- // Find all invitations for those workspaces
const invitations = await db
.select()
.from(workspaceInvitation)
diff --git a/apps/sim/app/chat/[identifier]/chat.tsx b/apps/sim/app/chat/[identifier]/chat.tsx
index 94082ffec..549e450d4 100644
--- a/apps/sim/app/chat/[identifier]/chat.tsx
+++ b/apps/sim/app/chat/[identifier]/chat.tsx
@@ -14,11 +14,11 @@ import {
ChatMessageContainer,
EmailAuth,
PasswordAuth,
- SSOAuth,
VoiceInterface,
} from '@/app/chat/components'
import { CHAT_ERROR_MESSAGES, CHAT_REQUEST_TIMEOUT_MS } from '@/app/chat/constants'
import { useAudioStreaming, useChatStreaming } from '@/app/chat/hooks'
+import SSOAuth from '@/ee/sso/components/sso-auth'
const logger = createLogger('ChatClient')
diff --git a/apps/sim/app/chat/components/index.ts b/apps/sim/app/chat/components/index.ts
index 4be7ea2f1..eef5a82c4 100644
--- a/apps/sim/app/chat/components/index.ts
+++ b/apps/sim/app/chat/components/index.ts
@@ -1,6 +1,5 @@
export { default as EmailAuth } from './auth/email/email-auth'
export { default as PasswordAuth } from './auth/password/password-auth'
-export { default as SSOAuth } from './auth/sso/sso-auth'
export { ChatErrorState } from './error-state/error-state'
export { ChatHeader } from './header/header'
export { ChatInput } from './input/input'
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/page.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/page.tsx
index a5c1eadeb..a449539d5 100644
--- a/apps/sim/app/workspace/[workspaceId]/knowledge/page.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/knowledge/page.tsx
@@ -1,7 +1,7 @@
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { Knowledge } from './knowledge'
interface KnowledgePageProps {
@@ -23,7 +23,6 @@ export default async function KnowledgePage({ params }: KnowledgePageProps) {
redirect('/')
}
- // Check permission group restrictions
const permissionConfig = await getUserPermissionConfig(session.user.id)
if (permissionConfig?.hideKnowledgeBaseTab) {
redirect(`/workspace/${workspaceId}`)
diff --git a/apps/sim/app/workspace/[workspaceId]/templates/page.tsx b/apps/sim/app/workspace/[workspaceId]/templates/page.tsx
index 9955c2433..8e5194cee 100644
--- a/apps/sim/app/workspace/[workspaceId]/templates/page.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/templates/page.tsx
@@ -6,7 +6,7 @@ import { getSession } from '@/lib/auth'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
import type { Template as WorkspaceTemplate } from '@/app/workspace/[workspaceId]/templates/templates'
import Templates from '@/app/workspace/[workspaceId]/templates/templates'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
interface TemplatesPageProps {
params: Promise<{
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credential-sets/credential-sets.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credential-sets/credential-sets.tsx
index a1fae5b1a..ce6154939 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credential-sets/credential-sets.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credential-sets/credential-sets.tsx
@@ -246,7 +246,6 @@ export function CredentialSets() {
setNewSetDescription('')
setNewSetProvider('google-email')
- // Open detail view for the newly created group
if (result?.credentialSet) {
setViewingSet(result.credentialSet)
}
@@ -336,7 +335,6 @@ export function CredentialSets() {
email,
})
- // Start 60s cooldown
setResendCooldowns((prev) => ({ ...prev, [invitationId]: 60 }))
const interval = setInterval(() => {
setResendCooldowns((prev) => {
@@ -393,7 +391,6 @@ export function CredentialSets() {
return
}
- // All hooks must be called before any early returns
const activeMemberships = useMemo(
() => memberships.filter((m) => m.status === 'active'),
[memberships]
@@ -447,7 +444,6 @@ export function CredentialSets() {
- {/* Group Info */}
@@ -471,7 +467,6 @@ export function CredentialSets() {
- {/* Invite Section - Email Tags Input */}
{emailError}}
- {/* Members List - styled like team members */}
Members
@@ -519,7 +513,6 @@ export function CredentialSets() {
) : (
- {/* Active Members */}
{activeMembers.map((member) => {
const name = member.userName || 'Unknown'
const avatarInitial = name.charAt(0).toUpperCase()
@@ -572,7 +565,6 @@ export function CredentialSets() {
)
})}
- {/* Pending Invitations */}
{pendingInvitations.map((invitation) => {
const email = invitation.email || 'Unknown'
const emailPrefix = email.split('@')[0]
@@ -641,7 +633,6 @@ export function CredentialSets() {
- {/* Footer Actions */}
- {/* Create Polling Group Modal */}
Create Polling Group
@@ -895,7 +885,6 @@ export function CredentialSets() {
- {/* Leave Confirmation Modal */}
setLeavingMembership(null)}>
Leave Polling Group
@@ -923,7 +912,6 @@ export function CredentialSets() {
- {/* Delete Confirmation Modal */}
setDeletingSet(null)}>
Delete Polling Group
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts
index e2241137f..db87eaf39 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts
@@ -1,4 +1,3 @@
-export { AccessControl } from './access-control/access-control'
export { ApiKeys } from './api-keys/api-keys'
export { BYOK } from './byok/byok'
export { Copilot } from './copilot/copilot'
@@ -10,7 +9,6 @@ export { Files as FileUploads } from './files/files'
export { General } from './general/general'
export { Integrations } from './integrations/integrations'
export { MCP } from './mcp/mcp'
-export { SSO } from './sso/sso'
export { Subscription } from './subscription/subscription'
export { TeamManagement } from './team-management/team-management'
export { WorkflowMcpServers } from './workflow-mcp-servers/workflow-mcp-servers'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
index d4103702b..89dc83172 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
@@ -407,14 +407,12 @@ export function MCP({ initialServerId }: MCPProps) {
const [urlScrollLeft, setUrlScrollLeft] = useState(0)
const [headerScrollLeft, setHeaderScrollLeft] = useState>({})
- // Auto-select server when initialServerId is provided
useEffect(() => {
if (initialServerId && servers.some((s) => s.id === initialServerId)) {
setSelectedServerId(initialServerId)
}
}, [initialServerId, servers])
- // Force refresh tools when entering server detail view to detect stale schemas
useEffect(() => {
if (selectedServerId) {
forceRefreshTools(workspaceId)
@@ -717,7 +715,6 @@ export function MCP({ initialServerId }: MCPProps) {
`Refreshed MCP server: ${serverId}, workflows updated: ${result.workflowsUpdated}`
)
- // If the active workflow was updated, reload its subblock values from DB
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
if (activeWorkflowId && result.updatedWorkflowIds?.includes(activeWorkflowId)) {
logger.info(`Active workflow ${activeWorkflowId} was updated, reloading subblock values`)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx
index d2a72a998..f0b749f68 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx
@@ -41,7 +41,6 @@ import { getEnv, isTruthy } from '@/lib/core/config/env'
import { isHosted } from '@/lib/core/config/feature-flags'
import { getUserRole } from '@/lib/workspaces/organization'
import {
- AccessControl,
ApiKeys,
BYOK,
Copilot,
@@ -53,15 +52,16 @@ import {
General,
Integrations,
MCP,
- SSO,
Subscription,
TeamManagement,
WorkflowMcpServers,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components'
import { TemplateProfile } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/template-profile/template-profile'
+import { AccessControl } from '@/ee/access-control/components/access-control'
+import { SSO } from '@/ee/sso/components/sso-settings'
+import { ssoKeys, useSSOProviders } from '@/ee/sso/hooks/sso'
import { generalSettingsKeys, useGeneralSettings } from '@/hooks/queries/general-settings'
import { organizationKeys, useOrganizations } from '@/hooks/queries/organization'
-import { ssoKeys, useSSOProviders } from '@/hooks/queries/sso'
import { subscriptionKeys, useSubscriptionData } from '@/hooks/queries/subscription'
import { usePermissionConfig } from '@/hooks/use-permission-config'
import { useSettingsModalStore } from '@/stores/modals/settings/store'
diff --git a/apps/sim/ee/LICENSE b/apps/sim/ee/LICENSE
new file mode 100644
index 000000000..ba5405dbf
--- /dev/null
+++ b/apps/sim/ee/LICENSE
@@ -0,0 +1,43 @@
+Sim Enterprise License
+
+Copyright (c) 2025-present Sim Studio, Inc.
+
+This software and associated documentation files (the "Software") are licensed
+under the following terms:
+
+1. LICENSE GRANT
+
+ Subject to the terms of this license, Sim Studio, Inc. grants you a limited,
+ non-exclusive, non-transferable license to use the Software for:
+
+ - Development, testing, and evaluation purposes
+ - Internal non-production use
+
+ Production use of the Software requires a valid Sim Enterprise subscription.
+
+2. RESTRICTIONS
+
+ You may not:
+
+ - Use the Software in production without a valid Enterprise subscription
+ - Modify, adapt, or create derivative works of the Software
+ - Redistribute, sublicense, or transfer the Software
+ - Remove or alter any proprietary notices in the Software
+
+3. ENTERPRISE SUBSCRIPTION
+
+ Production deployment of enterprise features requires an active Sim Enterprise
+ subscription. Contact sales@simstudio.ai for licensing information.
+
+4. DISCLAIMER
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+
+5. LIMITATION OF LIABILITY
+
+ IN NO EVENT SHALL SIM STUDIO, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
+
+For questions about enterprise licensing, contact: sales@simstudio.ai
diff --git a/apps/sim/ee/README.md b/apps/sim/ee/README.md
new file mode 100644
index 000000000..d9e91afaf
--- /dev/null
+++ b/apps/sim/ee/README.md
@@ -0,0 +1,21 @@
+# Sim Enterprise Edition
+
+This directory contains enterprise features that require a Sim Enterprise subscription
+for production use.
+
+## Features
+
+- **SSO (Single Sign-On)**: OIDC and SAML authentication integration
+- **Access Control**: Permission groups for fine-grained user access management
+- **Credential Sets**: Shared credential pools for email polling workflows
+
+## Licensing
+
+See [LICENSE](./LICENSE) for terms. Development and testing use is permitted.
+Production deployment requires an active Enterprise subscription.
+
+## Architecture
+
+Enterprise features are imported directly throughout the codebase. The `ee/` directory
+is required at build time. Feature visibility is controlled at runtime via environment
+variables (e.g., `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED`).
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx b/apps/sim/ee/access-control/components/access-control.tsx
similarity index 99%
rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx
rename to apps/sim/ee/access-control/components/access-control.tsx
index af7db3fcc..83f2f28dc 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx
+++ b/apps/sim/ee/access-control/components/access-control.tsx
@@ -29,7 +29,6 @@ import type { PermissionGroupConfig } from '@/lib/permission-groups/types'
import { getUserColor } from '@/lib/workspaces/colors'
import { getUserRole } from '@/lib/workspaces/organization'
import { getAllBlocks } from '@/blocks'
-import { useOrganization, useOrganizations } from '@/hooks/queries/organization'
import {
type PermissionGroup,
useBulkAddPermissionGroupMembers,
@@ -39,7 +38,8 @@ import {
usePermissionGroups,
useRemovePermissionGroupMember,
useUpdatePermissionGroup,
-} from '@/hooks/queries/permission-groups'
+} from '@/ee/access-control/hooks/permission-groups'
+import { useOrganization, useOrganizations } from '@/hooks/queries/organization'
import { useSubscriptionData } from '@/hooks/queries/subscription'
import { PROVIDER_DEFINITIONS } from '@/providers/models'
import { getAllProviderIds } from '@/providers/utils'
@@ -255,7 +255,6 @@ export function AccessControl() {
queryEnabled
)
- // Show loading while dependencies load, or while permission groups query is pending
const isLoading = orgsLoading || subLoading || (queryEnabled && groupsLoading)
const { data: organization } = useOrganization(activeOrganization?.id || '')
@@ -410,10 +409,8 @@ export function AccessControl() {
}, [viewingGroup, editingConfig])
const allBlocks = useMemo(() => {
- // Filter out hidden blocks and start_trigger (which should never be disabled)
const blocks = getAllBlocks().filter((b) => !b.hideFromToolbar && b.type !== 'start_trigger')
return blocks.sort((a, b) => {
- // Group by category: triggers first, then blocks, then tools
const categoryOrder = { triggers: 0, blocks: 1, tools: 2 }
const catA = categoryOrder[a.category] ?? 3
const catB = categoryOrder[b.category] ?? 3
@@ -555,10 +552,9 @@ export function AccessControl() {
}, [viewingGroup, editingConfig, activeOrganization?.id, updatePermissionGroup])
const handleOpenAddMembersModal = useCallback(() => {
- const existingMemberUserIds = new Set(members.map((m) => m.userId))
setSelectedMemberIds(new Set())
setShowAddMembersModal(true)
- }, [members])
+ }, [])
const handleAddSelectedMembers = useCallback(async () => {
if (!viewingGroup || selectedMemberIds.size === 0) return
@@ -891,7 +887,6 @@ export function AccessControl() {
prev
? {
...prev,
- // When deselecting all, keep start_trigger allowed (it should never be disabled)
allowedIntegrations: allAllowed ? ['start_trigger'] : null,
}
: prev
diff --git a/apps/sim/hooks/queries/permission-groups.ts b/apps/sim/ee/access-control/hooks/permission-groups.ts
similarity index 99%
rename from apps/sim/hooks/queries/permission-groups.ts
rename to apps/sim/ee/access-control/hooks/permission-groups.ts
index 6832d5188..91f838ced 100644
--- a/apps/sim/hooks/queries/permission-groups.ts
+++ b/apps/sim/ee/access-control/hooks/permission-groups.ts
@@ -1,3 +1,5 @@
+'use client'
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { PermissionGroupConfig } from '@/lib/permission-groups/types'
import { fetchJson } from '@/hooks/selectors/helpers'
diff --git a/apps/sim/executor/utils/permission-check.ts b/apps/sim/ee/access-control/utils/permission-check.ts
similarity index 100%
rename from apps/sim/executor/utils/permission-check.ts
rename to apps/sim/ee/access-control/utils/permission-check.ts
diff --git a/apps/sim/app/chat/components/auth/sso/sso-auth.tsx b/apps/sim/ee/sso/components/sso-auth.tsx
similarity index 100%
rename from apps/sim/app/chat/components/auth/sso/sso-auth.tsx
rename to apps/sim/ee/sso/components/sso-auth.tsx
diff --git a/apps/sim/app/(auth)/sso/sso-form.tsx b/apps/sim/ee/sso/components/sso-form.tsx
similarity index 100%
rename from apps/sim/app/(auth)/sso/sso-form.tsx
rename to apps/sim/ee/sso/components/sso-form.tsx
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx b/apps/sim/ee/sso/components/sso-settings.tsx
similarity index 97%
rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx
rename to apps/sim/ee/sso/components/sso-settings.tsx
index 2657c8204..a43e15ff3 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx
+++ b/apps/sim/ee/sso/components/sso-settings.tsx
@@ -11,55 +11,13 @@ import { isBillingEnabled } from '@/lib/core/config/feature-flags'
import { cn } from '@/lib/core/utils/cn'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { getUserRole } from '@/lib/workspaces/organization/utils'
+import { SSO_TRUSTED_PROVIDERS } from '@/ee/sso/constants'
+import { useConfigureSSO, useSSOProviders } from '@/ee/sso/hooks/sso'
import { useOrganizations } from '@/hooks/queries/organization'
-import { useConfigureSSO, useSSOProviders } from '@/hooks/queries/sso'
import { useSubscriptionData } from '@/hooks/queries/subscription'
const logger = createLogger('SSO')
-const TRUSTED_SSO_PROVIDERS = [
- 'okta',
- 'okta-saml',
- 'okta-prod',
- 'okta-dev',
- 'okta-staging',
- 'okta-test',
- 'azure-ad',
- 'azure-active-directory',
- 'azure-corp',
- 'azure-enterprise',
- 'adfs',
- 'adfs-company',
- 'adfs-corp',
- 'adfs-enterprise',
- 'auth0',
- 'auth0-prod',
- 'auth0-dev',
- 'auth0-staging',
- 'onelogin',
- 'onelogin-prod',
- 'onelogin-corp',
- 'jumpcloud',
- 'jumpcloud-prod',
- 'jumpcloud-corp',
- 'ping-identity',
- 'ping-federate',
- 'pingone',
- 'shibboleth',
- 'shibboleth-idp',
- 'google-workspace',
- 'google-sso',
- 'saml',
- 'saml2',
- 'saml-sso',
- 'oidc',
- 'oidc-sso',
- 'openid-connect',
- 'custom-sso',
- 'enterprise-sso',
- 'company-sso',
-]
-
interface SSOProvider {
id: string
providerId: string
@@ -565,7 +523,7 @@ export function SSO() {
handleInputChange('providerId', value)}
- options={TRUSTED_SSO_PROVIDERS.map((id) => ({
+ options={SSO_TRUSTED_PROVIDERS.map((id) => ({
label: id,
value: id,
}))}
diff --git a/apps/sim/lib/auth/sso/constants.ts b/apps/sim/ee/sso/constants.ts
similarity index 85%
rename from apps/sim/lib/auth/sso/constants.ts
rename to apps/sim/ee/sso/constants.ts
index ca246f8cf..67cfee94f 100644
--- a/apps/sim/lib/auth/sso/constants.ts
+++ b/apps/sim/ee/sso/constants.ts
@@ -1,3 +1,7 @@
+/**
+ * List of trusted SSO provider identifiers.
+ * Used for validation and autocomplete in SSO configuration.
+ */
export const SSO_TRUSTED_PROVIDERS = [
'okta',
'okta-saml',
diff --git a/apps/sim/hooks/queries/sso.ts b/apps/sim/ee/sso/hooks/sso.ts
similarity index 69%
rename from apps/sim/hooks/queries/sso.ts
rename to apps/sim/ee/sso/hooks/sso.ts
index 7c5c769ab..2dfa1592e 100644
--- a/apps/sim/hooks/queries/sso.ts
+++ b/apps/sim/ee/sso/hooks/sso.ts
@@ -1,3 +1,5 @@
+'use client'
+
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { organizationKeys } from '@/hooks/queries/organization'
@@ -75,39 +77,3 @@ export function useConfigureSSO() {
},
})
}
-
-/**
- * Delete SSO provider mutation
- */
-interface DeleteSSOParams {
- providerId: string
- orgId?: string
-}
-
-export function useDeleteSSO() {
- const queryClient = useQueryClient()
-
- return useMutation({
- mutationFn: async ({ providerId }: DeleteSSOParams) => {
- const response = await fetch(`/api/auth/sso/providers/${providerId}`, {
- method: 'DELETE',
- })
-
- if (!response.ok) {
- const error = await response.json()
- throw new Error(error.message || 'Failed to delete SSO provider')
- }
-
- return response.json()
- },
- onSuccess: (_data, variables) => {
- queryClient.invalidateQueries({ queryKey: ssoKeys.providers() })
-
- if (variables.orgId) {
- queryClient.invalidateQueries({
- queryKey: organizationKeys.detail(variables.orgId),
- })
- }
- },
- })
-}
diff --git a/apps/sim/executor/execution/block-executor.ts b/apps/sim/executor/execution/block-executor.ts
index d17da0e7c..59b08e4a9 100644
--- a/apps/sim/executor/execution/block-executor.ts
+++ b/apps/sim/executor/execution/block-executor.ts
@@ -5,6 +5,7 @@ import {
hydrateUserFilesWithBase64,
} from '@/lib/uploads/utils/user-file-base64.server'
import { sanitizeInputFormat, sanitizeTools } from '@/lib/workflows/comparison/normalize'
+import { validateBlockType } from '@/ee/access-control/utils/permission-check'
import {
BlockType,
buildResumeApiUrl,
@@ -31,7 +32,6 @@ import { streamingResponseFormatProcessor } from '@/executor/utils'
import { buildBlockExecutionError, normalizeError } from '@/executor/utils/errors'
import { isJSONString } from '@/executor/utils/json'
import { filterOutputForLog } from '@/executor/utils/output-filter'
-import { validateBlockType } from '@/executor/utils/permission-check'
import type { VariableResolver } from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
import type { SubflowType } from '@/stores/workflows/workflow/types'
diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts
index 007833d9c..40c7b9ba8 100644
--- a/apps/sim/executor/handlers/agent/agent-handler.ts
+++ b/apps/sim/executor/handlers/agent/agent-handler.ts
@@ -6,6 +6,12 @@ import { createMcpToolId } from '@/lib/mcp/utils'
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
import { getAllBlocks } from '@/blocks'
import type { BlockOutput } from '@/blocks/types'
+import {
+ validateBlockType,
+ validateCustomToolsAllowed,
+ validateMcpToolsAllowed,
+ validateModelProvider,
+} from '@/ee/access-control/utils/permission-check'
import { AGENT, BlockType, DEFAULTS, REFERENCE, stripCustomToolPrefix } from '@/executor/constants'
import { memoryService } from '@/executor/handlers/agent/memory'
import type {
@@ -18,12 +24,6 @@ import type { BlockHandler, ExecutionContext, StreamingExecution } from '@/execu
import { collectBlockData } from '@/executor/utils/block-data'
import { buildAPIUrl, buildAuthHeaders } from '@/executor/utils/http'
import { stringifyJSON } from '@/executor/utils/json'
-import {
- validateBlockType,
- validateCustomToolsAllowed,
- validateMcpToolsAllowed,
- validateModelProvider,
-} from '@/executor/utils/permission-check'
import { executeProviderRequest } from '@/providers'
import { getProviderFromModel, transformBlockTool } from '@/providers/utils'
import type { SerializedBlock } from '@/serializer/types'
diff --git a/apps/sim/executor/handlers/evaluator/evaluator-handler.ts b/apps/sim/executor/handlers/evaluator/evaluator-handler.ts
index b383bdce0..3e95b2f85 100644
--- a/apps/sim/executor/handlers/evaluator/evaluator-handler.ts
+++ b/apps/sim/executor/handlers/evaluator/evaluator-handler.ts
@@ -4,11 +4,11 @@ import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
import type { BlockOutput } from '@/blocks/types'
+import { validateModelProvider } from '@/ee/access-control/utils/permission-check'
import { BlockType, DEFAULTS, EVALUATOR } from '@/executor/constants'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import { buildAPIUrl, buildAuthHeaders, extractAPIErrorMessage } from '@/executor/utils/http'
import { isJSONString, parseJSON, stringifyJSON } from '@/executor/utils/json'
-import { validateModelProvider } from '@/executor/utils/permission-check'
import { calculateCost, getProviderFromModel } from '@/providers/utils'
import type { SerializedBlock } from '@/serializer/types'
diff --git a/apps/sim/executor/handlers/router/router-handler.ts b/apps/sim/executor/handlers/router/router-handler.ts
index 12acf6c4c..766a4aac6 100644
--- a/apps/sim/executor/handlers/router/router-handler.ts
+++ b/apps/sim/executor/handlers/router/router-handler.ts
@@ -6,6 +6,7 @@ import { getBaseUrl } from '@/lib/core/utils/urls'
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
import { generateRouterPrompt, generateRouterV2Prompt } from '@/blocks/blocks/router'
import type { BlockOutput } from '@/blocks/types'
+import { validateModelProvider } from '@/ee/access-control/utils/permission-check'
import {
BlockType,
DEFAULTS,
@@ -15,7 +16,6 @@ import {
} from '@/executor/constants'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import { buildAuthHeaders } from '@/executor/utils/http'
-import { validateModelProvider } from '@/executor/utils/permission-check'
import { calculateCost, getProviderFromModel } from '@/providers/utils'
import type { SerializedBlock } from '@/serializer/types'
diff --git a/apps/sim/hooks/queries/credential-sets.ts b/apps/sim/hooks/queries/credential-sets.ts
index 33da082f7..a18374b75 100644
--- a/apps/sim/hooks/queries/credential-sets.ts
+++ b/apps/sim/hooks/queries/credential-sets.ts
@@ -1,3 +1,5 @@
+'use client'
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { fetchJson } from '@/hooks/selectors/helpers'
diff --git a/apps/sim/hooks/use-permission-config.ts b/apps/sim/hooks/use-permission-config.ts
index 994656fdc..3c536caf5 100644
--- a/apps/sim/hooks/use-permission-config.ts
+++ b/apps/sim/hooks/use-permission-config.ts
@@ -1,3 +1,5 @@
+'use client'
+
import { useMemo } from 'react'
import { getEnv, isTruthy } from '@/lib/core/config/env'
import { isAccessControlEnabled, isHosted } from '@/lib/core/config/feature-flags'
@@ -5,8 +7,8 @@ import {
DEFAULT_PERMISSION_GROUP_CONFIG,
type PermissionGroupConfig,
} from '@/lib/permission-groups/types'
+import { useUserPermissionConfig } from '@/ee/access-control/hooks/permission-groups'
import { useOrganizations } from '@/hooks/queries/organization'
-import { useUserPermissionConfig } from '@/hooks/queries/permission-groups'
export interface PermissionConfigResult {
config: PermissionGroupConfig
diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts
index 9241eaf09..d5ac1a8c2 100644
--- a/apps/sim/lib/auth/auth.ts
+++ b/apps/sim/lib/auth/auth.ts
@@ -59,8 +59,8 @@ import { sendEmail } from '@/lib/messaging/email/mailer'
import { getFromEmailAddress, getPersonalEmailFrom } from '@/lib/messaging/email/utils'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
+import { SSO_TRUSTED_PROVIDERS } from '@/ee/sso/constants'
import { createAnonymousSession, ensureAnonymousUserExists } from './anonymous'
-import { SSO_TRUSTED_PROVIDERS } from './sso/constants'
const logger = createLogger('Auth')
diff --git a/apps/sim/lib/copilot/process-contents.ts b/apps/sim/lib/copilot/process-contents.ts
index ff1dbf497..13a0015f0 100644
--- a/apps/sim/lib/copilot/process-contents.ts
+++ b/apps/sim/lib/copilot/process-contents.ts
@@ -5,8 +5,8 @@ import { and, eq, isNull } from 'drizzle-orm'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer'
import { isHiddenFromDisplay } from '@/blocks/types'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { escapeRegExp } from '@/executor/constants'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
import type { ChatContext } from '@/stores/panel/copilot/types'
export type AgentContextType =
diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-block-config.ts b/apps/sim/lib/copilot/tools/server/blocks/get-block-config.ts
index 3d6ebba17..cd95577d7 100644
--- a/apps/sim/lib/copilot/tools/server/blocks/get-block-config.ts
+++ b/apps/sim/lib/copilot/tools/server/blocks/get-block-config.ts
@@ -7,7 +7,7 @@ import {
} from '@/lib/copilot/tools/shared/schemas'
import { registry as blockRegistry, getLatestBlock } from '@/blocks/registry'
import { isHiddenFromDisplay, type SubBlockConfig } from '@/blocks/types'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { PROVIDER_DEFINITIONS } from '@/providers/models'
import { tools as toolsRegistry } from '@/tools/registry'
import { getTrigger, isTriggerValid } from '@/triggers'
diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-block-options.ts b/apps/sim/lib/copilot/tools/server/blocks/get-block-options.ts
index b5e5b2373..177482fc3 100644
--- a/apps/sim/lib/copilot/tools/server/blocks/get-block-options.ts
+++ b/apps/sim/lib/copilot/tools/server/blocks/get-block-options.ts
@@ -6,7 +6,7 @@ import {
type GetBlockOptionsResultType,
} from '@/lib/copilot/tools/shared/schemas'
import { registry as blockRegistry, getLatestBlock } from '@/blocks/registry'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { tools as toolsRegistry } from '@/tools/registry'
export const getBlockOptionsServerTool: BaseServerTool<
diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-and-tools.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-and-tools.ts
index 222288aab..9413dc278 100644
--- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-and-tools.ts
+++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-and-tools.ts
@@ -6,7 +6,7 @@ import {
} from '@/lib/copilot/tools/shared/schemas'
import { registry as blockRegistry } from '@/blocks/registry'
import type { BlockConfig } from '@/blocks/types'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
export const getBlocksAndToolsServerTool: BaseServerTool<
ReturnType,
diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts
index dc4615777..7b945d6b0 100644
--- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts
+++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts
@@ -8,7 +8,7 @@ import {
} from '@/lib/copilot/tools/shared/schemas'
import { registry as blockRegistry } from '@/blocks/registry'
import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { PROVIDER_DEFINITIONS } from '@/providers/models'
import { tools as toolsRegistry } from '@/tools/registry'
import { getTrigger, isTriggerValid } from '@/triggers'
diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-trigger-blocks.ts b/apps/sim/lib/copilot/tools/server/blocks/get-trigger-blocks.ts
index c5f3b75b4..5f5820e20 100644
--- a/apps/sim/lib/copilot/tools/server/blocks/get-trigger-blocks.ts
+++ b/apps/sim/lib/copilot/tools/server/blocks/get-trigger-blocks.ts
@@ -3,7 +3,7 @@ import { z } from 'zod'
import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool'
import { registry as blockRegistry } from '@/blocks/registry'
import type { BlockConfig } from '@/blocks/types'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
export const GetTriggerBlocksInput = z.object({})
export const GetTriggerBlocksResult = z.object({
diff --git a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts
index 61866dbd9..f484ea5d8 100644
--- a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts
+++ b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts
@@ -15,8 +15,8 @@ import { buildCanonicalIndex, isCanonicalPair } from '@/lib/workflows/subblocks/
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
import { getAllBlocks, getBlock } from '@/blocks/registry'
import type { BlockConfig, SubBlockConfig } from '@/blocks/types'
+import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { EDGE, normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
-import { getUserPermissionConfig } from '@/executor/utils/permission-check'
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
import { TRIGGER_RUNTIME_SUBBLOCK_IDS } from '@/triggers/constants'