Files
web-antrean/docs/hakakses_implementation_plan_final.md

182 lines
10 KiB
Markdown

# 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 `<v-checkbox>` / `<v-switch>` 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: `<v-btn v-permission="'canEdit'">Edit</v-btn>`
- Multiple (AND): `<v-btn v-permission="['canEdit', 'canDelete']">...</v-btn>`
- **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.