Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions apps/web/app/(ee)/api/partner-profile/invites/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -161,6 +155,9 @@ export const POST = withPartnerProfile(

return NextResponse.json({ message: "Invite(s) sent" });
},
{
requiredPermission: "user_invites.create",
},
);

const updateInviteRoleSchema = z.object({
Expand All @@ -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: {
Expand Down Expand Up @@ -210,6 +202,9 @@ export const PATCH = withPartnerProfile(

return NextResponse.json(response);
},
{
requiredPermission: "user_invites.update",
},
);

const removeInviteSchema = z.object({
Expand All @@ -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: {
Expand All @@ -245,4 +235,7 @@ export const DELETE = withPartnerProfile(

return NextResponse.json({ email });
},
{
requiredPermission: "user_invites.delete",
},
);
10 changes: 4 additions & 6 deletions apps/web/app/(ee)/api/partner-profile/users/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
Expand All @@ -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([
Expand Down Expand Up @@ -132,6 +127,9 @@ export const PATCH = withPartnerProfile(

return NextResponse.json(response);
},
{
requiredPermission: "users.update",
},
);

const removeUserSchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/auth/partner-user-permissions.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
17 changes: 16 additions & 1 deletion apps/web/lib/auth/partner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -26,7 +27,14 @@ interface WithPartnerProfileHandler {
}): Promise<Response>;
}

export const withPartnerProfile = (handler: WithPartnerProfileHandler) => {
interface WithPartnerProfileOptions {
requiredPermission?: Permission;
}

export const withPartnerProfile = (
handler: WithPartnerProfileHandler,
{ requiredPermission }: WithPartnerProfileOptions = {},
) => {
return withAxiom(
async (
req: AxiomRequest,
Expand Down Expand Up @@ -79,6 +87,13 @@ export const withPartnerProfile = (handler: WithPartnerProfileHandler) => {
});
}

if (requiredPermission) {
throwIfNoPermission({
role: partnerUser.role,
permission: requiredPermission,
});
}

const {
industryInterests,
preferredEarningStructures,
Expand Down