diff --git a/apps/web/app/(ee)/api/partner-profile/invites/route.ts b/apps/web/app/(ee)/api/partner-profile/invites/route.ts index 28bac0f33e..dbcbcea9fb 100644 --- a/apps/web/app/(ee)/api/partner-profile/invites/route.ts +++ b/apps/web/app/(ee)/api/partner-profile/invites/route.ts @@ -2,7 +2,6 @@ import { DubApiError } from "@/lib/api/errors"; import { invitePartnerUser } from "@/lib/api/partners/invite-partner-user"; import { parseRequestBody } from "@/lib/api/utils"; import { withPartnerProfile } from "@/lib/auth/partner"; -import { throwIfNoPermission } from "@/lib/auth/partner-user-permissions"; import { MAX_INVITES_PER_REQUEST, MAX_PARTNER_USERS, @@ -45,16 +44,11 @@ export const GET = withPartnerProfile(async ({ partner, searchParams }) => { // POST /api/partner-profile/invites - invite team members export const POST = withPartnerProfile( - async ({ partner, req, session, partnerUser }) => { + async ({ partner, req, session }) => { const invites = z .array(invitePartnerUserSchema) .parse(await parseRequestBody(req)); - throwIfNoPermission({ - role: partnerUser.role, - permission: "user_invites.create", - }); - if (invites.length > MAX_INVITES_PER_REQUEST) { throw new DubApiError({ code: "bad_request", @@ -161,6 +155,9 @@ export const POST = withPartnerProfile( return NextResponse.json({ message: "Invite(s) sent" }); }, + { + requiredPermission: "user_invites.create", + }, ); const updateInviteRoleSchema = z.object({ @@ -170,16 +167,11 @@ const updateInviteRoleSchema = z.object({ // PATCH /api/partner-profile/invites - update an invite's role export const PATCH = withPartnerProfile( - async ({ req, partner, partnerUser }) => { + async ({ req, partner }) => { const { email, role } = updateInviteRoleSchema.parse( await parseRequestBody(req), ); - throwIfNoPermission({ - role: partnerUser.role, - permission: "user_invites.update", - }); - const invite = await prisma.partnerInvite.findUnique({ where: { email_partnerId: { @@ -210,6 +202,9 @@ export const PATCH = withPartnerProfile( return NextResponse.json(response); }, + { + requiredPermission: "user_invites.update", + }, ); const removeInviteSchema = z.object({ @@ -218,14 +213,9 @@ const removeInviteSchema = z.object({ // DELETE /api/partner-profile/invites?email={email} - remove an invite export const DELETE = withPartnerProfile( - async ({ searchParams, partner, partnerUser }) => { + async ({ searchParams, partner }) => { const { email } = removeInviteSchema.parse(searchParams); - throwIfNoPermission({ - role: partnerUser.role, - permission: "user_invites.delete", - }); - await prisma.$transaction([ prisma.partnerInvite.delete({ where: { @@ -245,4 +235,7 @@ export const DELETE = withPartnerProfile( return NextResponse.json({ email }); }, + { + requiredPermission: "user_invites.delete", + }, ); diff --git a/apps/web/app/(ee)/api/partner-profile/users/route.ts b/apps/web/app/(ee)/api/partner-profile/users/route.ts index 4177f6a12a..80d6453517 100644 --- a/apps/web/app/(ee)/api/partner-profile/users/route.ts +++ b/apps/web/app/(ee)/api/partner-profile/users/route.ts @@ -61,7 +61,7 @@ const updateRoleSchema = z.object({ // PATCH /api/partner-profile/users - update a user's role export const PATCH = withPartnerProfile( - async ({ req, partner, partnerUser, session }) => { + async ({ req, partner, session }) => { const { userId, role } = updateRoleSchema.parse( await parseRequestBody(req), ); @@ -73,11 +73,6 @@ export const PATCH = withPartnerProfile( }); } - throwIfNoPermission({ - role: partnerUser.role, - permission: "users.update", - }); - // Wrap read and mutation in a transaction to prevent TOCTOU race conditions const response = await prisma.$transaction(async (tx) => { const [partnerUserFound, totalOwners] = await Promise.all([ @@ -132,6 +127,9 @@ export const PATCH = withPartnerProfile( return NextResponse.json(response); }, + { + requiredPermission: "users.update", + }, ); const removeUserSchema = z.object({ diff --git a/apps/web/lib/auth/partner-user-permissions.ts b/apps/web/lib/auth/partner-user-permissions.ts index d4e25d02c7..f6254c6b9d 100644 --- a/apps/web/lib/auth/partner-user-permissions.ts +++ b/apps/web/lib/auth/partner-user-permissions.ts @@ -1,7 +1,7 @@ import type { PartnerRole } from "@prisma/client"; import { DubApiError } from "../api/errors"; -type Permission = (typeof PERMISSIONS)[number]; +export type Permission = (typeof PERMISSIONS)[number]; const PERMISSIONS = [ "users.update", diff --git a/apps/web/lib/auth/partner.ts b/apps/web/lib/auth/partner.ts index 8c59a3b8bf..5fd5388148 100644 --- a/apps/web/lib/auth/partner.ts +++ b/apps/web/lib/auth/partner.ts @@ -4,6 +4,7 @@ import { prisma } from "@dub/prisma"; import { getSearchParams } from "@dub/utils"; import { PartnerUser } from "@prisma/client"; import { AxiomRequest, withAxiom } from "next-axiom"; +import { Permission, throwIfNoPermission } from "./partner-user-permissions"; import { Session, getSession } from "./utils"; interface WithPartnerProfileHandler { @@ -26,7 +27,14 @@ interface WithPartnerProfileHandler { }): Promise; } -export const withPartnerProfile = (handler: WithPartnerProfileHandler) => { +interface WithPartnerProfileOptions { + requiredPermission?: Permission; +} + +export const withPartnerProfile = ( + handler: WithPartnerProfileHandler, + { requiredPermission }: WithPartnerProfileOptions = {}, +) => { return withAxiom( async ( req: AxiomRequest, @@ -79,6 +87,13 @@ export const withPartnerProfile = (handler: WithPartnerProfileHandler) => { }); } + if (requiredPermission) { + throwIfNoPermission({ + role: partnerUser.role, + permission: requiredPermission, + }); + } + const { industryInterests, preferredEarningStructures,