diff --git a/src/app/api/teams/[teamId]/users/[userId]/route.ts b/src/app/api/teams/[teamId]/users/[userId]/route.ts index d09af9da6..32d820215 100644 --- a/src/app/api/teams/[teamId]/users/[userId]/route.ts +++ b/src/app/api/teams/[teamId]/users/[userId]/route.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TEAM_ROLE_RANK } from '@/lib/constants'; import { parseRequest } from '@/lib/request'; import { badRequest, json, ok, unauthorized } from '@/lib/response'; import { teamRoleParam } from '@/lib/schema'; @@ -52,6 +53,17 @@ export async function POST( return badRequest({ message: 'The User does not exists on this team.' }); } + // Server-side rank check: actor must outrank target to modify their role. + if (!auth.user.isAdmin) { + const actorTeamUser = await getTeamUser(teamId, auth.user.id); + const actorRank = TEAM_ROLE_RANK[actorTeamUser?.role] ?? -1; + const targetRank = TEAM_ROLE_RANK[teamUser.role] ?? -1; + + if (actorRank <= targetRank) { + return unauthorized({ message: 'You do not have permission to modify this user.' }); + } + } + const user = await updateTeamUser(teamUser.id, body); return json(user); @@ -79,6 +91,17 @@ export async function DELETE( return badRequest({ message: 'The User does not exists on this team.' }); } + // Server-side rank check: actor must outrank target to remove them. + if (!auth.user.isAdmin && userId !== auth.user.id) { + const actorTeamUser = await getTeamUser(teamId, auth.user.id); + const actorRank = TEAM_ROLE_RANK[actorTeamUser?.role] ?? -1; + const targetRank = TEAM_ROLE_RANK[teamUser.role] ?? -1; + + if (actorRank <= targetRank) { + return unauthorized({ message: 'You do not have permission to remove this user.' }); + } + } + await deleteTeamUser(teamId, userId); return ok(); diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 6a85c082f..5b0ce277c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -180,6 +180,13 @@ export const ROLES = { teamViewOnly: 'team-view-only', } as const; +export const TEAM_ROLE_RANK: Record = { + [ROLES.teamViewOnly]: 0, + [ROLES.teamMember]: 1, + [ROLES.teamManager]: 2, + [ROLES.teamOwner]: 3, +} as const; + export const PERMISSIONS = { all: 'all', websiteCreate: 'website:create',