fix(dimentorin): verify-email validates OTP before activating user
- new app_otp_cache table + OtpCache entity (ResourceEnum::OtpCache) - PostgresOtpRepository upsert/find/delete keyed by email - register/resend persist otp_hash+expiry after email sent (no orphan OTP) - verify_email validates via OtpManager::validate_otp_hash, single-use delete - 8 unit tests pass, e2e verified: wrong OTP 400, correct OTP 200
This commit is contained in:
@@ -10,23 +10,18 @@ Semua diuji lokal (Postgres `dimentorin`, backend :4099).
|
||||
- **Dampak**: mentee/mentor baru tak bisa menerima OTP lewat email → tak bisa aktivasi → tak bisa login, kecuali via verify-email langsung.
|
||||
- **Diperlukan**: SMTP credential institution yang valid (Gmail App Password atau SMTP relay), sebaiknya dari BWS secret management, bukan hardcode.
|
||||
|
||||
## 2. verify-email TIDAK memverifikasi OTP (security issue)
|
||||
## 2. ✅ FIXED — verify-email TIDAK memverifikasi OTP (security issue)
|
||||
|
||||
**Status: FIXED di branch feat/dimentorin-postgres (2026-08-04).**
|
||||
|
||||
`imphnen-iam/src/auth/application/mod.rs` → `verify_email()`:
|
||||
|
||||
```rust
|
||||
async fn verify_email(&self, payload: VerifyEmailInput) -> ... {
|
||||
let user = ...find_by_email...;
|
||||
if user.is_active { return Err(...); }
|
||||
self.user_repo.update(UserEntity { is_active: true, ..user }).await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
- OTP di-generate (`OtpManager::generate_otp`) + dikirim via email, TAPI **`payload.otp` tidak pernah divalidasi**.
|
||||
- `OtpManager::validate_otp` ada tapi tak dipanggil di handler ini.
|
||||
- **Dampak**: siapa pun yang tahu email bisa mengaktifkan akun dengan otp sembarang. Verifikasi email berbasis kepemilikan = tidak ada.
|
||||
- **Fix yang disarankan**: `verify_email` harus menyimpan `OtpData` (hash+expiry) saat kirim, lalu memanggil `OtpManager::validate_otp(stored, payload.otp)` sebelum set `is_active`.
|
||||
- OTP sekarang dipersist ke tabel **`app_otp_cache`** (entity baru `imphnen-entities/src/seaorm/common/otp_cache.rs`, resource `app_otp_cache` sudah direncanakan di `ResourceEnum::OtpCache`).
|
||||
- `register()` & `resend_otp()` menyimpan `otp_hash` + `expires_at` setelah email terkirim (kalau email gagal, tidak ada OTP yatim / OTP lama tidak di-overwrite).
|
||||
- `verify_email()` memanggil `OtpManager::validate_otp_hash(stored_hash, expires_at, payload.otp)` sebelum set `is_active`. `validate_otp_hash` ditambahkan ke `OtpManager` (pure hash+expiry tanpa perlu plaintext code).
|
||||
- OTP **single-use**: di-delete setelah verifikasi sukses. Reuse / OTP tanpa cache / OTP expired semua ditolak (400).
|
||||
- Uji lokal (Postgres, :4099): OTP salah → 400 "Invalid or expired OTP", user tetap inactive; OTP benar → 200 "Email verified successfully", user aktif, OTP dihapus; verify ulang → 400 "User already active"; email tanpa OTP → 400 "No OTP issued".
|
||||
- Tabel dibuat via SQL manual (`create_schema.rs` ditambah `otp_cache` untuk bootstrap penuh).
|
||||
|
||||
## 3. (OK, sudah benar) Register mentor + booking
|
||||
|
||||
|
||||
Reference in New Issue
Block a user