AOSSIE-Org / AOSSIE-Org/DebateAI
[BUG]: Password reset code never expires — indefinite account-takeover window
- Ngôn ngữ chính
- TypeScript
- Star
- 84
- Fork
- 198
- Merge trung bình
- 2 ngày 19 giờ
- Pull request đã merge (30 ngày)
- 30
Mô tả
### Bug Description
The password reset flow issues a 6-digit reset code that **never expires**. `ResetPassword` validates only that the code matches — there is no time-based expiry check, and no expiry timestamp is stored when the code is generated.
**`ForgotPassword` (auth.go ~424):** generates the code and stores it with no expiry:
```go
resetCode := utils.GenerateRandomCode(6)
update := bson.M{"$set": bson.M{
"resetPasswordCode": resetCode,
"updatedAt": now, // not an expiry; just a generic timestamp
}}
```
**`ResetPassword` (auth.go ~463):** matches the code and resets immediately — no expiry check:
```go
err := ...FindOne(dbCtx, bson.M{"email": request.Email, "resetPasswordCode": request.Code}).Decode(&user)
if err != nil { /* invalid */ }
// no time check here — proceeds straight to hashing + updating the password
```
**For contrast, the signup OTP flow does enforce expiry** (auth.go ~240):
```go
if time.Since(user.CreatedAt) > 24*time.Hour {
ctx.JSON(400, gin.H{"error": "Verification code expired. Please sign up again."})
return
}
```
The `User` model has `ResetPasswordCode` but no `resetPasswordCodeExpiry` field, so there's nothing to check against.
**Security impact:** a reset code stays valid indefinitely until used. If a reset email is ever exposed later (old/archived email, shared or compromised inbox, mail forwarding, a device the user no longer controls), the code can still reset the password weeks or months on. Reset codes are meant to be short-lived (typically 15–60 minutes) to bound this window. With only 6 digits and no visible rate limiting on the verify endpoint, a never-expiring code raises account-takeover risk further.
### Steps to Reproduce
1. Request a password reset (`/forgotpassword`) for an account — a 6-digit code is emailed and stored.
2. Wait an arbitrarily long time (hours, days, weeks).
3. Submit the reset (`/resetpassword`) with that old code and a new password.
4. Observe the password is reset successfully — the code is still accepted with no expiry.
### Logs and Screenshots
ForgotPassword stores the code with no expiry (auth.go ~424):
"resetPasswordCode": resetCode,
"updatedAt": now,
ResetPassword accepts it with no time check (auth.go ~463):
FindOne(..., bson.M{"email": request.Email, "resetPasswordCode": request.Code})
// → straight to password update, no expiry validation
Signup OTP, by contrast, does check (auth.go ~240):
if time.Since(user.CreatedAt) > 24*time.Hour { /* expired */ }
### Environment Details
- File: backend/controllers/auth.go (ForgotPassword, ResetPassword)
- Model: backend/models/user.go (ResetPasswordCode; no expiry field)
- Backend: Go / MongoDB
- Branch: main
- Suggested fix: add a resetPasswordCodeExpiry timestamp on generation (e.g. now + 30 min) and reject in ResetPassword when time.Now() is past it; clear the code after use (already cleared on success)
### Impact
Medium - Feature works but has issues
### Code of Conduct
- [x] I have joined the [Discord server](https://discord.gg/hjUhu33uAn) and will post updates there
- [x] I have searched existing issues to avoid duplicates
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Hướng nghiên cứu
Start in backend/controllers/auth.go at ForgotPassword and ResetPassword, then inspect backend/models/user.go and the existing signup OTP expiry check around line 240. Verify that reset-code expiry is recorded on generation, expired codes are rejected, and successful resets still clear the code; run the relevant backend authentication tests if available.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- go, mongodb
- Lĩnh vực
- authentication, backend, databases
- Loại issue
- Lỗi
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 68/100