Files
pieced-portal/src/app/api/tenants/[name]/secrets/route.ts
admin 7c4e20099d
All checks were successful
Build and Push / build (push) Successful in 1m24s
Role split and owner gating
2026-04-26 22:45:38 +02:00

74 lines
2.1 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { getSessionUser, canMutate } from "@/lib/session";
import { getTenant } from "@/lib/k8s";
import { writePackageSecrets } from "@/lib/openbao";
import { getPackageDef } from "@/lib/packages";
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ name: 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 } = await params;
const body = await req.json();
const { packageId, secrets } = body as {
packageId: string;
secrets: Record<string, string>;
};
if (!packageId || !secrets || typeof secrets !== "object") {
return NextResponse.json(
{ error: "Missing packageId or secrets" },
{ status: 400 }
);
}
const pkgDef = getPackageDef(packageId);
if (!pkgDef)
return NextResponse.json({ error: "Unknown package" }, { status: 400 });
if (!pkgDef.requiresSecrets)
return NextResponse.json(
{ error: "Package does not require secrets" },
{ status: 400 }
);
const requiredKeys = (pkgDef.secrets || []).map((s) => s.key);
const missing = requiredKeys.filter((k) => !secrets[k]?.trim());
if (missing.length > 0) {
return NextResponse.json(
{ error: `Missing: ${missing.join(", ")}` },
{ status: 400 }
);
}
try {
const tenant = await getTenant(name);
if (!tenant)
return NextResponse.json({ error: "Not found" }, { status: 404 });
if (
!user.isPlatform &&
tenant.metadata.labels?.["pieced.ch/zitadel-org-id"] !== user.orgId
) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
// Use tenant-{name} to match the operator's vault path convention
await writePackageSecrets(`tenant-${name}`, packageId, secrets);
return NextResponse.json({ ok: true });
} catch (e: any) {
console.error("Secret write error:", e.message);
return NextResponse.json(
{ error: "Failed to store secrets" },
{ status: 500 }
);
}
}