From 999bc60ba08db12353ee8c279518186270831de5 Mon Sep 17 00:00:00 2001 From: tsushanth <78000697+tsushanth@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:06:21 -0700 Subject: [PATCH] fix(credentials): grant admin role to workspace admins on credential creation When seeding credential_member rows for workspace-scoped credentials, the role was determined solely by whether the acting user is the creator (actingUserId === memberUserId). Workspace admin users therefore received the 'member' role and could not edit or delete secrets they should control. Fix: read permissionType from the permissions table inside getWorkspaceMembership and expose an adminUserIds set. All three creation paths (ensureWorkspaceCredentialMemberships, createWorkspaceEnvCredentials, and the POST /api/credentials handler) now use adminUserIds.has(userId) to assign 'admin' vs 'member', matching the workspace's permission structure. --- apps/sim/app/api/credentials/route.ts | 5 ++--- apps/sim/lib/credentials/environment.ts | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/sim/app/api/credentials/route.ts b/apps/sim/app/api/credentials/route.ts index 318b905987b..9368284d4d3 100644 --- a/apps/sim/app/api/credentials/route.ts +++ b/apps/sim/app/api/credentials/route.ts @@ -507,7 +507,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const now = new Date() const credentialId = generateId() - const { ownerId: workspaceOwnerId, memberUserIds: workspaceMemberUserIds } = + const { ownerId: workspaceOwnerId, memberUserIds: workspaceMemberUserIds, adminUserIds: workspaceAdminUserIds } = await getWorkspaceMembership(workspaceId) await db.transaction(async (tx) => { @@ -543,12 +543,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if ((type === 'env_workspace' || type === 'service_account') && workspaceOwnerId) { if (workspaceMemberUserIds.length > 0) { for (const memberUserId of workspaceMemberUserIds) { - const isAdmin = memberUserId === session.user.id await tx.insert(credentialMember).values({ id: generateId(), credentialId, userId: memberUserId, - role: isAdmin ? 'admin' : 'member', + role: workspaceAdminUserIds.has(memberUserId) ? 'admin' : 'member', status: 'active', joinedAt: now, invitedBy: session.user.id, diff --git a/apps/sim/lib/credentials/environment.ts b/apps/sim/lib/credentials/environment.ts index 5fd7b711adf..1652a28e018 100644 --- a/apps/sim/lib/credentials/environment.ts +++ b/apps/sim/lib/credentials/environment.ts @@ -8,6 +8,8 @@ export interface WorkspaceMembership { ownerId: string | null /** All workspace members: the owner plus everyone with a workspace permission. */ memberUserIds: string[] + /** Subset of memberUserIds with admin-level workspace permission (owner + explicit admins). */ + adminUserIds: Set } /** @@ -23,18 +25,22 @@ export async function getWorkspaceMembership(workspaceId: string): Promise(permissionRows.map((row) => row.userId)) + const adminUserIds = new Set( + permissionRows.filter((row) => row.permissionType === 'admin').map((row) => row.userId) + ) if (ownerId) { memberUserIds.add(ownerId) + adminUserIds.add(ownerId) } - return { ownerId, memberUserIds: Array.from(memberUserIds) } + return { ownerId, memberUserIds: Array.from(memberUserIds), adminUserIds } } export interface WorkspaceEnvKeyAdminAccess { @@ -122,7 +128,8 @@ export async function getUserWorkspaceIds(userId: string): Promise { async function ensureWorkspaceCredentialMemberships( credentialId: string, memberUserIds: string[], - invitedBy: string + invitedBy: string, + adminUserIds: Set ) { if (!memberUserIds.length) return @@ -151,7 +158,7 @@ async function ensureWorkspaceCredentialMemberships( id: generateId(), credentialId, userId: memberUserId, - role: 'member' as const, + role: (adminUserIds.has(memberUserId) ? 'admin' : 'member') as 'admin' | 'member', status: 'active' as const, joinedAt: now, invitedBy, @@ -180,7 +187,7 @@ export async function syncWorkspaceEnvCredentials(params: { actingUserId: string }) { const { workspaceId, envKeys, actingUserId } = params - const { ownerId, memberUserIds } = await getWorkspaceMembership(workspaceId) + const { ownerId, memberUserIds, adminUserIds } = await getWorkspaceMembership(workspaceId) if (!ownerId) return @@ -231,7 +238,7 @@ export async function syncWorkspaceEnvCredentials(params: { } for (const credentialId of credentialIdsToEnsureMembership) { - await ensureWorkspaceCredentialMemberships(credentialId, memberUserIds, ownerId) + await ensureWorkspaceCredentialMemberships(credentialId, memberUserIds, ownerId, adminUserIds) } if (normalizedKeys.length > 0) { @@ -265,7 +272,7 @@ export async function createWorkspaceEnvCredentials(params: { const keys = Array.from(new Set(newKeys.filter(Boolean))) if (keys.length === 0) return - const { ownerId, memberUserIds } = await getWorkspaceMembership(workspaceId) + const { ownerId, memberUserIds, adminUserIds } = await getWorkspaceMembership(workspaceId) if (!ownerId) return @@ -297,7 +304,7 @@ export async function createWorkspaceEnvCredentials(params: { id: generateId(), credentialId, userId: memberUserId, - role: (memberUserId === actingUserId ? 'admin' : 'member') as 'admin' | 'member', + role: (adminUserIds.has(memberUserId) ? 'admin' : 'member') as 'admin' | 'member', status: 'active' as const, joinedAt: now, invitedBy: actingUserId,