OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
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
This commit is contained in:
commit
eaf05f2811
280 files changed
+123975
No files matched your search
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in new issue
Block a user