diff --git a/meet-ce/backend/src/controllers/user.controller.ts b/meet-ce/backend/src/controllers/user.controller.ts index 70c87896..91e4e829 100644 --- a/meet-ce/backend/src/controllers/user.controller.ts +++ b/meet-ce/backend/src/controllers/user.controller.ts @@ -131,6 +131,25 @@ export const getMe = (_req: Request, res: Response) => { return res.status(200).json(userDTO); }; +export const resetUserPassword = async (req: Request, res: Response) => { + const { userId } = req.params; + const { newPassword } = req.body as { newPassword: string }; + + const logger = container.get(LoggerService); + logger.verbose(`Admin resetting password for user '${userId}'`); + + try { + const userService = container.get(UserService); + await userService.resetUserPassword(userId, newPassword); + + return res.status(200).json({ + message: `Password for user '${userId}' has been reset successfully. User must change password on next login.` + }); + } catch (error) { + handleError(res, error, 'resetting user password'); + } +}; + export const changePassword = async (req: Request, res: Response) => { const requestSessionService = container.get(RequestSessionService); const user = requestSessionService.getAuthenticatedUser(); diff --git a/meet-ce/backend/src/middlewares/request-validators/user-validator.middleware.ts b/meet-ce/backend/src/middlewares/request-validators/user-validator.middleware.ts index 343ba5e4..26b67354 100644 --- a/meet-ce/backend/src/middlewares/request-validators/user-validator.middleware.ts +++ b/meet-ce/backend/src/middlewares/request-validators/user-validator.middleware.ts @@ -3,6 +3,7 @@ import { rejectUnprocessableRequest } from '../../models/error.model.js'; import { BulkDeleteUsersReqSchema, ChangePasswordReqSchema, + ResetUserPasswordReqSchema, UserFiltersSchema, UserOptionsSchema } from '../../models/zod-schemas/user.schema.js'; @@ -53,3 +54,14 @@ export const validateChangePasswordReq = (req: Request, res: Response, next: Nex req.body = data; next(); }; + +export const validateResetUserPasswordReq = (req: Request, res: Response, next: NextFunction) => { + const { success, error, data } = ResetUserPasswordReqSchema.safeParse(req.body); + + if (!success) { + return rejectUnprocessableRequest(res, error); + } + + req.body = data; + next(); +}; diff --git a/meet-ce/backend/src/models/zod-schemas/user.schema.ts b/meet-ce/backend/src/models/zod-schemas/user.schema.ts index 06e0c958..27506d8f 100644 --- a/meet-ce/backend/src/models/zod-schemas/user.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/user.schema.ts @@ -54,3 +54,7 @@ export const ChangePasswordReqSchema = z.object({ currentPassword: z.string(), newPassword: z.string().min(5, 'New password must be at least 5 characters long') }); + +export const ResetUserPasswordReqSchema = z.object({ + newPassword: z.string().min(5, 'New password must be at least 5 characters long') +}); diff --git a/meet-ce/backend/src/routes/user.routes.ts b/meet-ce/backend/src/routes/user.routes.ts index 8024fe85..ff18fe3f 100644 --- a/meet-ce/backend/src/routes/user.routes.ts +++ b/meet-ce/backend/src/routes/user.routes.ts @@ -7,7 +7,8 @@ import { validateBulkDeleteUsersReq, validateChangePasswordReq, validateCreateUserReq, - validateGetUsersReq + validateGetUsersReq, + validateResetUserPasswordReq } from '../middlewares/request-validators/user-validator.middleware.js'; export const userRouter: Router = Router(); @@ -42,4 +43,10 @@ userRouter.post( ); userRouter.get('/:userId', withAuth(tokenAndRoleValidator(MeetUserRole.ADMIN, MeetUserRole.USER)), userCtrl.getUser); +userRouter.put( + '/:userId/password', + withAuth(tokenAndRoleValidator(MeetUserRole.ADMIN)), + validateResetUserPasswordReq, + userCtrl.resetUserPassword +); userRouter.delete('/:userId', withAuth(tokenAndRoleValidator(MeetUserRole.ADMIN)), userCtrl.deleteUser); diff --git a/meet-ce/backend/src/services/user.service.ts b/meet-ce/backend/src/services/user.service.ts index 27bd3e45..32f2bfec 100644 --- a/meet-ce/backend/src/services/user.service.ts +++ b/meet-ce/backend/src/services/user.service.ts @@ -108,6 +108,27 @@ export class UserService { await this.userRepository.update(user); } + /** + * Reset user password by admin. This is used when a user forgets their password. + * The mustChangePassword flag is set to true to force the user to change the password on next login. + * + * @param userId - The ID of the user whose password will be reset + * @param newPassword - The new temporary password set by admin + */ + async resetUserPassword(userId: string, newPassword: string): Promise { + const user = await this.userRepository.findByUserId(userId); + + if (!user) { + throw errorUserNotFound(userId); + } + + user.passwordHash = await PasswordHelper.hashPassword(newPassword); + user.mustChangePassword = true; // Force password change on next login + + await this.userRepository.update(user); + this.logger.info(`Password reset for user '${userId}' by admin. User must change password on next login.`); + } + async deleteUser(userId: string): Promise { const user = await this.userRepository.findByUserId(userId);