const express = require('express'); const router = express.Router(); const db = require('../db'); const crypto = require('crypto'); const { isAuthenticated, hasRole } = require('../middleware/authMiddleware'); const { generateToken } = require('../middleware/csrfMiddleware'); // --- Crypto Utilities --- // Use a fixed key for MVP. In production, store this securely in .env // Key must be 32 bytes for aes-256-cbc // 'my_super_secret_key_manage_asset' is 32 chars? // let's use a simpler approach to ensure length on startup or fallback const SECRET_KEY = process.env.ENCRYPTION_KEY || 'smartasset_secret_key_0123456789'; // 32 chars needed // Ideally use a buffer from hex, but string is okay if 32 chars. // Let's pad it to ensure stability if env is missing. const keyBuffer = crypto.scryptSync(SECRET_KEY, 'salt', 32); const ALGORITHM = 'aes-256-cbc'; function encrypt(text) { if (!text) return text; const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(ALGORITHM, keyBuffer, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); return iv.toString('hex') + ':' + encrypted; } function decrypt(text) { if (!text) return text; // Check if it looks like our encrypted format (hexIV:hexContent) if (!text.includes(':')) { return text; // Assume plain text if no separator } try { const textParts = text.split(':'); const ivHex = textParts.shift(); // IV for AES-256-CBC must be 16 bytes (32 hex characters) if (!ivHex || ivHex.length !== 32) { return text; // Invalid IV length, return original } const iv = Buffer.from(ivHex, 'hex'); const encryptedText = textParts.join(':'); const decipher = crypto.createDecipheriv(ALGORITHM, keyBuffer, iv); let decrypted = decipher.update(encryptedText, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (e) { console.error('Decryption failed for:', text, e.message); return text; // Return original if fail } } function hashPassword(password) { return crypto.createHash('sha256').update(password).digest('hex'); } // --- Routes --- // 1. Login router.post('/login', async (req, res) => { const { id, password } = req.body; try { const hashedPassword = hashPassword(password); const [rows] = await db.query('SELECT * FROM users WHERE id = ? AND password = ?', [id, hashedPassword]); if (rows.length > 0) { const user = rows[0]; // Update last_login await db.query('UPDATE users SET last_login = NOW() WHERE id = ?', [user.id]); // Remove sensitive data delete user.password; // Should we decrypt phone? Maybe not needed for session, but let's decrypt just in case UI needs it if (user.phone) user.phone = decrypt(user.phone); // Save user to session req.session.user = user; // Generate CSRF Token const csrfToken = generateToken(); req.session.csrfToken = csrfToken; // Explicitly save session before response (optional but safer for race conditions) req.session.save(err => { if (err) { console.error('Session save error:', err); return res.status(500).json({ success: false, message: 'Session error' }); } res.json({ success: true, user, csrfToken }); }); } else { res.status(401).json({ success: false, message: 'Invalid credentials' }); } } catch (err) { console.error('Login error:', err); res.status(500).json({ success: false, message: 'Server error' }); } }); // 1.5. Check Session (New) router.get('/check', (req, res) => { if (req.session.user) { // Ensure CSRF token exists, if not generate one (edge case) if (!req.session.csrfToken) { req.session.csrfToken = generateToken(); } res.json({ isAuthenticated: true, user: req.session.user, csrfToken: req.session.csrfToken }); } else { res.json({ isAuthenticated: false }); } }); // 1.6. Logout (New) router.post('/logout', (req, res) => { req.session.destroy(err => { if (err) { console.error('Logout error:', err); return res.status(500).json({ success: false, message: 'Logout failed' }); } res.clearCookie('smartasset_sid'); // matching key in index.js res.json({ success: true, message: 'Logged out' }); }); }); // 2. List Users (Admin Only) router.get('/users', isAuthenticated, hasRole('admin'), async (req, res) => { try { // ideally check req.user.role if we had middleware, for now assuming client logic protection + internal/local usage const [rows] = await db.query('SELECT id, name, department, position, phone, role, last_login, created_at, updated_at FROM users ORDER BY created_at DESC'); const users = rows.map(u => ({ ...u, phone: decrypt(u.phone) // Decrypt phone for admin view })); res.json(users); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); // 3. Create User router.post('/users', isAuthenticated, hasRole('admin'), async (req, res) => { const { id, password, name, department, position, phone, role } = req.body; if (!id || !password || !name) { return res.status(400).json({ error: 'Missing required fields' }); } try { // Check if ID exists const [existing] = await db.query('SELECT id FROM users WHERE id = ?', [id]); if (existing.length > 0) { return res.status(409).json({ error: 'User ID already exists' }); } const hashedPassword = hashPassword(password); const encryptedPhone = encrypt(phone); const sql = ` INSERT INTO users (id, password, name, department, position, phone, role) VALUES (?, ?, ?, ?, ?, ?, ?) `; await db.query(sql, [id, hashedPassword, name, department, position, encryptedPhone, role || 'user']); res.status(201).json({ message: 'User created' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); // 4. Update User router.put('/users/:id', isAuthenticated, hasRole('admin'), async (req, res) => { const { password, name, department, position, phone, role } = req.body; const userId = req.params.id; try { // Fetch current to keep old values if not provided? Frontend usually sends all. // We update everything provided. // Build query dynamically or just assume full update let updates = []; let params = []; if (password) { updates.push('password = ?'); params.push(hashPassword(password)); } if (name) { updates.push('name = ?'); params.push(name); } if (department !== undefined) { updates.push('department = ?'); params.push(department); } if (position !== undefined) { updates.push('position = ?'); params.push(position); } if (phone !== undefined) { updates.push('phone = ?'); params.push(encrypt(phone)); } if (role) { updates.push('role = ?'); params.push(role); } if (updates.length === 0) return res.json({ message: 'No changes' }); const sql = `UPDATE users SET ${updates.join(', ')} WHERE id = ?`; params.push(userId); await db.query(sql, params); res.json({ message: 'User updated' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); // 5. Delete User router.delete('/users/:id', isAuthenticated, hasRole('admin'), async (req, res) => { try { // Prevent deleting last admin? Optional. // Prevent deleting self? await db.query('DELETE FROM users WHERE id = ?', [req.params.id]); res.json({ message: 'User deleted' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); module.exports = router;