Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor, admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS. Kredit & lisensi (ISC): - Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak atas base kode dan repo yang bermanfaat. - Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder. - Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
351 lines
8.6 KiB
JavaScript
351 lines
8.6 KiB
JavaScript
/**
|
|
* Settings API Routes
|
|
* Untuk manage settings lewat API dengan security & validation
|
|
*/
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const { logger } = require('../config/logger');
|
|
const { getSetting, getSettings, saveSettings } = require('../config/settingsManager');
|
|
const { getMaskedSettings } = require('../config/settingsEncryption');
|
|
const { getAllRules } = require('../config/settingsValidator');
|
|
const { getChangeHistory, getAuditStats, exportAuditLog } = require('../config/settingsAudit');
|
|
const {
|
|
requireSuperAdmin,
|
|
validateSettingsMiddleware,
|
|
maskSensitiveValues,
|
|
logSettingsAccess,
|
|
rateLimitSettings
|
|
} = require('../middleware/settingsMiddleware');
|
|
|
|
// Apply middleware
|
|
router.use(rateLimitSettings);
|
|
router.use(maskSensitiveValues);
|
|
router.use(logSettingsAccess);
|
|
|
|
/**
|
|
* GET /api/settings
|
|
* Get all settings (masked)
|
|
*/
|
|
router.get('/', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const settings = getSettings();
|
|
const masked = getMaskedSettings(settings);
|
|
|
|
return res.json({
|
|
success: true,
|
|
settings: masked
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error getting settings: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/settings/rules
|
|
* Get validation rules untuk setiap field
|
|
*/
|
|
router.get('/rules', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const rules = getAllRules();
|
|
|
|
return res.json({
|
|
success: true,
|
|
rules
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error getting rules: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/settings/:field
|
|
* Get single setting (masked)
|
|
*/
|
|
router.get('/:field', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const { field } = req.params;
|
|
const value = getSetting(field);
|
|
|
|
if (value === null || value === undefined) {
|
|
return res.status(404).json({
|
|
error: 'Setting not found'
|
|
});
|
|
}
|
|
|
|
const { getMaskedSettings } = require('../config/settingsEncryption');
|
|
const masked = getMaskedSettings({ [field]: value });
|
|
|
|
return res.json({
|
|
success: true,
|
|
field,
|
|
value: masked[field]
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error getting setting: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/settings
|
|
* Update settings dengan validation & encryption
|
|
*/
|
|
router.post('/', requireSuperAdmin, validateSettingsMiddleware, (req, res) => {
|
|
try {
|
|
const settings = req.validatedSettings;
|
|
const actor = req.session?.username || 'unknown';
|
|
const metadata = {
|
|
ip: req.ip,
|
|
userAgent: req.get('user-agent')
|
|
};
|
|
|
|
const result = saveSettings(settings, actor, metadata);
|
|
|
|
if (!result.success) {
|
|
return res.status(400).json({
|
|
error: 'Failed to save settings',
|
|
errors: result.errors
|
|
});
|
|
}
|
|
|
|
return res.json({
|
|
success: true,
|
|
message: 'Settings saved successfully',
|
|
changes: result.changes
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error saving settings: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* PUT /api/settings/:field
|
|
* Update single setting
|
|
*/
|
|
router.put('/:field', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const { field } = req.params;
|
|
const { value } = req.body;
|
|
|
|
if (value === undefined) {
|
|
return res.status(400).json({
|
|
error: 'Value is required'
|
|
});
|
|
}
|
|
|
|
const settings = { [field]: value };
|
|
const actor = req.session?.username || 'unknown';
|
|
const metadata = {
|
|
ip: req.ip,
|
|
userAgent: req.get('user-agent')
|
|
};
|
|
|
|
const result = saveSettings(settings, actor, metadata);
|
|
|
|
if (!result.success) {
|
|
return res.status(400).json({
|
|
error: 'Failed to save setting',
|
|
errors: result.errors
|
|
});
|
|
}
|
|
|
|
return res.json({
|
|
success: true,
|
|
message: `Setting ${field} updated successfully`
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error updating setting: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/settings/audit/history
|
|
* Get settings change history
|
|
*/
|
|
router.get('/audit/history', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const limit = Math.min(parseInt(req.query.limit) || 100, 1000);
|
|
const history = getChangeHistory(limit);
|
|
|
|
return res.json({
|
|
success: true,
|
|
count: history.length,
|
|
history
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error getting history: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/settings/audit/stats
|
|
* Get audit statistics
|
|
*/
|
|
router.get('/audit/stats', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const stats = getAuditStats();
|
|
|
|
return res.json({
|
|
success: true,
|
|
stats
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error getting stats: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/settings/audit/export
|
|
* Export audit log
|
|
*/
|
|
router.get('/audit/export', requireSuperAdmin, (req, res) => {
|
|
try {
|
|
const format = req.query.format || 'json';
|
|
const result = exportAuditLog(format);
|
|
|
|
if (!result) {
|
|
return res.status(500).json({
|
|
error: 'Failed to export audit log'
|
|
});
|
|
}
|
|
|
|
if (format === 'csv') {
|
|
res.setHeader('Content-Type', 'text/csv');
|
|
res.setHeader('Content-Disposition', 'attachment; filename="settings-audit.csv"');
|
|
return res.send(result.data);
|
|
}
|
|
|
|
return res.json({
|
|
success: true,
|
|
format: 'json',
|
|
count: result.count,
|
|
data: result.data
|
|
});
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error exporting audit: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/settings/test-connection/:service
|
|
* Test connection ke external service
|
|
*/
|
|
router.post('/test-connection/:service', requireSuperAdmin, async (req, res) => {
|
|
try {
|
|
const { service } = req.params;
|
|
let result = { success: false, message: '' };
|
|
|
|
switch (service) {
|
|
case 'genieacs':
|
|
result = await testGenieACSConnection();
|
|
break;
|
|
case 'mikrotik':
|
|
result = await testMikroTikConnection();
|
|
break;
|
|
default:
|
|
return res.status(400).json({
|
|
error: 'Unknown service'
|
|
});
|
|
}
|
|
|
|
return res.json(result);
|
|
} catch (error) {
|
|
logger.error(`[settings-api] Error testing connection: ${error.message}`);
|
|
return res.status(500).json({
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Test GenieACS connection
|
|
*/
|
|
async function testGenieACSConnection() {
|
|
try {
|
|
const axios = require('axios');
|
|
const url = getSetting('genieacs_url');
|
|
const username = getSetting('genieacs_username');
|
|
const password = getSetting('genieacs_password');
|
|
|
|
if (!url || !username || !password) {
|
|
return { success: false, message: 'GenieACS credentials not configured' };
|
|
}
|
|
|
|
const response = await axios.get(`${url}/devices`, {
|
|
auth: { username, password },
|
|
timeout: 5000
|
|
});
|
|
|
|
return { success: true, message: 'GenieACS connection successful' };
|
|
} catch (error) {
|
|
return { success: false, message: `GenieACS connection failed: ${error.message}` };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test MikroTik connection
|
|
*/
|
|
async function testMikroTikConnection() {
|
|
try {
|
|
const { RouterOSClient } = require('routeros-client');
|
|
const host = getSetting('mikrotik_host');
|
|
const user = getSetting('mikrotik_user');
|
|
const password = getSetting('mikrotik_password');
|
|
const port = getSetting('mikrotik_port') || 8728;
|
|
|
|
if (!host || !user || !password) {
|
|
return { success: false, message: 'MikroTik credentials not configured' };
|
|
}
|
|
|
|
const useTls = Number(port) === 8729 || getSetting('mikrotik_tls') === true;
|
|
const api = new RouterOSClient({
|
|
host,
|
|
port: Number(port),
|
|
user,
|
|
password,
|
|
tls: useTls ? { rejectUnauthorized: false } : false, // dukung self-signed cert (8729)
|
|
timeout: 5000
|
|
});
|
|
|
|
const client = await api.connect();
|
|
await client.close();
|
|
|
|
return { success: true, message: 'MikroTik connection successful' };
|
|
} catch (error) {
|
|
return { success: false, message: `MikroTik connection failed: ${error.message}` };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Tripay connection
|
|
*/
|
|
|
|
/**
|
|
* Test Midtrans connection
|
|
*/
|
|
|
|
module.exports = router;
|