AOSSIE-Org / AOSSIE-Org/DebateAI

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

已关闭
#485 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
bug
主要语言
TypeScript
星标
84
派生
198
平均合并
2 天 19 小时
30 天内合并 PR
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 摘要。