Conversation
|
@greptileai review por favor? |
Greptile SummaryThis PR adds email verification flows: users can add email addresses, receive a verification link, resend with a 10-minute cooldown, and unlink emails from the Integrations settings page. The
Confidence Score: 4/5Not safe to merge as-is — expired verification requests create an irrecoverable deadlock that locks users out of an email address permanently. Two P1 bugs interact: the unscoped exists? in add_email and the .valid filter in unlink_email together mean that once a 30-minute verification window lapses, there is no UI path for the user to escape. Both fixes are one-liners but the issue affects the primary user path for the new feature. app/controllers/sessions_controller.rb — lines 194 (add_email exists? check) and 261 (unlink_email .valid scope) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User submits add_email] --> B{EmailAddress.exists?}
B -- yes --> C[Alert: already associated]
B -- no --> D{EmailVerificationRequest.exists?\nNO expiry/deleted filter ⚠️}
D -- yes --> E[Alert: already pending\neven if expired or deleted!]
D -- no --> F[Create EmailVerificationRequest\nexpires in 30min]
F --> G[Send verification email]
G --> H{User clicks link\nbefore 30min?}
H -- yes --> I[token action:\nEmailVerificationRequest.valid]
I --> J[verify! → create EmailAddress\n+ soft_delete request]
H -- no --> K[Request expires]
K --> L{User tries resend}
L --> M[resend_email_verification\nuses .valid scope]
M -- expired not found --> N[Alert: No pending verification]
K --> O{User tries unlink}
O --> P[unlink_email\nuses .valid scope ⚠️]
P -- expired not found --> Q[Alert: Email must exist to be removed]
K --> R{User tries re-add}
R --> D
D -- expired record found --> E
N & Q & E --> S[User permanently locked out\nfrom this email address]
|
| unless email_record | ||
| redirect_to my_settings_path, alert: "Email must exist to be unlinked" | ||
| pending_request = current_user.email_verification_requests.valid.find_by(email: email) |
There was a problem hiding this comment.
Expired pending requests cannot be removed
The .valid scope requires expires_at > Time.current, so an expired (but non-deleted) verification request is invisible here and can't be cancelled. Combined with the add_email check above, users who miss the 30-minute window end up permanently blocked from that email address in the UI.
| unless email_record | |
| redirect_to my_settings_path, alert: "Email must exist to be unlinked" | |
| pending_request = current_user.email_verification_requests.valid.find_by(email: email) | |
| pending_request = current_user.email_verification_requests.where(deleted_at: nil).find_by(email: email) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/controllers/sessions_controller.rb
Line: 260-261
Comment:
**Expired pending requests cannot be removed**
The `.valid` scope requires `expires_at > Time.current`, so an expired (but non-deleted) verification request is invisible here and can't be cancelled. Combined with the `add_email` check above, users who miss the 30-minute window end up permanently blocked from that email address in the UI.
```suggestion
pending_request = current_user.email_verification_requests.where(deleted_at: nil).find_by(email: email)
```
How can I resolve this? If you propose a fix, please make it concise.| <form method="post" action={paths.resend_email_verification_path}> | ||
| <input | ||
| type="hidden" | ||
| name="authenticity_token" | ||
| value={csrfToken} | ||
| /> | ||
| <input type="hidden" name="email" value={email.email} /> | ||
| <Button | ||
| type="submit" | ||
| variant="surface" | ||
| size="xs" | ||
| class="rounded-md" | ||
| disabled={!email.can_resend} | ||
| > | ||
| {email.can_resend | ||
| ? "Resend" | ||
| : formatCooldown(email.resend_cooldown_seconds)} | ||
| </Button> | ||
| </form> | ||
| {/if} |
There was a problem hiding this comment.
Manual CSRF handling instead of Inertia form utilities
The resend and remove forms (and others in this file) manually fetch the CSRF token via onMount + document.querySelector. The project rules prefer Inertia's useForm / router.post utilities, which handle CSRF, validation state, and redirects automatically. This is a pattern across the entire component that could be addressed as a follow-up.
Rule Used: What: Always use Inertia's <Form> or useForm u... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/javascript/pages/Users/Settings/Integrations.svelte
Line: 200-219
Comment:
**Manual CSRF handling instead of Inertia form utilities**
The resend and remove forms (and others in this file) manually fetch the CSRF token via `onMount` + `document.querySelector`. The project rules prefer Inertia's `useForm` / `router.post` utilities, which handle CSRF, validation state, and redirects automatically. This is a pattern across the entire component that could be addressed as a follow-up.
**Rule Used:** What: Always use Inertia's `<Form>` or `useForm` u... ([source](https://app.greptile.com/review/custom-context?memory=03426195-806a-4980-ad3d-f1c3db3c22fb))
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Uh oh!
There was an error while loading. Please reload this page.