backend: add password reset functionality for admin users

This commit is contained in:
juancarmore 2026-01-12 17:03:40 +01:00
parent 1498332d28
commit 23154bd380
5 changed files with 64 additions and 1 deletions

View File

@ -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();

View File

@ -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();
};

View File

@ -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')
});

View File

@ -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);

View File

@ -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<void> {
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<void> {
const user = await this.userRepository.findByUserId(userId);