const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const pool = require('../config/database'); const logger = require('../config/logger'); const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; const JWT_EXPIRES_IN = '24h'; const REFRESH_TOKEN_EXPIRES_DAYS = 7; class AuthController { // Login async login(req, res) { try { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username and password required' }); } // Find user const userResult = await pool.query( 'SELECT * FROM users WHERE username = $1 AND is_active = true', [username] ); if (userResult.rows.length === 0) { return res.status(401).json({ error: 'Invalid credentials' }); } const user = userResult.rows[0]; // Verify password const validPassword = await bcrypt.compare(password, user.password_hash); if (!validPassword) { return res.status(401).json({ error: 'Invalid credentials' }); } // Generate tokens const accessToken = jwt.sign( { id: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN } ); const refreshToken = jwt.sign( { id: user.id, type: 'refresh' }, JWT_SECRET, { expiresIn: `${REFRESH_TOKEN_EXPIRES_DAYS}d` } ); // Save refresh token const expiresAt = new Date(); expiresAt.setDate(expiresAt.getDate() + REFRESH_TOKEN_EXPIRES_DAYS); await pool.query( `INSERT INTO user_sessions (user_id, refresh_token, expires_at, ip_address, user_agent) VALUES ($1, $2, $3, $4, $5)`, [user.id, refreshToken, expiresAt, req.ip, req.get('user-agent')] ); // Update last login await pool.query( 'UPDATE users SET last_login = NOW() WHERE id = $1', [user.id] ); // Remove password hash from response delete user.password_hash; logger.info(`User logged in: ${username}`); res.json({ user, accessToken, refreshToken }); } catch (error) { logger.error('Login error:', error); res.status(500).json({ error: 'Internal server error' }); } } // Refresh token async refresh(req, res) { try { const { refreshToken } = req.body; if (!refreshToken) { return res.status(400).json({ error: 'Refresh token required' }); } // Verify refresh token let decoded; try { decoded = jwt.verify(refreshToken, JWT_SECRET); } catch (error) { return res.status(401).json({ error: 'Invalid refresh token' }); } // Check if session exists and not expired const sessionResult = await pool.query( `SELECT s.*, u.username, u.role, u.is_active FROM user_sessions s JOIN users u ON s.user_id = u.id WHERE s.refresh_token = $1 AND s.expires_at > NOW()`, [refreshToken] ); if (sessionResult.rows.length === 0) { return res.status(401).json({ error: 'Session expired or invalid' }); } const session = sessionResult.rows[0]; if (!session.is_active) { return res.status(401).json({ error: 'User is inactive' }); } // Generate new access token const accessToken = jwt.sign( { id: session.user_id, username: session.username, role: session.role }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN } ); res.json({ accessToken }); } catch (error) { logger.error('Token refresh error:', error); res.status(500).json({ error: 'Internal server error' }); } } // Logout async logout(req, res) { try { const { refreshToken } = req.body; if (refreshToken) { await pool.query( 'DELETE FROM user_sessions WHERE refresh_token = $1', [refreshToken] ); } res.json({ message: 'Logged out successfully' }); } catch (error) { logger.error('Logout error:', error); res.status(500).json({ error: 'Internal server error' }); } } // Get current user async me(req, res) { try { const userResult = await pool.query( 'SELECT id, username, full_name, email, role, avatar_url, last_login, created_at FROM users WHERE id = $1', [req.user.id] ); if (userResult.rows.length === 0) { return res.status(404).json({ error: 'User not found' }); } res.json(userResult.rows[0]); } catch (error) { logger.error('Get user error:', error); res.status(500).json({ error: 'Internal server error' }); } } // Change password async changePassword(req, res) { try { const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) { return res.status(400).json({ error: 'Current and new password required' }); } if (newPassword.length < 6) { return res.status(400).json({ error: 'Password must be at least 6 characters' }); } // Get user const userResult = await pool.query( 'SELECT password_hash FROM users WHERE id = $1', [req.user.id] ); if (userResult.rows.length === 0) { return res.status(404).json({ error: 'User not found' }); } // Verify current password const validPassword = await bcrypt.compare( currentPassword, userResult.rows[0].password_hash ); if (!validPassword) { return res.status(401).json({ error: 'Current password is incorrect' }); } // Hash new password const newPasswordHash = await bcrypt.hash(newPassword, 10); // Update password await pool.query( 'UPDATE users SET password_hash = $1 WHERE id = $2', [newPasswordHash, req.user.id] ); // Invalidate all sessions except current const { refreshToken } = req.body; if (refreshToken) { await pool.query( 'DELETE FROM user_sessions WHERE user_id = $1 AND refresh_token != $2', [req.user.id, refreshToken] ); } logger.info(`Password changed for user: ${req.user.username}`); res.json({ message: 'Password changed successfully' }); } catch (error) { logger.error('Change password error:', error); res.status(500).json({ error: 'Internal server error' }); } } } module.exports = new AuthController();