import { Request, Response } from 'express'; import { AuthenticatedRequest } from '../middleware/auth'; import { UserModel } from '../models/UserModel'; import { generateAuthTokens, verifyRefreshToken, hashPassword, comparePassword, validatePassword } from '../utils/auth'; import { sessionService } from '../services/sessionService'; import logger from '../utils/logger'; export interface RegisterRequest extends Request { body: { email: string; name: string; password: string; }; } export interface LoginRequest extends Request { body: { email: string; password: string; }; } export interface RefreshTokenRequest extends Request { body: { refreshToken: string; }; } /** * Register a new user */ export async function register(req: RegisterRequest, res: Response): Promise { try { const { email, name, password } = req.body; // Validate input if (!email || !name || !password) { res.status(400).json({ success: false, message: 'Email, name, and password are required' }); return; } // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { res.status(400).json({ success: false, message: 'Invalid email format' }); return; } // Validate password strength const passwordValidation = validatePassword(password); if (!passwordValidation.isValid) { res.status(400).json({ success: false, message: 'Password does not meet requirements', errors: passwordValidation.errors }); return; } // Check if user already exists const existingUser = await UserModel.findByEmail(email); if (existingUser) { res.status(409).json({ success: false, message: 'User with this email already exists' }); return; } // Hash password const hashedPassword = await hashPassword(password); // Create user const user = await UserModel.create({ email, name, password: hashedPassword, role: 'user' }); // Generate tokens const tokens = generateAuthTokens({ userId: user.id, email: user.email, role: user.role }); // Store session await sessionService.storeSession(user.id, { userId: user.id, email: user.email, role: user.role, refreshToken: tokens.refreshToken }); logger.info(`New user registered: ${email}`); res.status(201).json({ success: true, message: 'User registered successfully', data: { user: { id: user.id, email: user.email, name: user.name, role: user.role }, tokens: { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, expiresIn: tokens.expiresIn } } }); } catch (error) { logger.error('Registration error:', error); res.status(500).json({ success: false, message: 'Internal server error during registration' }); } } /** * Login user */ export async function login(req: LoginRequest, res: Response): Promise { try { const { email, password } = req.body; // Validate input if (!email || !password) { res.status(400).json({ success: false, message: 'Email and password are required' }); return; } // Find user by email const user = await UserModel.findByEmail(email); if (!user) { res.status(401).json({ success: false, message: 'Invalid email or password' }); return; } // Check if user is active if (!user.is_active) { res.status(401).json({ success: false, message: 'Account is deactivated' }); return; } // Verify password const isPasswordValid = await comparePassword(password, user.password_hash); if (!isPasswordValid) { res.status(401).json({ success: false, message: 'Invalid email or password' }); return; } // Generate tokens const tokens = generateAuthTokens({ userId: user.id, email: user.email, role: user.role }); // Store session await sessionService.storeSession(user.id, { userId: user.id, email: user.email, role: user.role, refreshToken: tokens.refreshToken }); // Update last login await UserModel.updateLastLogin(user.id); logger.info(`User logged in: ${email}`); res.status(200).json({ success: true, message: 'Login successful', data: { user: { id: user.id, email: user.email, name: user.name, role: user.role }, tokens: { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, expiresIn: tokens.expiresIn } } }); } catch (error) { logger.error('Login error:', error); res.status(500).json({ success: false, message: 'Internal server error during login' }); } } /** * Logout user */ export async function logout(req: AuthenticatedRequest, res: Response): Promise { try { if (!req.user) { res.status(401).json({ success: false, message: 'Authentication required' }); return; } // Get the token from header for blacklisting const authHeader = req.headers.authorization; if (authHeader) { const token = authHeader.split(' ')[1]; if (token) { // Blacklist the access token await sessionService.blacklistToken(token, 3600); // 1 hour } } // Remove session await sessionService.removeSession(req.user.userId); logger.info(`User logged out: ${req.user.email}`); res.status(200).json({ success: true, message: 'Logout successful' }); } catch (error) { logger.error('Logout error:', error); res.status(500).json({ success: false, message: 'Internal server error during logout' }); } } /** * Refresh access token */ export async function refreshToken(req: RefreshTokenRequest, res: Response): Promise { try { const { refreshToken } = req.body; if (!refreshToken) { res.status(400).json({ success: false, message: 'Refresh token is required' }); return; } // Verify refresh token const decoded = verifyRefreshToken(refreshToken); // Check if user exists and is active const user = await UserModel.findById(decoded.userId); if (!user || !user.is_active) { res.status(401).json({ success: false, message: 'Invalid refresh token' }); return; } // Check if session exists and matches const session = await sessionService.getSession(decoded.userId); if (!session || session.refreshToken !== refreshToken) { res.status(401).json({ success: false, message: 'Invalid refresh token' }); return; } // Generate new tokens const tokens = generateAuthTokens({ userId: user.id, email: user.email, role: user.role }); // Update session with new refresh token await sessionService.storeSession(user.id, { userId: user.id, email: user.email, role: user.role, refreshToken: tokens.refreshToken }); // Blacklist old refresh token await sessionService.blacklistToken(refreshToken, 86400); // 24 hours logger.info(`Token refreshed for user: ${user.email}`); res.status(200).json({ success: true, message: 'Token refreshed successfully', data: { tokens: { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, expiresIn: tokens.expiresIn } } }); } catch (error) { logger.error('Token refresh error:', error); res.status(401).json({ success: false, message: 'Invalid refresh token' }); } } /** * Get current user profile */ export async function getProfile(req: AuthenticatedRequest, res: Response): Promise { try { if (!req.user) { res.status(401).json({ success: false, message: 'Authentication required' }); return; } const user = await UserModel.findById(req.user.userId); if (!user) { res.status(404).json({ success: false, message: 'User not found' }); return; } res.status(200).json({ success: true, data: { user: { id: user.id, email: user.email, name: user.name, role: user.role, created_at: user.created_at, last_login: user.last_login } } }); } catch (error) { logger.error('Get profile error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } /** * Update user profile */ export async function updateProfile(req: AuthenticatedRequest, res: Response): Promise { try { if (!req.user) { res.status(401).json({ success: false, message: 'Authentication required' }); return; } const { name, email } = req.body; // Validate input if (email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { res.status(400).json({ success: false, message: 'Invalid email format' }); return; } // Check if email is already taken by another user const existingUser = await UserModel.findByEmail(email); if (existingUser && existingUser.id !== req.user.userId) { res.status(409).json({ success: false, message: 'Email is already taken' }); return; } } // Update user const updatedUser = await UserModel.update(req.user.userId, { name: name || undefined, email: email || undefined }); if (!updatedUser) { res.status(404).json({ success: false, message: 'User not found' }); return; } logger.info(`Profile updated for user: ${req.user.email}`); res.status(200).json({ success: true, message: 'Profile updated successfully', data: { user: { id: updatedUser.id, email: updatedUser.email, name: updatedUser.name, role: updatedUser.role, created_at: updatedUser.created_at, last_login: updatedUser.last_login } } }); } catch (error) { logger.error('Update profile error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } }