* wip * update layoutHOC * wip * remove app router related code no longer used * remove more * await cookies, headers, params, serachparams * update yarn.lock * await cookies, headers, params, serachparams more * update yarn lock again * downgrade next-auth to 4.22.1 * update yarn lock * fix * update yarn lock * fix type checks * update yarn lock * await headers and cookies * restore pages folder * restore yarn.lock * update yarn.lock * await headers and cookies * remove * await params in API routes * updates * restore next.config.js * remove i18n from next.config.js * Fixed tests * Fixed types * Removed duplicate favicon.ico * Fixing more types * ImageResponse moved to next/og * Fixed prisma import issues * dynamic import for @ewsjs/xhr * remove deasync dep * dynamic import for @tryvital/vital-node * fix type checks * add back turbopack command * Type fix * Removed unneeded file * fix turbopack relative path errors * add comments * remove unneeded code * Fixed build errors * await apis * use Promise<Params> type in defaultResponderForAppDir util * refactor scim api route * fix type checks * separate app-routing.config into client-config and server-config * wip * refactor routing forms components * revert unneeded changes for easier review * fix * fix * use CustomTrans * fix type * fix unit tests * fix type error * fix build error * fix build error * fix build error * fix warnings * fix warnings * upgrade @tremor/react and tailwindcss * numCols -> numItems * fix forgot-password e2e test * fix 1 more e2e test * fix login e2e test * fix e2e tests * fix e2e tests * clean up * remove error * use tremor/react 2.11.0 * fix * rename CustomTrans to ServerTrans * address comment * fix test * fix ServerTrans * fix * fix type * fix ServerTrans usages 1 * fix translations * fix type checks * fix type checks * link styling * fix --------- Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
import type { DirectorySyncEvent, DirectorySyncRequest } from "@boxyhq/saml-jackson";
|
|
import type { Params } from "app/_types";
|
|
import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
|
|
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
|
|
import handleGroupEvents from "@calcom/features/ee/dsync/lib/handleGroupEvents";
|
|
import handleUserEvents from "@calcom/features/ee/dsync/lib/handleUserEvents";
|
|
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
|
import { DIRECTORY_IDS_TO_LOG } from "@calcom/lib/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["[scim]"] });
|
|
|
|
// Extract the auth token from the request headers
|
|
const extractAuthToken = (req: NextRequest): string | null => {
|
|
const authHeader = req.headers.get("authorization") || null;
|
|
return authHeader ? authHeader.split(" ")[1] : null;
|
|
};
|
|
|
|
// Handle the SCIM events
|
|
const handleEvents = async (event: DirectorySyncEvent) => {
|
|
log.debug("handleEvents", safeStringify(event));
|
|
const dSyncData = await prisma.dSyncData.findFirst({
|
|
where: {
|
|
directoryId: event.directory_id,
|
|
},
|
|
select: {
|
|
id: true,
|
|
organizationId: true,
|
|
},
|
|
});
|
|
|
|
if (!dSyncData) {
|
|
throw new Error("Directory sync data not found");
|
|
}
|
|
|
|
const { organizationId } = dSyncData;
|
|
|
|
if (!organizationId) {
|
|
throw new Error(`Org ID not found for dsync ${dSyncData.id}`);
|
|
}
|
|
|
|
if (event.event.includes("group")) {
|
|
handleGroupEvents(event, organizationId);
|
|
}
|
|
|
|
if (event.event === "user.created" || event.event === "user.updated") {
|
|
await handleUserEvents(event, organizationId);
|
|
}
|
|
};
|
|
|
|
const querySchema = z.object({
|
|
directory: z.string().array(),
|
|
});
|
|
|
|
// This is the handler for the SCIM API requests
|
|
async function getHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
|
|
return handleScimRequest(request, "GET", params);
|
|
}
|
|
|
|
async function postHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
|
|
return handleScimRequest(request, "POST", params);
|
|
}
|
|
|
|
async function putHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
|
|
return handleScimRequest(request, "PUT", params);
|
|
}
|
|
|
|
async function patchHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
|
|
return handleScimRequest(request, "PATCH", params);
|
|
}
|
|
|
|
async function deleteHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
|
|
return handleScimRequest(request, "DELETE", params);
|
|
}
|
|
|
|
async function handleScimRequest(request: NextRequest, method: string, params: Promise<Params>) {
|
|
const parsed = querySchema.safeParse(await params);
|
|
if (!parsed.success || parsed.data.directory.length === 0) {
|
|
return NextResponse.json({ error: "Missing directory parameters" }, { status: 400 });
|
|
}
|
|
|
|
const { dsyncController } = await jackson();
|
|
|
|
const [directoryId, path, resourceId] = parsed.data.directory;
|
|
const shouldLog = DIRECTORY_IDS_TO_LOG.includes(directoryId);
|
|
|
|
const searchParams = request.nextUrl.searchParams;
|
|
|
|
if (shouldLog) {
|
|
console.log(
|
|
"SCIM API request",
|
|
safeStringify({
|
|
method,
|
|
url: request.url,
|
|
params: parsed.data.directory,
|
|
searchParams: Object.fromEntries(searchParams.entries()),
|
|
body: request.body
|
|
? await request
|
|
.clone()
|
|
.json()
|
|
.catch(() => ({}))
|
|
: undefined,
|
|
})
|
|
);
|
|
}
|
|
|
|
let body: object | undefined = undefined;
|
|
try {
|
|
body = await request.json().catch(() => undefined);
|
|
} catch (e) {
|
|
log.error(`Error parsing SCIM event for directoryId ${directoryId} with error: ${e}`);
|
|
}
|
|
|
|
// Handle the SCIM API requests
|
|
const scimRequest: DirectorySyncRequest = {
|
|
method,
|
|
directoryId,
|
|
resourceId,
|
|
apiSecret: extractAuthToken(request),
|
|
resourceType: path === "Users" ? "users" : "groups",
|
|
body,
|
|
query: {
|
|
count: searchParams.get("count") ? parseInt(searchParams.get("count") as string) : undefined,
|
|
startIndex: searchParams.get("startIndex")
|
|
? parseInt(searchParams.get("startIndex") as string)
|
|
: undefined,
|
|
filter: searchParams.get("filter") as string,
|
|
},
|
|
};
|
|
|
|
const { status, data } = await dsyncController.requests.handle(scimRequest, handleEvents);
|
|
|
|
if (shouldLog) {
|
|
console.log(
|
|
"Response to SCIM",
|
|
safeStringify({
|
|
status,
|
|
data,
|
|
})
|
|
);
|
|
}
|
|
|
|
return NextResponse.json(data, { status });
|
|
}
|
|
export const GET = defaultResponderForAppDir(getHandler);
|
|
export const POST = defaultResponderForAppDir(postHandler);
|
|
export const PATCH = defaultResponderForAppDir(patchHandler);
|
|
export const DELETE = defaultResponderForAppDir(deleteHandler);
|
|
export const PUT = defaultResponderForAppDir(putHandler);
|