# 2025-07-22: Forgot Password - Backend ## Summary Menambahkan fitur Lupa Kata Sandi: endpoint `POST /v1/auth/forgot-password` dan `POST /v1/auth/reset-password` dengan email via Resend. --- ## Changes | File | Change | |------|--------| | `app/config.py` | Tambah `RESEND_API_KEY`, `EMAIL_FROM`, `FRONTEND_URL` | | `.env.example` | **BARU** - Template env lengkap (DB, JWT, Google, Resend, Frontend URL) | | `app/models/password_reset_token.py` | **BARU** - Model `password_reset_tokens` (id, user_id, token, expires_at, used_at, created_at) | | `app/models/user.py` | Tambah relationship `password_reset_tokens` | | `app/models/__init__.py` | Register `PasswordResetToken` | | `app/schemas/auth.py` | **BARU** - `ForgotPasswordRequest(email)`, `ResetPasswordRequest(token, password, password_confirmation)` | | `app/services/email_service.py` | **BARU** - Kirim email HTML via Resend SDK (green theme, Poppins font, rounded UI) | | `app/services/auth_service.py` | **BARU** - Logic `forgot_password()` dan `reset_password()` | | `app/routers/auth.py` | Tambah endpoint `POST /v1/auth/forgot-password` dan `POST /v1/auth/reset-password` | | `requirements.txt` | Tambah `resend==2.10.0` | --- ## API Endpoints ### POST /v1/auth/forgot-password Request: ```json { "email": "user@example.com" } ``` Logic: 1. Cek email ada di DB → kalau tidak, return 404 2. Cek apakah ada token aktif (unused + belum expired) → kalau ada, return "link masih aktif" 3. Invalidate token lama, buat token baru (bcrypt hashed), simpan ke DB 4. Kirim email via Resend dengan link reset 5. Token berlaku 1 jam, single-use Response (200): ```json { "message": "Link reset password telah dikirim ke email Anda" } ``` ### POST /v1/auth/reset-password Request: ```json { "token": "...", "password": "newpassword123", "password_confirmation": "newpassword123" } ``` Logic: 1. Cari token unused di DB, verifikasi hash 2. Cek expiry (1 jam) 3. Update password user 4. Invalidate SEMUA token lama user tersebut --- ## Database Table ```sql CREATE TABLE password_reset_tokens ( id CHAR(36) PRIMARY KEY, user_id CHAR(36) NOT NULL, token VARCHAR(255) NOT NULL, expires_at DATETIME NOT NULL, used_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_token (token), UNIQUE INDEX idx_token_unique (token), FOREIGN KEY (user_id) REFERENCES users(id) ); ``` --- ## Security Notes - Token asli dikirim ke user via email, yang disimpan di DB adalah **bcrypt hash** - Token berlaku **1 jam** - **Single-use**: setelah dipakai, `used_at` diisi - Semua token lama user di-invalidate saat: - Request forgot-password baru - Password berhasil di-reset - Anti-spam: jika token aktif masih ada, tidak kirim email baru --- ## Troubleshooting - Email tidak sampai: cek `RESEND_API_KEY` di `.env`, cek folder spam, pastikan domain verified di Resend dashboard - Error 500: cek terminal backend, pastikan tabel `password_reset_tokens` sudah dibuat - Server harus di-restart setelah tambah env vars baru