/** * 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:

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