api/docs/history/2025-07-22-forgot-password.md
Yoga Pangestu 104a58e6e7 Add password reset functionality with email notifications
- Implemented endpoints for forgot password and reset password
- Added PasswordResetToken model and relationships
- Integrated email service using Resend for sending reset links
- Updated configuration for email and frontend URL
- Enhanced user model to include password reset tokens
- Added necessary schemas for password reset requests
- Updated requirements to include Resend SDK
- Created comprehensive documentation for the new feature
2026-07-22 20:22:58 +07:00

3.0 KiB

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:

{ "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):

{ "message": "Link reset password telah dikirim ke email Anda" }

POST /v1/auth/reset-password

Request:

{ "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

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