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'); const cryptoUtil = require('../utils/cryptoUtil'); async function encrypt(text) { return await cryptoUtil.encrypt(text); } async function decrypt(text) { return await cryptoUtil.decrypt(text); } 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 = await 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', async (req, res) => { try { if (req.session.user) { // Ensure CSRF token exists, if not generate one (edge case) if (!req.session.csrfToken) { req.session.csrfToken = generateToken(); } // Fetch session timeout from settings const [rows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'session_timeout'"); const timeoutMinutes = rows.length > 0 ? parseInt(rows[0].setting_value) : 60; res.json({ isAuthenticated: true, user: req.session.user, csrfToken: req.session.csrfToken, sessionTimeout: timeoutMinutes * 60 * 1000 // Convert to ms }); } else { res.json({ isAuthenticated: false }); } } catch (err) { console.error('Check session error:', err); res.status(500).json({ success: false, message: 'Server error' }); } }); // 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('smartims_sid'); // matching key in index.js res.json({ success: true, message: 'Logged out' }); }); }); // 1.7 Verify Supervisor (For sensitive settings) router.post('/verify-supervisor', isAuthenticated, async (req, res) => { const { password } = req.body; if (!password) return res.status(400).json({ error: 'Password required' }); try { if (req.session.user.role !== 'supervisor') { return res.status(403).json({ error: '권한이 없습니다. 최고관리자만 접근 가능합니다.' }); } const hashedPassword = hashPassword(password); const [rows] = await db.query('SELECT 1 FROM users WHERE id = ? AND password = ?', [req.session.user.id, hashedPassword]); if (rows.length > 0) { res.json({ success: true, message: 'Verification successful' }); } else { res.status(401).json({ success: false, message: '비밀번호가 일치하지 않습니다.' }); } } catch (err) { console.error('Verify supervisor error:', err); res.status(500).json({ error: '인증 처리 중 오류가 발생했습니다.' }); } }); // 2. List Users (Admin Only) router.get('/users', isAuthenticated, hasRole('admin'), async (req, res) => { try { 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'); if (!rows || rows.length === 0) { return res.json([]); } // Use Promise.all for safe async decryption const users = await Promise.all(rows.map(async (u) => { const decryptedPhone = await decrypt(u.phone); return { ...u, phone: decryptedPhone }; })); res.json(users); } catch (err) { console.error('Failed to list users:', err); res.status(500).json({ 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 = await 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(await 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;