AOSSIE-Org / AOSSIE-Org/DebateAI
[BUG]: Password reset code never expires — indefinite account-takeover window
- Vorherrschende Sprache
- TypeScript
- Sterne
- 84
- Forks
- 198
- Ø Merge
- 2 T. 19 Std.
- Gemergte PRs (30 T.)
- 30
Beschreibung
### 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
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Rechercherichtung
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.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- go, mongodb
- Bereich
- authentication, backend, databases
- Issue-Typ
- Bug
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Klar beschrieben
- Anfängerfreundlichkeit
- 68/100