const express = require('express'); const router = express.Router(); const Joi = require('joi'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { User } = require('../models'); const { Op } = require('sequelize'); const { validateRequest } = require('../middleware/validation'); const { authenticateToken, requireRole } = require('../middleware/auth'); // Validation schemas const registerSchema = Joi.object({ username: Joi.string().min(3).max(50).required(), email: Joi.string().email().required(), password: Joi.string().min(6).required(), first_name: Joi.string().optional(), last_name: Joi.string().optional(), phone_number: Joi.string().optional(), role: Joi.string().valid('admin', 'operator', 'viewer').default('viewer') }); const loginSchema = Joi.object({ username: Joi.string().required(), password: Joi.string().required() }); const updateProfileSchema = Joi.object({ first_name: Joi.string().optional(), last_name: Joi.string().optional(), phone_number: Joi.string().optional(), sms_alerts_enabled: Joi.boolean().optional(), email_alerts_enabled: Joi.boolean().optional(), timezone: Joi.string().optional() }); // POST /api/users/register - Register new user router.post('/register', validateRequest(registerSchema), async (req, res) => { try { const { password, ...userData } = req.body; // Hash password const saltRounds = 12; const password_hash = await bcrypt.hash(password, saltRounds); // Create user const user = await User.create({ ...userData, password_hash }); // Remove password hash from response const { password_hash: _, ...userResponse } = user.toJSON(); res.status(201).json({ success: true, data: userResponse, message: 'User registered successfully' }); } catch (error) { console.error('Error registering user:', error); if (error.name === 'SequelizeUniqueConstraintError') { return res.status(409).json({ success: false, message: 'Username or email already exists' }); } res.status(500).json({ success: false, message: 'Failed to register user', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // POST /api/users/login - User login (direct API access - for legacy support) router.post('/login', validateRequest(loginSchema), async (req, res) => { try { const { username, password } = req.body; // Find user by username or email const user = await User.findOne({ where: { [Op.or]: [ { username: username }, { email: username } ], is_active: true } }); if (!user || !await bcrypt.compare(password, user.password_hash)) { return res.status(401).json({ success: false, message: 'Invalid credentials' }); } // Update last login await user.update({ last_login: new Date() }); // Generate JWT token const token = jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' } ); // Remove password hash from response const { password_hash: _, ...userResponse } = user.toJSON(); res.json({ success: true, data: { user: userResponse, token, expires_in: '24h' }, message: 'Login successful' }); } catch (error) { console.error('Error during login:', error); res.status(500).json({ success: false, message: 'Login failed', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/users/profile - Get current user profile router.get('/profile', authenticateToken, async (req, res) => { try { const { password_hash: _, ...userProfile } = req.user.toJSON(); res.json({ success: true, data: userProfile }); } catch (error) { console.error('Error fetching user profile:', error); res.status(500).json({ success: false, message: 'Failed to fetch user profile', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // PUT /api/users/profile - Update user profile router.put('/profile', authenticateToken, validateRequest(updateProfileSchema), async (req, res) => { try { await req.user.update(req.body); const { password_hash: _, ...userResponse } = req.user.toJSON(); res.json({ success: true, data: userResponse, message: 'Profile updated successfully' }); } catch (error) { console.error('Error updating user profile:', error); res.status(500).json({ success: false, message: 'Failed to update profile', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/users - Get all users (admin only) router.get('/', authenticateToken, requireRole(['admin']), async (req, res) => { try { const { limit = 50, offset = 0, role, is_active } = req.query; const whereClause = {}; if (role) whereClause.role = role; if (is_active !== undefined) whereClause.is_active = is_active === 'true'; const users = await User.findAndCountAll({ where: whereClause, attributes: { exclude: ['password_hash'] }, limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: users.rows, pagination: { total: users.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(users.count / parseInt(limit)) } }); } catch (error) { console.error('Error fetching users:', error); res.status(500).json({ success: false, message: 'Failed to fetch users', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); /** * Tenant-aware local authentication function * Called by multi-tenant auth middleware */ async function loginLocal(req, res, next) { try { const { username, password } = req.body; const { Tenant } = require('../models'); // Get tenant information from request (set by multi-tenant auth middleware) let tenantId = null; if (req.tenant && req.tenant.id) { // Find the actual tenant in database const tenant = await Tenant.findOne({ where: { slug: req.tenant.id } }); if (tenant) { tenantId = tenant.id; } } console.log(`🔐 Login attempt for user "${username}" in tenant "${req.tenant?.id}" (${tenantId})`); // Find user by username or email within the specific tenant const whereClause = { [Op.and]: [ { [Op.or]: [ { username: username }, { email: username } ] }, { is_active: true } ] }; // Add tenant filtering - only include users from this tenant if (tenantId) { whereClause[Op.and].push({ tenant_id: tenantId }); } else { // For default tenant, look for users with null tenant_id whereClause[Op.and].push({ tenant_id: null }); } const user = await User.findOne({ where: whereClause }); if (!user || !await bcrypt.compare(password, user.password_hash)) { console.log(`❌ Authentication failed for "${username}" in tenant "${req.tenant?.id}"`); return res.status(401).json({ success: false, message: 'Invalid credentials' }); } console.log(`✅ Authentication successful for "${username}" in tenant "${req.tenant?.id}"`); // Update last login await user.update({ last_login: new Date() }); // Generate JWT token with tenant information const token = jwt.sign( { userId: user.id, username: user.username, role: user.role, tenantId: user.tenant_id, tenantSlug: req.tenant?.id }, process.env.JWT_SECRET, { expiresIn: '24h' } ); // Remove password hash from response const { password_hash: _, ...userResponse } = user.toJSON(); res.json({ success: true, data: { user: userResponse, token, expires_in: '24h' }, message: 'Login successful' }); } catch (error) { console.error('Error during tenant-aware login:', error); res.status(500).json({ success: false, message: 'Login failed', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } } module.exports = router; module.exports.loginLocal = loginLocal;