Model Keamanan
Catera memberlakukan otorisasi di dua lapis independen: lapis frontend (mencegah navigasi ke halaman yang tidak relevan) dan lapis database (mencegah mutasi data lewat Row Level Security dan RPC, terlepas dari apa yang ditampilkan UI). Keduanya harus diasumsikan bisa dilewati sendiri- sendiri — UI tidak boleh jadi satu-satunya penjaga.
Role kanonik
Section titled “Role kanonik”| Role kanonik | Alias lama yang dipetakan |
|---|---|
pemilik |
owner, admin |
manager |
staff |
kasir |
— |
produksi |
staff_produksi |
kurir |
driver |
staf |
— |
gudang |
pengemasan |
Pemetaan alias ke role kanonik didefinisikan dua kali dengan kontrak yang
sama — di frontend (normalizeUserRole(), src/lib/roles.ts) dan di
database (permission_normalized_role(),
supabase/migrations/0075_role_permissions.sql) — agar keputusan akses
konsisten walau diambil dari sisi mana pun.
Lapis 1 — Role-gating di frontend
Section titled “Lapis 1 — Role-gating di frontend”RouteGuard menolak render halaman jika role (setelah dinormalisasi) tidak
ada di daftar ROUTE_ACCESS untuk path tersebut, dan mengarahkan pengguna
ke halaman landing sesuai rolenya. Ini murni soal pengalaman navigasi —
mencegah pengguna “nyasar” ke halaman yang tidak relevan dengan tugasnya.
Lihat matriks lengkapnya di Matriks Peran & Hak Akses.
Lapis 2 — Row Level Security & RPC di Postgres
Section titled “Lapis 2 — Row Level Security & RPC di Postgres”Setiap tabel sensitif mengaktifkan enable row level security. Alih-alih
menulis kondisi role berulang di tiap policy, Catera memusatkan pengecekan
ke fungsi security definer yang dipanggil dari dalam policy maupun dari
RPC mutasi:
is_owner()— benar jika profil aktif dengan role pemilik/owner/admin.is_active_user()— benar jika profil berstatusactivedan role bukanpending.can_manage_<domain>()— satu fungsi per domain (mis.can_manage_hr(),can_manage_finance(),can_manage_inventory()) yang mengecek role mana saja yang boleh menulis ke domain itu. RPC mutasi domain tersebut memanggil fungsi ini di awal dan menolak denganraise exceptionjika gagal — bukan mengandalkan RLS policy saja.
Pola ini membuat hak akses tetap berlaku walau request datang langsung ke Supabase API, bukan lewat UI aplikasi.
Lapis tambahan — override permission per-role (dikonfigurasi Owner)
Section titled “Lapis tambahan — override permission per-role (dikonfigurasi Owner)”Di atas role kanonik, ada satu lapis konfigurasi halus:
public.role_permission_overrides menyimpan toggle boolean per-role
(mis. menonaktifkan satu kemampuan spesifik untuk role kasir tanpa
mengubah kode). Hanya pemilik yang bisa membaca/mengubahnya, lewat RPC:
permission_my_overrides()— role yang sedang login membaca override-nya sendiri.permission_all_overrides()/permission_save_overrides()— khusus pemilik, untuk melihat dan mengubah override semua role.- Setiap perubahan otomatis tercatat di
public.permission_audit_logs(siapa, kapan, field apa yang berubah dari nilai apa ke apa), dibaca lewatpermission_logs_list().
Onboarding & status akun
Section titled “Onboarding & status akun”User Auth baru otomatis mendapat baris profiles berstatus pending lewat
trigger on_auth_user_created — tidak ada role aktif sampai pemilik/manager
menyetujuinya secara manual. Status blocked memutus akses tanpa menghapus
riwayat data milik user tersebut.
Sumber: supabase/migrations/0002_identity_and_rls.sql,
supabase/migrations/0007_harden_profile_authorization.sql,
supabase/migrations/0032_employee_attendance_domain.sql,
supabase/migrations/0075_role_permissions.sql, src/lib/roles.ts,
src/lib/role-access.ts, src/components/RouteGuard.tsx.