410 lines
10 KiB
JavaScript
410 lines
10 KiB
JavaScript
const express = require('express');
|
|
const { body, validationResult } = require('express-validator');
|
|
const { protect, generateToken, hashPassword, comparePassword, authRateLimit, logAuthAttempt } = require('../middleware/auth');
|
|
const database = require('../config/database');
|
|
const redis = require('../config/redis');
|
|
const logger = require('../utils/logger');
|
|
|
|
const router = express.Router();
|
|
|
|
// Register new user
|
|
router.post('/register', [
|
|
body('username').isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
|
|
body('email').isEmail().withMessage('Must be a valid email'),
|
|
body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'),
|
|
body('role').optional().isIn(['admin', 'operator', 'viewer']).withMessage('Invalid role')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
errors: errors.array()
|
|
});
|
|
}
|
|
|
|
const { username, email, password, role = 'viewer' } = req.body;
|
|
|
|
// Check if user already exists
|
|
const existingUsers = await database.query(
|
|
'SELECT id FROM users WHERE username = ? OR email = ?',
|
|
[username, email]
|
|
);
|
|
|
|
if (existingUsers.length > 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Username or email already exists'
|
|
});
|
|
}
|
|
|
|
// Hash password
|
|
const passwordHash = await hashPassword(password);
|
|
|
|
// Create user
|
|
const result = await database.query(
|
|
'INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)',
|
|
[username, email, passwordHash, role]
|
|
);
|
|
|
|
const userId = result.insertId;
|
|
|
|
// Generate token
|
|
const token = generateToken(userId);
|
|
|
|
// Store session in Redis
|
|
await redis.cacheUserSession(userId, {
|
|
userId,
|
|
username,
|
|
email,
|
|
role,
|
|
loginTime: new Date().toISOString()
|
|
});
|
|
|
|
logger.logSecurity('user_registered', { username, email, role }, 'info');
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'User registered successfully',
|
|
data: {
|
|
user: {
|
|
id: userId,
|
|
username,
|
|
email,
|
|
role
|
|
},
|
|
token
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Registration error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Registration failed'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Login user
|
|
router.post('/login', [
|
|
body('username').notEmpty().withMessage('Username is required'),
|
|
body('password').notEmpty().withMessage('Password is required')
|
|
], authRateLimit, logAuthAttempt, async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
errors: errors.array()
|
|
});
|
|
}
|
|
|
|
const { username, password } = req.body;
|
|
|
|
// Find user
|
|
const users = await database.query(
|
|
'SELECT id, username, email, password_hash, role, status FROM users WHERE username = ? OR email = ?',
|
|
[username, username]
|
|
);
|
|
|
|
if (users.length === 0) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid credentials'
|
|
});
|
|
}
|
|
|
|
const user = users[0];
|
|
|
|
// Check if user is active
|
|
if (user.status !== 'active') {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Account is not active'
|
|
});
|
|
}
|
|
|
|
// Verify password
|
|
const isValidPassword = await comparePassword(password, user.password_hash);
|
|
if (!isValidPassword) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid credentials'
|
|
});
|
|
}
|
|
|
|
// Generate token
|
|
const token = generateToken(user.id);
|
|
|
|
// Store session in Redis
|
|
await redis.cacheUserSession(user.id, {
|
|
userId: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
role: user.role,
|
|
loginTime: new Date().toISOString()
|
|
});
|
|
|
|
// Update last login
|
|
await database.query(
|
|
'UPDATE users SET last_login = NOW() WHERE id = ?',
|
|
[user.id]
|
|
);
|
|
|
|
logger.logSecurity('user_login', { username: user.username, userId: user.id }, 'info');
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Login successful',
|
|
data: {
|
|
user: {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
role: user.role
|
|
},
|
|
token
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Login error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Login failed'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get current user profile
|
|
router.get('/profile', protect, async (req, res) => {
|
|
try {
|
|
const user = await database.query(
|
|
'SELECT id, username, email, role, status, created_at, last_login FROM users WHERE id = ?',
|
|
[req.user.id]
|
|
);
|
|
|
|
if (user.length === 0) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'User not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: user[0]
|
|
});
|
|
} catch (error) {
|
|
logger.error('Get profile error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to get profile'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Update user profile
|
|
router.put('/profile', protect, [
|
|
body('email').optional().isEmail().withMessage('Must be a valid email'),
|
|
body('currentPassword').optional().notEmpty().withMessage('Current password is required for changes')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
errors: errors.array()
|
|
});
|
|
}
|
|
|
|
const { email, currentPassword, newPassword } = req.body;
|
|
const updates = {};
|
|
const params = [];
|
|
|
|
// Check if user wants to change password
|
|
if (newPassword) {
|
|
if (!currentPassword) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Current password is required to change password'
|
|
});
|
|
}
|
|
|
|
// Verify current password
|
|
const user = await database.query(
|
|
'SELECT password_hash FROM users WHERE id = ?',
|
|
[req.user.id]
|
|
);
|
|
|
|
const isValidPassword = await comparePassword(currentPassword, user[0].password_hash);
|
|
if (!isValidPassword) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Current password is incorrect'
|
|
});
|
|
}
|
|
|
|
// Hash new password
|
|
const newPasswordHash = await hashPassword(newPassword);
|
|
updates.password_hash = newPasswordHash;
|
|
params.push(newPasswordHash);
|
|
}
|
|
|
|
// Update email if provided
|
|
if (email) {
|
|
// Check if email is already taken
|
|
const existingUser = await database.query(
|
|
'SELECT id FROM users WHERE email = ? AND id != ?',
|
|
[email, req.user.id]
|
|
);
|
|
|
|
if (existingUser.length > 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Email is already taken'
|
|
});
|
|
}
|
|
|
|
updates.email = email;
|
|
params.push(email);
|
|
}
|
|
|
|
if (Object.keys(updates).length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No updates provided'
|
|
});
|
|
}
|
|
|
|
// Build update query
|
|
const updateFields = Object.keys(updates).map(key => `${key} = ?`).join(', ');
|
|
params.push(req.user.id);
|
|
|
|
await database.query(
|
|
`UPDATE users SET ${updateFields} WHERE id = ?`,
|
|
params
|
|
);
|
|
|
|
logger.logSecurity('profile_updated', { userId: req.user.id }, 'info');
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Profile updated successfully'
|
|
});
|
|
} catch (error) {
|
|
logger.error('Update profile error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to update profile'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Logout user
|
|
router.post('/logout', protect, async (req, res) => {
|
|
try {
|
|
// Invalidate session in Redis
|
|
await redis.invalidateUserSession(req.user.id);
|
|
|
|
logger.logSecurity('user_logout', { userId: req.user.id }, 'info');
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Logout successful'
|
|
});
|
|
} catch (error) {
|
|
logger.error('Logout error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Logout failed'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Refresh token
|
|
router.post('/refresh', protect, async (req, res) => {
|
|
try {
|
|
// Generate new token
|
|
const newToken = generateToken(req.user.id);
|
|
|
|
// Update session in Redis
|
|
await redis.cacheUserSession(req.user.id, {
|
|
userId: req.user.id,
|
|
username: req.user.username,
|
|
email: req.user.email,
|
|
role: req.user.role,
|
|
loginTime: new Date().toISOString()
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Token refreshed successfully',
|
|
data: {
|
|
token: newToken
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Token refresh error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Token refresh failed'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Change password
|
|
router.post('/change-password', protect, [
|
|
body('currentPassword').notEmpty().withMessage('Current password is required'),
|
|
body('newPassword').isLength({ min: 6 }).withMessage('New password must be at least 6 characters')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
errors: errors.array()
|
|
});
|
|
}
|
|
|
|
const { currentPassword, newPassword } = req.body;
|
|
|
|
// Get current user
|
|
const user = await database.query(
|
|
'SELECT password_hash FROM users WHERE id = ?',
|
|
[req.user.id]
|
|
);
|
|
|
|
// Verify current password
|
|
const isValidPassword = await comparePassword(currentPassword, user[0].password_hash);
|
|
if (!isValidPassword) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Current password is incorrect'
|
|
});
|
|
}
|
|
|
|
// Hash new password
|
|
const newPasswordHash = await hashPassword(newPassword);
|
|
|
|
// Update password
|
|
await database.query(
|
|
'UPDATE users SET password_hash = ? WHERE id = ?',
|
|
[newPasswordHash, req.user.id]
|
|
);
|
|
|
|
logger.logSecurity('password_changed', { userId: req.user.id }, 'info');
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Password changed successfully'
|
|
});
|
|
} catch (error) {
|
|
logger.error('Change password error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to change password'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|