From 7d7f66edf3f997c744c7291ecdae728bb402be7f Mon Sep 17 00:00:00 2001 From: juancarmore Date: Thu, 22 Jan 2026 10:55:03 +0100 Subject: [PATCH] backend: prevent admins to delete their own account or root admin user account --- meet-ce/backend/src/models/error.model.ts | 32 +++++++++ meet-ce/backend/src/services/user.service.ts | 69 ++++++++++++++++++-- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/meet-ce/backend/src/models/error.model.ts b/meet-ce/backend/src/models/error.model.ts index 7c823a13..77f49dde 100644 --- a/meet-ce/backend/src/models/error.model.ts +++ b/meet-ce/backend/src/models/error.model.ts @@ -217,6 +217,38 @@ export const errorUserAlreadyExists = (userId: string): OpenViduMeetError => { return new OpenViduMeetError('User Error', `User '${userId}' already exists`, 409); }; +export const errorCannotResetRootAdminPassword = (): OpenViduMeetError => { + return new OpenViduMeetError( + 'User Error', + 'Cannot reset password for the root admin user. The root admin must change their own password.', + 403 + ); +}; + +export const errorCannotResetOwnPassword = (): OpenViduMeetError => { + return new OpenViduMeetError( + 'User Error', + 'Cannot reset your own password. Please use the change-password endpoint to change your password.', + 403 + ); +}; + +export const errorCannotDeleteRootAdmin = (): OpenViduMeetError => { + return new OpenViduMeetError( + 'User Error', + 'Cannot delete the root admin user. This account is required for system administration.', + 403 + ); +}; + +export const errorCannotDeleteOwnAccount = (): OpenViduMeetError => { + return new OpenViduMeetError( + 'User Error', + 'Cannot delete your own account. Please have another administrator delete your account if needed.', + 403 + ); +}; + // Room errors export const errorRoomNotFound = (roomId: string): OpenViduMeetError => { diff --git a/meet-ce/backend/src/services/user.service.ts b/meet-ce/backend/src/services/user.service.ts index 32f2bfec..1ff77bd4 100644 --- a/meet-ce/backend/src/services/user.service.ts +++ b/meet-ce/backend/src/services/user.service.ts @@ -2,11 +2,20 @@ import { MeetUser, MeetUserDTO, MeetUserFilters, MeetUserOptions, MeetUserRole } import { inject, injectable } from 'inversify'; import { MEET_ENV } from '../environment.js'; import { PasswordHelper } from '../helpers/password.helper.js'; -import { errorInvalidPassword, errorUserAlreadyExists, errorUserNotFound } from '../models/error.model.js'; +import { + errorCannotDeleteOwnAccount, + errorCannotDeleteRootAdmin, + errorCannotResetOwnPassword, + errorCannotResetRootAdminPassword, + errorInvalidPassword, + errorUserAlreadyExists, + errorUserNotFound +} from '../models/error.model.js'; import { RoomMemberRepository } from '../repositories/room-member.repository.js'; import { RoomRepository } from '../repositories/room.repository.js'; import { UserRepository } from '../repositories/user.repository.js'; import { LoggerService } from './logger.service.js'; +import { RequestSessionService } from './request-session.service.js'; @injectable() export class UserService { @@ -14,7 +23,8 @@ export class UserService { @inject(LoggerService) protected logger: LoggerService, @inject(UserRepository) protected userRepository: UserRepository, @inject(RoomRepository) protected roomRepository: RoomRepository, - @inject(RoomMemberRepository) protected roomMemberRepository: RoomMemberRepository + @inject(RoomMemberRepository) protected roomMemberRepository: RoomMemberRepository, + @inject(RequestSessionService) protected requestSessionService: RequestSessionService ) {} /** @@ -116,6 +126,18 @@ export class UserService { * @param newPassword - The new temporary password set by admin */ async resetUserPassword(userId: string, newPassword: string): Promise { + // Prevent resetting own password (use change-password endpoint instead) + const authenticatedUser = this.requestSessionService.getAuthenticatedUser(); + + if (authenticatedUser && authenticatedUser.userId === userId) { + throw errorCannotResetOwnPassword(); + } + + // Prevent resetting password for the root admin user + if (userId === MEET_ENV.INITIAL_ADMIN_USER) { + throw errorCannotResetRootAdminPassword(); + } + const user = await this.userRepository.findByUserId(userId); if (!user) { @@ -130,6 +152,18 @@ export class UserService { } async deleteUser(userId: string): Promise { + // Prevent deleting the root admin user + if (userId === MEET_ENV.INITIAL_ADMIN_USER) { + throw errorCannotDeleteRootAdmin(); + } + + // Prevent self-deletion + const authenticatedUser = this.requestSessionService.getAuthenticatedUser(); + + if (authenticatedUser && authenticatedUser.userId === userId) { + throw errorCannotDeleteOwnAccount(); + } + const user = await this.userRepository.findByUserId(userId); if (!user) { @@ -147,12 +181,35 @@ export class UserService { async bulkDeleteUsers( userIds: string[] ): Promise<{ deleted: string[]; failed: { userId: string; error: string }[] }> { - const usersToDelete = await this.userRepository.findByUserIds(userIds); + const rootAdminUserId = MEET_ENV.INITIAL_ADMIN_USER; + const authenticatedUser = this.requestSessionService.getAuthenticatedUser(); + + // Filter out the root admin user and authenticated user from the deletion list + const failed: { userId: string; error: string }[] = []; + let filteredUserIds = [...userIds]; + + if (userIds.includes(rootAdminUserId)) { + failed.push({ userId: rootAdminUserId, error: 'Cannot delete the root admin user' }); + filteredUserIds = filteredUserIds.filter((id) => id !== rootAdminUserId); + } + + if ( + authenticatedUser && + authenticatedUser?.userId !== rootAdminUserId && + userIds.includes(authenticatedUser.userId) + ) { + failed.push({ userId: authenticatedUser.userId, error: 'Cannot delete your own account' }); + filteredUserIds = filteredUserIds.filter((id) => id !== authenticatedUser.userId); + } + + const usersToDelete = await this.userRepository.findByUserIds(filteredUserIds); const foundUserIds = usersToDelete.map((u) => u.userId); - const failed = userIds - .filter((id) => !foundUserIds.includes(id)) - .map((id) => ({ userId: id, error: 'User not found' })); + failed.push( + ...filteredUserIds + .filter((id) => !foundUserIds.includes(id)) + .map((id) => ({ userId: id, error: 'User not found' })) + ); if (foundUserIds.length > 0) { // Clean up resources for all users in batches