fix(webhook): auto secret token + URL dari public base/host
All checks were successful
CI / test (push) Successful in 2m5s
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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)})`);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.2
|
||||
0.6.3
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user