# SPBU Manager — Refactor Plan

> Generated by project-audit · 2026-04-11
> Reference: AUDIT-REPORT.md
> Execute items in order. Setiap item bisa didelegasi ke `senior-backend` atau `senior-frontend`.

---

## Priority Ordering
1. Critical security (fix regardless of effort)
2. High security + low effort (quick wins)
3. High security + high effort (schedule dedicated time)
4. Medium security
5. Code quality / informational

---

## Items

### R-01 — Upgrade axios ke ≥ 1.15.0 [CRITICAL]
- **Effort**: 1 point
- **Severity**: Critical (SSRF)
- **Module**: frontend
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-frontend
- **Summary**: `npm install axios@^1.15.0` di folder frontend
- **Acceptance**: `npm audit` tidak menunjukkan axios SSRF. App masih bisa login dan load data.

### R-02 — Hapus default superadmin password dari config.py [CRITICAL]
- **Effort**: 1 point
- **Severity**: Critical
- **Module**: backend / config
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Hapus `= "Admin123!"` default dari `SUPERADMIN_PASSWORD` di `config.py`. Field menjadi required. Update `.env` dev dan production. Tambahkan validasi `len >= 12`.
- **Acceptance**: Backend gagal start jika `SUPERADMIN_PASSWORD` tidak di-set di env.

### R-03 — Fix: Internal error messages tidak ter-expose di production [HIGH]
- **Effort**: 1 point
- **Severity**: High
- **Module**: backend / main
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Di `global_exception_handler` (`main.py:55`), return `"Internal server error"` saat production. Detail `str(exc)` hanya saat development.
- **Acceptance**: `curl -X POST /api/v1/nonexistent` di production mengembalikan pesan generik.

### R-04 — Disable FastAPI docs di production [HIGH]
- **Effort**: 1 point
- **Severity**: High
- **Module**: backend / main
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Set `docs_url=None, redoc_url=None` saat `settings.is_production`. Dev tetap bisa akses `/api/docs`.
- **Acceptance**: `curl https://spbu.goteku.com/api/docs` returns 404.

### R-05 — Fix default superadmin password di seed + CORS origins dari env [HIGH]
- **Effort**: 2 point
- **Severity**: High
- **Module**: backend / config + main
- **Blocked by**: R-02
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: (1) `CORS_ORIGINS` env var, parse jadi list di `main.py`. Default `http://localhost:8007` untuk dev. (2) Tambahkan `https://spbu.goteku.com` di production `.env`.
- **Acceptance**: Frontend di production bisa hit API tanpa CORS error.

### R-06 — Add rate limiting ke auth endpoints [HIGH]
- **Effort**: 2 point
- **Severity**: High
- **Module**: backend / auth
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Install `slowapi`. Apply `@limiter.limit("10/minute")` ke `POST /api/v1/auth/login` dan `POST /api/v1/auth/refresh`. Return 429 dengan pesan yang jelas.
- **Acceptance**: 11 login request dalam 1 menit dari IP yang sama → 429.

### R-07 — Protect uploaded files dengan auth [HIGH]
- **Effort**: 3 point
- **Severity**: High
- **Module**: backend / file-storage
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Hapus `StaticFiles` mount. Buat endpoint `GET /api/v1/files/{path:path}` dengan `Depends(get_current_user)`. Serve file dari disk dengan `FileResponse`. Frontend update URL untuk file display.
- **Acceptance**: Akses `/api/v1/files/dev/...` tanpa token → 401. Dengan token → file ter-serve.

### R-08 — Fix IDOR: tambahkan SPBU access check di semua operational services [HIGH]
- **Effort**: 5 point
- **Severity**: High (IDOR)
- **Module**: backend / semua operational modules
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Buat helper `assert_spbu_access(user, spbu_id)` di `app/dependencies/__init__.py` — raise `PermissionError` jika user bukan superadmin dan tidak punya assignment di spbu_id. Inject ke semua service functions yang menerima `spbu_id` + `current_user`.
- **Acceptance**: Request ke `/api/v1/spbus/2/laporan-shift` oleh user yang hanya assigned di spbu_id=1 → 403.

### R-09 — Fix: `create_user` perlu permission check [HIGH]
- **Effort**: 2 point
- **Severity**: High
- **Module**: backend / users
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: `POST /users` harus require superadmin atau `users:create` permission. Tambahkan check di service atau gunakan `Depends(require_superadmin)` di router.
- **Acceptance**: Operator yang bukan superadmin POST ke `/api/v1/users` → 403.

### R-10 — Fix: `deactivate_user` hanya superadmin [MEDIUM]
- **Effort**: 1 point
- **Severity**: Medium
- **Module**: backend / users
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Tambahkan `if not actor.is_superadmin: raise PermissionError(...)` di baris pertama `deactivate_user`.
- **Acceptance**: Regular user mencoba deactivate user lain → 403.

### R-11 — Fix: `remove_assignment` perlu permission check [MEDIUM]
- **Effort**: 1 point
- **Severity**: Medium
- **Module**: backend / users
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-backend
- **Summary**: Tambahkan check di `remove_assignment`: actor harus superadmin ATAU actor punya assignment di spbu_id yang sama.
- **Acceptance**: Operator SPBU A tidak bisa remove assignment user di SPBU B.

### R-12 — Upgrade Next.js ke ≥ 16.2.3 [HIGH]
- **Effort**: 1 point
- **Severity**: High (DoS)
- **Module**: frontend
- **Blocked by**: nothing
- **Blocks**: nothing
- **Execution skill**: senior-frontend
- **Summary**: `npm install next@^16.2.3 -C frontend`. Test build dan jalankan.
- **Acceptance**: `npm audit` tidak menunjukkan Next.js DoS. `npm run build` berhasil.

### R-13 — Add .env.example ke backend [LOW]
- **Effort**: 1 point
- **Severity**: Low
- **Module**: backend
- **Blocked by**: R-02
- **Execution skill**: senior-backend
- **Summary**: Buat `backend/.env.example` dengan semua keys dari `config.py`. Nilai placeholder (bukan secret nyata). Commit ke git.
- **Acceptance**: File ada di git, semua key dari Settings ada di dalamnya.

### R-14 — Tambahkan security response headers [MEDIUM]
- **Effort**: 2 point
- **Severity**: Medium
- **Module**: backend / nginx
- **Blocked by**: nothing
- **Execution skill**: senior-backend
- **Summary**: Tambahkan di Nginx config: `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Strict-Transport-Security: max-age=31536000`. Untuk CSP, buat minimal policy dulu.
- **Acceptance**: `curl -I https://spbu.goteku.com` menampilkan header tersebut.

### R-15 — JWT audience/issuer validation [MEDIUM]
- **Effort**: 2 point
- **Severity**: Medium
- **Module**: backend / security
- **Blocked by**: nothing
- **Execution skill**: senior-backend
- **Summary**: Tambahkan `iss: "spbu-manager"` dan `aud: "spbu-manager-api"` saat create token. Validate saat decode. **Catat: ini invalidate semua token yang ada — coordinate dengan maintenance window atau bump token version.**
- **Acceptance**: Token tanpa `iss`/`aud` ditolak dengan 401.

### R-16 — Hapus default Google Drive folder ID dari config [MEDIUM]
- **Effort**: 1 point
- **Severity**: Medium
- **Module**: backend / config
- **Blocked by**: nothing
- **Execution skill**: senior-backend
- **Summary**: Hapus default value `"1wnTodR_..."` dari `GDRIVE_ROOT_FOLDER_ID`. Jadikan empty string default dengan validasi: jika `STORAGE_TYPE=gdrive`, field ini wajib.
- **Acceptance**: Backend raise error saat startup jika `STORAGE_TYPE=gdrive` tapi `GDRIVE_ROOT_FOLDER_ID` kosong.

### R-17 — Install bandit + pip-audit di CI/dev [INFORMATIONAL]
- **Effort**: 2 point
- **Severity**: Informational
- **Module**: backend / CI
- **Blocked by**: nothing
- **Execution skill**: senior-backend
- **Summary**: Tambahkan `bandit` dan `pip-audit` ke `pyproject.toml` dev deps. Buat `Makefile` target `make lint-security` yang run keduanya. Optional: tambahkan ke GitHub Actions workflow jika ada.
- **Acceptance**: `bandit -r app/ -ll` dan `pip-audit` bisa dijalankan dari root backend.

---

## Quick Wins (1–2 pts — kerjakan dulu sebelum yang lain)
| Item | Effort | Impact |
|---|---|---|
| R-01 — axios upgrade | 1pt | Critical SSRF fix |
| R-02 — hapus default password | 1pt | Critical |
| R-03 — hide internal errors | 1pt | High |
| R-04 — disable docs production | 1pt | High |
| R-06 — rate limiting login | 2pt | High |
| R-09 — create_user permission | 2pt | High |
| R-10 — deactivate_user fix | 1pt | Medium |
| R-11 — remove_assignment fix | 1pt | Medium |
| R-12 — next.js upgrade | 1pt | High DoS |

**Total quick wins**: ~12 pts, bisa selesai dalam 1-2 hari.

## Large Items (3+ pts — butuh waktu dedicated)
| Item | Effort | Impact |
|---|---|---|
| R-07 — protect uploaded files | 3pt | High |
| R-08 — SPBU IDOR fix | 5pt | High |

---

## Suggested Execution Order
1. R-01, R-02, R-03, R-04, R-12 — security-critical, masing-masing < 30 menit
2. R-09, R-10, R-11 — permission gaps, simple service-layer fixes
3. R-06 — rate limiting (install dep baru)
4. R-05 — CORS env config
5. R-07 — protected file serving (butuh frontend update juga)
6. R-08 — SPBU IDOR (terbesar, test menyeluruh)
7. R-13, R-14, R-15, R-16 — medium/low polish
8. R-17 — CI setup

## Follow-up Recommendations
- **Zero test coverage** adalah risiko terbesar untuk long-term maintainability. Sebelum eksekusi R-08 (SPBU IDOR fix), tambahkan minimal smoke tests untuk auth dan user endpoints.
- **Audit log** (CLAUDE.md rule #14) sudah di-TODO — ini compliance gap. Prioritaskan setelah security fixes selesai.
- **Dashboard + Laporan + Analytics** halaman belum ada — ini business-critical feature yang belum dibangun.
- Pasang `bandit` + `pip-audit` di CI agar temuan dependency vulnerability tidak terlewat lagi.
