import { NextRequest, NextResponse } from "next/server"; import { getSessionUser, canMutate } from "@/lib/session"; import { getTenant } from "@/lib/k8s"; import { removeTenantAssignment } from "@/lib/db"; import { safeError } from "@/lib/errors"; /** * DELETE /api/tenants/[name]/assignments/[userId] * * Revoke a user's assignment to a tenant. Owner+platform only. * * No-op if the assignment didn't exist (delete is idempotent at the * DB layer). We don't surface "not found" because that would let a * caller probe for assignment existence — the boolean response is * just "you're authorized to do this". * * Note on self-revocation: an owner can revoke their own row even * though it has no practical effect (owners see all tenants). A * `user`-role member cannot revoke their own assignment because * they're already gated out by canMutate. */ export async function DELETE( _req: NextRequest, { params }: { params: Promise<{ name: string; userId: string }> } ) { const user = await getSessionUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (!canMutate(user)) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const { name, userId } = await params; const tenant = await getTenant(name); if (!tenant) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } // Same cross-org boundary as assign: customer owners can only manage // their own org's tenants; platform users can manage anywhere. const tenantOrgId = tenant.metadata.labels?.["pieced.ch/zitadel-org-id"]; if (!user.isPlatform && tenantOrgId !== user.orgId) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } try { await removeTenantAssignment(name, userId); return NextResponse.json({ message: "Assignment revoked." }); } catch (e: any) { console.error("Failed to remove tenant assignment:", e); return NextResponse.json( { error: safeError(e, "Failed to revoke assignment") }, { status: 500 } ); } }