/**
* Password Reset routes: /forgot-password, /verify-reset-token, /reset-password
*/
import { sendEmailWithFallback } from '../../../utils/email.js';
import {
respondSuccess, respondError,
JWT_SECRET,
dbGet, dbRun, authLogger, jwt
} from './authShared.js';
/**
* @param {import('express').Router} router
*/
export default function registerPasswordResetRoutes(router) {
// POST /api/v3/auth/forgot-password - Request password reset
router.post('/forgot-password', async (req, res) => {
try {
const { email } = req.body;
if (!email) {
return respondError(res, 400, 'EMAIL_REQUIRED', 'Email is required');
}
authLogger.info(' Password reset request for:', email);
const user = await dbGet('SELECT id, email, name FROM users WHERE email = ?', [email]);
if (!user) {
// Security: don't reveal if user exists
authLogger.info(' User not found:', email);
return respondSuccess(res, {
message: 'If the email exists, a reset link will be sent'
});
}
// Generate password reset token
const resetToken = jwt.sign(
{ id: user.id, email: user.email, purpose: 'password-reset' },
JWT_SECRET,
{ expiresIn: '1h' }
);
const resetLink = `${process.env.APP_URL || 'https://crm.hltrn.cc'}/reset-password?token=${resetToken}`;
authLogger.info(' Reset link generated for:', user.email);
// Send email via sendEmailWithFallback
try {
const emailHtml = `
Password Reset Request
Hello ${user.name},
You requested to reset your password for GOD CRM.
Click the button below to reset your password:
Or copy this link to your browser:
${resetLink}
This link will expire in 1 hour.
If you didn't request this, please ignore this email.
`;
const result = await sendEmailWithFallback(
user.email,
'Password Reset - GOD CRM',
emailHtml
);
if (result.success) {
authLogger.info(' Reset email sent to:', user.email);
} else {
authLogger.error({ err: result.error }, 'Email send error:', result.error);
}
} catch (emailError) {
authLogger.error({ err: emailError }, 'Email send error:', emailError.message);
// Continue even if email fails (for dev mode)
}
return respondSuccess(res, {
message: 'Password reset instructions have been sent to your email',
// In dev mode, return token for testing
...(process.env.NODE_ENV === 'development' && { resetToken, resetLink })
});
} catch (error) {
authLogger.error({ err: error }, 'Forgot password error:', error);
return respondError(res, 500, 'FORGOT_PASSWORD_FAILED', 'Failed to process password reset request');
}
});
// GET /api/v3/auth/verify-reset-token/:token - Verify reset token is valid
router.get('/verify-reset-token/:token', async (req, res) => {
try {
const { token } = req.params;
if (!token) {
return respondError(res, 400, 'TOKEN_REQUIRED', 'Reset token is required');
}
// Verify token
let decoded;
try {
decoded = jwt.verify(token, JWT_SECRET);
if (decoded.purpose !== 'password-reset') {
return respondError(res, 400, 'INVALID_TOKEN', 'Invalid reset token');
}
} catch (err) {
return respondError(res, 400, 'INVALID_TOKEN', 'Reset token is invalid or expired');
}
// Get user info
const user = await dbGet('SELECT email FROM users WHERE id = ?', [decoded.id]);
if (!user) {
return respondError(res, 400, 'USER_NOT_FOUND', 'User not found');
}
return respondSuccess(res, {
valid: true,
email: user.email
});
} catch (error) {
authLogger.error({ err: error }, 'Verify reset token error:', error);
return respondError(res, 500, 'VERIFY_TOKEN_FAILED', 'Failed to verify reset token');
}
});
// POST /api/v3/auth/reset-password - Reset password with token
router.post('/reset-password', async (req, res) => {
try {
const { token, password } = req.body;
if (!token || !password) {
return respondError(res, 400, 'VALIDATION_ERROR', 'Token and new password are required');
}
if (password.length < 8) {
return respondError(res, 400, 'WEAK_PASSWORD', 'Password must be at least 8 characters long');
}
authLogger.info(' Password reset attempt with token');
// Verify token
let decoded;
try {
decoded = jwt.verify(token, JWT_SECRET);
if (decoded.purpose !== 'password-reset') {
return respondError(res, 400, 'INVALID_TOKEN', 'Invalid reset token');
}
} catch (err) {
return respondError(res, 400, 'INVALID_TOKEN', 'Reset token is invalid or expired');
}
// Update password
const bcrypt = await import('bcrypt');
const passwordHash = await bcrypt.default.hash(password, 10);
await dbRun(
'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[passwordHash, decoded.id]
);
authLogger.info(' Password reset successful for user:', decoded.email);
return respondSuccess(res, {
message: 'Password has been reset successfully'
});
} catch (error) {
authLogger.error({ err: error }, 'Reset password error:', error);
return respondError(res, 500, 'RESET_PASSWORD_FAILED', 'Failed to reset password');
}
});
}