Skip to content

Feat: history password#1659

Open
pandigresik wants to merge 3 commits into
rilis-devfrom
feat/history_password
Open

Feat: history password#1659
pandigresik wants to merge 3 commits into
rilis-devfrom
feat/history_password

Conversation

@pandigresik

@pandigresik pandigresik commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

feat: Terapkan Password History (10 Kata Sandi Terakhir)

Deskripsi

Menerapkan mekanisme Password History yang mencegah pengguna menggunakan kembali kata sandi yang pernah digunakan sebelumnya. Sistem menyimpan maksimal 10 kata sandi terakhir untuk setiap akun dan melakukan validasi pada setiap proses perubahan maupun reset kata sandi. Apabila kata sandi baru sama dengan salah satu dari 10 kata sandi terakhir, proses akan ditolak dengan pesan kesalahan.

Perubahan

  1. Migrasidatabase/migrations/2026_07_13_000001_create_password_histories_table.php

    • Membuat tabel password_histories dengan kolom user_id (foreign key ke users), password (bcrypt hash), dan timestamps.
  2. Model Baruapp/Models/PasswordHistory.php

    • Model Eloquent dengan HasFactory dan relasi belongsTo(User::class).
  3. Service Baruapp/Services/PasswordHistoryService.php

    • isPasswordReused(User $user, string $password): bool — memeriksa apakah password sudah pernah digunakan (current + history).
    • storeCurrentPassword(User $user): void — menyimpan password saat ini ke riwayat lalu memangkas ke 10.
    • prune(User $user): void — mempertahankan hanya 10 riwayat terbaru.
  4. Custom Validation Ruleapp/Rules/NotInPasswordHistory.php

    • Rule reusable yang menolak password jika ditemukan di current/history user.
    • Digunakan di 3 FormRequest tanpa duplikasi kode.
  5. Observerapp/Observers/UserObserver.php

    • Event saving: otomatis menyimpan password lama ke riwayat sebelum diubah.
    • Event saved: otomatis memangkas riwayat ke maksimal 10 setelah perubahan.
    • Transparan — tidak perlu modifikasi controller manapun.
  6. Modifikasi User Modelapp/Models/User.php

    • Relasi passwordHistories() (hasMany).
    • Registrasi UserObserver di method booted().
  7. Validasi di FormRequest — 3 file berubah:

    • ChangePasswordRequest — tambah new NotInPasswordHistory() untuk ubah password profil.
    • ChangeRequest — tambah new NotInPasswordHistory() untuk ubah password default pertama login.
    • UserUpdateRequest — tambah new NotInPasswordHistory($targetUser) untuk reset password oleh admin.
  8. Validasi di Reset Password via Emailapp/Http/Controllers/Auth/ResetPasswordController.php

    • Override resetPassword() untuk memeriksa riwayat sebelum menyimpan password baru (tidak ada FormRequest khusus untuk flow ini).
  9. Translasilang/id/passwords.php dan lang/en/passwords.php

    • Menambah kunci history_found dengan pesan error dalam Bahasa Indonesia dan Inggris.
  10. Factorydatabase/factories/PasswordHistoryFactory.php

    • Factory untuk pembuatan data testing.
  11. Unit Test Servicetests/Unit/Services/PasswordHistoryServiceTest.php (17 test cases)

    • Instantiasi service.
    • isPasswordReused — 6 skenario (match current, match history, not found, no history, multiple entries, other users ignored).
    • storeCurrentPassword — 3 skenario (create record, preserve user password, store hash correctly).
    • prune — 4 skenario (within limit, trim to 10, remove oldest, preserve 10 recent).
    • Integrasi store + prune — 2 skenario (auto-prune, store even when full).
  12. Feature Testtests/Feature/Auth/PasswordHistoryTest.php (13 test cases)

    • Service integration (5 skenario).
    • Profile self-service password change (3 skenario — block current, block history, allow new).
    • Default password change (2 skenario — block reuse, allow new).
    • Admin password reset via UserController (2 skenario — block reuse, allow new).
    • Password history pruning (1 skenario — keep last 10).

Alasan

Meningkatkan keamanan akun pengguna dengan mencegah penggunaan ulang kata sandi lama sesuai standar keamanan yang lebih baik. Mengurangi risiko kompromi akun akibat kebiasaan pengguna menggunakan kembali kata sandi yang sama secara periodik.

Dampak

  • Positif: Seluruh perubahan/reset password tervalidasi terhadap 10 riwayat terakhir.
  • Positif: Tidak ada perubahan pada controller (ProfileController, ChangeDefaultController, UserController tetap original).
  • Positif: Penyimpanan riwayat bersifat transparan via Observer — tidak perlu dipanggil manual.
  • Positif: Validasi dilakukan di backend via FormRequest — tidak bisa dilewati manipulasi request frontend.
  • Negatif: Tidak ada.

Related Issue

#1646

Steps to Reproduce

Sebelum

  1. Login sebagai user biasa.
  2. Buka halaman Ganti Password.
  3. Ubah password ke Password1!, lalu Password2!, lalu Password1! lagi.
  4. Berhasil — password lama bisa digunakan kembali.

Sesudah

  1. Login sebagai user biasa.
  2. Buka halaman Ganti Password.
  3. Ubah password ke Password1!, lalu Password2!, lalu Password1! lagi.
  4. Ditolak — muncul pesan: "Password ini pernah digunakan sebelumnya. Silakan gunakan password yang berbeda."

Testing Checklist

Automated Tests

  • php artisan test --filter=PasswordHistoryServiceTest — 17 test cases (unit)
  • php artisan test --filter=PasswordHistoryTest — 13 test cases (feature)
  • php artisan test --filter=PasswordTest — 3 test cases (existing, masih pass)
  • Seluruh test suite fitur Auth tidak ada regresi

Manual Testing

  • Ubah password via profil → password sama dengan saat ini ditolak
  • Ubah password via profil → password lama dari history ditolak
  • Ubah password via profil → password baru diverbolehkan
  • Ganti password default (first login) → password dari history ditolak
  • Reset password oleh admin → password dari history ditolak
  • Riwayat terisi penuh (10) → password baru tetap tersimpan, yang lama terhapus otomatis

Technical Details

Arsitektur

User (model)
  ├── passwordHistories() → hasMany(PasswordHistory)
  └── UserObserver (saving/saved)
        ├── saving : simpan password lama ke password_histories
        └── saved  : prune ke max 10 records

PasswordHistoryService
  ├── isPasswordReused() → Hash::check() terhadap current + history
  ├── storeCurrentPassword() → create + prune
  └── prune() → ORDER BY created_at DESC, slice 10

NotInPasswordHistory (ValidationRule)
  └── panggil PasswordHistoryService::isPasswordReused()

FormRequests (ChangePasswordRequest, ChangeRequest, UserUpdateRequest)
  └── rules[] → new NotInPasswordHistory()

ResetPasswordController
  └── override resetPassword() → validasi manual via service

Alur Validasi

  1. User submit form → FormRequest validasi aturan standar (min:8, max:32, regex, confirmed).
  2. NotInPasswordHistory rule dijalankan:
    • Hash::check(password_baru, password_saat_ini) → jika cocok, tolak.
    • Iterasi 10 riwayat → Hash::check(password_baru, riwayat_hash) → jika cocok, tolak.
  3. Jika valid, controller proses seperti biasa → $user->save() memicu Observer.
  4. Observer saving: simpan password lama ke password_histories.
  5. Observer saved: panggil prune() → buang riwayat paling tua jika > 10.

Keamanan

  • Semua password di-hash bcrypt (baik password aktif maupun riwayat).
  • Validasi 100% backend — tidak bisa dilewati via JS/hacking frontend.
  • Observer bekerja di level model — menjamin konsistensi data.
  • Audit log tidak menyimpan hash password.

Breaking Changes

Tidak ada. Semua perubahan bersifat non-breaking dan kompatibel mundur.

Total Perubahan

  • 15 files changed, 637 insertions, 5 deletions
  • 8 file baru, 4 file modifikasi
  • 30 test cases (17 unit + 13 feature)

Video

simplescreenrecorder-2026-07-14_05.54.14.mp4

@pandigresik pandigresik requested a review from vickyrolanda July 13, 2026 09:45
@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI PR Review sedang antri di server...

Proses review akan segera dimulai di background — hasil akan muncul sebagai komentar setelah selesai.
Powered by CrewAI · PR #1659

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant