const express = require('express'); const router = express.Router(); const db = require('../db'); const path = require('path'); const fs = require('fs'); const { isAuthenticated, hasRole } = require('../middleware/authMiddleware'); const { generateLicense, verifyLicense } = require('../utils/licenseManager'); const { checkRemoteKey } = require('../utils/remoteLicense'); // Load Public Key for Verification const publicKeyPath = path.join(__dirname, '../config/public_key.pem'); let publicKey = null; try { if (fs.existsSync(publicKeyPath)) { publicKey = fs.readFileSync(publicKeyPath, 'utf8'); console.log('✅ Public Key loaded successfully for license verification'); } else { console.error('❌ WARNING: public_key.pem not found at:', publicKeyPath); } } catch (e) { console.error('❌ Error loading public key:', e); } // 0. Server Configuration (Subscriber ID & Session Timeout) router.get('/settings', isAuthenticated, hasRole('admin'), async (req, res) => { try { const [rows] = await db.query("SELECT setting_key, setting_value FROM system_settings WHERE setting_key IN ('subscriber_id', 'session_timeout')"); const settings = {}; rows.forEach(r => settings[r.setting_key] = r.setting_value); res.json({ subscriber_id: settings.subscriber_id || '', session_timeout: parseInt(settings.session_timeout) || 60 // Default 60 min }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); router.post('/settings', isAuthenticated, hasRole('admin'), async (req, res) => { const { subscriber_id, session_timeout } = req.body; try { if (subscriber_id !== undefined) { await db.query(`INSERT INTO system_settings (setting_key, setting_value) VALUES ('subscriber_id', ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)`, [subscriber_id]); } if (session_timeout !== undefined) { await db.query(`INSERT INTO system_settings (setting_key, setting_value) VALUES ('session_timeout', ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)`, [session_timeout.toString()]); } res.json({ message: 'Settings saved' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); // 1. Get All Modules Status router.get('/modules', isAuthenticated, async (req, res) => { try { const [rows] = await db.query('SELECT * FROM system_modules'); const modules = {}; const defaults = ['asset', 'production', 'monitoring']; // Get stored subscriber ID const [subRows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'subscriber_id'"); const serverSubscriberId = subRows.length > 0 ? subRows[0].setting_value : null; defaults.forEach(code => { const found = rows.find(r => r.code === code); if (found) { modules[code] = { active: !!found.is_active, type: found.license_type, expiry: found.expiry_date, subscriber_id: found.subscriber_id // Return who verified it }; } else { modules[code] = { active: false, type: null, expiry: null, subscriber_id: null }; } }); res.json({ modules, serverSubscriberId }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); const axios = require('axios'); // 2. Activate Module (Apply License) // Only admin can manage system modules router.post('/modules/:code/activate', isAuthenticated, hasRole('admin'), async (req, res) => { const { code } = req.params; let { licenseKey } = req.body; if (!licenseKey) { return res.status(400).json({ error: 'License key is required' }); } licenseKey = licenseKey.trim(); // 1. Verify Key validity const result = verifyLicense(licenseKey, publicKey); if (!result.isValid) { return res.status(400).json({ error: `Invalid License: ${result.reason}` }); } // 2. Check Module match if (result.module !== code) { return res.status(400).json({ error: `This license is for '${result.module}' module, not '${code}'` }); } // 3. Check Subscriber match try { const [subRows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'subscriber_id'"); const serverSubscriberId = subRows.length > 0 ? subRows[0].setting_value : null; if (!serverSubscriberId) { return res.status(400).json({ error: '서버 구독자 ID가 설정되지 않았습니다. [서버 환경 설정]에서 먼저 설정해주세요.' }); } // Allow 'dev' type to bypass strict subscriber check, but log it if (result.type === 'dev') { if (result.subscriberId !== serverSubscriberId) { console.warn(`⚠️ Dev License used: Subscriber ID mismatch allowed. Key: ${result.subscriberId}, Server: ${serverSubscriberId}`); } } else { if (result.subscriberId !== serverSubscriberId) { return res.status(403).json({ error: `구독자 ID 불일치: 라이선스 키는 [${result.subscriberId}] 전용이지만, 현재 서버는 [${serverSubscriberId}]로 설정되어 있습니다.` }); } } // 4. Archive Current License if exists const [current] = await db.query('SELECT * FROM system_modules WHERE code = ?', [code]); if (current.length > 0 && current[0].is_active && current[0].license_key) { const old = current[0]; const historySql = ` INSERT INTO license_history (module_code, license_key, license_type, subscriber_id, activated_at) VALUES (?, ?, ?, ?, NOW()) `; await db.query(historySql, [old.code, old.license_key, old.license_type, old.subscriber_id]); } // Upsert into system_modules const sql = ` INSERT INTO system_modules (code, name, is_active, license_key, license_type, expiry_date, subscriber_id) VALUES (?, ?, true, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE is_active = true, license_key = VALUES(license_key), license_type = VALUES(license_type), expiry_date = VALUES(expiry_date), subscriber_id = VALUES(subscriber_id) `; // Map codes to names const names = { 'asset': '자산 관리', 'production': '생산 관리', 'monitoring': 'CCTV' }; await db.query(sql, [code, names[code] || code, licenseKey, result.type, result.expiryDate, result.subscriberId]); // 5. Sync status with License Manager const licenseManagerUrl = process.env.LICENSE_MANAGER_URL || 'http://localhost:3006/api'; try { await axios.post(`${licenseManagerUrl}/licenses/activate`, { licenseKey }); console.log(`✅ Synced activation status for key: ${licenseKey.substring(0, 20)}...`); } catch (syncErr) { const errorDetail = syncErr.response ? `Status: ${syncErr.response.status}, Data: ${JSON.stringify(syncErr.response.data)}` : syncErr.message; console.error(`⚠️ Failed to sync status with License Manager: ${errorDetail}`); // We don't fail the whole activation if sync fails, but we log it } res.json({ success: true, message: 'Module activated', type: result.type, expiry: result.expiryDate }); } catch (err) { console.error('❌ License Activation Error:', err); res.status(500).json({ success: false, error: err.message || 'Database error', stack: process.env.NODE_ENV === 'development' ? err.stack : undefined }); } }); // 3. Get Module History router.get('/modules/:code/history', isAuthenticated, hasRole('admin'), async (req, res) => { try { const [rows] = await db.query('SELECT * FROM license_history WHERE module_code = ? ORDER BY id DESC LIMIT 10', [req.params.code]); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); // 4. Deactivate Module router.post('/modules/:code/deactivate', isAuthenticated, hasRole('admin'), async (req, res) => { const { code } = req.params; try { // Archive on deactivate too? const [current] = await db.query('SELECT * FROM system_modules WHERE code = ?', [code]); if (current.length > 0 && current[0].is_active) { const old = current[0]; const historySql = ` INSERT INTO license_history (module_code, license_key, license_type, subscriber_id, activated_at) VALUES (?, ?, ?, ?, NOW()) `; await db.query(historySql, [old.code, old.license_key, old.license_type, old.subscriber_id]); } await db.query('UPDATE system_modules SET is_active = false WHERE code = ?', [code]); res.json({ success: true, message: 'Module deactivated' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Database error' }); } }); module.exports = router;