# Integrasi Hak Akses (Permissions) di UI beserta Konfigurasi CRUD ## ✅ Hasil Pengecekan Kesiapan Project 1. **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). 2. **Library Komponen**: Vuetify 3 sudah tersedia, tinggal memasang `` / `` di tabel dialog Edit Hak Akses. 3. **Mock Backend API**: `server/api/hak-akses/index.ts` sudah 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-permission` dan middleware di bawah ini berjalan di client — lihat bagian **Keamanan** sebelum dianggap selesai. --- ## Bagan Alur Sistem Hak Akses (Permission Flow) ```mermaid 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.ts` dan composable pemanggil membaca konfigurasi *runtime* (`useRuntimeConfig()`). - Tambahkan flag `USE_MOCK_PERMISSION_API: true`. - Saat backend siap, cukup ubah `NUXT_PUBLIC_USE_MOCK_PERMISSION_API=false` di `.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.json` agar field `hakAksesMenu` berisi boolean: `canAccess`, `canView`, `canAdd`, `canEdit`, `canDelete`. - **Perubahan struktur — pakai key stabil, bukan label**: setiap entri `hakAksesMenu` menyimpan `menuKey` (mengacu ke `key`/`routeName` unik di `navItems1.ts`), bukan `name` (label tampilan). Label bisa berubah/di-rename tanpa memutus mapping izin. ```json { "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 untuk `canAccess`, `canView`, `canAdd`, `canEdit`, `canDelete`, dikunci ke `menuKey` masing-masing. - Payload POST mengarah ke API Mock/Real sesuai flag. - **Aturan precedence eksplisit** (harus didefinisikan sebelum coding, karena ada `role`, `group`, dan `isGroupBased` sekaligus): 1. Jika user punya override individual (role-based) → pakai itu. 2. Jika tidak ada override individual dan `isGroupBased: true` → pakai izin dari group. 3. 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.ts` untuk registrasi directive. - Dukungan penggunaan: - Single: `Edit` - Multiple (AND): `...` - **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 jika `canAccess` false. - **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 berdasarkan `canView`, dikunci ke `menuKey`. ### 7. Keamanan Backend (wajib, non-negotiable) - `v-permission` dan 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`: ```bash 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 1. Buka **Setting > Hak Akses**, atur hak untuk Role tertentu (centang `canView`, matikan `canDelete`). 2. Login sebagai akun dengan Role tersebut. 3. Buka halaman target. 4. **Validasi**: Halaman terbuka, tombol "Delete" tersembunyi/disabled sesuai mode `v-permission`. 5. **Validasi fetch gagal**: Simulasikan permission API error (mis. matikan endpoint sementara) → pastikan sistem deny-by-default, bukan default terbuka. 6. **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, evaluasi `canAccess`/`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-permission` menyembunyikan/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 `zod` untuk request dan response `hak-akses` **sekarang**, 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/check` yang 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: ```ts // 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 `hakAksesMenu` identik). - [ ] 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.