mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-15 00:44:56 -05:00
address bugbot
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user