Skip to content

fix: add captcha to password verification flows#1701

Merged
riderx merged 2 commits into
mainfrom
fix/captcha-password-verification
Feb 25, 2026
Merged

fix: add captcha to password verification flows#1701
riderx merged 2 commits into
mainfrom
fix/captcha-password-verification

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented Feb 25, 2026

Summary

  • Remove dead unauthenticated captcha check from validate_password_compliance (nobody calls this endpoint unauthenticated)
  • Add Turnstile captcha widget to ChangePassword.vue
  • Wire captcha tokens into both signInWithPassword call paths (verify password + change password reauthentication)
  • Handle captcha failures on both frontend (toast + widget reset) and backend (distinct captcha_failed error code)

Context

PR #1687 added captcha hardening to validate_password_compliance but broke the authenticated user flow. The endpoint calls signInWithPassword via 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

  • Manual: verify password on ChangePassword page with correct password → should succeed
  • Manual: verify password with wrong password → should show "invalid password"
  • Manual: change password flow with reauthentication → should succeed
  • Manual: verify Turnstile widget renders on the page
  • Manual: verify captcha failure shows toast error

Summary by CodeRabbit

  • New Features

    • CAPTCHA now appears as part of the password change and sign-in flows when required.
  • Improvements

    • Improved handling of CAPTCHA interactions: clearer failure messages, automatic CAPTCHA reset on failure and after success, and conditional inclusion of CAPTCHA tokens during verification.
    • Sign-in and password verification flows surface specific CAPTCHA failure feedback when applicable.

- 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)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 620cddd and 4332f1a.

📒 Files selected for processing (1)
  • src/pages/settings/account/ChangePassword.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/settings/account/ChangePassword.vue

📝 Walkthrough

Walkthrough

Adds 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 captcha_failed.

Changes

Cohort / File(s) Summary
Frontend — ChangePassword component
src/pages/settings/account/ChangePassword.vue
Integrates VueTurnstile and new reactive state (turnstileToken, captchaKey, captchaComponent), reads VITE_CAPTCHA_KEY, renders CAPTCHA when key present, adds resetCaptcha, includes captcha token in verification and signInWithPassword flows, and resets CAPTCHA on various error/success paths with toasts for captcha failures.
Backend — password compliance function
supabase/functions/_backend/private/validate_password_compliance.ts
Removes server-side CAPTCHA verification/secret checks; always accepts and forwards provided captchaToken to Supabase login options, logs Supabase errors, and returns captcha_failed when the Supabase error message indicates a CAPTCHA issue while preserving other failure paths.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped to the gate with a tiny token tight,
Turnstile blinked bright as I solved the small plight,
Frontend holds the token, backend forwards the cry,
A twitch of my whiskers — the login passed by,
Hooray for small puzzles that keep the night right! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding CAPTCHA to password verification flows, which aligns with the primary intent of both frontend and backend modifications.
Description check ✅ Passed The description includes all major required sections: Summary explaining the changes, Context providing background, and a detailed Test plan with specific manual testing steps. All critical information is present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/captcha-password-verification

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@WcaleNieWolny WcaleNieWolny marked this pull request as ready for review February 25, 2026 16:42
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_compliance and instead forwards captcha_token into signInWithPassword.
  • Adds a Turnstile widget to ChangePassword.vue and 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.

Comment thread src/pages/settings/account/ChangePassword.vue
Comment thread src/pages/settings/account/ChangePassword.vue
Comment thread src/pages/settings/account/ChangePassword.vue
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Do not send empty CAPTCHA fields in auth payloads.

turnstileToken starts as '', so both flows can send an empty token. In the verify-password path, this can fail backend body validation because captcha_token is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6de8c0d and 620cddd.

📒 Files selected for processing (2)
  • src/pages/settings/account/ChangePassword.vue
  • supabase/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)
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 2b3778a into main Feb 25, 2026
14 checks passed
@riderx riderx deleted the fix/captcha-password-verification branch February 25, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants