address bugbot

This commit is contained in:
Vikhyath Mondreti
2026-02-14 14:10:56 -08:00
parent 140f870cfc
commit 9584b99c8a
3 changed files with 114 additions and 48 deletions

View File

@@ -22,9 +22,10 @@
*/
import { db } from '@sim/db'
import { credential, credentialMember, permissions, user, workspace } from '@sim/db/schema'
import { permissions, user, workspace } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, inArray } from 'drizzle-orm'
import { and, eq } from 'drizzle-orm'
import { revokeWorkspaceCredentialMemberships } from '@/lib/credentials/access'
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
import {
badRequestResponse,
@@ -215,27 +216,7 @@ export const DELETE = withAdminAuthParams<RouteParams>(async (_, context) => {
await db.delete(permissions).where(eq(permissions.id, memberId))
// Revoke credential memberships for all credentials in this workspace
const workspaceCredentialIds = await db
.select({ id: credential.id })
.from(credential)
.where(eq(credential.workspaceId, workspaceId))
if (workspaceCredentialIds.length > 0) {
await db
.update(credentialMember)
.set({ status: 'revoked', updatedAt: new Date() })
.where(
and(
eq(credentialMember.userId, existingMember.userId),
eq(credentialMember.status, 'active'),
inArray(
credentialMember.credentialId,
workspaceCredentialIds.map((c) => c.id)
)
)
)
}
await revokeWorkspaceCredentialMemberships(workspaceId, existingMember.userId)
logger.info(`Admin API: Removed member ${memberId} from workspace ${workspaceId}`, {
userId: existingMember.userId,

View File

@@ -1,10 +1,11 @@
import { db } from '@sim/db'
import { credential, credentialMember, permissions, workspace } from '@sim/db/schema'
import { permissions, workspace } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, inArray } from 'drizzle-orm'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { revokeWorkspaceCredentialMemberships } from '@/lib/credentials/access'
import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils'
const logger = createLogger('WorkspaceMemberAPI')
@@ -101,27 +102,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
)
)
// Revoke credential memberships for all credentials in this workspace
const workspaceCredentialIds = await db
.select({ id: credential.id })
.from(credential)
.where(eq(credential.workspaceId, workspaceId))
if (workspaceCredentialIds.length > 0) {
await db
.update(credentialMember)
.set({ status: 'revoked', updatedAt: new Date() })
.where(
and(
eq(credentialMember.userId, userId),
eq(credentialMember.status, 'active'),
inArray(
credentialMember.credentialId,
workspaceCredentialIds.map((c) => c.id)
)
)
)
}
await revokeWorkspaceCredentialMemberships(workspaceId, userId)
return NextResponse.json({ success: true })
} catch (error) {

View File

@@ -1,8 +1,11 @@
import { db } from '@sim/db'
import { credential, credentialMember } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { credential, credentialMember, workspace } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, inArray, ne } from 'drizzle-orm'
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
const logger = createLogger('CredentialAccess')
type ActiveCredentialMember = typeof credentialMember.$inferSelect
type CredentialRecord = typeof credential.$inferSelect
@@ -60,3 +63,104 @@ export async function getCredentialActorContext(
isAdmin,
}
}
/**
* Revokes all credential memberships for a user across a workspace.
* Before revoking, ensures the workspace owner is an admin on any credential
* where the removed user is the sole active admin, preventing orphaned credentials.
*/
export async function revokeWorkspaceCredentialMemberships(
workspaceId: string,
userId: string
): Promise<void> {
const workspaceCredentialIds = await db
.select({ id: credential.id })
.from(credential)
.where(eq(credential.workspaceId, workspaceId))
if (workspaceCredentialIds.length === 0) return
const credIds = workspaceCredentialIds.map((c) => c.id)
const [workspaceRow] = await db
.select({ ownerId: workspace.ownerId })
.from(workspace)
.where(eq(workspace.id, workspaceId))
.limit(1)
const ownerId = workspaceRow?.ownerId
if (ownerId && ownerId !== userId) {
const userAdminMemberships = await db
.select({ credentialId: credentialMember.credentialId })
.from(credentialMember)
.where(
and(
eq(credentialMember.userId, userId),
eq(credentialMember.role, 'admin'),
eq(credentialMember.status, 'active'),
inArray(credentialMember.credentialId, credIds)
)
)
for (const { credentialId: credId } of userAdminMemberships) {
const otherAdmins = await db
.select({ id: credentialMember.id })
.from(credentialMember)
.where(
and(
eq(credentialMember.credentialId, credId),
eq(credentialMember.role, 'admin'),
eq(credentialMember.status, 'active'),
ne(credentialMember.userId, userId)
)
)
.limit(1)
if (otherAdmins.length > 0) continue
const now = new Date()
const [existingOwnerMembership] = await db
.select({ id: credentialMember.id, status: credentialMember.status })
.from(credentialMember)
.where(and(eq(credentialMember.credentialId, credId), eq(credentialMember.userId, ownerId)))
.limit(1)
if (existingOwnerMembership) {
await db
.update(credentialMember)
.set({ role: 'admin', status: 'active', updatedAt: now })
.where(eq(credentialMember.id, existingOwnerMembership.id))
} else {
await db.insert(credentialMember).values({
id: crypto.randomUUID(),
credentialId: credId,
userId: ownerId,
role: 'admin',
status: 'active',
joinedAt: now,
invitedBy: ownerId,
createdAt: now,
updatedAt: now,
})
}
logger.info('Assigned workspace owner as credential admin before member removal', {
credentialId: credId,
ownerId,
removedUserId: userId,
})
}
}
await db
.update(credentialMember)
.set({ status: 'revoked', updatedAt: new Date() })
.where(
and(
eq(credentialMember.userId, userId),
eq(credentialMember.status, 'active'),
inArray(credentialMember.credentialId, credIds)
)
)
}