AOSSIE-Org / AOSSIE-Org/DebateAI

[BUG]: Password reset code never expires — indefinite account-takeover window

クローズ
#485 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る
bug
主要言語
TypeScript
スター
84
フォーク
198
平均マージ
2日 19時間
マージ済み PR(30日)
30

説明

### 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

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
go, mongodb
領域
authentication, backend, databases
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
活発
明瞭さ
明確に書かれている
初心者へのやさしさ
68/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。