const express = require('express'); const router = express.Router(); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const Guest = require('../models/Guest'); const { body, validationResult } = require('express-validator'); const auth = require('../middleware/auth'); const sendEmail = require('../utils/sendEmail'); const logger = require('../utils/logger'); const crypto = require('crypto'); // Generate JWT token const generateToken = (payload) => { return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }); }; // @route POST /api/auth/register // @desc Register a new guest // @access Public router.post('/register', [ body('firstName').notEmpty().withMessage('First name is required').trim(), body('lastName').notEmpty().withMessage('Last name is required').trim(), body('email').isEmail().withMessage('Valid email is required').normalizeEmail(), body('phone').notEmpty().withMessage('Phone number is required').trim(), body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'), body('confirmPassword').custom((value, { req }) => { if (value !== req.body.password) { throw new Error('Password confirmation does not match password'); } return value; }) ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Validation errors', errors: errors.array() }); } const { firstName, lastName, email, phone, password } = req.body; // Check if guest already exists let guest = await Guest.findOne({ email }); if (guest && guest.isRegistered) { return res.status(400).json({ success: false, message: 'Guest already registered with this email' }); } // Create or update guest if (guest) { // Update existing guest profile guest.firstName = firstName; guest.lastName = lastName; guest.phone = phone; guest.password = password; guest.isRegistered = true; guest.emailVerified = false; } else { // Create new guest guest = new Guest({ firstName, lastName, email, phone, password, isRegistered: true, emailVerified: false }); } await guest.save(); // Generate email verification token const verificationToken = crypto.randomBytes(32).toString('hex'); guest.emailVerificationToken = verificationToken; await guest.save(); // Send verification email try { await sendEmail({ to: email, subject: 'Verify Your Email - The Old Vine Hotel', html: `

The Old Vine Hotel

Email Verification

Dear ${firstName} ${lastName},

Thank you for registering with The Old Vine Hotel! Please verify your email address to complete your registration.

Verify Email Address

If the button doesn't work, copy and paste this link into your browser:

${process.env.CLIENT_URL}/verify-email?token=${verificationToken}

This verification link will expire in 24 hours.

If you didn't create an account with us, please ignore this email.

Best regards,
The Old Vine Hotel Team

© 2025 The Old Vine Hotel. All rights reserved.

` }); } catch (emailError) { logger.error('Failed to send verification email:', emailError); // Don't fail registration if email fails } // Generate JWT token const token = generateToken({ id: guest._id, email: guest.email, isAdmin: false }); logger.info('Guest registered successfully', { guestId: guest._id, email: guest.email }); res.status(201).json({ success: true, message: 'Registration successful. Please check your email to verify your account.', data: { token, guest: { id: guest._id, firstName: guest.firstName, lastName: guest.lastName, email: guest.email, phone: guest.phone, emailVerified: guest.emailVerified, loyaltyProgram: guest.loyaltyProgram } } }); } catch (error) { logger.error('Registration error:', error); res.status(500).json({ success: false, message: 'Server error during registration' }); } }); // @route POST /api/auth/login // @desc Login guest // @access Public router.post('/login', [ body('email').isEmail().withMessage('Valid email is required').normalizeEmail(), body('password').notEmpty().withMessage('Password is required') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Validation errors', errors: errors.array() }); } const { email, password } = req.body; // Find guest with password field const guest = await Guest.findOne({ email, isRegistered: true }).select('+password'); if (!guest) { return res.status(401).json({ success: false, message: 'Invalid email or password' }); } if (!guest.isActive) { return res.status(401).json({ success: false, message: 'Account is deactivated. Please contact support.' }); } // Check password const isMatch = await guest.comparePassword(password); if (!isMatch) { return res.status(401).json({ success: false, message: 'Invalid email or password' }); } // Update last login guest.lastLogin = new Date(); guest.lastActivity = new Date(); await guest.save(); // Generate JWT token const token = generateToken({ id: guest._id, email: guest.email, isAdmin: false }); logger.info('Guest login successful', { guestId: guest._id, email: guest.email }); res.json({ success: true, message: 'Login successful', data: { token, guest: { id: guest._id, firstName: guest.firstName, lastName: guest.lastName, email: guest.email, phone: guest.phone, emailVerified: guest.emailVerified, loyaltyProgram: guest.loyaltyProgram, preferences: guest.preferences, isVIP: guest.isVIP } } }); } catch (error) { logger.error('Login error:', error); res.status(500).json({ success: false, message: 'Server error during login' }); } }); // @route GET /api/auth/me // @desc Get current guest // @access Private router.get('/me', auth, async (req, res) => { try { // Update last activity req.guest.lastActivity = new Date(); await req.guest.save(); res.json({ success: true, data: { guest: { id: req.guest._id, firstName: req.guest.firstName, lastName: req.guest.lastName, email: req.guest.email, phone: req.guest.phone, emailVerified: req.guest.emailVerified, loyaltyProgram: req.guest.loyaltyProgram, preferences: req.guest.preferences, isVIP: req.guest.isVIP, totalStays: req.guest.totalStays, totalSpent: req.guest.totalSpent } } }); } catch (error) { logger.error('Get current guest error:', error); res.status(500).json({ success: false, message: 'Server error' }); } }); // @route POST /api/auth/forgot-password // @desc Send password reset email // @access Public router.post('/forgot-password', [ body('email').isEmail().withMessage('Valid email is required').normalizeEmail() ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Validation errors', errors: errors.array() }); } const { email } = req.body; const guest = await Guest.findOne({ email, isRegistered: true }); if (!guest) { // Don't reveal if email exists return res.json({ success: true, message: 'If an account with that email exists, we have sent a password reset link.' }); } // Generate reset token const resetToken = guest.createPasswordResetToken(); await guest.save(); // Send reset email try { await sendEmail({ to: email, subject: 'Password Reset - The Old Vine Hotel', html: `

The Old Vine Hotel

Password Reset

Dear ${guest.firstName} ${guest.lastName},

You have requested to reset your password. Click the button below to reset it:

Reset Password

If the button doesn't work, copy and paste this link into your browser:

${process.env.CLIENT_URL}/reset-password?token=${resetToken}

This reset link will expire in 10 minutes.

If you didn't request this password reset, please ignore this email.

Best regards,
The Old Vine Hotel Team

© 2025 The Old Vine Hotel. All rights reserved.

` }); } catch (emailError) { logger.error('Failed to send password reset email:', emailError); guest.passwordResetToken = undefined; guest.passwordResetExpires = undefined; await guest.save(); return res.status(500).json({ success: false, message: 'Error sending password reset email' }); } res.json({ success: true, message: 'If an account with that email exists, we have sent a password reset link.' }); } catch (error) { logger.error('Forgot password error:', error); res.status(500).json({ success: false, message: 'Server error' }); } }); // @route POST /api/auth/reset-password // @desc Reset password with token // @access Public router.post('/reset-password', [ body('token').notEmpty().withMessage('Reset token is required'), body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'), body('confirmPassword').custom((value, { req }) => { if (value !== req.body.password) { throw new Error('Password confirmation does not match password'); } return value; }) ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Validation errors', errors: errors.array() }); } const { token, password } = req.body; // Hash the token const hashedToken = crypto.createHash('sha256').update(token).digest('hex'); // Find guest by token and check if token is still valid const guest = await Guest.findOne({ passwordResetToken: hashedToken, passwordResetExpires: { $gt: Date.now() }, isRegistered: true }); if (!guest) { return res.status(400).json({ success: false, message: 'Token is invalid or has expired' }); } // Set new password guest.password = password; guest.passwordResetToken = undefined; guest.passwordResetExpires = undefined; await guest.save(); logger.info('Password reset successful', { guestId: guest._id, email: guest.email }); res.json({ success: true, message: 'Password reset successful. You can now log in with your new password.' }); } catch (error) { logger.error('Reset password error:', error); res.status(500).json({ success: false, message: 'Server error during password reset' }); } }); // @route POST /api/auth/verify-email // @desc Verify email with token // @access Public router.post('/verify-email', [ body('token').notEmpty().withMessage('Verification token is required') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Validation errors', errors: errors.array() }); } const { token } = req.body; const guest = await Guest.findOne({ emailVerificationToken: token, isRegistered: true }); if (!guest) { return res.status(400).json({ success: false, message: 'Invalid verification token' }); } // Verify email guest.emailVerified = true; guest.emailVerificationToken = undefined; await guest.save(); logger.info('Email verification successful', { guestId: guest._id, email: guest.email }); res.json({ success: true, message: 'Email verified successfully!' }); } catch (error) { logger.error('Email verification error:', error); res.status(500).json({ success: false, message: 'Server error during email verification' }); } }); module.exports = router;