feat(gowa): device id kosong ikut device terhubung + auto webhook secret
All checks were successful
CI / test (push) Successful in 1m45s

- hapus auto-pin gowa_device_id pada QR/status
- hint device id + secret otomatis
- ensureGowaWebhookSecret saat boot & simpan pengaturan
- versi 0.5.18
This commit is contained in:
oxpoe
2026-11-06 03:50:06 +07:00
parent fadbf0084b
commit ad62aab07d
8 changed files with 37 additions and 13 deletions

View File

@@ -2,6 +2,12 @@
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/). Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
## [0.5.18] — 2026-11-06 — "gowa-autodevice"
### Diperbaiki
- **Device ID kosong** = otomatis mengikuti device yang terhubung lewat QR dashboard (tidak lagi dipin/disimpan otomatis). Isi Device ID hanya untuk menunjuk device GOWA tertentu.
- **Webhook secret GOWA** dibuat otomatis (32 byte hex) bila kosong atau masih default `secret`, lalu ditampilkan di pengaturan untuk disamakan dengan `WHATSAPP_WEBHOOK_SECRET` di GOWA.
## [0.5.17] — 2026-11-06 — "gowa-connected" ## [0.5.17] — 2026-11-06 — "gowa-connected"
### Diperbaiki ### Diperbaiki

View File

@@ -11,7 +11,7 @@
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC"> <img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
<img src="https://img.shields.io/badge/versi-0.5.17_gowa--connected-ADFF2F" alt="Versi 0.5.17 (gowa-connected)"> <img src="https://img.shields.io/badge/versi-0.5.18_gowa--autodevice-ADFF2F" alt="Versi 0.5.18 (gowa-autodevice)">
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20"> <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"> <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> <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

@@ -60,6 +60,7 @@ const { SUPPORTED_LANGS, FALLBACK_LANG, normalizeLang, t } = require('./config/i
// Pastikan semua default settings ada (untuk migrasi/update dari Gitea) // Pastikan semua default settings ada (untuk migrasi/update dari Gitea)
if (typeof ensureDefaultSettings === 'function') { if (typeof ensureDefaultSettings === 'function') {
ensureDefaultSettings(); ensureDefaultSettings();
try { require('./config/settingsManager').ensureGowaWebhookSecret(); } catch (e) {}
} }
// Inisialisasi aplikasi Express // Inisialisasi aplikasi Express

View File

@@ -278,6 +278,18 @@ function formatTimeLocal(date) {
* Memastikan semua setting wajib ada di settings.json dengan default value. * Memastikan semua setting wajib ada di settings.json dengan default value.
* Dijalankan saat startup aplikasi untuk migrasi/inisialisasi setting baru. * Dijalankan saat startup aplikasi untuk migrasi/inisialisasi setting baru.
*/ */
/**
* Pastikan gowa_webhook_secret tidak memakai default 'secret'.
* Dibuat otomatis (32 byte hex) bila kosong/default/terlalu pendek.
*/
function ensureGowaWebhookSecret(force) {
const current = String(getSetting('gowa_webhook_secret', '') || '').trim();
if (!force && current && current !== 'secret' && current.length >= 24) return current;
const secret = crypto.randomBytes(32).toString('hex');
saveSettings({ gowa_webhook_secret: secret });
return secret;
}
function ensureDefaultSettings() { function ensureDefaultSettings() {
try { try {
const currentSettings = getSettings(); const currentSettings = getSettings();
@@ -361,5 +373,6 @@ module.exports = {
getNowLocalISO, getNowLocalISO,
parseDateInTimezone, parseDateInTimezone,
startSettingsWatcher, startSettingsWatcher,
ensureDefaultSettings ensureDefaultSettings,
ensureGowaWebhookSecret
}; };

View File

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

View File

@@ -6738,7 +6738,7 @@ router.get('/whatsapp/gowa/qr', requireAdminSession, async (req, res) => {
try { try {
const gowa = require('../services/gowaWhatsappService'); const gowa = require('../services/gowaWhatsappService');
const dev = await gowa.ensureDevice(); const dev = await gowa.ensureDevice();
if (dev && dev !== getSetting('gowa_device_id', '')) saveSettings({ gowa_device_id: dev }); // Jangan simpan otomatis: device id kosong = ikuti device yang terhubung via QR.
const r = await gowa.getLoginQr(dev); const r = await gowa.getLoginQr(dev);
if (!r.ok) { if (!r.ok) {
const detail = r.message || r.code || 'pastikan GOWA berjalan & username/password Basic Auth benar'; const detail = r.message || r.code || 'pastikan GOWA berjalan & username/password Basic Auth benar';
@@ -6782,13 +6782,15 @@ router.get('/whatsapp', requireAdminSession, requireSidebarMenuAccess('whatsapp'
fonnte_country_code: getSetting('fonnte_country_code', '62') fonnte_country_code: getSetting('fonnte_country_code', '62')
}; };
const gowaBaseUrl = getSetting('gowa_base_url', 'http://127.0.0.1:3000'); const gowaBaseUrl = getSetting('gowa_base_url', 'http://127.0.0.1:3000');
let gowaWebhookSecret = getSetting('gowa_webhook_secret', '');
try { gowaWebhookSecret = require('../config/settingsManager').ensureGowaWebhookSecret(); } catch (e) {}
const gowaSettings = { const gowaSettings = {
gowa_mode: getSetting('gowa_mode', /^https:/i.test(String(gowaBaseUrl)) ? 'cloud' : 'local'), gowa_mode: getSetting('gowa_mode', /^https:/i.test(String(gowaBaseUrl)) ? 'cloud' : 'local'),
gowa_base_url: gowaBaseUrl, gowa_base_url: gowaBaseUrl,
gowa_username: getSetting('gowa_username', ''), gowa_username: getSetting('gowa_username', ''),
gowa_password: getSetting('gowa_password', ''), gowa_password: getSetting('gowa_password', ''),
gowa_device_id: getSetting('gowa_device_id', ''), gowa_device_id: getSetting('gowa_device_id', ''),
gowa_webhook_secret: getSetting('gowa_webhook_secret', 'secret') gowa_webhook_secret: gowaWebhookSecret
}; };
const gowaWebhookUrl = String(getSetting('app_url', '') || ((req.headers['x-forwarded-proto'] || req.protocol) + '://' + req.get('host'))).replace(/\/+$/, '') + '/api/webhook/gowa'; const gowaWebhookUrl = String(getSetting('app_url', '') || ((req.headers['x-forwarded-proto'] || req.protocol) + '://' + req.get('host'))).replace(/\/+$/, '') + '/api/webhook/gowa';
const gowaUiHost = String(req.get('host') || '').split(':')[0]; const gowaUiHost = String(req.get('host') || '').split(':')[0];
@@ -6844,7 +6846,10 @@ router.post('/whatsapp/gateway-settings', requireAdminSession, express.urlencode
gowa_username: String(gowa_username || '').trim(), gowa_username: String(gowa_username || '').trim(),
gowa_password: String(gowa_password || '').trim(), gowa_password: String(gowa_password || '').trim(),
gowa_device_id: String(gowa_device_id || '').trim(), gowa_device_id: String(gowa_device_id || '').trim(),
gowa_webhook_secret: String(gowa_webhook_secret || 'secret').trim() gowa_webhook_secret: (function () {
const v = String(gowa_webhook_secret || '').trim();
return (v && v !== 'secret' && v.length >= 24) ? v : crypto.randomBytes(32).toString('hex');
})()
}); });
if ((wa_gateway_type || 'baileys') === 'baileys') { if ((wa_gateway_type || 'baileys') === 'baileys') {
@@ -7400,10 +7405,7 @@ router.get('/api/whatsapp/status', requireAdmin, async (req, res) => {
const isConn = !!(pick.is_connected || pick.connected || pick.state === 'connected'); const isConn = !!(pick.is_connected || pick.connected || pick.state === 'connected');
const conn = isConn ? 'open' : (loggedIn ? 'connecting' : 'close'); const conn = isConn ? 'open' : (loggedIn ? 'connecting' : 'close');
const deviceId = String(pick.device_id || pick.id || '').trim(); const deviceId = String(pick.device_id || pick.id || '').trim();
// Simpan otomatis device id agar status berikutnya memakai /devices/:id/status. // device id kosong = selalu ikuti device yang terhubung (tidak dipin otomatis)
if (deviceId && !String(getSetting('gowa_device_id', '') || '').trim()) {
try { saveSettings({ gowa_device_id: deviceId }); } catch (e) {}
}
res.json({ res.json({
connection: conn, gateway: 'gowa', loggedIn: loggedIn, deviceId: deviceId, connection: conn, gateway: 'gowa', loggedIn: loggedIn, deviceId: deviceId,
user: { id: deviceId || 'gowa_gateway', name: loggedIn ? 'GOWA (WhatsApp)' : 'GOWA Gateway' }, user: { id: deviceId || 'gowa_gateway', name: loggedIn ? 'GOWA (WhatsApp)' : 'GOWA Gateway' },

View File

@@ -1 +1 @@
0.5.17 0.5.18

View File

@@ -181,7 +181,8 @@
</div> </div>
<div class="fg" style="flex:1;"> <div class="fg" style="flex:1;">
<label class="fl"><%= t('admin.wa.ph_deviceid','Device ID (opsional)') %></label> <label class="fl"><%= t('admin.wa.ph_deviceid','Device ID (opsional)') %></label>
<input class="fc" name="gowa_device_id" value="<%= gowaSettings.gowa_device_id %>" placeholder="multi-device (mis. device1)"> <input class="fc" name="gowa_device_id" value="<%= gowaSettings.gowa_device_id %>" placeholder="kosongkan = ikuti device yang terhubung">
<div style="font-size:11px;color:var(--muted);margin-top:4px">Kosongkan untuk otomatis mengikuti device yang login via QR. Isi hanya bila ingin menunjuk device GOWA tertentu.</div>
</div> </div>
</div> </div>
<div class="form-row mb12"> <div class="form-row mb12">
@@ -219,7 +220,8 @@
</div> </div>
<div class="fg" style="flex:1;"> <div class="fg" style="flex:1;">
<label class="fl">Webhook Secret (HMAC)</label> <label class="fl">Webhook Secret (HMAC)</label>
<input class="fc" name="gowa_webhook_secret" value="<%= gowaSettings.gowa_webhook_secret %>" placeholder="secret"> <input class="fc" name="gowa_webhook_secret" value="<%= gowaSettings.gowa_webhook_secret %>" placeholder="otomatis dibuat">
<div style="font-size:11px;color:var(--muted);margin-top:4px">Dibuat otomatis (32 byte). Samakan dengan <code>WHATSAPP_WEBHOOK_SECRET</code> di GOWA.</div>
</div> </div>
</div> </div>
</div> </div>