diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts index 592c58fc3..99c60807e 100755 --- a/server/src/controllers/controllerUtils.ts +++ b/server/src/controllers/controllerUtils.ts @@ -112,6 +112,13 @@ const requireUserEmail = (userEmail?: string): string => { return userEmail; }; +export const requireUserRoles = (userRoles?: string[]): string[] => { + if (!userRoles || userRoles.length === 0) { + throw new AppError({ message: "User roles are required", status: 400 }); + } + return userRoles; +}; + export { fetchMonitorCertificate, requireString, diff --git a/server/src/controllers/inviteController.ts b/server/src/controllers/inviteController.ts index 664d84d98..e6d5d7ccb 100755 --- a/server/src/controllers/inviteController.ts +++ b/server/src/controllers/inviteController.ts @@ -1,5 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/joi.js"; +import { requireTeamId, requireUserRoles } from "@/controllers/controllerUtils.js"; const SERVICE_NAME = "inviteController"; class InviteController { @@ -14,12 +15,14 @@ class InviteController { } getInviteToken = async (req: Request, res: Response, next: NextFunction) => { + console.log(req.body); try { + const teamId = requireTeamId(req.user?.teamId); + const userRoles = requireUserRoles(req.user?.role); const invite = req.body; - const teamId = req?.user?.teamId; invite.teamId = teamId; await inviteBodyValidation.validateAsync(invite); - const inviteToken = await this.inviteService.getInviteToken({ invite, teamId }); + const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles }); return res.status(200).json({ success: true, msg: "Invite token generated successfully", @@ -32,13 +35,17 @@ class InviteController { sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => { try { + const teamId = requireTeamId(req?.user?.teamId); + const userRoles = requireUserRoles(req?.user?.role); + const inviteRequest = req.body; - inviteRequest.teamId = req?.user?.teamId; + inviteRequest.teamId = teamId; await inviteBodyValidation.validateAsync(inviteRequest); const inviteToken = await this.inviteService.sendInviteEmail({ invite: inviteRequest, firstName: req?.user?.firstName, + userRoles, }); return res.status(200).json({ success: true, diff --git a/server/src/service/business/inviteService.ts b/server/src/service/business/inviteService.ts index eee0f993c..e4069d485 100644 --- a/server/src/service/business/inviteService.ts +++ b/server/src/service/business/inviteService.ts @@ -1,4 +1,5 @@ -import type { Invite } from "@/types/index.js"; +import type { Invite, UserRole } from "@/types/index.js"; +import { canManageRole } from "@/types/user.js"; import type { IInvitesRepository } from "@/repositories/index.js"; import { AppError } from "@/utils/AppError.js"; @@ -29,13 +30,42 @@ class InviteService { return InviteService.SERVICE_NAME; } - getInviteToken = async ({ invite, teamId }: { invite: Partial; teamId: string }) => { + getInviteToken = async ({ invite, teamId, userRoles }: { invite: Partial; teamId: string; userRoles: UserRole[] }) => { invite.teamId = teamId; + + const inviteRoles = invite.role ?? []; + + for (const targetRole of inviteRoles) { + const canManage = userRoles.some((actorRole) => canManageRole(actorRole, targetRole)); + if (!canManage) { + throw new AppError({ + message: "You do not have permission to create this invite", + service: SERVICE_NAME, + method: "getInviteToken", + status: 403, + }); + } + } + const inviteToken = await this.invitesRepository.create(invite); return inviteToken; }; - sendInviteEmail = async ({ invite, firstName }: { invite: Partial; firstName: any }) => { + sendInviteEmail = async ({ invite, firstName, userRoles }: { invite: Partial; firstName: any; userRoles: UserRole[] }) => { + const inviteRoles = invite.role ?? []; + + for (const targetRole of inviteRoles) { + const canManage = userRoles.some((actorRole) => canManageRole(actorRole, targetRole)); + if (!canManage) { + throw new AppError({ + message: "You do not have permission to create this invite", + service: SERVICE_NAME, + method: "getInviteToken", + status: 403, + }); + } + } + const inviteToken = await this.invitesRepository.create(invite); const { clientHost } = this.settingsService.getSettings(); diff --git a/server/src/types/user.ts b/server/src/types/user.ts index 41c009791..2ee07d487 100644 --- a/server/src/types/user.ts +++ b/server/src/types/user.ts @@ -1,6 +1,17 @@ export const UserRoles = ["user", "admin", "superadmin", "demo"] as const; export type UserRole = (typeof UserRoles)[number]; +export const RoleHierarchy: Record = { + demo: 0, + user: 1, + admin: 2, + superadmin: 3, +}; + +export const canManageRole = (actorRole: UserRole, targetRole: UserRole): boolean => { + return RoleHierarchy[actorRole] > RoleHierarchy[targetRole]; +}; + export interface UserProfileImage { data?: Buffer; contentType?: string;