11const bcrypt = require ( 'bcryptjs' ) ;
22const jwt = require ( 'jsonwebtoken' ) ;
3+ const crypto = require ( 'crypto' ) ;
34const { v4 : uuidv4 } = require ( 'uuid' ) ;
45const sequelize = require ( '../config/database' ) ;
56const { QueryTypes } = require ( 'sequelize' ) ;
7+ const emailService = require ( '../services/emailService' ) ;
68
79const signup = async ( req , res ) => {
810 const { username, email, password, fullName } = req . body ;
@@ -159,7 +161,180 @@ const login = async (req, res) => {
159161 }
160162} ;
161163
164+ const forgotPassword = async ( req , res ) => {
165+ const { email } = req . body ;
166+
167+ try {
168+ console . log ( `[LOG forgot_password] ========= Password reset request for email: ${ email } ` ) ;
169+
170+ // Find user by email
171+ const [ user ] = await sequelize . query (
172+ 'SELECT * FROM users WHERE email = ?' ,
173+ {
174+ replacements : [ email ] ,
175+ type : QueryTypes . SELECT ,
176+ }
177+ ) ;
178+
179+ if ( ! user ) {
180+ // Don't reveal if email exists or not for security
181+ return res . json ( {
182+ success : true ,
183+ message : 'If an account with that email exists, we have sent a password reset link.'
184+ } ) ;
185+ }
186+
187+ // Generate reset token
188+ const resetToken = crypto . randomBytes ( 32 ) . toString ( 'hex' ) ;
189+ const resetTokenExpiry = new Date ( Date . now ( ) + 3600000 ) ; // 1 hour from now
190+
191+ // Save reset token to database
192+ await sequelize . query (
193+ 'UPDATE users SET reset_password_token = ?, reset_password_expires = ? WHERE id = ?' ,
194+ {
195+ replacements : [ resetToken , resetTokenExpiry , user . id ] ,
196+ type : QueryTypes . UPDATE
197+ }
198+ ) ;
199+
200+ // Send reset email
201+ try {
202+ await emailService . sendPasswordResetEmail ( email , resetToken , user . username || user . full_name || 'User' ) ;
203+ console . log ( `[LOG forgot_password] ========= Password reset email sent to ${ email } ` ) ;
204+ } catch ( emailError ) {
205+ console . error ( `[LOG forgot_password] ========= Failed to send email to ${ email } :` , emailError ) ;
206+ // Clear the reset token if email fails
207+ await sequelize . query (
208+ 'UPDATE users SET reset_password_token = NULL, reset_password_expires = NULL WHERE id = ?' ,
209+ {
210+ replacements : [ user . id ] ,
211+ type : QueryTypes . UPDATE
212+ }
213+ ) ;
214+ return res . status ( 500 ) . json ( {
215+ success : false ,
216+ message : 'Failed to send password reset email. Please try again later.'
217+ } ) ;
218+ }
219+
220+ res . json ( {
221+ success : true ,
222+ message : 'If an account with that email exists, we have sent a password reset link.'
223+ } ) ;
224+
225+ } catch ( error ) {
226+ console . error ( '[LOG forgot_password] ========= Error in forgot password:' , error ) ;
227+ res . status ( 500 ) . json ( {
228+ success : false ,
229+ message : 'An error occurred while processing your request. Please try again later.'
230+ } ) ;
231+ }
232+ } ;
233+
234+ const resetPassword = async ( req , res ) => {
235+ const { token, newPassword } = req . body ;
236+
237+ try {
238+ console . log ( `[LOG reset_password] ========= Password reset attempt with token: ${ token ?. substring ( 0 , 8 ) } ...` ) ;
239+
240+ // Find user by reset token and check if token is not expired
241+ const [ user ] = await sequelize . query (
242+ 'SELECT * FROM users WHERE reset_password_token = ? AND reset_password_expires > NOW()' ,
243+ {
244+ replacements : [ token ] ,
245+ type : QueryTypes . SELECT ,
246+ }
247+ ) ;
248+
249+ if ( ! user ) {
250+ return res . status ( 400 ) . json ( {
251+ success : false ,
252+ message : 'Invalid or expired reset token. Please request a new password reset.'
253+ } ) ;
254+ }
255+
256+ // Hash new password
257+ const salt = await bcrypt . genSalt ( 10 ) ;
258+ const hashedPassword = await bcrypt . hash ( newPassword , salt ) ;
259+
260+ // Update password and clear reset token
261+ await sequelize . query (
262+ 'UPDATE users SET password_hash = ?, reset_password_token = NULL, reset_password_expires = NULL WHERE id = ?' ,
263+ {
264+ replacements : [ hashedPassword , user . id ] ,
265+ type : QueryTypes . UPDATE
266+ }
267+ ) ;
268+
269+ // Send confirmation email
270+ try {
271+ await emailService . sendPasswordChangeConfirmation ( user . email , user . username || user . full_name || 'User' ) ;
272+ console . log ( `[LOG reset_password] ========= Password change confirmation sent to ${ user . email } ` ) ;
273+ } catch ( emailError ) {
274+ console . error ( `[LOG reset_password] ========= Failed to send confirmation email:` , emailError ) ;
275+ // Don't fail the request if confirmation email fails
276+ }
277+
278+ console . log ( `[LOG reset_password] ========= Password successfully reset for user ${ user . username } ` ) ;
279+
280+ res . json ( {
281+ success : true ,
282+ message : 'Password has been reset successfully. You can now log in with your new password.'
283+ } ) ;
284+
285+ } catch ( error ) {
286+ console . error ( '[LOG reset_password] ========= Error in reset password:' , error ) ;
287+ res . status ( 500 ) . json ( {
288+ success : false ,
289+ message : 'An error occurred while resetting your password. Please try again later.'
290+ } ) ;
291+ }
292+ } ;
293+
294+ const validateResetToken = async ( req , res ) => {
295+ const { token } = req . params ;
296+
297+ try {
298+ console . log ( `[LOG validate_token] ========= Validating reset token: ${ token ?. substring ( 0 , 8 ) } ...` ) ;
299+
300+ // Check if token exists and is not expired
301+ const [ user ] = await sequelize . query (
302+ 'SELECT id, email, username FROM users WHERE reset_password_token = ? AND reset_password_expires > NOW()' ,
303+ {
304+ replacements : [ token ] ,
305+ type : QueryTypes . SELECT ,
306+ }
307+ ) ;
308+
309+ if ( ! user ) {
310+ return res . status ( 400 ) . json ( {
311+ success : false ,
312+ message : 'Invalid or expired reset token.'
313+ } ) ;
314+ }
315+
316+ res . json ( {
317+ success : true ,
318+ message : 'Token is valid.' ,
319+ data : {
320+ email : user . email ,
321+ username : user . username
322+ }
323+ } ) ;
324+
325+ } catch ( error ) {
326+ console . error ( '[LOG validate_token] ========= Error validating token:' , error ) ;
327+ res . status ( 500 ) . json ( {
328+ success : false ,
329+ message : 'An error occurred while validating the token.'
330+ } ) ;
331+ }
332+ } ;
333+
162334module . exports = {
163335 signup,
164- login
336+ login,
337+ forgotPassword,
338+ resetPassword,
339+ validateResetToken
165340} ;
0 commit comments