diff --git a/apps/web/lib/auth/partner.ts b/apps/web/lib/auth/partner.ts index 51a82a2962..bd27e8b36f 100644 --- a/apps/web/lib/auth/partner.ts +++ b/apps/web/lib/auth/partner.ts @@ -24,10 +24,7 @@ interface WithPartnerProfileHandler { }): Promise; } -export const withPartnerProfile = ( - handler: WithPartnerProfileHandler, - {}: {} = {}, -) => { +export const withPartnerProfile = (handler: WithPartnerProfileHandler) => { return withAxiom( async ( req: AxiomRequest, diff --git a/apps/web/lib/auth/program.ts b/apps/web/lib/auth/program.ts new file mode 100644 index 0000000000..a2218c3f46 --- /dev/null +++ b/apps/web/lib/auth/program.ts @@ -0,0 +1,98 @@ +import { DubApiError } from "@/lib/api/errors"; +import { ProgramProps, WorkspaceWithUsers } from "@/lib/types"; +import { getProgramOrThrow } from "../api/programs/get-program-or-throw"; +import { PermissionAction } from "../api/rbac/permissions"; +import { parseRequestBody } from "../api/utils"; +import { Session } from "./utils"; +import { withWorkspace } from "./workspace"; + +export interface WithProgramHandler { + (args: { + req: Request; + body: any; + params: Record; + searchParams: Record; + headers?: Record; + session: Session; + permissions: PermissionAction[]; + workspace: WorkspaceWithUsers; + program: ProgramProps; + }): Promise; +} + +interface WithProgramOptions { + requiredPermissions?: PermissionAction[]; + includeDiscounts?: boolean; +} + +/** + * withProgram wraps a handler with both workspace-level and program-level + * access control. + * + * It first uses withWorkspace to perform all the workspace and API key checks. + * Then it retrieves the program (using getProgramOrThrow) based on the program + * identifier (which should be supplied in `params.programId` or `searchParams.programId`). + * If the program does not exist or the user does not have access, an error will be thrown. + */ +export const withProgram = ( + handler: WithProgramHandler, + options?: WithProgramOptions, +) => { + return withWorkspace( + async ({ + req, + params, + searchParams, + headers, + session, + workspace, + permissions, + }) => { + const body = req.body ? await parseRequestBody(req) : undefined; + + const programId = + params.programId || searchParams.programId || body?.programId; + + if (!programId) { + throw new DubApiError({ + code: "bad_request", + message: + "Program ID not found. Did you forget to include a `programId` query parameter?", + }); + } + + const program = await getProgramOrThrow( + { + programId, + workspaceId: workspace.id, + }, + { + ...(options?.includeDiscounts && { includeDiscounts: true }), + }, + ); + + return await handler({ + req, + body, + params, + searchParams, + headers, + session, + permissions, + workspace, + program, + }); + }, + + { + ...options, + requiredPlan: [ + "business", + "business extra", + "business max", + "business plus", + "enterprise", + ], + }, + ); +};