Lewati ke konten utama

Keamanan dan Isolasi Workspace

Mekanisme keamanan yang menjaga batas antar-workspace (tenant) terdiri atas: autentikasi JWT, sesi perangkat, kebijakan kata sandi, pembatasan laju, batas peran dan izin, isolasi baris data (RLS), serta verifikasi tanda tangan webhook. Dalam dokumentasi produk, workspace adalah istilah utama; pada database dan API batas yang sama disebut account/account_id.

Tujuan

  • Memahami alur autentikasi: token JWT, sesi perangkat, dan kata sandi.
  • Mengetahui batas yang ditegakkan server: peran/izin, isolasi per-akun, dan proteksi brute-force.
  • Memverifikasi tanda tangan webhook masuk dan keluar dengan benar.
  • Mengetahui keterbatasan keamanan saat ini (misalnya tidak ada MFA) sebelum menaruh data sensitif.

Untuk siapa

  • Owner/admin yang mengelola pengguna, sesi, dan kebijakan keamanan akun.
  • Developer yang memanggil API atau membangun integrasi webhook.

Prasyarat

  • Aplikasi berjalan dengan JWT_SECRET (minimal 32 byte), JWT_ISSUER, dan JWT_AUDIENCE terkonfigurasi; aplikasi menolak mulai bila kunci JWT kosong.
  • Pengguna memiliki peran aktif (users.status='active' dan memberships.status='active').
  • Untuk widget dari situs eksternal: daftarkan origin pada CORS_ORIGINS (lihat Deployment dan runtime).

Langkah-langkah

1. Masuk dan memahami token JWT

  1. Masuk dengan email dan kata sandi pada halaman masuk.
  2. Server menerbitkan JWT dengan algoritma HS256 (algoritma lain ditolak), berisi klaim account_id, user_id, role, dan permissions, dengan issuer, audience, dan masa berlaku (8 jam) yang divalidasi pada setiap permintaan.
  3. Kirim token pada header Authorization: Bearer <token> untuk seluruh permintaan API terautentikasi.
  4. Token tidak dapat diperbarui (refresh) — setelah kedaluwarsa, masuk kembali.

2. Mengelola sesi perangkat

  1. Buka Profil → Sesi untuk melihat perangkat yang sedang masuk (GET /profile/sessions).
  2. Cabut sesi yang tidak dikenal dari daftar Anda (DELETE /profile/sessions/:id).
  3. Owner/admin dapat melihat sesi pengguna lain (GET /users/:id/sessions); sesi lintas-akun atau pengguna yang tidak dikenal mengembalikan 404 agar tidak membocorkan keberadaan akun.
  4. Server menyimpan hanya hash SHA-256 token dan jti — token mentah tidak pernah disimpan; mencabut sesi mencabut jti tersebut sehingga token yang beredar langsung ditolak (revoked_at IS NULL AND expires_at > now()).

3. Mengubah kata sandi

  1. Buka pengaturan profil → ubah kata sandi.
  2. Masukkan kata sandi saat ini — server memverifikasinya (bcrypt) sebelum mengizinkan perubahan.
  3. Kata sandi baru harus 8–72 byte; disimpan sebagai hash bcrypt (DefaultCost), tidak pernah sebagai teks mentah.
  4. Pastikan kata sandi tidak dipakai ulang lintas layanan; tidak ada alur "lupa kata sandi" yang mengirim tautan reset.

4. Memahami proteksi brute-force dan pembatasan laju

  1. Kegagalan masuk dibatasi 5 percobaan per 5 menit per IP, lalu akun/IP terkunci 15 menit; respons menyertakan header Retry-After.
  2. Permintaan terautentikasi dibatasi 300/menit per akun (atau per IP); jalur webhook masuk dikecualikan karena sudah diverifikasi HMAC dan dideduplikasi.
  3. Pembuatan sesi widget memiliki proteksi terpisah (10 percobaan per 5 menit, terkunci 10 menit; lihat Widget Situs Web).

5. Memahami batas peran dan izin

  1. Peran yang tersedia: owner, admin, agent, dan viewer. Owner/admin memegang izin kelola akun (manage_account, manage_users, export_data, view_audit_log, manage_webhooks, dan lain-lain); agent bekerja dalam lingkup tim; viewer hanya membaca.
  2. Izin diperiksa di server pada setiap permintaan (middleware RequireRole/RequirePermission) dan gagal dengan 403 FORBIDDEN — bukan sekadar disembunyikan di antarmuka.
  3. Klaim peran/izin dibawa dalam JWT dan dimuat saat masuk dari tabel role_permissions/permissions; perubahan peran berlaku pada masuk berikutnya.

6. Memahami isolasi workspace (RLS)

  1. Setiap tabel berisi data akun diberlakukan Row-Level Security (ENABLE + FORCE RLS) dengan kebijakan USING (account_id = current_account_id()).
  2. Pada setiap transaksi, server menjalankan SET LOCAL app.account_id dan app.user_id dari klaim token yang sudah divalidasi; fungsi current_account_id()/current_user_id() membacanya, sehingga kueri apa pun hanya menjangkau baris akun yang sedang aktif.
  3. Jalur yang tidak diautentikasi (misalnya webhook masuk) memakai konteks penyewa eksplisit (dbconn.WithTenantContext) — bukan "tanpa akun".
  4. Untuk menguji isolasi antar-penyewa secara langsung, ikuti langkah verifikasi di data-governance.md (endpoint /data-governance/verify-isolation).

7. Memahami isolasi WAHA multi-session

  1. Satu workspace menyimpan satu set Base URL, API key, dan webhook secret WAHA yang terenkripsi pada konfigurasi milik workspace.
  2. Satu konfigurasi tersebut dapat mengakses banyak WAHA session. Setiap session diikat ke satu inbox WhatsApp dalam workspace yang sama.
  3. Resolver webhook mencari session yang tepat sebelum menetapkan account_id dan inbox_id; setelah itu seluruh penulisan dilakukan dalam transaksi RLS workspace tersebut.
  4. Banyak user dalam workspace boleh memakai session yang sama jika memiliki akses ke inbox terkait. Ini adalah sharing di dalam workspace, bukan sharing credential lintas tenant.
  5. Session WAHA yang sama tidak boleh didaftarkan ke dua workspace. Custelio belum mendukung shared channel lintas-workspace; gunakan session dan credential yang berbeda untuk menjaga routing serta audit tetap deterministik.

8. Memverifikasi tanda tangan webhook

Webhook masuk (dari penyedia, misalnya WAHA):

  1. Penyedia menandatangani body mentah dengan HMAC (SHA-256 default, SHA-512 tersedia) menggunakan rahasia per-akun.
  2. Header yang dikirim: X-Webhook-Hmac (nilai hex) dan X-Webhook-Hmac-Algorithm.
  3. Server membandingkan tanda tangan dengan perbandingan waktu-konstan; rahasia kosong atau akun kosong → 401. Event dideduplikasi berdasarkan ID event; tipe event yang tidak dikenal dijawab 200 "ignored" tanpa percobaan ulang.
  4. Untuk integrasi Anda sendiri yang menerima panggilan balik, gunakan pola yang sama: verifikasi HMAC atas body mentah, jangan pernah memercayai body tanpa tanda tangan. Lihat Webhook masuk dan keluar.

Webhook keluar (langganan acara domain):

  1. Buat langganan dengan endpoint dan rahasia penanda tangan; rahasia disimpan terenkripsi dan tidak pernah dikembalikan lewat jalur baca HTTP mana pun.
  2. Pengiriman menyertakan header X-Webhook-Signature berbentuk sha256=<hex HMAC-SHA256 dari body>.
  3. Status pengiriman: 2xx dianggap sukses; kegagalan masuk antrean mati (DLQ) setelah anggaran percobaan ulang habis; pengiriman dapat diputar ulang dari daftar pengiriman.
  4. Verifikasi header ini di penerima Anda sebelum memproses payload.

9. Menjaga privasi dan jejak audit

  1. Jangan mencatat (log) token mentah, kata sandi, rahasia penanda tangan, atau PII di aplikasi klien atau skrip Anda sendiri — server tidak menyimpannya dalam bentuk tersebut.
  2. Gunakan token platform (prefiks crma_) hanya pada integrasi server-side dengan scope minimum; jangan pernah mengirim token admin ke browser.
  3. Endpoint publik widget (bootstrap sesi, konfigurasi) sengaja tidak diautentikasi; lindungi halaman sematan dan batasi paparan data publik (lihat Widget Situs Web).

Tanda berhasil

  • Login gagal berulang dari IP yang sama ditolak dengan 429 dan Retry-After; login sah tetap berhasil setelah jendela berakhir.
  • Sesi yang dicabut langsung kehilangan akses; daftar sesi tidak pernah menampilkan token atau hash.
  • Pengguna dengan peran viewer tidak dapat melakukan perubahan; permintaan tanpa izin menerima 403.
  • Kueri lintas-akun (misalnya mencoba membaca baris akun lain lewat API) tidak mengembalikan data apa pun.
  • Penerima webhook dapat memverifikasi X-Webhook-Signature/X-Webhook-Hmac dan menolak payload tanpa tanda tangan yang sah.

Jika terjadi masalah

  • 401 token tidak valid/kedaluwarsa/dicabut: token salah, lewat 8 jam, atau sesinya dicabut. Masuk kembali untuk memperoleh token baru.
  • 403 FORBIDDEN: peran tidak memiliki izin yang diminta; minta owner/admin menyesuaikan peran, atau gunakan akun dengan izin sesuai.
  • 429 dengan Retry-After: terkena pembatasan laju atau kunci brute-force; tunggu hingga jendela berakhir sebelum mencoba lagi.
  • 422 aturan kata sandi: kata sandi baru di luar 8–72 byte, atau kata sandi saat ini salah; perbaiki lalu ulangi.

Batasan

  • Tidak ada MFA/TOTP/2FA — autentikasi hanya email+kata sandi dengan sesi JWT; pertimbangkan lapisan tambahan untuk akun berisiko tinggi.
  • Tidak ada alur "lupa kata sandi"/reset berbasis tautan; pemulihan akun bergantung pada admin.
  • Penyimpanan brute-force bersifat in-memory per instance: pencacah hilang saat proses dimulai ulang dan tidak dibagi antar-instance; pada penempatan multi-instance, gunakan pembatas di lapisan lain bila perlu.
  • Token tidak dapat diperbarui (refresh); kedaluwarsa 8 jam mengharuskan masuk ulang.
  • Penandatanganan webhook keluar (X-Webhook-Signature) dan detail pengiriman/DLQ tersedia pada implementasi saat ini; dokumentasi pengembang menandai cakupan tertentu limited — lihat Webhook masuk dan keluar untuk statusnya.
  • CORS adalah kontrol peramban, bukan otorisasi API; klien non-peramban tidak terikat daftar origin.
  • Perubahan peran/izin baru berlaku pada masuk berikutnya (klaim di dalam JWT).
  • Detail penetapan izin per peran dapat berubah; lihat sumber kebenaran di apps/api/internal/security untuk daftar terkini.

Tugas terkait