fix(webhook): auto secret token + URL dari public base/host
All checks were successful
CI / test (push) Successful in 2m5s

- ensureWebhookSecret saat boot dan buka Pengaturan
- URL webhook memakai public_base_url fallback host
- tolak default billing-rtrw-secret-key
- versi 0.6.3
This commit is contained in:
oxpoe
2026-11-06 04:55:12 +07:00
parent 6f6960ce42
commit b72dbf3e55
8 changed files with 29 additions and 9 deletions

View File

@@ -2,6 +2,13 @@
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
## [0.6.3] — 2026-11-06 — "webhook-secret-auto"
### Diperbaiki
- **Secret Token webhook pembayaran** dibuat otomatis (64 hex) bila kosong/default, ditampilkan di Pengaturan dan langsung dipakai pada URL webhook.
- URL webhook memakai **Public Base URL**; bila kosong memakai host saat ini (tidak lagi menghasilkan URL relatif).
- Default lama `billing-rtrw-secret-key` tidak lagi diterima — forwarder harus memakai secret dari Pengaturan.
## [0.6.2] — 2026-11-06 — "webhook-diag"
### Diperbaiki

View File

@@ -11,7 +11,7 @@
<p align="center">
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
<img src="https://img.shields.io/badge/versi-0.6.2_webhook--diag-ADFF2F" alt="Versi 0.6.2 (webhook-diag)">
<img src="https://img.shields.io/badge/versi-0.6.3_webhook--secret-ADFF2F" alt="Versi 0.6.3 (webhook-secret-auto)">
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>

View File

@@ -61,6 +61,7 @@ const { SUPPORTED_LANGS, FALLBACK_LANG, normalizeLang, t } = require('./config/i
if (typeof ensureDefaultSettings === 'function') {
ensureDefaultSettings();
try { require('./config/settingsManager').ensureGowaWebhookSecret(); } catch (e) {}
try { require('./config/settingsManager').ensureWebhookSecret(); } catch (e) {}
// Proses baru = kode terbaru sudah dimuat; hapus penanda restart.
try { fs.rmSync(path.join(__dirname, '.restart-required'), { force: true }); } catch (e) {}
}
@@ -901,7 +902,7 @@ app.post('/api/webhook/v1/payment-notif', multer().any(), async (req, res) => {
gotTrim = gotTrim.replace(/^[\"'\s]+|[\"'\s]+$/g, '');
if (/^bearer\s+/i.test(gotTrim)) gotTrim = gotTrim.replace(/^bearer\s+/i, '').trim();
const isSecretValid = (gotTrim === expectedTrim) || (gotTrim === 'billing-rtrw-secret-key');
const isSecretValid = gotTrim === expectedTrim;
if (!isSecretValid) {
logger.warn(`[WEBHOOK][payment-notif] Forbidden: secret_key mismatch. service=${String(service || '-')} (gotLen=${gotTrim.length}${gotTrim ? ', ***' + gotTrim.slice(-3) : ', empty'}, expectedLen=${expectedTrim.length}, ***${expectedTrim.slice(-3)})`);

View File

@@ -282,6 +282,14 @@ function formatTimeLocal(date) {
* Pastikan gowa_webhook_secret tidak memakai default 'secret'.
* Dibuat otomatis (32 byte hex) bila kosong/default/terlalu pendek.
*/
function ensureWebhookSecret(force) {
const current = String(getSetting('webhook_secret', '') || '').trim();
if (!force && current && current !== 'billing-rtrw-secret-key' && current.length >= 24) return current;
const secret = crypto.randomBytes(32).toString('hex');
saveSettings({ webhook_secret: secret });
return secret;
}
function ensureGowaWebhookSecret(force) {
const current = String(getSetting('gowa_webhook_secret', '') || '').trim();
if (!force && current && current !== 'secret' && current.length >= 24) return current;
@@ -374,5 +382,6 @@ module.exports = {
parseDateInTimezone,
startSettingsWatcher,
ensureDefaultSettings,
ensureGowaWebhookSecret
ensureGowaWebhookSecret,
ensureWebhookSecret
};

View File

@@ -1,6 +1,6 @@
{
"name": "open-isp",
"version": "0.6.2",
"version": "0.6.3",
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
"main": "app-customer.js",
"scripts": {

View File

@@ -4490,15 +4490,17 @@ router.post('/automation/send-now', requireAdminSession, express.urlencoded({ ex
});
router.get('/settings', requireAdminSession, requireSidebarMenuAccess('settings'), (req, res) => {
const settings = getSettings();
let settings = getSettings();
try { require('../config/settingsManager').ensureWebhookSecret(); settings = getSettings(); } catch (e) {}
const protocol = req.headers['x-forwarded-proto'] || req.protocol;
const host = req.get('host');
const baseUrl = (settings && settings.app_url ? String(settings.app_url) : `${protocol}://${host}`).replace(/\/+$/, '');
const baseUrl = (settings.public_base_url || (settings && settings.app_url) || `${protocol}://${host}`).toString().replace(/\/+$/, '');
const paymentWebhookUrl = `${baseUrl}/customer/payment/callback`;
res.render('admin/settings', {
title: 'Pengaturan Sistem', company: company(), activePage: 'settings',
settings, msg: flashMsg(req),
paymentWebhookUrl
paymentWebhookUrl,
webhookBaseUrl: baseUrl
});
});

View File

@@ -1 +1 @@
0.6.2
0.6.3

View File

@@ -815,7 +815,7 @@
<div style="margin-top: 15px; border-top: 1px dashed rgba(0,0,0,0.1); padding-top: 15px;">
<h6 style="font-size:11px; margin-bottom: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px;"><i class="bi bi-phone"></i> Hubungkan Webhook Notifikasi E-Wallet (Mode 2)</h6>
<%
const _base = (settings.public_base_url || '').replace(/\/+$/, '');
const _base = String(typeof webhookBaseUrl !== 'undefined' && webhookBaseUrl ? webhookBaseUrl : (settings.public_base_url || '')).replace(/\/+$/, '');
const _secret = String(settings.webhook_secret || '');
const macroWebhookUrlFull = _base + '/api/webhook/v1/payment-notif?secret_key=' + encodeURIComponent(_secret);
%>
@@ -841,6 +841,7 @@
<option value="false" <%= (settings.webhook_unmatched_notify_enabled === undefined ? true : settings.webhook_unmatched_notify_enabled) ? '' : 'selected' %>><%= t('common.inactive','Nonaktif') %></option>
</select>
<div class="hint"><%= t('admin.set.wh_wa_hint','Kirim WA ke admin tiap ada pembayaran terdeteksi tapi belum cocok (untuk dicocokkan manual via jam notif vs bukti transfer).') %></div>
<div class="hint" style="margin-top:6px"><i class="bi bi-info-circle"></i> Secret dibuat otomatis jika kosong. URL memakai <b>Public Base URL</b>; bila kosong memakai host saat ini. Salin URL + token ke aplikasi notification forwarder.</div>
</div>
<div class="fg" style="margin-top:10px">
<label class="fl"><i class="bi bi-clock-history"></i> <%= t('admin.set.wh_retention','Retensi Notifikasi Webhook (Otomasi)') %></label>