CRUD Paket Sewa yang Tahu Scope-nya
Modul CRUD paket sewa dengan scope bidang: fail-closed lewat pasangan sentinel nil dan slice kosong, lima domain error, satu mapError.
Ringkasan
Jadi gini, di KotaPortal staf cuma boleh utak-atik paket sesuai modul departemen mereka, kalau kategorinya NULL berarti lintas departemen dan cuma aktor global yang boleh. Alih-alih nyebar if di mana-mana, kontrak scope dipusatkan di service layer pakai lima error yang diterjemahin mapError ke kode HTTP. Trik kuncinya: nil artinya tanpa filter, slice kosong artinya deny-all, jadi query gagal aman.
Di layar admin KotaPortal, dua staf dari departemen berbeda melihat tabel daftar paket sewa yang sama. Keduanya memiliki hak untuk mengelola data, tetapi satu staf hanya boleh mengelola paket dengan modul PARIWISATA, sementara staf lain menangani EKONOMI_KREATIF. Masalah muncul ketika sebuah tipe layanan memiliki kategori bernilai NULL, yang berarti lintas departemen. Staf departemen biasa tidak boleh melihat atau mengubah paket tersebut. Hanya aktor global yang memiliki akses penuh.
Masalah Pembagian Akses pada Satu Daftar
Pendekatan awal yang sering diambil adalah menambahkan pemeriksaan kondisional secara tersebar di setiap handler. Pendekatan ini rapuh. Ketika logika bisnis berkembang, celah keamanan muncul karena satu titik pemeriksaan terlewat. Solusi yang lebih kokoh adalah memusatkan kontrak scope pada lapisan layanan yang mengembalikan sekumpulan domain error tertentu. Satu fungsi mapError di handler bertugas menerjemahkannya menjadi respons HTTP yang tepat. Seluruh kontrak scope ini hidup dalam lima nilai error yang terdefinisi dengan jelas.
Pasangan Sentinel untuk Mekanisme Fail-Closed
Kunci dari pembatasan akses yang aman adalah membedakan antara tidak ada filter dan tolak semua. Fungsi listModules mengembalikan nilai nil untuk menandakan tidak ada filter atau akses global. Fungsi ini mengembalikan slice kosong untuk menandakan tolak semua bagi pengguna dengan bidang yang tidak dikenali. Repositori harus menjawab dengan daftar kosong sebelum menyentuh basis data sama sekali.
// nil = tanpa filter; kosong = deny-all fail-closed
if q.Modules != nil && len(q.Modules) == 0 {
return "", nil // jawab kosong tanpa menyentuh DB
}
Pola ini memastikan bahwa ketika pengguna tidak memiliki modul yang valid, kueri basis data tidak pernah dieksekusi. Hal ini menghemat sumber daya dan mencegah kebocoran data melalui side-channel.
Translasi Error Layanan ke Respons HTTP
Lapisan layanan mengembalikan lima jenis error spesifik. Nilai tersebut adalah ErrInvalidInput, ErrServiceTypeNotFound, ErrForbidden, ErrNotFound, dan ErrInUse. Fungsi mapError di handler memetakan nilai-nilai ini ke kode status HTTP 400, 400, 403, 404, dan 409.
Kasus 403 Forbidden memiliki makna HTTP yang spesifik. Server memahami permintaan, tetapi menolak untuk mengizinkannya karena kurangnya izin akses [1]. Kasus 409 Conflict muncul ketika upaya penghapusan paket sewa masih dirujuk oleh data pengajuan. Mesin basis data MySQL akan menolak penghapusan baris induk karena kendala RESTRICT pada foreign key [5]. Lapisan layanan mendeteksi error mesin ini dan mengembalikan ErrInUse. Handler kemudian merespons dengan 409 CONFLICT, disertai saran operasional untuk menonaktifkan status paket sewa alih-alih menghapusnya. Kode 409 menandakan konflik dengan keadaan sumber daya saat ini [2].
Kebiasaan Verifikasi dan Prinsip Deny-by-Default
Setiap perilaku ini harus diverifikasi melalui pengujian otomatis. Pengujian tidak hanya memastikan kode status HTTP yang benar, tetapi juga memverifikasi tidak adanya kebocoran data masukan dalam pesan kesalahan. Penerapan prinsip deny-by-default sesuai panduan OWASP mengharuskan aplikasi untuk tetap membuat keputusan penolakan yang aman ketika tidak ada aturan yang cocok [4]. Mekanisme ini menutup celah akses yang tidak terduga secara sistematis.
Sumber