more permissions stuff

This commit is contained in:
Vikhyath Mondreti
2026-02-14 12:47:03 -08:00
parent 3769da88b0
commit d235d747ca
4 changed files with 94 additions and 8 deletions

View File

@@ -13,7 +13,18 @@ interface RouteContext {
params: Promise<{ id: string }>
}
async function requireAdminMembership(credentialId: string, userId: string) {
async function requireWorkspaceAdminMembership(credentialId: string, userId: string) {
const [cred] = await db
.select({ id: credential.id, workspaceId: credential.workspaceId })
.from(credential)
.where(eq(credential.id, credentialId))
.limit(1)
if (!cred) return null
const perm = await getUserEntityPermissions(userId, 'workspace', cred.workspaceId)
if (perm === null) return null
const [membership] = await db
.select({ role: credentialMember.role, status: credentialMember.status })
.from(credentialMember)
@@ -91,7 +102,7 @@ export async function POST(request: NextRequest, context: RouteContext) {
const { id: credentialId } = await context.params
const admin = await requireAdminMembership(credentialId, session.user.id)
const admin = await requireWorkspaceAdminMembership(credentialId, session.user.id)
if (!admin) {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
@@ -153,7 +164,7 @@ export async function DELETE(request: NextRequest, context: RouteContext) {
return NextResponse.json({ error: 'userId query parameter required' }, { status: 400 })
}
const admin = await requireAdminMembership(credentialId, session.user.id)
const admin = await requireWorkspaceAdminMembership(credentialId, session.user.id)
if (!admin) {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}

View File

@@ -1,10 +1,11 @@
import { db } from '@sim/db'
import { pendingCredentialDraft } from '@sim/db/schema'
import { credential, credentialMember, pendingCredentialDraft } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, lt } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
const logger = createLogger('CredentialDraftAPI')
@@ -33,6 +34,36 @@ export async function POST(request: Request) {
const { workspaceId, providerId, displayName, description, credentialId } = parsed.data
const userId = session.user.id
const workspaceAccess = await checkWorkspaceAccess(workspaceId, userId)
if (!workspaceAccess.canWrite) {
return NextResponse.json({ error: 'Write permission required' }, { status: 403 })
}
if (credentialId) {
const [membership] = await db
.select({ role: credentialMember.role, status: credentialMember.status })
.from(credentialMember)
.innerJoin(credential, eq(credential.id, credentialMember.credentialId))
.where(
and(
eq(credentialMember.credentialId, credentialId),
eq(credentialMember.userId, userId),
eq(credentialMember.status, 'active'),
eq(credentialMember.role, 'admin'),
eq(credential.workspaceId, workspaceId)
)
)
.limit(1)
if (!membership) {
return NextResponse.json(
{ error: 'Admin access required on the target credential' },
{ status: 403 }
)
}
}
const now = new Date()
await db

View File

@@ -22,9 +22,9 @@
*/
import { db } from '@sim/db'
import { permissions, user, workspace } from '@sim/db/schema'
import { credential, credentialMember, permissions, user, workspace } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { and, eq, inArray } from 'drizzle-orm'
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
import {
badRequestResponse,
@@ -215,6 +215,28 @@ 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)
)
)
)
}
logger.info(`Admin API: Removed member ${memberId} from workspace ${workspaceId}`, {
userId: existingMember.userId,
})

View File

@@ -1,7 +1,7 @@
import { db } from '@sim/db'
import { permissions, workspace } from '@sim/db/schema'
import { credential, credentialMember, permissions, workspace } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
@@ -101,6 +101,28 @@ 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)
)
)
)
}
return NextResponse.json({ success: true })
} catch (error) {
logger.error('Error removing workspace member:', error)