mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-10-30 14:47:36 +00:00
hanzo-build-linux-amd64 is the only ARC runner pool installed on hanzo-k8s today (arm64 pool is paused until DigitalOcean ships arm64 droplets, per ~/work/hanzo/.github/RUNNERS.md). The arm64 job was waiting on spark-hanzoai-arm64 — not provisioned in this cluster — so every build sat in queue forever and the amd64 leg got cancelled. Set platforms: linux/amd64 on the reusable build call to skip arm64 entirely. Re-add arm64 if/when DO ships arm64 droplets and the spark runner pool comes back online.
187 lines
5.3 KiB
TypeScript
187 lines
5.3 KiB
TypeScript
import { jackson, jacksonProduct } from "@/lib/jackson";
|
|
import prisma from "@/lib/prisma";
|
|
import { CustomUser } from "@/lib/types";
|
|
import { getServerSession } from "next-auth/next";
|
|
import { NextResponse } from "next/server";
|
|
|
|
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
|
|
|
const SSO_ELIGIBLE_PLANS = ["datarooms-premium", "datarooms-premium+old"];
|
|
|
|
function isJacksonUnavailableError(error: unknown): boolean {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return (
|
|
message.includes("error connecting to engine") ||
|
|
message.includes("Missing Jackson DB URL") ||
|
|
message.includes("ENOENT: no such file or directory, open 'system'")
|
|
);
|
|
}
|
|
|
|
async function getAuthenticatedAdmin(teamId: string) {
|
|
const session = await getServerSession(authOptions);
|
|
if (!session) return null;
|
|
|
|
const userId = (session.user as CustomUser).id;
|
|
|
|
const teamAccess = await prisma.userTeam.findUnique({
|
|
where: { userId_teamId: { userId, teamId } },
|
|
select: { role: true },
|
|
});
|
|
|
|
if (!teamAccess || teamAccess.role !== "ADMIN") return null;
|
|
|
|
const team = await prisma.team.findUnique({
|
|
where: { id: teamId },
|
|
select: { id: true, plan: true, ssoEnabled: true },
|
|
});
|
|
|
|
if (!team) return null;
|
|
|
|
return { userId, team };
|
|
}
|
|
|
|
// GET /api/teams/:teamId/directory-sync — list SCIM directories
|
|
export async function GET(
|
|
req: Request,
|
|
{ params }: { params: Promise<{ teamId: string }> },
|
|
) {
|
|
const { teamId } = await params;
|
|
const auth = await getAuthenticatedAdmin(teamId);
|
|
if (!auth) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
try {
|
|
if (!auth.team.ssoEnabled || !SSO_ELIGIBLE_PLANS.includes(auth.team.plan)) {
|
|
return NextResponse.json({ directories: [] });
|
|
}
|
|
|
|
const { directorySyncController } = await jackson();
|
|
|
|
const { data, error } =
|
|
await directorySyncController.directories.getByTenantAndProduct(
|
|
teamId,
|
|
jacksonProduct,
|
|
);
|
|
|
|
if (error) {
|
|
return NextResponse.json({ error: error.message }, { status: 400 });
|
|
}
|
|
|
|
return NextResponse.json({ directories: data });
|
|
} catch (error: any) {
|
|
if (isJacksonUnavailableError(error)) {
|
|
console.warn("[SCIM] Jackson unavailable, returning empty directories", error);
|
|
return NextResponse.json({ directories: [] });
|
|
}
|
|
|
|
console.error("[SCIM] Get directories error:", error);
|
|
return NextResponse.json(
|
|
{ error: error.message || "Internal server error" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|
|
|
|
// POST /api/teams/:teamId/directory-sync — create a SCIM directory connection
|
|
export async function POST(
|
|
req: Request,
|
|
{ params }: { params: Promise<{ teamId: string }> },
|
|
) {
|
|
const { teamId } = await params;
|
|
const auth = await getAuthenticatedAdmin(teamId);
|
|
if (!auth) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
// Plan gate
|
|
if (!SSO_ELIGIBLE_PLANS.includes(auth.team.plan)) {
|
|
return NextResponse.json(
|
|
{ error: "SCIM Directory Sync requires a Datarooms Premium plan" },
|
|
{ status: 403 },
|
|
);
|
|
}
|
|
|
|
// Feature flag gate
|
|
if (!auth.team.ssoEnabled) {
|
|
return NextResponse.json(
|
|
{ error: "SSO is not enabled for this team" },
|
|
{ status: 403 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
const { directorySyncController } = await jackson();
|
|
const body = await req.json();
|
|
const { name, type, currentDirectoryId } = body;
|
|
|
|
// Create the new directory first; only delete the old one on success
|
|
const result = await directorySyncController.directories.create({
|
|
tenant: teamId,
|
|
product: jacksonProduct,
|
|
name: name || "Hanzo Dataroom SCIM Directory",
|
|
type: type || "azure-scim-v2",
|
|
});
|
|
|
|
if (result.error) {
|
|
return NextResponse.json(
|
|
{ error: result.error.message },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// If replacing an existing directory, delete the old one after successful create
|
|
if (currentDirectoryId) {
|
|
await directorySyncController.directories.delete(currentDirectoryId);
|
|
}
|
|
|
|
return NextResponse.json(result, { status: 201 });
|
|
} catch (error: any) {
|
|
console.error("[SCIM] Create directory error:", error);
|
|
return NextResponse.json(
|
|
{ error: error.message || "Internal server error" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|
|
|
|
// DELETE /api/teams/:teamId/directory-sync — delete a SCIM directory
|
|
export async function DELETE(
|
|
req: Request,
|
|
{ params }: { params: Promise<{ teamId: string }> },
|
|
) {
|
|
const { teamId } = await params;
|
|
const auth = await getAuthenticatedAdmin(teamId);
|
|
if (!auth) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
try {
|
|
const { directorySyncController } = await jackson();
|
|
const body = await req.json();
|
|
const { directoryId } = body;
|
|
|
|
if (!directoryId) {
|
|
return NextResponse.json(
|
|
{ error: "directoryId is required" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const { error } =
|
|
await directorySyncController.directories.delete(directoryId);
|
|
|
|
if (error) {
|
|
return NextResponse.json({ error: error.message }, { status: 400 });
|
|
}
|
|
|
|
return NextResponse.json({ ok: true });
|
|
} catch (error: any) {
|
|
console.error("[SCIM] Delete directory error:", error);
|
|
return NextResponse.json(
|
|
{ error: error.message || "Internal server error" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|