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,155 @@
|
||||
/**
|
||||
* Settings Middleware
|
||||
* Untuk access control & validation pada settings endpoints
|
||||
*/
|
||||
const { logger } = require('../config/logger');
|
||||
const { validateSettings } = require('../config/settingsValidator');
|
||||
const { getMaskedSettings } = require('../config/settingsEncryption');
|
||||
|
||||
/**
|
||||
* Require Super Admin untuk akses settings
|
||||
*/
|
||||
function requireSuperAdmin(req, res, next) {
|
||||
if (req.session?.isAdmin && req.session?.isSuperAdmin) {
|
||||
return next();
|
||||
}
|
||||
|
||||
logger.warn(`[settings] Unauthorized settings access attempt from ${req.ip}`);
|
||||
return res.status(403).json({
|
||||
error: 'Forbidden - Super Admin access required'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate settings sebelum save
|
||||
*/
|
||||
function validateSettingsMiddleware(req, res, next) {
|
||||
try {
|
||||
let settings = req.body;
|
||||
|
||||
// Normalize boolean fields dari form submission
|
||||
const booleanFields = [
|
||||
'whatsapp_enabled',
|
||||
'telegram_enabled',
|
||||
'tripay_enabled',
|
||||
'midtrans_enabled',
|
||||
'xendit_enabled',
|
||||
'duitku_enabled',
|
||||
'auto_backup_enabled',
|
||||
'login_otp_enabled'
|
||||
];
|
||||
|
||||
booleanFields.forEach(field => {
|
||||
if (field in settings) {
|
||||
const value = settings[field];
|
||||
if (typeof value === 'string') {
|
||||
settings[field] = value === 'true' || value === '1' || value === 'on';
|
||||
} else if (value === undefined || value === null) {
|
||||
settings[field] = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Trim string fields yang sensitive terhadap whitespace
|
||||
const stringTrimFields = ['office_lat', 'office_lng', 'company_phone', 'company_email'];
|
||||
stringTrimFields.forEach(field => {
|
||||
if (field in settings && typeof settings[field] === 'string') {
|
||||
settings[field] = settings[field].trim();
|
||||
}
|
||||
});
|
||||
|
||||
// Validate
|
||||
const validation = validateSettings(settings);
|
||||
if (!validation.valid) {
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
errors: validation.errors
|
||||
});
|
||||
}
|
||||
|
||||
// Attach validated settings ke request
|
||||
req.validatedSettings = settings;
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.error(`[settings] Validation error: ${error.message}`);
|
||||
return res.status(500).json({
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask sensitive values dalam response
|
||||
*/
|
||||
function maskSensitiveValues(req, res, next) {
|
||||
// Override res.json untuk mask sensitive values
|
||||
const originalJson = res.json.bind(res);
|
||||
|
||||
res.json = function(data) {
|
||||
if (data && data.settings) {
|
||||
data.settings = getMaskedSettings(data.settings);
|
||||
}
|
||||
return originalJson(data);
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log settings access
|
||||
*/
|
||||
function logSettingsAccess(req, res, next) {
|
||||
const originalJson = res.json.bind(res);
|
||||
|
||||
res.json = function(data) {
|
||||
if (req.method === 'POST' || req.method === 'PUT') {
|
||||
logger.info(`[settings] Settings ${req.method} by ${req.session?.username || 'unknown'}`, {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent')
|
||||
});
|
||||
}
|
||||
return originalJson(data);
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limiting untuk settings endpoints
|
||||
*/
|
||||
function rateLimitSettings(req, res, next) {
|
||||
// Simple rate limiting: max 10 requests per minute per IP
|
||||
const key = `settings:${req.ip}`;
|
||||
const store = req.app.locals.rateLimitStore || new Map();
|
||||
|
||||
if (!store.has(key)) {
|
||||
store.set(key, { count: 1, resetTime: Date.now() + 60000 });
|
||||
req.app.locals.rateLimitStore = store;
|
||||
return next();
|
||||
}
|
||||
|
||||
const limit = store.get(key);
|
||||
if (Date.now() > limit.resetTime) {
|
||||
limit.count = 1;
|
||||
limit.resetTime = Date.now() + 60000;
|
||||
return next();
|
||||
}
|
||||
|
||||
if (limit.count >= 10) {
|
||||
logger.warn(`[settings] Rate limit exceeded for ${req.ip}`);
|
||||
return res.status(429).json({
|
||||
error: 'Too many requests - please try again later'
|
||||
});
|
||||
}
|
||||
|
||||
limit.count++;
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requireSuperAdmin,
|
||||
validateSettingsMiddleware,
|
||||
maskSensitiveValues,
|
||||
logSettingsAccess,
|
||||
rateLimitSettings
|
||||
};
|
||||
Reference in new issue
Block a user