CentralBackend/controller/password-reset/PasswordResetController.js
2025-09-08 16:05:37 +02:00

138 lines
5.4 KiB
JavaScript

const crypto = require('crypto');
const MailService = require('../../services/email/MailService');
const UnitOfWork = require('../../database/UnitOfWork');
const PersonalUserRepository = require('../../repositories/user/personal/PersonalUserRepository');
const CompanyUserRepository = require('../../repositories/user/company/CompanyUserRepository');
const PasswordResetRepository = require('../../repositories/passwordReset/PasswordResetRepository');
const User = require('../../models/User');
const { logger } = require('../../middleware/logger');
function generateToken() {
return crypto.randomBytes(32).toString('hex');
}
module.exports = {
async requestPasswordReset(req, res) {
// If rate limiter already sent a response, do not continue
if (res.headersSent) return;
const { email } = req.body;
if (!email || typeof email !== 'string') {
logger.warn('passwordReset:invalid_request', { email });
return res.status(400).json({ success: false, message: 'Invalid request.' });
}
// Always respond generically
let user = null, userType = null;
const uow = new UnitOfWork();
try {
await uow.start();
const personalRepo = new PersonalUserRepository(uow);
const companyRepo = new CompanyUserRepository(uow);
const passwordResetRepo = new PasswordResetRepository(uow);
user = await personalRepo.findByEmail(email);
userType = user ? 'personal' : null;
if (!user) {
user = await companyRepo.findByEmail(email);
userType = user ? 'company' : null;
}
if (user) {
logger.info('passwordReset:user_found', { userId: user.id, userType });
const existingToken = await passwordResetRepo.findValidTokenByUserId(user.id);
if (existingToken) {
await uow.rollback();
logger.info('passwordReset:existing_token', { userId: user.id });
return res.json({
success: true,
message: 'A password reset link has already been sent and is still valid. Please check your email.'
});
}
// Generate token and expiry
const token = generateToken();
const expiresAt = new Date(Date.now() + 25 * 60 * 1000);
// Store in password_resets
await passwordResetRepo.createToken(user.id, token, expiresAt);
logger.info('passwordReset:token_created', { userId: user.id, token });
// Send email
await MailService.sendPasswordResetEmail({
email,
firstName: userType === 'personal' ? user.firstName : undefined,
companyName: userType === 'company' ? user.companyName : undefined,
token,
lang: req.body.lang || 'en'
});
logger.info('passwordReset:email_sent', { email });
}
await uow.commit();
} catch (err) {
await uow.rollback();
logger.error('passwordReset:request_error', { error: err });
}
return res.json({
success: true,
message: 'If an account exists for this email, a password reset link has been sent.'
});
},
async verifyPasswordResetToken(req, res) {
const { token } = req.query;
if (!token || typeof token !== 'string') {
logger.warn('passwordReset:invalid_token', { token });
return res.status(400).json({ success: false, message: 'Invalid token.' });
}
const uow = new UnitOfWork();
try {
await uow.start();
const passwordResetRepo = new PasswordResetRepository(uow);
const row = await passwordResetRepo.findValidToken(token);
if (!row) {
await uow.rollback();
logger.warn('passwordReset:token_invalid_or_expired', { token });
return res.status(400).json({ success: false, message: 'Invalid or expired token.' });
}
await uow.commit();
logger.info('passwordReset:token_valid', { token });
return res.json({ success: true, message: 'Token is valid.' });
} catch (err) {
await uow.rollback();
logger.error('passwordReset:verify_error', { error: err });
return res.status(500).json({ success: false, message: 'Internal server error.' });
}
},
async resetPassword(req, res) {
const { token, newPassword } = req.body;
if (!token || typeof token !== 'string' || !newPassword || typeof newPassword !== 'string' || newPassword.length < 8) {
logger.warn('passwordReset:invalid_reset_request', { token });
return res.status(400).json({ success: false, message: 'Invalid request.' });
}
const uow = new UnitOfWork();
try {
await uow.start();
const passwordResetRepo = new PasswordResetRepository(uow);
const row = await passwordResetRepo.findValidToken(token);
if (!row) {
await uow.rollback();
logger.warn('passwordReset:token_invalid_or_expired', { token });
return res.status(400).json({ success: false, message: 'Invalid or expired token.' });
}
// Update password
const hashed = await User.hashPassword(newPassword);
await passwordResetRepo.updateUserPassword(row.user_id, hashed);
await passwordResetRepo.markTokenUsed(row.id);
await uow.commit();
logger.info('passwordReset:password_reset', { userId: row.user_id });
return res.json({ success: true, message: 'Password has been reset.' });
} catch (err) {
await uow.rollback();
logger.error('passwordReset:reset_error', { error: err });
return res.status(500).json({ success: false, message: 'Internal server error.' });
}
}
};