Align password reset active token index schema#270
Conversation
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
|
CodeAnt AI is reviewing your PR. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughEndpoint now, under an advisory DB lock, consumes any existing unused password-reset token, inserts a new token and returns both IDs with the plaintext token; if email send fails a compensating transaction marks the new token used and restores the previous token’s unused state. Schema: partial unique index on unused tokens per user. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API
participant DB
participant EmailService
Client->>API: POST /auth/password-reset/request (email)
API->>DB: begin tx, acquire advisory lock
API->>DB: SELECT existing unused token id FOR UPDATE
API->>DB: UPDATE passwordResetTokens SET usedAt=now() WHERE userId = X AND usedAt IS NULL
API->>DB: INSERT new token RETURNING id
DB-->>API: { previousTokenId, newTokenId, token }
API->>EmailService: send reset email (tokenIssue.token)
alt email success
EmailService-->>API: success
API->>DB: commit tx (releases lock)
API-->>Client: 200 OK
else email failure
EmailService-->>API: error
API->>DB: begin compensating tx, acquire advisory lock
API->>DB: UPDATE inserted token SET usedAt=now()
API->>DB: IF previousTokenId NOT NULL -> UPDATE previous token SET usedAt=NULL
DB-->>API: compensating tx committed
API-->>Client: 500 / error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 13257f7. Configure here.
|
CodeAnt AI finished reviewing your PR. |
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/api/auth/password-reset/request/route.ts`:
- Around line 66-69: The current transaction marks the existing token used via
tx.update(passwordResetTokens).set({ usedAt: new Date() }) before calling
sendPasswordResetEmail, which risks revoking the only usable token if email
sending fails; change the flow in route handler (around sendPasswordResetEmail,
tx, and passwordResetTokens) to create/insert the replacement token first (or
queue/send the email first), ensure sendPasswordResetEmail succeeds (or the job
is enqueued) and only then update the prior token with usedAt, or alternatively
perform the revocation inside a compensating step that runs after a successful
send; locate usages of sendPasswordResetEmail, the tx block, and
passwordResetTokens update and reorder so revocation happens after successful
send/queue.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8fc10a5f-ab99-46a0-a256-7eca435e8a77
📒 Files selected for processing (2)
src/app/api/auth/password-reset/request/route.tssrc/db/schema.ts
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/api/auth/password-reset/request/route.ts`:
- Around line 66-75: The current select-then-update has a TOCTOU race: instead
of selecting a previous token with tx.select(...passwordResetTokens...) and then
calling tx.update(...passwordResetTokens...).set({usedAt}), change the logic to
perform the update and capture the updated row atomically (use
tx.update(...).set({ usedAt: new Date() }).where(...).returning({ id:
passwordResetTokens.id }) or the DB/ORM equivalent) so you derive
previousTokenId from the update result that was actually marked used; this
ensures consumePasswordResetToken or other concurrent operations can't cause you
to revert a token you didn't actually update when you later set usedAt = null.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e0e71a61-47d9-4b81-be60-1ee134e6d878
📒 Files selected for processing (1)
src/app/api/auth/password-reset/request/route.ts
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
|
Sweep of unresolved review threads — all five findings are already addressed at HEAD
Resolving all five threads. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR updates the password reset request flow so that each user has at most one unused reset token, clears stale tokens before issuing a new one, and restores the prior token if email delivery of the new token fails. sequenceDiagram
participant User
participant API
participant Database
participant Email
User->>API: Request password reset
API->>Database: Transaction to check recent token, clear stale tokens, create new token
alt New token created
API->>Email: Send password reset email
alt Email delivery fails
API->>Database: Transaction to mark new token used and restore previous token
else Email delivered
end
else Recent unused token exists
API->>API: Skip issuing new token and email
end
API-->>User: Return generic reset requested message
Generated by CodeAnt AI |
| if (tokenIssue) { | ||
| try { | ||
| await sendPasswordResetEmail({ to: user.email, token }); | ||
| await sendPasswordResetEmail({ to: user.email, token: tokenIssue.token }); |
There was a problem hiding this comment.
Suggestion: This call assumes email delivery failure will throw, but sendPasswordResetEmail can return { delivered: false } without throwing (for example when delivery is skipped in non-production). In that path, the replacement token remains active and no rollback runs, so the code does not actually restore the prior token on all delivery failures. Check the returned delivered flag and trigger the same compensating transaction when it is false. [logic error]
Severity Level: Major ⚠️
- ⚠️ Password reset API keeps active token when email skipped.
- ⚠️ Prior reset token not restored on skipped delivery path.
- ⚠️ Dev/staging DB invariants differ from intended failure semantics.Steps of Reproduction ✅
1. Run the app in a non-production environment where `process.env.NODE_ENV !==
"production"` and at least one of `EMAIL_FROM` or `RESEND_API_KEY` is unset so that
`sendEmail` takes its non-throwing skip path at `src/lib/email.ts:25-32` (returns `{
delivered: false }` after logging a dev message).
2. Trigger the password reset request API by sending `POST
/api/auth/password-reset/request` (handled by `export async function POST` in
`src/app/api/auth/password-reset/request/route.ts:18-44`) with a JSON body containing an
email that matches an existing user in the `users` table (queried at lines 33-40).
3. In the POST handler, the code enters the transactional block at `route.ts:46-82`,
acquires the advisory lock, marks any previously unused reset token for that user as used
(`update passwordResetTokens` at lines 66-70), then inserts a new `passwordResetTokens`
row and returns `{ token, newTokenId, previousTokenId }` as `tokenIssue`.
4. Execution reaches the email send section at `route.ts:84-87`: `await
sendPasswordResetEmail({ to: user.email, token: tokenIssue.token });` calls
`sendPasswordResetEmail` in `src/lib/email.ts:39-22`, which in turn calls `sendEmail`.
Because email configuration is missing in this non-production setup, `sendEmail` returns
`{ delivered: false }` without throwing. The `try` block completes successfully, the
`catch` at `route.ts:87-32` (which would mark `newTokenId` as used and optionally restore
`previousTokenId`) is never entered, so the unsent replacement token remains the only
active token and the prior token is not restored on this non-throwing delivery failure
path, contrary to the intended "restore prior unused token if replacement email delivery
fails" behavior described in the PR.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/app/api/auth/password-reset/request/route.ts
**Line:** 86:86
**Comment:**
*Logic Error: This call assumes email delivery failure will throw, but `sendPasswordResetEmail` can return `{ delivered: false }` without throwing (for example when delivery is skipped in non-production). In that path, the replacement token remains active and no rollback runs, so the code does not actually restore the prior token on all delivery failures. Check the returned `delivered` flag and trigger the same compensating transaction when it is false.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR updates the password reset flow so the database enforces a single unused token per user and the API clears stale tokens, while also restoring the prior token if email delivery fails. sequenceDiagram
participant User
participant PasswordResetAPI as Password reset API
participant Database
participant EmailService as Email service
User->>PasswordResetAPI: Submit password reset request
PasswordResetAPI->>Database: Lock user and check for recent unused token
alt Recent unused token exists
PasswordResetAPI-->>User: Return generic reset requested response
else No recent unused token
PasswordResetAPI->>Database: Mark prior unused tokens used and insert new token
PasswordResetAPI->>EmailService: Send password reset email with new token
alt Email sent successfully
PasswordResetAPI-->>User: Return generic reset requested response
else Email delivery failed
PasswordResetAPI->>Database: Mark new token used and restore prior unused token
PasswordResetAPI-->>User: Return generic reset requested response
end
end
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR enforces a single unused password reset token per user and updates the request flow to clear stale tokens and restore a prior token if email delivery fails. sequenceDiagram
participant User
participant API
participant Database
participant Email
User->>API: Submit password reset request
API->>Database: Start transaction and lock user
API->>Database: Find recent unused reset token
alt Recent unused token exists
API-->>User: Return generic reset message
else No recent token
API->>Database: Clear stale tokens and create new reset token
API->>Email: Send password reset email
alt Email fails
Email-->>API: Report delivery error
API->>Database: Mark new token used and restore previous token
end
API-->>User: Return generic reset message
end
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR enforces a single unused password reset token per user via a unique index and transactional logic that replaces any prior token, then restores it if email delivery of the new token fails. sequenceDiagram
participant User
participant PasswordResetAPI
participant Database
participant EmailService
User->>PasswordResetAPI: Request password reset
PasswordResetAPI->>Database: Verify rate limit and replace unused reset token for user
Database-->>PasswordResetAPI: New token and prior token id
PasswordResetAPI->>EmailService: Send reset email with new token
alt Email sent successfully
EmailService-->>PasswordResetAPI: Email sent
PasswordResetAPI-->>User: Generic success message
else Email delivery failed
EmailService-->>PasswordResetAPI: Error sending email
PasswordResetAPI->>Database: Mark new token used and restore prior token unused
PasswordResetAPI-->>User: Generic success message
end
Generated by CodeAnt AI |
|
CodeAnt AI finished running the review. |

User description
Summary
password_reset_tokens.activeUserIdxwith the deployed unique partial index onuser_idwhereused_at is null.expires_atis not part of the uniqueness scope.Testing
npx drizzle-kit generate(emitted an untracked initial baseline because the repo has no Drizzle journal; generated index shape matches the deployed unique partial index and does not emit an alter/drop migration for this index)npx tsc --noEmitnpm run buildcoderabbit review --prompt-only(blocked: CLI not installed in the cloud image)npx coderabbit review --prompt-only(blocked: npm could not determine executable)npm run test:unit(existing Vitest collection issue insrc/lib/thoughtSaving.test.ts)CodeAnt-AI Description
Prevent duplicate password reset tokens and keep email retries reliable
What Changed
Impact
✅ Fewer broken password reset links✅ Safer resend flow for password resets✅ Less mismatch between app behavior and stored reset-token rules🔄 Retrigger CodeAnt AI Review
Details
💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.