backend: implement user role update functionality

This commit is contained in:
juancarmore 2026-01-22 11:38:40 +01:00
parent 7d7f66edf3
commit de0674d82c
6 changed files with 95 additions and 2 deletions

View File

@ -1,4 +1,4 @@
import { MeetUserFilters, MeetUserOptions } from '@openvidu-meet/typings';
import { MeetUserFilters, MeetUserOptions, MeetUserRole } from '@openvidu-meet/typings';
import { Request, Response } from 'express';
import { container } from '../config/dependency-injector.config.js';
import { INTERNAL_CONFIG } from '../config/internal-config.js';
@ -150,6 +150,26 @@ export const resetUserPassword = async (req: Request, res: Response) => {
}
};
export const updateUserRole = async (req: Request, res: Response) => {
const { userId } = req.params;
const { role } = req.body as { role: MeetUserRole };
const logger = container.get(LoggerService);
logger.verbose(`Admin updating role for user '${userId}' to '${role}'`);
try {
const userService = container.get(UserService);
const user = await userService.changeUserRole(userId, role);
return res.status(200).json({
message: `Role for user '${userId}' updated successfully to '${role}'`,
user: userService.convertToDTO(user)
});
} catch (error) {
handleError(res, error, 'updating user role');
}
};
export const changePassword = async (req: Request, res: Response) => {
const requestSessionService = container.get(RequestSessionService);
const user = requestSessionService.getAuthenticatedUser();

View File

@ -4,6 +4,7 @@ import {
BulkDeleteUsersReqSchema,
ChangePasswordReqSchema,
ResetUserPasswordReqSchema,
UpdateUserRoleReqSchema,
UserFiltersSchema,
UserOptionsSchema
} from '../../models/zod-schemas/user.schema.js';
@ -65,3 +66,14 @@ export const validateResetUserPasswordReq = (req: Request, res: Response, next:
req.body = data;
next();
};
export const validateUpdateUserRoleReq = (req: Request, res: Response, next: NextFunction) => {
const { success, error, data } = UpdateUserRoleReqSchema.safeParse(req.body);
if (!success) {
return rejectUnprocessableRequest(res, error);
}
req.body = data;
next();
};

View File

@ -249,6 +249,22 @@ export const errorCannotDeleteOwnAccount = (): OpenViduMeetError => {
);
};
export const errorCannotChangeRootAdminRole = (): OpenViduMeetError => {
return new OpenViduMeetError(
'User Error',
'Cannot change the role of the root admin user. This account must retain administrative privileges.',
403
);
};
export const errorCannotChangeOwnRole = (): OpenViduMeetError => {
return new OpenViduMeetError(
'User Error',
'Cannot change your own role. Please have another administrator change your role if needed.',
403
);
};
// Room errors
export const errorRoomNotFound = (roomId: string): OpenViduMeetError => {

View File

@ -58,3 +58,7 @@ export const ChangePasswordReqSchema = z.object({
export const ResetUserPasswordReqSchema = z.object({
newPassword: z.string().min(5, 'New password must be at least 5 characters long')
});
export const UpdateUserRoleReqSchema = z.object({
role: z.nativeEnum(MeetUserRole)
});

View File

@ -8,7 +8,8 @@ import {
validateChangePasswordReq,
validateCreateUserReq,
validateGetUsersReq,
validateResetUserPasswordReq
validateResetUserPasswordReq,
validateUpdateUserRoleReq
} from '../middlewares/request-validators/user-validator.middleware.js';
export const userRouter: Router = Router();
@ -49,4 +50,10 @@ userRouter.put(
validateResetUserPasswordReq,
userCtrl.resetUserPassword
);
userRouter.put(
'/:userId/role',
withAuth(tokenAndRoleValidator(MeetUserRole.ADMIN)),
validateUpdateUserRoleReq,
userCtrl.updateUserRole
);
userRouter.delete('/:userId', withAuth(tokenAndRoleValidator(MeetUserRole.ADMIN)), userCtrl.deleteUser);

View File

@ -3,6 +3,8 @@ import { inject, injectable } from 'inversify';
import { MEET_ENV } from '../environment.js';
import { PasswordHelper } from '../helpers/password.helper.js';
import {
errorCannotChangeOwnRole,
errorCannotChangeRootAdminRole,
errorCannotDeleteOwnAccount,
errorCannotDeleteRootAdmin,
errorCannotResetOwnPassword,
@ -151,6 +153,38 @@ export class UserService {
this.logger.info(`Password reset for user '${userId}' by admin. User must change password on next login.`);
}
/**
* Change user role by admin.
*
* @param userId - The ID of the user whose role will be changed
* @param newRole - The new role to assign to the user
*/
async changeUserRole(userId: string, newRole: MeetUserRole): Promise<MeetUser> {
// Prevent changing role of the root admin user
if (userId === MEET_ENV.INITIAL_ADMIN_USER) {
throw errorCannotChangeRootAdminRole();
}
// Prevent changing own role
const authenticatedUser = this.requestSessionService.getAuthenticatedUser();
if (authenticatedUser && authenticatedUser.userId === userId) {
throw errorCannotChangeOwnRole();
}
const user = await this.userRepository.findByUserId(userId);
if (!user) {
throw errorUserNotFound(userId);
}
user.role = newRole;
await this.userRepository.update(user);
this.logger.info(`Role for user '${userId}' changed to '${newRole}' by admin`);
return user;
}
async deleteUser(userId: string): Promise<void> {
// Prevent deleting the root admin user
if (userId === MEET_ENV.INITIAL_ADMIN_USER) {