10 KiB
Integrasi Hak Akses (Permissions) di UI beserta Konfigurasi CRUD
✅ Hasil Pengecekan Kesiapan Project
- Navigasi (Menu/Routing): Sudah tersedia di
stores/navItems1.ts(defaultNavItems). Array ini akan menjadi sumber daftar halaman di dialog "Edit Hak Akses" — dengan catatan penting di bagian struktur data (lihat poin 2 di bawah). - Library Komponen: Vuetify 3 sudah tersedia, tinggal memasang
<v-checkbox>/<v-switch>di tabel dialog Edit Hak Akses. - Mock Backend API:
server/api/hak-akses/index.tssudah bisa menahan format data CRUD yang akan dikirim dari UI.
Karena API backend sesungguhnya belum siap, kita pakai Nuxt Local API (Mock Backend) sebagai Backend-for-Frontend sementara, dengan arsitektur toggling API sejak awal agar migrasi ke backend asli mulus.
⚠️ Catatan penting: Toggle mock/real API ini menyelesaikan masalah sumber data, bukan masalah keamanan. Semua pengecekan
v-permissiondan middleware di bawah ini berjalan di client — lihat bagian Keamanan sebelum dianggap selesai.
Bagan Alur Sistem Hak Akses (Permission Flow)
flowchart TD
User([Pengguna]) --> Login(Berhasil Login via Keycloak)
Login --> Auth[useAuth.ts: Dapat Data Roles & Groups]
Auth --> PermStore[permissionStore.ts memuat Permissions]
PermStore --> CheckFlag{USE_MOCK_API ?}
CheckFlag -- TRUE --> MockAPI[Nuxt Local API: /api/hak-akses]
CheckFlag -- FALSE --> RealAPI[Real Backend: /api/v1/permission]
MockAPI --> FetchFail{Fetch gagal?}
RealAPI --> FetchFail
FetchFail -- Ya --> DenyDefault[Deny-by-default: anggap tanpa izin]
FetchFail -- Tidak --> PermState(Permission State Disimpan)
PermState --> Router[Vue Router Middleware - client]
PermState --> ServerCheck[Server Middleware/Plugin - SSR guard]
PermState --> Sidebar[Sidebar Navigation]
Router -- canAccess: False --> Deny[Redirect ke Error/403]
Router -- canAccess: True --> Page[Buka Halaman]
ServerCheck -- canAccess: False --> Deny
Sidebar -- canView: False --> HideMenu[Sembunyikan Menu]
Page --> Directive[v-permission directive pada Komponen]
Directive -- canDelete: False --> HideBtn[Tombol Hapus Hilang/Disabled]
Directive -- canAdd: True --> ShowBtn[Tombol Tambah Tampil]
Page -.-> BackendGuard[[Backend API tetap validasi ulang izin]]
Proposed Changes
1. Seamless Backend Migration Strategy (Persiapan API Asli)
stores/permissionStore.tsdan composable pemanggil membaca konfigurasi runtime (useRuntimeConfig()).- Tambahkan flag
USE_MOCK_PERMISSION_API: true. - Saat backend siap, cukup ubah
NUXT_PUBLIC_USE_MOCK_PERMISSION_API=falsedi.env. - Tambahan — contract test: buat satu skema/interface TypeScript (idealnya divalidasi dengan
zod) yang dipakai bersama oleh mock API dan dipakai untuk memvalidasi response real API nanti. Ini memastikan klaim "tinggal ganti flag" benar-benar teruji, bukan asumsi.
2. Upgrade Local API Mock (server/api/hak-akses/index.ts)
- Perbarui
data/mock/hakAkses.jsonagar fieldhakAksesMenuberisi boolean:canAccess,canView,canAdd,canEdit,canDelete. - Perubahan struktur — pakai key stabil, bukan label: setiap entri
hakAksesMenumenyimpanmenuKey(mengacu kekey/routeNameunik dinavItems1.ts), bukanname(label tampilan). Label bisa berubah/di-rename tanpa memutus mapping izin.
{
"menuKey": "master-klinik-ruang",
"name": "Master Klinik Ruang",
"canAccess": true,
"canView": true,
"canAdd": false,
"canEdit": false,
"canDelete": false
}
- Validasi payload masuk dengan skema (zod) di endpoint mock, supaya struktur tidak diam-diam berubah antara UI dan backend.
3. Memperbarui Setting/HakAkses.vue (UI Konfigurasi CRUD)
- Dialog Edit melooping
navItems1.ts, menampilkan matriks checkbox untukcanAccess,canView,canAdd,canEdit,canDelete, dikunci kemenuKeymasing-masing. - Payload POST mengarah ke API Mock/Real sesuai flag.
- Aturan precedence eksplisit (harus didefinisikan sebelum coding, karena ada
role,group, danisGroupBasedsekaligus):- Jika user punya override individual (role-based) → pakai itu.
- Jika tidak ada override individual dan
isGroupBased: true→ pakai izin dari group. - Jika keduanya tidak ada → deny-by-default.
- Tuliskan aturan ini sebagai komentar di
permissionStore.ts, bukan hanya di dokumen, supaya tidak jadi sumber bug tersembunyi saat logic berkembang.
4. Vue Custom Directive v-permission
- File baru
plugins/permission.tsuntuk registrasi directive. - Dukungan penggunaan:
- Single:
<v-btn v-permission="'canEdit'">Edit</v-btn> - Multiple (AND):
<v-btn v-permission="['canEdit', 'canDelete']">...</v-btn>
- Single:
- Mode hide vs disable: tambahkan modifier, misal
v-permission:disable="'canDelete'", agar tombol bisa di-disable dengan tooltip ("Anda tidak punya izin") alih-alih hilang total tanpa penjelasan — pilih sesuai konteks UX per halaman.
5. stores/permissionStore.ts (State Management)
- Mengambil data izin dari Local/Real API saat login.
- Menyimpan state global.
- Deny-by-default: jika fetch permission gagal (network error, token expired), state dianggap "tanpa izin sama sekali", bukan default terbuka.
- Refresh strategy: tentukan apakah perubahan hak akses oleh admin berlaku langsung (polling/refetch berkala) atau baru berlaku setelah re-login. Pilih salah satu secara eksplisit dan dokumentasikan, jangan dibiarkan implisit.
6. Middleware & Dynamic Sidebar
- Client middleware (
middleware/permissions.ts): mencegat rute jikacanAccessfalse. - Server-side guard: karena project ini SSR (Nuxt), tambahkan pengecekan di server middleware/plugin juga — bukan hanya client — untuk mencegah flash of unauthorized content (halaman sempat ter-render sebelum redirect).
- Sidebar (
stores/navItems1.ts): filter menu berdasarkancanView, dikunci kemenuKey.
7. Keamanan Backend (wajib, non-negotiable)
v-permissiondan middleware di atas adalah UX, bukan kontrol akses sesungguhnya — keduanya berjalan di client dan bisa dilewati siapa saja yang memanggil API langsung.- Backend API asli wajib memvalidasi ulang setiap permission di server berdasarkan identitas user dari token, tidak pernah mempercayai payload/izin yang dikirim dari frontend.
Verification Plan
1. Verifikasi API Lokal via cURL
Pastikan API Lokal mampu membaca dan menyimpan JSON berformat CRUD dengan menuKey:
curl -X POST "http://localhost:3000/api/hak-akses" \
-H "Content-Type: application/json" \
-d '{
"role": "admin",
"group": "LOKET",
"namaTipeUser": "Admin Loket",
"isGroupBased": true,
"hakAksesMenu": [
{
"menuKey": "master-klinik-ruang",
"name": "Master Klinik Ruang",
"canAccess": true,
"canView": true,
"canAdd": false,
"canEdit": false,
"canDelete": false
}
]
}'
2. Manual UI Verification
- Buka Setting > Hak Akses, atur hak untuk Role tertentu (centang
canView, matikancanDelete). - Login sebagai akun dengan Role tersebut.
- Buka halaman target.
- Validasi: Halaman terbuka, tombol "Delete" tersembunyi/disabled sesuai mode
v-permission. - Validasi fetch gagal: Simulasikan permission API error (mis. matikan endpoint sementara) → pastikan sistem deny-by-default, bukan default terbuka.
- Validasi kesiapan API asli: Cek logika precedence dan toggle flag di
permissionStore.ts.
3. Automated Testing (baru)
- Unit test untuk
permissionStore.ts: precedence role vs group, deny-by-default saat fetch gagal, evaluasicanAccess/canView/dst. - Contract test: bandingkan skema response Mock API vs Real API (setelah Real API tersedia) menggunakan skema TypeScript/zod yang sama, untuk memastikan switch flag benar-benar tanpa perubahan kode lain.
- Directive test: pastikan
v-permissionmenyembunyikan/disable elemen dengan benar untuk kombinasi single dan multiple permission.
Strategi Selama Backend Asli Belum Tersedia
Karena tim belum bisa mengimplementasikan validasi izin di server sungguhan, mock API diperlakukan sebagai kontrak (contract-first), bukan sekadar penyimpanan data sementara. Tujuannya: frontend sudah teruji terhadap semua skenario yang nanti jadi tanggung jawab backend asli, dan tidak perlu dirombak saat migrasi.
1. Mock API mengikuti skema, bukan menerima apa saja
- Definisikan interface TypeScript / skema
zoduntuk request dan responsehak-aksessekarang, bukan menunggu backend asli. - Mock API menolak (400) payload yang tidak sesuai skema.
- Skema ini menjadi kontrak yang wajib dipatuhi backend asli nanti — perbedaan struktur akan ketahuan lewat contract test, bukan saat production.
2. Simulasikan tanggung jawab yang nanti dipegang backend
- Tambahkan endpoint mock
/api/hak-akses/checkyang bisa mensimulasikan penolakan server (403) karena user tidak punya izin — supaya UI dan middleware sudah teruji menangani penolakan dari server, bukan hanya dari state client. - Simulasikan juga kegagalan fetch (delay/error 500) untuk memverifikasi deny-by-default benar-benar berjalan.
3. Tandai eksplisit bagian yang "sementara tidak aman"
Beri komentar TODO(security) di titik-titik yang wajib diperkuat saat backend asli terpasang, contoh:
// TODO(security): saat backend asli terpasang, endpoint ini WAJIB
// memvalidasi ulang permission dari token JWT/session di server,
// jangan percaya payload role/group yang dikirim dari client.
Ini mencegah asumsi keliru saat handoff bahwa "karena UI sudah mengatur tampilan sesuai izin, backend tidak perlu memvalidasi ulang".
4. Definition of Done — Migrasi ke Backend Asli
Checklist ini harus tercentang semua sebelum flag USE_MOCK_PERMISSION_API dimatikan (false) di production:
- Endpoint real API memvalidasi permission berdasarkan identitas dari token (JWT/session), bukan dari body request yang dikirim client.
- Response real API lolos contract test terhadap skema yang sama dengan mock API (field, tipe data, struktur
hakAksesMenuidentik). - Skenario penolakan server (403) dan fetch gagal (500/timeout) sudah diuji terhadap real API, tidak hanya terhadap mock.
- Ada audit log untuk setiap perubahan hak akses (siapa mengubah, kapan, dari-ke apa) — direkomendasikan mengingat ini fitur kontrol akses.
- Rate limiting pada endpoint pengubahan hak akses, untuk mencegah penyalahgunaan.
- Semua komentar
TODO(security)di kode sudah diselesaikan atau dipindahkan menjadi tiket tersendiri yang dilacak.