// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential. // XCU Omni-Relay v2: API Gateway + WebSocket Sync Engine // Port 4000 — Sovereign XCU API Layer (connects to xcu_iam PostgreSQL) require('dotenv').config(); const { WebSocketServer } = require('ws'); const http = require('http'); const { Pool } = require('pg'); const fs = require('fs'); const { exec } = require('child_process'); const PORT = process.env.PORT || 4000; // Connect to XCU's own PostgreSQL (xcu_iam), NOT jumpadb const pool = new Pool({ host: process.env.XCU_DB_HOST || '127.0.0.1', port: process.env.XCU_DB_PORT || 5432, database: 'xcu_iam', user: process.env.XCU_DB_USER || 'postgres', password: process.env.XCU_DB_PASSWORD || '', }); pool.on('error', (err) => { console.error('[XCU API] PostgreSQL pool error:', err.message); }); // JUMPA DB pool for ONE-WAY PUSH bridge const jumpaPool = new Pool({ host: process.env.JUMPA_DB_HOST || '127.0.0.1', port: process.env.JUMPA_DB_PORT || 5432, database: 'jumpadb', user: process.env.JUMPA_DB_USER || 'jumpa_admin', password: process.env.JUMPA_DB_PASSWORD || 'JumpaS3cur3!@#', }); // ═══════════════════════════════════════ // HTTP REST API // ═══════════════════════════════════════ const parseBody = (req) => new Promise((resolve, reject) => { let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { try { resolve(body ? JSON.parse(body) : {}); } catch (e) { reject(e); } }); }); const sendJson = (res, status, data) => { res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }); res.end(JSON.stringify(data)); }; const server = http.createServer(async (req, res) => { // CORS preflight if (req.method === 'OPTIONS') { res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }); return res.end(); } const url = new URL(req.url, `http://localhost:${PORT}`); const path = url.pathname; try { // ─── GET /api/v1/modules ─── if (path === '/api/v1/modules' && req.method === 'GET') { const result = await pool.query( 'SELECT id, name, group_id, group_name, sort_order, is_active FROM modules WHERE is_active = true ORDER BY sort_order' ); // Group by group_id for frontend consumption const registry = {}; for (const row of result.rows) { if (!registry[row.group_id]) { registry[row.group_id] = { name: row.group_name, modules: [] }; } registry[row.group_id].modules.push({ id: row.id, name: `${row.name} (Modul ${row.sort_order})` }); } return sendJson(res, 200, registry); } // ─── POST /api/v1/modules ─── if (path === '/api/v1/modules' && req.method === 'POST') { const body = await parseBody(req); const { id, name, group_id, group_name, sort_order } = body; if (!id || !name || !group_id || !group_name) { return sendJson(res, 400, { error: 'Missing required fields: id, name, group_id, group_name' }); } await pool.query( 'INSERT INTO modules (id, name, group_id, group_name, sort_order) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO UPDATE SET name = $2, group_id = $3, group_name = $4, sort_order = $5', [id, name, group_id, group_name, sort_order || 0] ); // Also insert into module_prices if not exists await pool.query( 'INSERT INTO module_prices (module_id, price) VALUES ($1, 1500000) ON CONFLICT (module_id) DO NOTHING', [id] ); broadcastModuleUpdate(); return sendJson(res, 201, { status: 'MODULE_CREATED', id }); } // ─── DELETE /api/v1/modules/:id ─── if (path.startsWith('/api/v1/modules/') && req.method === 'DELETE') { const moduleId = path.split('/').pop(); await pool.query('UPDATE modules SET is_active = false WHERE id = $1', [moduleId]); broadcastModuleUpdate(); return sendJson(res, 200, { status: 'MODULE_DEACTIVATED', id: moduleId }); } // ─── GET /api/v1/tenants ─── if (path === '/api/v1/tenants' && req.method === 'GET') { const result = await pool.query( 'SELECT tenant_key, tenant_name, base_tier_id, custom_modules, custom_tier_name, status, created_at FROM tenant_allocations ORDER BY created_at DESC' ); return sendJson(res, 200, result.rows); } // ─── POST /api/v1/tenants ─── if (path === '/api/v1/tenants' && req.method === 'POST') { const body = await parseBody(req); const { tenant_key, tenant_name, base_tier_id, custom_modules, custom_tier_name } = body; if (!tenant_key || !tenant_name) { return sendJson(res, 400, { error: 'Missing required fields: tenant_key, tenant_name' }); } await pool.query( `INSERT INTO tenant_allocations (tenant_key, tenant_name, base_tier_id, custom_modules, custom_tier_name) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (tenant_key) DO UPDATE SET tenant_name = $2, base_tier_id = $3, custom_modules = $4, custom_tier_name = $5, updated_at = now()`, [tenant_key, tenant_name, base_tier_id || null, custom_modules || '{}', custom_tier_name || ''] ); return sendJson(res, 201, { status: 'TENANT_PROVISIONED', tenant_key }); } // ─── PUT /api/v1/tenants/:key ─── if (path.startsWith('/api/v1/tenants/') && req.method === 'PUT') { const tenantKey = decodeURIComponent(path.split('/api/v1/tenants/')[1]); const body = await parseBody(req); const { tenant_name, base_tier_id, custom_modules, custom_tier_name, status } = body; await pool.query( `UPDATE tenant_allocations SET tenant_name = COALESCE($2, tenant_name), base_tier_id = $3, custom_modules = COALESCE($4, custom_modules), custom_tier_name = COALESCE($5, custom_tier_name), status = COALESCE($6, status), updated_at = now() WHERE tenant_key = $1`, [tenantKey, tenant_name, base_tier_id || null, custom_modules || '{}', custom_tier_name || '', status] ); return sendJson(res, 200, { status: 'TENANT_UPDATED', tenant_key: tenantKey }); } // ─── DELETE /api/v1/tenants/:key ─── if (path.startsWith('/api/v1/tenants/') && req.method === 'DELETE') { const tenantKey = decodeURIComponent(path.split('/api/v1/tenants/')[1]); await pool.query('DELETE FROM tenant_allocations WHERE tenant_key = $1', [tenantKey]); return sendJson(res, 200, { status: 'TENANT_REVOKED', tenant_key: tenantKey }); } // ─── GET /api/v1/tiers ─── if (path === '/api/v1/tiers' && req.method === 'GET') { const result = await pool.query('SELECT * FROM tiers WHERE is_active = true ORDER BY price'); return sendJson(res, 200, result.rows); } // ─── GET /api/v1/module-prices ─── if (path === '/api/v1/module-prices' && req.method === 'GET') { const result = await pool.query('SELECT module_id, price FROM module_prices ORDER BY module_id'); const prices = {}; for (const row of result.rows) { prices[row.module_id] = Number(row.price); } return sendJson(res, 200, prices); } // ─── POST /api/v1/bridge/sync ── ONE-WAY PUSH to JUMPA ─── if (path === '/api/v1/bridge/sync' && req.method === 'POST') { const body = await parseBody(req); const { tenant_key } = body; if (!tenant_key) { return sendJson(res, 400, { error: 'Missing tenant_key' }); } // 1. Get tenant's allocated modules const tenantResult = await pool.query( 'SELECT base_tier_id, custom_modules FROM tenant_allocations WHERE tenant_key = $1', [tenant_key] ); if (tenantResult.rows.length === 0) { return sendJson(res, 404, { error: 'Tenant not found' }); } const tenant = tenantResult.rows[0]; let activeModules = tenant.custom_modules || []; // If tenant uses a base tier, get modules from tier if (tenant.base_tier_id) { const tierResult = await pool.query('SELECT modules FROM tiers WHERE id = $1', [tenant.base_tier_id]); if (tierResult.rows.length > 0) { activeModules = tierResult.rows[0].modules; } } // 2. Get bridge mappings for these modules const bridgeResult = await pool.query( 'SELECT xcu_module_id, jumpa_feature_key FROM module_feature_bridge WHERE xcu_module_id = ANY($1)', [activeModules] ); const activeFeatureKeys = bridgeResult.rows.map(r => r.jumpa_feature_key); // 3. ONE-WAY PUSH: Update jumpadb.system_features // Activate features that are in the bridge mapping if (activeFeatureKeys.length > 0) { await jumpaPool.query( `UPDATE system_features SET default_state = 'GRANTED' WHERE key = ANY($1)`, [activeFeatureKeys] ); } // Get ALL bridge keys to know which to disable const allBridgeResult = await pool.query( 'SELECT DISTINCT jumpa_feature_key FROM module_feature_bridge' ); const allBridgeKeys = allBridgeResult.rows.map(r => r.jumpa_feature_key); const disabledKeys = allBridgeKeys.filter(k => !activeFeatureKeys.includes(k)); if (disabledKeys.length > 0) { await jumpaPool.query( `UPDATE system_features SET default_state = 'UPSELL' WHERE key = ANY($1)`, [disabledKeys] ); } console.log(`[XCU BRIDGE] ONE-WAY PUSH to JUMPA: ${activeFeatureKeys.length} GRANTED, ${disabledKeys.length} UPSELL`); return sendJson(res, 200, { status: 'BRIDGE_SYNC_COMPLETE', granted: activeFeatureKeys.length, disabled: disabledKeys.length, tenant_key }); } // ─── GET /api/v1/bridge/mappings ─── if (path === '/api/v1/bridge/mappings' && req.method === 'GET') { const result = await pool.query( 'SELECT xcu_module_id, jumpa_feature_key FROM module_feature_bridge ORDER BY xcu_module_id' ); return sendJson(res, 200, result.rows); } // ─── POST /api/v1/bridge/mappings ─── if (path === '/api/v1/bridge/mappings' && req.method === 'POST') { const body = await parseBody(req); const { xcu_module_id, jumpa_feature_key } = body; if (!xcu_module_id || !jumpa_feature_key) { return sendJson(res, 400, { error: 'Missing xcu_module_id or jumpa_feature_key' }); } await pool.query( 'INSERT INTO module_feature_bridge (xcu_module_id, jumpa_feature_key) VALUES ($1, $2) ON CONFLICT DO NOTHING', [xcu_module_id, jumpa_feature_key] ); return sendJson(res, 201, { status: 'MAPPING_CREATED', xcu_module_id, jumpa_feature_key }); } // ═══════════════════════════════════════ // MONITORING API (Phase 1: Tenant Monitor) // ═══════════════════════════════════════ // ─── GET /api/v1/monitor/health ── PM2 service status ─── if (path === '/api/v1/monitor/health' && req.method === 'GET') { return new Promise((resolve) => { exec('pm2 jlist 2>/dev/null', (err, stdout) => { if (err) return resolve(sendJson(res, 200, { services: [], error: err.message })); try { const list = JSON.parse(stdout); const services = list.map(p => ({ name: p.name, pid: p.pid, status: p.pm2_env.status, uptime: p.pm2_env.pm_uptime, restarts: p.pm2_env.restart_time, memory: p.monit ? p.monit.memory : 0, cpu: p.monit ? p.monit.cpu : 0, })); resolve(sendJson(res, 200, { services, ts: Date.now() })); } catch (e) { resolve(sendJson(res, 200, { services: [], error: e.message })); } }); }); } // ─── GET /api/v1/monitor/traffic ── Nginx access log summary ─── if (path === '/api/v1/monitor/traffic' && req.method === 'GET') { const search = url.searchParams.get('q') || ''; const since = url.searchParams.get('since') || ''; return new Promise((resolve) => { let cmd = 'tail -200 /var/log/nginx/access.log 2>/dev/null'; exec(cmd, (err, stdout) => { if (err) return resolve(sendJson(res, 200, { entries: [], stats: {} })); let lines = stdout.trim().split('\n').filter(Boolean); // Filter by search query if (search) { const q = search.toLowerCase(); lines = lines.filter(l => l.toLowerCase().includes(q)); } // Filter by datetime if (since) { lines = lines.filter(l => { const m = l.match(/\[([^\]]+)\]/); return m ? new Date(m[1].replace(':', ' ').replace(/\//g, '-')) >= new Date(since) : true; }); } // Stats const stats = { total: lines.length, '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0 }; lines.forEach(l => { const m = l.match(/"\s(\d{3})\s/); if (m) { const code = parseInt(m[1]); if (code < 300) stats['2xx']++; else if (code < 400) stats['3xx']++; else if (code < 500) stats['4xx']++; else stats['5xx']++; } }); // Return last 10 const entries = lines.slice(-10).reverse(); resolve(sendJson(res, 200, { entries, stats, ts: Date.now() })); }); }); } // ─── GET /api/v1/monitor/logs/:service ── PM2 logs ─── if (path.startsWith('/api/v1/monitor/logs/') && req.method === 'GET') { const service = path.split('/api/v1/monitor/logs/')[1]; const search = url.searchParams.get('q') || ''; const lines = parseInt(url.searchParams.get('lines')) || 10; return new Promise((resolve) => { exec(`pm2 logs ${service} --nostream --lines ${Math.min(lines, 100)} 2>/dev/null`, (err, stdout) => { if (err) return resolve(sendJson(res, 200, { logs: [], error: err.message })); let logLines = stdout.trim().split('\n').filter(Boolean); if (search) { const q = search.toLowerCase(); logLines = logLines.filter(l => l.toLowerCase().includes(q)); } resolve(sendJson(res, 200, { logs: logLines.slice(-10), service, ts: Date.now() })); }); }); } // ─── GET /api/v1/monitor/intrusions ── Detect illegal access ─── if (path === '/api/v1/monitor/intrusions' && req.method === 'GET') { return new Promise((resolve) => { const cmds = [ // 1. Failed auth from Gatekeeper 'grep -i "Auth Failed\\|Invalid Key\\|TOKEN_EXPIRED" /root/.pm2/logs/xcu-gatekeeper-error.log 2>/dev/null | tail -20', // 2. 401/403 from Nginx 'grep -E "\\\" (401|403) " /var/log/nginx/access.log 2>/dev/null | tail -20', // 3. Suspicious patterns (scanning/probing) 'grep -E "\\\" (404) .*(/admin|/wp-|/phpmyadmin|/.env|/config)" /var/log/nginx/access.log 2>/dev/null | tail -10' ]; exec(cmds.join(' && echo "---SEPARATOR---" && '), { maxBuffer: 1024 * 1024 }, (err, stdout) => { const sections = (stdout || '').split('---SEPARATOR---'); const intrusions = []; // Parse failed auth (sections[0] || '').trim().split('\n').filter(Boolean).forEach(line => { intrusions.push({ type: 'AUTH_FAILED', severity: 'HIGH', raw: line.substring(0, 200), ts: Date.now() }); }); // Parse 401/403 (sections[1] || '').trim().split('\n').filter(Boolean).forEach(line => { const ipMatch = line.match(/^(\d+\.\d+\.\d+\.\d+)/); const pathMatch = line.match(/"[A-Z]+ ([^ ]+)/); const isCrossTenant = pathMatch && (pathMatch[1].includes('/xcu-api') || pathMatch[1].includes('/ws/gatekeeper')); intrusions.push({ type: isCrossTenant ? 'CROSS_TENANT_JUMP' : 'UNAUTHORIZED', severity: isCrossTenant ? 'CRITICAL' : 'MEDIUM', ip: ipMatch ? ipMatch[1] : 'unknown', target: pathMatch ? pathMatch[1] : 'unknown', raw: line.substring(0, 200), ts: Date.now() }); }); // Parse probes (sections[2] || '').trim().split('\n').filter(Boolean).forEach(line => { const ipMatch = line.match(/^(\d+\.\d+\.\d+\.\d+)/); intrusions.push({ type: 'API_PROBE', severity: 'LOW', ip: ipMatch ? ipMatch[1] : 'unknown', raw: line.substring(0, 200), ts: Date.now() }); }); // Brute force detection: same IP > 3 failures const ipCounts = {}; intrusions.filter(i => i.type === 'AUTH_FAILED' || i.type === 'UNAUTHORIZED').forEach(i => { if (i.ip) { ipCounts[i.ip] = (ipCounts[i.ip] || 0) + 1; } }); const bruteForceIPs = Object.entries(ipCounts).filter(([_, c]) => c > 3).map(([ip]) => ip); resolve(sendJson(res, 200, { intrusions: intrusions.slice(-30), bruteForceIPs, total: intrusions.length, ts: Date.now() })); }); }); } // ─── POST /api/v1/monitor/block-ip ── Manual IP block (default OFF) ─── if (path === '/api/v1/monitor/block-ip' && req.method === 'POST') { const body = await parseBody(req); const { ip, action } = body; // action: 'block' or 'unblock' if (!ip) return sendJson(res, 400, { error: 'Missing IP' }); return new Promise((resolve) => { // Always remove first to prevent duplicates, then add if blocking const cleanCmd = `while iptables -D INPUT -s ${ip} -j DROP 2>/dev/null; do :; done`; const cmd = action === 'unblock' ? `${cleanCmd}; echo "UNBLOCKED ${ip}"` : `${cleanCmd}; iptables -A INPUT -s ${ip} -j DROP; echo "BLOCKED ${ip}"`; exec(cmd, (err, stdout) => { resolve(sendJson(res, 200, { status: stdout.trim(), ip, action })); }); }); } // ─── GET /api/v1/monitor/blocked ── List blocked IPs ─── if (path === '/api/v1/monitor/blocked' && req.method === 'GET') { return new Promise((resolve) => { exec('iptables -L INPUT -n --line-numbers 2>/dev/null | grep DROP', (err, stdout) => { const blocked = (stdout || '').trim().split('\n').filter(Boolean).map(line => { const parts = line.trim().split(/\s+/); return { num: parts[0], ip: parts[4] || 'unknown', target: parts[1] || 'DROP' }; }).filter(b => b.ip !== 'unknown' && b.ip !== '0.0.0.0/0'); resolve(sendJson(res, 200, { blocked, total: blocked.length, ts: Date.now() })); }); }); } // ─── Health check ─── if (path === '/' || path === '/health') { return sendJson(res, 200, { service: 'XCU Omni-Relay API Gateway v2', status: 'ONLINE', sovereign: '[TSM.ID].[11031972]', port: PORT, database: 'xcu_iam (PostgreSQL)', bridge: 'ONE-WAY PUSH → JUMPA' }); } // 404 sendJson(res, 404, { error: 'NOT_FOUND' }); } catch (err) { console.error('[XCU API] Error:', err.message); sendJson(res, 500, { error: err.message }); } }); // ═══════════════════════════════════════ // WebSocket Sync (for Command Center live updates) // ═══════════════════════════════════════ const wss = new WebSocketServer({ server }); const broadcastModuleUpdate = async () => { try { const result = await pool.query( 'SELECT id, name, group_id, group_name, sort_order, is_active FROM modules WHERE is_active = true ORDER BY sort_order' ); const registry = {}; for (const row of result.rows) { if (!registry[row.group_id]) { registry[row.group_id] = { name: row.group_name, modules: [] }; } registry[row.group_id].modules.push({ id: row.id, name: `${row.name} (Modul ${row.sort_order})` }); } const msg = JSON.stringify({ type: 'SYNC_MODULES', payload: registry }); wss.clients.forEach(client => { if (client.readyState === 1) client.send(msg); }); } catch (err) { console.error('[XCU WS] Broadcast error:', err.message); } }; wss.on('connection', async (ws) => { console.log('[XCU API] Command Center Connected.'); // Send current module registry immediately await broadcastModuleUpdate(); ws.on('message', async (message) => { try { const data = JSON.parse(message); // Legacy support: handle UPDATE_PRICING from old Command Center if (data.type === 'UPDATE_PRICING') { console.log('[XCU API] Received legacy pricing update from Command Center.'); // Persist tiers to DB if provided if (data.payload.tiers) { for (const tier of data.payload.tiers) { await pool.query( `UPDATE tiers SET name = $2, price = $3, description = $4, modules = $5 WHERE id = $1`, [tier.id, tier.name, tier.price, tier.description || '', tier.modules || []] ); } } // Persist module prices if provided if (data.payload.modulePrices) { for (const [modId, price] of Object.entries(data.payload.modulePrices)) { await pool.query( 'UPDATE module_prices SET price = $2, updated_at = now() WHERE module_id = $1', [modId, Number(price)] ); } } // Broadcast to all connected clients const broadcastData = JSON.stringify({ type: 'SYNC_STATE', payload: data.payload }); wss.clients.forEach(client => { if (client.readyState === 1) client.send(broadcastData); }); } } catch (err) { console.error('[XCU API] WS message error:', err.message); } }); ws.on('close', () => { console.log('[XCU API] Command Center Disconnected.'); }); }); // ═══════════════════════════════════════ server.listen(PORT, () => { console.log(`[XCU API GATEWAY] Listening on http://localhost:${PORT}`); console.log(`[XCU API GATEWAY] Database: xcu_iam (PostgreSQL - INDEPENDENT)`); console.log(`[XCU API GATEWAY] Bridge: ONE-WAY PUSH → JUMPA`); console.log(`[XCU API GATEWAY] Sovereign: [TSM.ID].[11031972]`); });