fix: add captcha to password verification flows#1701
Conversation
- Remove dead unauthenticated captcha check from validate_password_compliance (no caller ever hits this path unauthenticated) - Simplify captcha token forwarding to signInWithPassword - Add captcha failure detection with distinct error code - Add Turnstile widget to ChangePassword.vue - Pass captcha token to both signInWithPassword call sites - Handle captcha failures on frontend (toast + widget reset) Fixes: validate_password_compliance returning "Invalid email or password" when password is correct (caused by missing captcha token on signInWithPassword via anon-key client)
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds Turnstile CAPTCHA to the ChangePassword Vue page (frontend manages token lifecycle and passes token to Supabase during sign-in). Backend password validation stops verifying CAPTCHA server-side but propagates captcha_token and classifies CAPTCHA-related Supabase errors as Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Frontend as ChangePassword.vue
participant Turnstile as VueTurnstile
participant Backend as validate_password_compliance
participant Supabase as Supabase Auth
User->>Frontend: Submit password change form
Frontend->>Turnstile: Render & request CAPTCHA (if key present)
Turnstile-->>User: Display challenge
User->>Turnstile: Solve CAPTCHA
Turnstile-->>Frontend: Return captchaToken
Frontend->>Backend: POST password + captchaToken for verification
Backend->>Supabase: signInWithPassword (includes captcha_token if present)
alt Supabase Auth success
Supabase-->>Backend: Auth success
Backend-->>Frontend: verification success
Frontend->>Turnstile: resetCaptcha (cleanup)
else Supabase indicates CAPTCHA issue
Supabase-->>Backend: Error mentioning CAPTCHA
Backend-->>Frontend: captcha_failed response
Frontend->>Turnstile: resetCaptcha
Frontend-->>User: show captcha failure toast
else Other auth failure
Supabase-->>Backend: Auth error
Backend-->>Frontend: invalid_credentials/other
Frontend->>Turnstile: resetCaptcha
Frontend-->>User: show error toast
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
This PR updates the password verification / password change flows to support Supabase Auth CAPTCHA requirements by routing Turnstile tokens through the frontend and surfacing a dedicated captcha_failed error for the compliance verification endpoint.
Changes:
- Removes the unauthenticated Turnstile secret verification path from
validate_password_complianceand instead forwardscaptcha_tokenintosignInWithPassword. - Adds a Turnstile widget to
ChangePassword.vueand sends the token to both backend password verification and Supabase reauthentication. - Distinguishes CAPTCHA failures via
captcha_failed(backend) and resets the widget / shows a toast (frontend).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| supabase/functions/_backend/private/validate_password_compliance.ts | Removes dead unauthenticated captcha verification and maps captcha-related auth failures to captcha_failed. |
| src/pages/settings/account/ChangePassword.vue | Adds Turnstile widget and wires captcha tokens into verify-password and reauth sign-in flows. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/settings/account/ChangePassword.vue (1)
109-114:⚠️ Potential issue | 🟠 MajorDo not send empty CAPTCHA fields in auth payloads.
turnstileTokenstarts as'', so both flows can send an empty token. In the verify-password path, this can fail backend body validation becausecaptcha_tokenis optional but must be non-empty when present.💡 Proposed fix
- body: JSON.stringify({ - email: user.email, - password: form.current_password, - org_id: orgId, - captcha_token: turnstileToken.value, - }), + body: JSON.stringify({ + email: user.email, + password: form.current_password, + org_id: orgId, + ...(turnstileToken.value ? { captcha_token: turnstileToken.value } : {}), + }), @@ - const { error: signInError } = await supabase.auth.signInWithPassword({ - email: user.email, - password: currentPassword, - options: { - captchaToken: turnstileToken.value, - }, - }) + const signInPayload = { + email: user.email, + password: currentPassword, + ...(turnstileToken.value + ? { options: { captchaToken: turnstileToken.value } } + : {}), + } + const { error: signInError } = await supabase.auth.signInWithPassword(signInPayload)Also applies to: 156-162
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/settings/account/ChangePassword.vue` around lines 109 - 114, The request bodies in ChangePassword.vue include captcha_token even when turnstileToken is an empty string; update the payload construction in both places where the body is built (the block using turnstileToken.value around the password change POST and the verify-password path around lines 156-162) to only add captcha_token when turnstileToken has a non-empty value—e.g., build the payload object, conditionally set payload.captcha_token = turnstileToken.value if truthy, then JSON.stringify that payload before sending.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/pages/settings/account/ChangePassword.vue`:
- Around line 109-114: The request bodies in ChangePassword.vue include
captcha_token even when turnstileToken is an empty string; update the payload
construction in both places where the body is built (the block using
turnstileToken.value around the password change POST and the verify-password
path around lines 156-162) to only add captcha_token when turnstileToken has a
non-empty value—e.g., build the payload object, conditionally set
payload.captcha_token = turnstileToken.value if truthy, then JSON.stringify that
payload before sending.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/pages/settings/account/ChangePassword.vuesupabase/functions/_backend/private/validate_password_compliance.ts
- Only include captcha_token in body when non-empty (avoids Zod minLength(1) failure) - Only pass captchaToken option to signInWithPassword when non-empty - Add resetCaptcha() helper that both resets widget and clears token ref - Clear captcha token after successful operations (tokens are single-use)
|



Summary
validate_password_compliance(nobody calls this endpoint unauthenticated)ChangePassword.vuesignInWithPasswordcall paths (verify password + change password reauthentication)captcha_failederror code)Context
PR #1687 added captcha hardening to
validate_password_compliancebut broke the authenticated user flow. The endpoint callssignInWithPasswordvia the anon-key client without a captcha token, which Supabase Auth rejects — causing "Invalid email or password" even with correct credentials.The unauthenticated captcha check was dead code (no frontend caller ever hits this path without a JWT). Removed it and instead added Turnstile to the frontend so a valid captcha token flows through to
signInWithPassword.Test plan
Summary by CodeRabbit
New Features
Improvements