-
Notifications
You must be signed in to change notification settings - Fork 3.6k
fix(credentials): reflect workspace permission in credential member role #4699
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Changes from all commits
8d78ecb
1bbf7c6
71db264
d1891c9
78b6c88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,7 +64,8 @@ export async function getUserWorkspaceIds(userId: string): Promise<string[]> { | |
| async function ensureWorkspaceCredentialMemberships( | ||
| credentialId: string, | ||
| memberUserIds: string[], | ||
| ownerUserId: string | ||
| ownerUserId: string, | ||
| wsPermissionByUser: Map<string, string> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exported function now unused after callers removedLow Severity
Reviewed by Cursor Bugbot for commit d1891c9. Configure here. |
||
| ) { | ||
| if (!memberUserIds.length) return | ||
|
|
||
|
|
@@ -87,7 +88,8 @@ async function ensureWorkspaceCredentialMemberships( | |
| const now = new Date() | ||
|
|
||
| for (const memberUserId of memberUserIds) { | ||
| const targetRole = memberUserId === ownerUserId ? 'admin' : 'member' | ||
| const wsPermission = wsPermissionByUser.get(memberUserId) | ||
| const targetRole = memberUserId === ownerUserId || wsPermission === 'admin' ? 'admin' : 'member' | ||
| const existing = byUserId.get(memberUserId) | ||
| if (existing) { | ||
| if (existing.status === 'revoked') { | ||
|
|
@@ -126,17 +128,27 @@ export async function syncWorkspaceEnvCredentials(params: { | |
| actingUserId: string | ||
| }) { | ||
| const { workspaceId, envKeys, actingUserId } = params | ||
| const [[workspaceRow], memberUserIds] = await Promise.all([ | ||
| const [[workspaceRow], wsPermissionRows] = await Promise.all([ | ||
| db | ||
| .select({ ownerId: workspace.ownerId }) | ||
| .from(workspace) | ||
| .where(eq(workspace.id, workspaceId)) | ||
| .limit(1), | ||
| getWorkspaceMemberUserIds(workspaceId), | ||
| db | ||
| .select({ userId: permissions.userId, permissionType: permissions.permissionType }) | ||
| .from(permissions) | ||
| .where(and(eq(permissions.entityType, 'workspace'), eq(permissions.entityId, workspaceId))), | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| ]) | ||
|
|
||
| if (!workspaceRow) return | ||
|
|
||
| const wsPermissionByUser = new Map( | ||
| wsPermissionRows.map((row) => [row.userId, row.permissionType]) | ||
| ) | ||
| const memberUserIds = Array.from( | ||
| new Set([workspaceRow.ownerId, ...wsPermissionRows.map((row) => row.userId)]) | ||
| ) | ||
|
|
||
| const normalizedKeys = Array.from(new Set(envKeys.filter(Boolean))) | ||
| const existingCredentials = await db | ||
| .select({ | ||
|
|
@@ -182,7 +194,12 @@ export async function syncWorkspaceEnvCredentials(params: { | |
| } | ||
|
|
||
| for (const credentialId of credentialIdsToEnsureMembership) { | ||
| await ensureWorkspaceCredentialMemberships(credentialId, memberUserIds, workspaceRow.ownerId) | ||
| await ensureWorkspaceCredentialMemberships( | ||
| credentialId, | ||
| memberUserIds, | ||
| workspaceRow.ownerId, | ||
| wsPermissionByUser | ||
| ) | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| if (normalizedKeys.length > 0) { | ||
|
|
@@ -216,18 +233,27 @@ 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], wsPermissionRows] = await Promise.all([ | ||
| db | ||
| .select({ ownerId: workspace.ownerId }) | ||
| .from(workspace) | ||
| .where(eq(workspace.id, workspaceId)) | ||
| .limit(1), | ||
| getWorkspaceMemberUserIds(workspaceId), | ||
| db | ||
| .select({ userId: permissions.userId, permissionType: permissions.permissionType }) | ||
| .from(permissions) | ||
| .where(and(eq(permissions.entityType, 'workspace'), eq(permissions.entityId, workspaceId))), | ||
| ]) | ||
|
|
||
| if (!workspaceRow) return | ||
|
|
||
| const ownerUserId = workspaceRow.ownerId | ||
| const wsPermissionByUser = new Map( | ||
| wsPermissionRows.map((row) => [row.userId, row.permissionType]) | ||
| ) | ||
| const memberUserIds = Array.from( | ||
| new Set([ownerUserId, ...wsPermissionRows.map((row) => row.userId)]) | ||
| ) | ||
| const now = new Date() | ||
| const createdIds: string[] = [] | ||
|
|
||
|
|
@@ -255,17 +281,21 @@ export async function createWorkspaceEnvCredentials(params: { | |
|
|
||
| // Bulk-insert memberships for all new credentials × all workspace members in one query | ||
| const membershipValues = createdIds.flatMap((credentialId) => | ||
| memberUserIds.map((memberUserId) => ({ | ||
| id: generateId(), | ||
| credentialId, | ||
| userId: memberUserId, | ||
| role: (memberUserId === ownerUserId ? 'admin' : 'member') as 'admin' | 'member', | ||
| status: 'active' as const, | ||
| joinedAt: now, | ||
| invitedBy: ownerUserId, | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| })) | ||
| memberUserIds.map((memberUserId) => { | ||
| const wsPermission = wsPermissionByUser.get(memberUserId) | ||
| const isAdmin = memberUserId === ownerUserId || wsPermission === 'admin' | ||
| return { | ||
| id: generateId(), | ||
| credentialId, | ||
| userId: memberUserId, | ||
| role: (isAdmin ? 'admin' : 'member') as 'admin' | 'member', | ||
| status: 'active' as const, | ||
| joinedAt: now, | ||
| invitedBy: ownerUserId, | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| } | ||
| }) | ||
| ) | ||
|
|
||
| await db.insert(credentialMember).values(membershipValues).onConflictDoNothing() | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Permissions query bypasses transaction using
dbinstead oftxMedium Severity
Inside
db.transaction(async (tx) => { ... }), the workspace permissions query on line 537 usesdbinstead of the transaction handletx. Every other query in this transaction block correctly usestx. This causes the permissions read to execute on a separate connection outside the transaction's isolation boundary, which means it won't see uncommitted changes from concurrent transactions consistently and consumes an extra connection from the pool. Sincetxis available and used for all other operations in the same block, this looks like an oversight.Reviewed by Cursor Bugbot for commit 78b6c88. Configure here.