diff --git a/.env.example b/.env.example index a2e9115..5b471c2 100644 --- a/.env.example +++ b/.env.example @@ -39,9 +39,8 @@ LOG_TO_CONSOLE=true # Max number of recent log entries kept in memory for GET /api/logs LOG_BUFFER_SIZE=1000 -# Exposes GET /api/logs, /api/logs/files, /api/logs/files/:filename, /api/logs/view. -# WARNING: defaults to true if unset — set to false unless you also configure -# LOG_BASIC_AUTH_USER/PASS below. +# Exposes GET /api/logs, /api/logs/files, /api/logs/files/:filename, /api/logs/view, +# /openapi.json, /docs. Defaults to false (closed) when unset — opt in explicitly. LOG_EXPOSE_API=false # Required in production to protect the log endpoints (HTTP Basic Auth, browser-native prompt). # Leaving these empty only works in non-production NODE_ENV. diff --git a/server/README.md b/server/README.md index ec65c90..8dbe465 100644 --- a/server/README.md +++ b/server/README.md @@ -79,7 +79,7 @@ ERP_ENABLE_NOTIF=true ### Logging & Admin Access ```env LOG_LEVEL=info -LOG_EXPOSE_API=false # WARNING: default true kalau tidak di-set +LOG_EXPOSE_API=false # default sudah tertutup (false) kalau tidak di-set LOG_BASIC_AUTH_USER= # wajib diisi di production untuk lindungi /api/logs* dan /docs LOG_BASIC_AUTH_PASS= LOG_RETENTION_DAYS=30 @@ -260,7 +260,7 @@ Paling nyaman dibaca lewat `GET /api/logs/view` (filter level, cari teks, klik n 1. **Signature Verification** — webhook, token payment link, notifikasi ERP semuanya ditandatangani/diverifikasi. 2. **Idempotency** — cegah duplikasi order/notifikasi ERP; state disimpan konsisten pakai `order_id` yang sudah disanitasi (Midtrans-safe) di semua map internal (`activeOrders`, `notifiedOrders`, `orderRetryCount`, `orderMerchantId`), sehingga tidak ada mismatch walau `order_id` asli mengandung karakter seperti `:`. 3. **API Key Authentication** — `X-API-KEY` untuk `/createtransaksi` dan `POST /api/payment-links`. Dev mode fallback (bypass) hanya aktif kalau `NODE_ENV !== 'production'` **dan** key belum di-set. -4. **HTTP Basic Auth untuk admin tooling** — `/api/logs*`, `/openapi.json`, `/docs` dilindungi `LOG_BASIC_AUTH_USER`/`PASS`. **Wajib di-set di production** — endpoint ini terbuka penuh secara default (`LOG_EXPOSE_API` default `true`) kalau tidak dikonfigurasi. +4. **HTTP Basic Auth untuk admin tooling** — `/api/logs*`, `/openapi.json`, `/docs` dilindungi `LOG_BASIC_AUTH_USER`/`PASS`, dan tertutup total secara default lewat `LOG_EXPOSE_API=false` (harus di-set eksplisit `true` untuk mengaktifkan). **Wajib set `LOG_BASIC_AUTH_USER`/`PASS` di production** sebelum mengaktifkan `LOG_EXPOSE_API`, karena gerbang Basic Auth-nya sendiri bergantung pada `NODE_ENV=production` untuk benar-benar dipaksa (lihat poin 8). 5. **Rate Limiting** — `/createtransaksi`, `/api/payment-links`, `/api/payments/charge`, `/api/payments/snap/token` dibatasi per-IP (`RATE_LIMIT_MAX` request per `RATE_LIMIT_WINDOW_MS`) untuk mencegah bot abuse / card-testing pada endpoint yang memang harus tetap bisa diakses langsung dari browser tanpa API key. 6. **CORS** — dibatasi lewat `CORS_ALLOWED_ORIGINS` (comma-separated). Kosong = izinkan semua origin (default dev). 7. **Payload Masking** — field sensitif (card number, CVV, token, server key) otomatis disamarkan di log. diff --git a/server/index.cjs b/server/index.cjs index fe8d2b7..29634a7 100644 --- a/server/index.cjs +++ b/server/index.cjs @@ -214,7 +214,9 @@ const notifiedOrders = watchedSet(PERSISTED_STATE.notifiedOrders) const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase() const levelOrder = { debug: 0, info: 1, warn: 2, error: 3 } -const LOG_EXPOSE_API = parseEnable(process.env.LOG_EXPOSE_API) +// Secure-by-default: unlike the other parseEnable() flags below (which default to +// enabled), an unset LOG_EXPOSE_API must default to CLOSED, not open. +const LOG_EXPOSE_API = parseEnable(process.env.LOG_EXPOSE_API ?? 'false') const LOG_BUFFER_SIZE = parseInt(process.env.LOG_BUFFER_SIZE || '1000', 10) const LOG_TO_FILE = parseEnable(process.env.LOG_TO_FILE ?? 'true') // Default enabled const LOG_TO_CONSOLE = parseEnable(process.env.LOG_TO_CONSOLE ?? 'false') // Default disabled