From d3009cd0225efe3c5f1fe22a876f10566b03a3ba Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Mon, 25 May 2026 09:09:17 +0700 Subject: [PATCH 1/2] fix(credentials): grant admin credential role to workspace admins Add getWorkspaceAdminUserIds helper that queries the permissions table for users with permissionType='admin' on the workspace (plus the owner). Update ensureWorkspaceCredentialMemberships, syncWorkspaceEnvCredentials, and createWorkspaceEnvCredentials to use adminUserIds when determining the credential_member role, so workspace admins receive 'admin' instead of 'member' and can edit or delete shared workspace secrets. --- apps/sim/lib/credentials/environment.ts | 40 +++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/apps/sim/lib/credentials/environment.ts b/apps/sim/lib/credentials/environment.ts index 0ace9884075..c0e239129a5 100644 --- a/apps/sim/lib/credentials/environment.ts +++ b/apps/sim/lib/credentials/environment.ts @@ -37,6 +37,31 @@ export async function getWorkspaceMemberUserIds(workspaceId: string): Promise> { + const [workspaceRows, adminPermissionRows] = await Promise.all([ + db + .select({ ownerId: workspace.ownerId }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .limit(1), + db + .select({ userId: permissions.userId }) + .from(permissions) + .where( + and( + eq(permissions.entityType, 'workspace'), + eq(permissions.entityId, workspaceId), + eq(permissions.permissionType, 'admin') + ) + ), + ]) + const adminIds = new Set(adminPermissionRows.map((row) => row.userId)) + if (workspaceRows[0]?.ownerId) { + adminIds.add(workspaceRows[0].ownerId) + } + return adminIds +} + export async function getUserWorkspaceIds(userId: string): Promise { const [permissionRows, ownedWorkspaceRows] = await Promise.all([ db @@ -64,7 +89,8 @@ export async function getUserWorkspaceIds(userId: string): Promise { async function ensureWorkspaceCredentialMemberships( credentialId: string, memberUserIds: string[], - ownerUserId: string + ownerUserId: string, + adminUserIds: Set ) { if (!memberUserIds.length) return @@ -87,7 +113,7 @@ async function ensureWorkspaceCredentialMemberships( const now = new Date() for (const memberUserId of memberUserIds) { - const targetRole = memberUserId === ownerUserId ? 'admin' : 'member' + const targetRole = adminUserIds.has(memberUserId) ? 'admin' : 'member' const existing = byUserId.get(memberUserId) if (existing) { if (existing.status === 'revoked') { @@ -126,13 +152,14 @@ export async function syncWorkspaceEnvCredentials(params: { actingUserId: string }) { const { workspaceId, envKeys, actingUserId } = params - const [[workspaceRow], memberUserIds] = await Promise.all([ + const [[workspaceRow], memberUserIds, adminUserIds] = await Promise.all([ db .select({ ownerId: workspace.ownerId }) .from(workspace) .where(eq(workspace.id, workspaceId)) .limit(1), getWorkspaceMemberUserIds(workspaceId), + getWorkspaceAdminUserIds(workspaceId), ]) if (!workspaceRow) return @@ -182,7 +209,7 @@ export async function syncWorkspaceEnvCredentials(params: { } for (const credentialId of credentialIdsToEnsureMembership) { - await ensureWorkspaceCredentialMemberships(credentialId, memberUserIds, workspaceRow.ownerId) + await ensureWorkspaceCredentialMemberships(credentialId, memberUserIds, workspaceRow.ownerId, adminUserIds) } if (normalizedKeys.length > 0) { @@ -216,13 +243,14 @@ export async function createWorkspaceEnvCredentials(params: { const keys = Array.from(new Set(newKeys.filter(Boolean))) if (keys.length === 0) return - const [[workspaceRow], memberUserIds] = await Promise.all([ + const [[workspaceRow], memberUserIds, adminUserIds] = await Promise.all([ db .select({ ownerId: workspace.ownerId }) .from(workspace) .where(eq(workspace.id, workspaceId)) .limit(1), getWorkspaceMemberUserIds(workspaceId), + getWorkspaceAdminUserIds(workspaceId), ]) if (!workspaceRow) return @@ -259,7 +287,7 @@ export async function createWorkspaceEnvCredentials(params: { id: generateId(), credentialId, userId: memberUserId, - role: (memberUserId === ownerUserId ? 'admin' : 'member') as 'admin' | 'member', + role: (adminUserIds.has(memberUserId) ? 'admin' : 'member') as 'admin' | 'member', status: 'active' as const, joinedAt: now, invitedBy: ownerUserId, From 7819ed3f128a2d840d8c529f32184860d83431c1 Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Mon, 25 May 2026 09:09:27 +0700 Subject: [PATCH 2/2] fix(credentials): apply admin role check in POST credential handler --- apps/sim/app/api/credentials/route.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/api/credentials/route.ts b/apps/sim/app/api/credentials/route.ts index 64a3d3f9511..1317ecea18a 100644 --- a/apps/sim/app/api/credentials/route.ts +++ b/apps/sim/app/api/credentials/route.ts @@ -22,7 +22,7 @@ import { normalizeAtlassianDomain, validateAtlassianServiceAccount, } from '@/lib/credentials/atlassian-service-account' -import { getWorkspaceMemberUserIds } from '@/lib/credentials/environment' +import { getWorkspaceAdminUserIds, getWorkspaceMemberUserIds } from '@/lib/credentials/environment' import { syncWorkspaceOAuthCredentialsForUser } from '@/lib/credentials/oauth' import { getServiceConfigByProviderId } from '@/lib/oauth' import { @@ -535,7 +535,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }) if ((type === 'env_workspace' || type === 'service_account') && workspaceRow?.ownerId) { - const workspaceUserIds = await getWorkspaceMemberUserIds(workspaceId) + const [workspaceUserIds, adminUserIds] = await Promise.all([ + getWorkspaceMemberUserIds(workspaceId), + getWorkspaceAdminUserIds(workspaceId), + ]) if (workspaceUserIds.length > 0) { for (const memberUserId of workspaceUserIds) { await tx.insert(credentialMember).values({ @@ -543,7 +546,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { credentialId, userId: memberUserId, role: - memberUserId === workspaceRow.ownerId || memberUserId === session.user.id + adminUserIds.has(memberUserId) || memberUserId === session.user.id ? 'admin' : 'member', status: 'active',