src/lib/auth/server.ts
Linked repository file from this build.
import { cache } from "react";
import { currentUser } from "@clerk/nextjs/server";
import { cookies, headers } from "next/headers";
import { actorSchema } from "./actor";
import type { Actor } from "@/features/reviews/types";
import { demoRoster } from "./roster";
import { AccessError } from "./scope";
export class AccountSetupError extends AccessError {
constructor(public fields: string[]) {
super(
`Your account is not set up for QC-CRM. Missing or invalid Clerk public metadata: ${fields.join(", ")}. Contact the QC administrator who assigns Clerk public metadata.`,
);
}
}
/** The browser-test login needs a secret, a localhost request and a server outside Vercel. */
export function testLoginAllowed(
env: Record<string, string | undefined>,
host: string | null,
): boolean {
return (
Boolean(env.QC_E2E_SECRET) &&
!env.VERCEL &&
/^(localhost|127\.0\.0\.1)(:\d+)?$/.test(host ?? "")
);
}
export interface ServerSession {
actor: Actor;
userId: string | null;
}
// React cache deduplicates layout/session reads within one server render only.
// The test bypass still requires a server secret and the same secret on each request.
export const getServerSession = cache(async (request?: Request): Promise<ServerSession> => {
const requestHeaders = request?.headers ?? (await headers());
const cookieJar = await cookies();
if (
testLoginAllowed(process.env, requestHeaders.get("host")) &&
(requestHeaders.get("x-qc-test-secret") === process.env.QC_E2E_SECRET ||
cookieJar.get("qc-test-secret")?.value === process.env.QC_E2E_SECRET)
) {
const staffId =
requestHeaders.get("x-qc-test-staff") ?? cookieJar.get("qc-test-staff")?.value ?? "101";
const actor = demoRoster.find((entry) => entry.staffId === staffId);
if (!actor) throw new AccessError("Unknown test staff member.");
return { actor, userId: null };
}
const user = await currentUser();
if (!user) throw new AccessError("Sign in to continue.", 401);
const metadata = user.publicMetadata;
const parsed = actorSchema.safeParse({
name: user.fullName || user.id,
role: metadata.role,
staffId: metadata.staffId,
reportsToStaffId: metadata.reportsToStaffId ?? undefined,
subordinateStaffIds: metadata.subordinateStaffIds ?? [],
program: metadata.program,
preferences: user.unsafeMetadata,
});
if (!parsed.success || !parsed.data.staffId || !parsed.data.program)
throw new AccountSetupError([
...new Set([
...["role", "staffId", "program"].filter((field) => !metadata[field]),
...(!parsed.success ? parsed.error.issues.map((issue) => String(issue.path[0])) : []),
]),
]);
return { actor: parsed.data, userId: user.id };
});
export async function getActor(request?: Request): Promise<Actor> {
return (await (request ? getServerSession(request) : getServerSession())).actor;
}