Skip to content

Commit 3283f60

Browse files
committed
address ui comments
1 parent 38c8356 commit 3283f60

7 files changed

Lines changed: 101 additions & 22 deletions

File tree

apps/sim/app/api/guardrails/validate/route.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { validateHallucination } from '@/lib/guardrails/validate_hallucination'
77
import { validateJson } from '@/lib/guardrails/validate_json'
88
import { validatePII } from '@/lib/guardrails/validate_pii'
99
import { validateRegex } from '@/lib/guardrails/validate_regex'
10+
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
1011
import {
1112
assertPermissionsAllowed,
1213
ProviderNotAllowedError,
@@ -43,7 +44,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
4344
bedrockSecretKey,
4445
bedrockRegion,
4546
workflowId,
46-
workspaceId,
4747
piiEntityTypes,
4848
piiMode,
4949
piiLanguage,
@@ -114,11 +114,46 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
114114
})
115115
}
116116

117-
if (validationType === 'hallucination' && model && workspaceId) {
117+
let resolvedWorkspaceId: string | undefined
118+
119+
if (validationType === 'hallucination' && model) {
120+
if (!workflowId || typeof workflowId !== 'string') {
121+
return NextResponse.json({
122+
success: true,
123+
output: {
124+
passed: false,
125+
validationType,
126+
input: input || '',
127+
error:
128+
'Workflow context is required for hallucination validation. Call this endpoint via a workflow execution, not directly.',
129+
},
130+
})
131+
}
132+
133+
const authorization = await authorizeWorkflowByWorkspacePermission({
134+
workflowId,
135+
userId: auth.userId,
136+
action: 'read',
137+
})
138+
139+
if (!authorization.allowed || !authorization.workflow?.workspaceId) {
140+
return NextResponse.json({
141+
success: true,
142+
output: {
143+
passed: false,
144+
validationType,
145+
input: input || '',
146+
error: authorization.message || 'Workflow not found or access denied.',
147+
},
148+
})
149+
}
150+
151+
resolvedWorkspaceId = authorization.workflow.workspaceId
152+
118153
try {
119154
await assertPermissionsAllowed({
120155
userId: auth.userId,
121-
workspaceId,
156+
workspaceId: resolvedWorkspaceId,
122157
model,
123158
})
124159
} catch (err) {
@@ -168,7 +203,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
168203
bedrockRegion,
169204
},
170205
workflowId,
171-
workspaceId,
206+
resolvedWorkspaceId,
172207
piiEntityTypes,
173208
piiMode,
174209
piiLanguage,

apps/sim/app/api/workspaces/[id]/permissions/route.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import { applyWorkspaceAutoAddGroup } from '@/lib/permission-groups/auto-add'
1313
import { captureServerEvent } from '@/lib/posthog/server'
1414
import {
1515
checkWorkspaceAccess,
16+
getUserEntityPermissions,
1617
getUsersWithPermissions,
1718
hasWorkspaceAdminAccess,
19+
type PermissionType,
1820
} from '@/lib/workspaces/permissions/utils'
1921

2022
const logger = createLogger('WorkspacesPermissionsAPI')
@@ -48,23 +50,25 @@ export const GET = withRouteHandler(
4850
}
4951

5052
const isAdmin = await hasWorkspaceAdminAccess(session.user.id, workspaceId)
53+
const access = await checkWorkspaceAccess(workspaceId, session.user.id)
5154

52-
let hasAccess = isAdmin
53-
if (!hasAccess) {
54-
const access = await checkWorkspaceAccess(workspaceId, session.user.id)
55-
if (!access.exists) {
56-
return NextResponse.json(
57-
{ error: 'Workspace not found or access denied' },
58-
{ status: 404 }
59-
)
60-
}
61-
hasAccess = access.hasAccess
55+
if (!access.exists) {
56+
return NextResponse.json({ error: 'Workspace not found or access denied' }, { status: 404 })
6257
}
6358

64-
if (!hasAccess) {
59+
if (!isAdmin && !access.hasAccess) {
6560
return NextResponse.json({ error: 'Workspace not found or access denied' }, { status: 404 })
6661
}
6762

63+
const explicitPermission = await getUserEntityPermissions(
64+
session.user.id,
65+
'workspace',
66+
workspaceId
67+
)
68+
const viewerPermissionType: PermissionType = isAdmin
69+
? 'admin'
70+
: (explicitPermission ?? 'read')
71+
6872
const result = await getUsersWithPermissions(workspaceId)
6973

7074
return NextResponse.json({
@@ -73,6 +77,7 @@ export const GET = withRouteHandler(
7377
viewer: {
7478
userId: session.user.id,
7579
isAdmin,
80+
permissionType: viewerPermissionType,
7681
},
7782
})
7883
} catch (error) {

apps/sim/ee/access-control/components/access-control.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ interface AddMembersModalProps {
6464
setSelectedMemberIds: React.Dispatch<React.SetStateAction<Set<string>>>
6565
onAddMembers: () => void
6666
isAdding: boolean
67+
errorMessage: string | null
6768
}
6869

6970
function AddMembersModal({
@@ -74,6 +75,7 @@ function AddMembersModal({
7475
setSelectedMemberIds,
7576
onAddMembers,
7677
isAdding,
78+
errorMessage,
7779
}: AddMembersModalProps) {
7880
const [searchTerm, setSearchTerm] = useState('')
7981

@@ -199,6 +201,9 @@ function AddMembersModal({
199201
</div>
200202
</div>
201203
)}
204+
{errorMessage && (
205+
<p className='mt-3 text-[var(--text-destructive)] text-xs'>{errorMessage}</p>
206+
)}
202207
</ModalBody>
203208
<ModalFooter>
204209
<Button
@@ -289,6 +294,7 @@ export function AccessControl() {
289294
const [showConfigModal, setShowConfigModal] = useState(false)
290295
const [editingConfig, setEditingConfig] = useState<PermissionGroupConfig | null>(null)
291296
const [showAddMembersModal, setShowAddMembersModal] = useState(false)
297+
const [addMembersError, setAddMembersError] = useState<string | null>(null)
292298
const [selectedMemberIds, setSelectedMemberIds] = useState<Set<string>>(() => new Set())
293299
const [providerSearchTerm, setProviderSearchTerm] = useState('')
294300
const [integrationSearchTerm, setIntegrationSearchTerm] = useState('')
@@ -628,11 +634,13 @@ export function AccessControl() {
628634

629635
const handleOpenAddMembersModal = useCallback(() => {
630636
setSelectedMemberIds(new Set())
637+
setAddMembersError(null)
631638
setShowAddMembersModal(true)
632639
}, [])
633640

634641
const handleAddSelectedMembers = useCallback(async () => {
635642
if (!viewingGroup || !workspaceId || selectedMemberIds.size === 0) return
643+
setAddMembersError(null)
636644
try {
637645
await bulkAddMembers.mutateAsync({
638646
workspaceId,
@@ -643,6 +651,9 @@ export function AccessControl() {
643651
setSelectedMemberIds(new Set())
644652
} catch (error) {
645653
logger.error('Failed to add members', error)
654+
setAddMembersError(
655+
error instanceof Error && error.message ? error.message : 'Failed to add members'
656+
)
646657
}
647658
}, [viewingGroup, workspaceId, selectedMemberIds, bulkAddMembers])
648659

@@ -1202,12 +1213,16 @@ export function AccessControl() {
12021213

12031214
<AddMembersModal
12041215
open={showAddMembersModal}
1205-
onOpenChange={setShowAddMembersModal}
1216+
onOpenChange={(open) => {
1217+
setShowAddMembersModal(open)
1218+
if (!open) setAddMembersError(null)
1219+
}}
12061220
availableMembers={availableMembersToAdd}
12071221
selectedMemberIds={selectedMemberIds}
12081222
setSelectedMemberIds={setSelectedMemberIds}
12091223
onAddMembers={handleAddSelectedMembers}
12101224
isAdding={bulkAddMembers.isPending}
1225+
errorMessage={addMembersError}
12111226
/>
12121227
</>
12131228
)

apps/sim/hooks/queries/workspace.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,12 @@ export interface WorkspacePermissionsViewer {
262262
* workspace. Use this instead of scanning `users` for a `permissionType === 'admin'` row.
263263
*/
264264
isAdmin: boolean
265+
/**
266+
* The viewer's effective permission level for this workspace. Resolves to `'admin'` whenever
267+
* `isAdmin` is true (including owner / org-admin paths where no explicit permissions row exists),
268+
* otherwise falls back to the user's explicit `permissions` row (`admin` | `write` | `read`).
269+
*/
270+
permissionType: 'admin' | 'write' | 'read'
265271
}
266272

267273
/** Workspace permissions data containing all users and their access levels. */

apps/sim/hooks/use-user-permissions.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,28 @@ export function useUserPermissions(
4848
}
4949
}
5050

51-
// Find current user in workspace permissions (case-insensitive)
51+
/**
52+
* Prefer the server-resolved `viewer.permissionType` — it already accounts for workspace
53+
* owners and organization owners/admins who have no explicit `permissions` row but are
54+
* effectively admins. Falls back to scanning `users` only if the server response predates
55+
* the viewer field (rolling deploy).
56+
*/
57+
const viewerPerms = workspacePermissions?.viewer?.permissionType
58+
if (viewerPerms) {
59+
return {
60+
canRead: true,
61+
canEdit: viewerPerms === 'write' || viewerPerms === 'admin',
62+
canAdmin: viewerPerms === 'admin',
63+
userPermissions: viewerPerms,
64+
isLoading: false,
65+
error: permissionsError,
66+
}
67+
}
68+
5269
const currentUser = workspacePermissions?.users?.find(
5370
(user) => user.email.toLowerCase() === sessionEmail.toLowerCase()
5471
)
5572

56-
// If user not found in workspace, they have no permissions
5773
if (!currentUser) {
5874
logger.warn('User not found in workspace permissions', {
5975
userEmail: sessionEmail,
@@ -72,14 +88,11 @@ export function useUserPermissions(
7288
}
7389

7490
const userPerms = currentUser.permissionType || 'read'
75-
76-
// Core permission checks
7791
const canAdmin = userPerms === 'admin'
7892
const canEdit = userPerms === 'write' || userPerms === 'admin'
79-
const canRead = true // If user is found in workspace permissions, they have read access
8093

8194
return {
82-
canRead,
95+
canRead: true,
8396
canEdit,
8497
canAdmin,
8598
userPermissions: userPerms,

apps/sim/lib/workspaces/permissions/utils.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ describe('Permission Utils', () => {
208208
userId: 'user1',
209209
email: 'alice@example.com',
210210
name: 'Alice Smith',
211+
image: 'https://example.com/alice.png',
211212
permissionType: 'admin' as PermissionType,
212213
},
213214
]
@@ -222,6 +223,7 @@ describe('Permission Utils', () => {
222223
userId: 'user1',
223224
email: 'alice@example.com',
224225
name: 'Alice Smith',
226+
image: 'https://example.com/alice.png',
225227
permissionType: 'admin',
226228
},
227229
])

apps/sim/lib/workspaces/permissions/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ export async function getUsersWithPermissions(workspaceId: string): Promise<
238238
userId: string
239239
email: string
240240
name: string
241+
image: string | null
241242
permissionType: PermissionType
242243
}>
243244
> {
@@ -246,6 +247,7 @@ export async function getUsersWithPermissions(workspaceId: string): Promise<
246247
userId: user.id,
247248
email: user.email,
248249
name: user.name,
250+
image: user.image,
249251
permissionType: permissions.permissionType,
250252
})
251253
.from(permissions)
@@ -264,6 +266,7 @@ export async function getUsersWithPermissions(workspaceId: string): Promise<
264266
userId: row.userId,
265267
email: row.email,
266268
name: row.name,
269+
image: row.image ?? null,
267270
permissionType: row.permissionType,
268271
}))
269272
}

0 commit comments

Comments
 (0)