const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); const authRepository = require('../repositories/authRepository'); const { sendEmail } = require('../utils/mailer'); /** * Auth Service - Business logic layer * Handles authentication business logic */ class AuthService { /** * Generate JWT tokens */ generateTokens(userId) { const accessToken = jwt.sign( { userId }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '1h' } ); const refreshToken = jwt.sign( { userId }, process.env.JWT_REFRESH_SECRET, { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d' } ); return { accessToken, refreshToken }; } /** * Verify JWT token */ verifyAccessToken(token) { return jwt.verify(token, process.env.JWT_SECRET); } /** * Verify refresh token */ verifyRefreshToken(token) { return jwt.verify(token, process.env.JWT_REFRESH_SECRET); } /** * Hash password */ async hashPassword(password) { return await bcrypt.hash(password, 10); } /** * Compare password */ async comparePassword(password, hashedPassword) { return await bcrypt.compare(password, hashedPassword); } /** * Format user response */ formatUserResponse(user) { return { id: user.id, name: user.full_name, email: user.email, phone: user.phone, role: user.role ? user.role.name : 'customer', createdAt: user.created_at, updatedAt: user.updated_at }; } /** * Register new user */ async register(data) { const { name, email, password, phone } = data; // Check if email exists const emailExists = await authRepository.isEmailExists(email); if (emailExists) { throw new Error('Email already registered'); } // Hash password const hashedPassword = await this.hashPassword(password); // Create user (default role_id = 3 for customer) const user = await authRepository.createUser({ full_name: name, email, password: hashedPassword, phone, role_id: 3 // Customer role }); // Generate tokens const { accessToken, refreshToken } = this.generateTokens(user.id); // Save refresh token (expires in 7 days) const expiresAt = new Date( Date.now() + 7 * 24 * 60 * 60 * 1000 ); await authRepository.saveRefreshToken( user.id, refreshToken, expiresAt ); // Remove password from response const userResponse = user.toJSON(); delete userResponse.password; // Send welcome email (non-blocking) try { await sendEmail({ to: user.email, subject: 'Welcome to Hotel Booking', html: `
Thank you for registering an account at Hotel Booking.
Your account has been successfully created with email: ${user.email}
You can:
If you have any questions, please contact us.
You (or someone) has requested to reset your password.
Click the link below to reset your password (expires in 1 hour):
` }); } catch (err) { console.error('Failed to send reset email:', err); // Do NOT log the raw reset token or URL in production. // Errors are logged above; token must remain secret. } return { success: true, message: 'Password reset link has been sent to your email' }; } /** * Reset Password - Update password with token */ async resetPassword(data) { const { token, password } = data; if (!token || !password) { throw new Error('Token and password are required'); } // Hash the token to compare const hashedToken = crypto .createHash('sha256') .update(token) .digest('hex'); // Find valid token const resetToken = await authRepository.findValidPasswordResetToken( hashedToken ); if (!resetToken) { throw new Error('Invalid or expired reset token'); } // Find user (include password so we can compare) const user = await authRepository.findUserById( resetToken.user_id, false, // includeRole true // includePassword ); if (!user) { throw new Error('User not found'); } // Check if new password matches old password const isSamePassword = await this.comparePassword( password, user.password ); if (isSamePassword) { // Return error message to the client throw new Error('New password must be different from the old password'); } // Hash new password const hashedPassword = await this.hashPassword(password); // Update password await authRepository.updateUserPassword( user.id, hashedPassword ); // Delete used token await authRepository.deletePasswordResetToken(resetToken.id); // Send confirmation email (non-blocking) try { await sendEmail({ to: user.email, subject: 'Password Changed', html: `The password for account ${user.email} has been changed successfully.
` }); } catch (err) { console.error('Failed to send confirmation email:', err); } return { success: true, message: 'Password has been reset successfully' }; } } module.exports = new AuthService();