Skip to content

fix(auth): sign out other sessions after password updates#1801

Merged
riderx merged 3 commits into
mainfrom
riderx/pwd-session-revoke
Mar 16, 2026
Merged

fix(auth): sign out other sessions after password updates#1801
riderx merged 3 commits into
mainfrom
riderx/pwd-session-revoke

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 16, 2026

Summary (AI generated)

  • Sign out other sessions with scope: 'others' after successful password updates in the account change-password, forgot-password, and onboarding set-password flows.

Motivation (AI generated)

  • Align the password-update behavior with the report request without invalidating the current session.

Business Impact (AI generated)

  • Reduces exposure from stale parallel sessions after a password update while preserving the active user flow.

Test Plan (AI generated)

  • Run bun run lint:backend && bun run lint

Screenshots (AI generated)

  • None.

Checklist (AI generated)

  • My code follows the code style of this project and passes
    bun run lint:backend && bun run lint.
  • My change requires a change to the documentation.
  • I have updated the documentation
    accordingly.
  • My change has adequate E2E test coverage.
  • I have tested my code manually, and I have provided steps how to reproduce
    my tests

Generated with AI

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling during password reset and change flows, preventing ambiguous success/failure states.
    • Now explicitly signs out other sessions after successful password updates and redirects users to the dashboard.
    • Success notifications are shown only after sign-out completes to avoid misleading messages.
    • Authentication flow made consistent across all password-related pages.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Three Vue authentication pages were updated to change post-action flows: each now performs an explicit sign-out call with { scope: 'others' }, adds sign-out error handling that aborts on failure, and defers success toasts/navigation until after sign-out. forgot_password now navigates to /dashboard instead of /login.

Changes

Cohort / File(s) Summary
Forgot Password
src/pages/forgot_password.vue
Replaced unspecified signOut() with signOut({ scope: 'others' }), added sign-out error handling that sets form errors and returns early, and changed redirect target from /login to /dashboard.
Onboarding — Set Password
src/pages/onboarding/set_password.vue
Refactored submit flow: on update error return early; after successful update call signOut({ scope: 'others' }) with error handling (abort on error); show success toast only after successful sign-out; navigate to dashboard.
Account — Change Password
src/pages/settings/account/ChangePassword.vue
Removed conditional organization refresh; added explicit signOut({ scope: 'others' }) after successful password update with error handling that aborts on failure; moved success toast to occur after sign-out.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Vue Component
    participant Auth as Supabase Auth
    participant Nav as Router/Navigation
    participant Toast

    User->>UI: submit password change / reset
    UI->>Auth: update password / complete reset
    alt update/reset error
        Auth-->>UI: error
        UI-->>User: show form error (abort)
    else update/reset success
        Auth-->>UI: success
        UI->>Auth: signOut({ scope: 'others' })
        alt signOut error
            Auth-->>UI: signOut error
            UI-->>User: set form error (abort)
        else signOut success
            Auth-->>UI: signed out
            UI->>Toast: show success toast
            UI->>Nav: navigate to /dashboard
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hopped through code with eager paws,
Signed out the others, checked the laws,
Toasts delayed until the end,
Dashboards wait for every friend,
A tidy hop — auth flows applause 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the summary, motivation, and business impact, but is incomplete: documentation updates and E2E test coverage are not done, and manual test steps are not provided. Complete the documentation update section, add E2E test coverage, and provide detailed manual test steps for reproduction. Also address the failing CI test (tests/app.test.ts:241).
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main change: signing out other sessions after password updates across three authentication flows.
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 riderx/pwd-session-revoke
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b3c8e34de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!organizationStore.currentOrganization?.password_has_access) {
await organizationStore.fetchOrganizations()
}
await supabase.auth.signOut({ scope: 'others' })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh org access state after successful password change

After a successful password update, this flow no longer refreshes organization data, so users who were blocked by password policy can remain stuck with stale password_has_access === false in the client store until a manual reload. ChangePassword.vue and pages like dashboard.vue derive access gating directly from organizationStore.currentOrganization.password_has_access, so removing the organizationStore.fetchOrganizations() call here regresses the “regain access immediately after changing password” path for locked-out users.

Useful? React with 👍 / 👎.

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.

Actionable comments posted: 3

🤖 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/pages/forgot_password.vue`:
- Around line 142-143: The current flow calls supabase.auth.signOut({ scope:
'others' }) and immediately router.push('/dashboard') without checking the
signOut result; change this to await the signOut response, handle errors (e.g.,
check for error in the returned object or thrown exception from
supabase.auth.signOut) and only call router.push('/dashboard') on success,
otherwise surface/log the error and show an appropriate user-facing message or
retry path; update the logic around supabase.auth.signOut and router.push to
conditionally redirect based on success.

In `@src/pages/onboarding/set_password.vue`:
- Around line 46-48: After showing toast.success, await and check the result of
supabase.auth.signOut({ scope: 'others' }) (or wrap it in try/catch) and only
call router.replace('/dashboard') when signOut completed without error; if
signOut returns an error or throws, surface an error toast (or processLogger)
and do not navigate. Update the code around toast.success,
supabase.auth.signOut, and router.replace to handle the signOut response/error
path explicitly.

In `@src/pages/settings/account/ChangePassword.vue`:
- Line 277: The call to supabase.auth.signOut({ scope: 'others' }) in the
password change flow must have its outcome checked and any error surfaced to the
user form; update the handler around supabase.auth.signOut in ChangePassword.vue
to await the result, inspect the returned error or status (e.g., the
payload/error fields from supabase.auth.signOut), and if revocation fails set
the form error/state (or throw) so the UI shows failure instead of reporting
success—ensure you reference the exact call site (supabase.auth.signOut) and
propagate or display the error message to the component's form error handling
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c2d664a-a9df-4526-8249-2f78994fa1c5

📥 Commits

Reviewing files that changed from the base of the PR and between 18d2f2a and 5b3c8e3.

📒 Files selected for processing (3)
  • src/pages/forgot_password.vue
  • src/pages/onboarding/set_password.vue
  • src/pages/settings/account/ChangePassword.vue

Comment thread src/pages/forgot_password.vue Outdated
Comment on lines +46 to 48
toast.success(t('changed-password-suc'))
await supabase.auth.signOut({ scope: 'others' })
router.replace('/dashboard')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t navigate on successful password update if session revocation fails.

The flow currently proceeds to /dashboard even if revoking other sessions fails.

Proposed fix
   toast.success(t('changed-password-suc'))
-  await supabase.auth.signOut({ scope: 'others' })
+  const { error: signOutError } = await supabase.auth.signOut({ scope: 'others' })
+  if (signOutError) {
+    setErrors('set-password', [signOutError.message], {})
+    return
+  }
   router.replace('/dashboard')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/onboarding/set_password.vue` around lines 46 - 48, After showing
toast.success, await and check the result of supabase.auth.signOut({ scope:
'others' }) (or wrap it in try/catch) and only call router.replace('/dashboard')
when signOut completed without error; if signOut returns an error or throws,
surface an error toast (or processLogger) and do not navigate. Update the code
around toast.success, supabase.auth.signOut, and router.replace to handle the
signOut response/error path explicitly.

Comment thread src/pages/settings/account/ChangePassword.vue Outdated
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 16, 2026

I checked the current failing CI job before making changes. The red check is in the Cloudflare backend suite, not the frontend auth files:\n\n- failing suite: \n- failing assertion: \n- location from CI log: \n\nI did not push a fix here because this branch currently contains a much broader backend/security diff in addition to the password-session UI changes, so the failure is not isolated to a simple one-line patch from the PR title alone. The next step is to inspect the subkey ownership behavior in the Cloudflare path on this branch and compare it against the same test on the latest base.

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 16, 2026

Correcting the exact failing CI details from the Cloudflare backend job:

  • failing suite: tests/app.test.ts
  • failing assertion: expected 401 to be 200
  • location from CI log: tests/app.test.ts:241
  • failing step: Run Cloudflare Workers backend tests

I still did not push a fix here because this branch currently includes a broader backend/security diff beyond the password-session UI changes, so the failure is not isolated to a simple one-line frontend patch.

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 (2)
src/pages/settings/account/ChangePassword.vue (1)

262-288: ⚠️ Potential issue | 🟡 Minor

Do not drop submit lock before post-update security steps finish.

At Line 264, isLoading is reset before the organization refresh and Line 281 sign-out call, which re-enables submit too early.

Proposed fix
 async function submit(form: { current_password?: string, password: string, password_confirm: string }) {
   if (isLoading.value)
     return
   isLoading.value = true
-
-  if (needsReauthentication.value) {
-    const currentPasswordValid = await verifyCurrentPassword(form.current_password ?? '')
-    if (!currentPasswordValid) {
-      isLoading.value = false
-      return
-    }
-  }
-
-  const mfaOk = await ensureMfaIfNeeded()
-  if (!mfaOk) {
-    isLoading.value = false
-    return
-  }
-  const { error: updateError } = await supabase.auth.updateUser({ password: form.password })
-
-  isLoading.value = false
-  if (updateError) {
+  try {
+    if (needsReauthentication.value) {
+      const currentPasswordValid = await verifyCurrentPassword(form.current_password ?? '')
+      if (!currentPasswordValid)
+        return
+    }
+
+    const mfaOk = await ensureMfaIfNeeded()
+    if (!mfaOk)
+      return
+
+    const { error: updateError } = await supabase.auth.updateUser({ password: form.password })
+    if (updateError) {
       if (updateError.code === 'reauthentication_needed' || updateError.code === 'reauthentication_not_valid') {
         needsReauthentication.value = true
-      isLoading.value = false
         return
       }
       setErrors('change-pass', [t('account-password-error')], {})
-  }
-  else {
-    needsReauthentication.value = false
-    resetCaptcha()
+    }
+    else {
+      needsReauthentication.value = false
+      resetCaptcha()
 
-    if (!organizationStore.currentOrganization?.password_has_access) {
-      await organizationStore.fetchOrganizations()
-    }
+      if (!organizationStore.currentOrganization?.password_has_access) {
+        await organizationStore.fetchOrganizations()
+      }
 
-    const { error: signOutError } = await supabase.auth.signOut({ scope: 'others' })
-    if (signOutError) {
-      setErrors('change-pass', [signOutError.message], {})
-      return
-    }
+      const { error: signOutError } = await supabase.auth.signOut({ scope: 'others' })
+      if (signOutError) {
+        setErrors('change-pass', [signOutError.message], {})
+        return
+      }
 
-    toast.success(t('changed-password-suc'))
-  }
-  if (!updateError) {
-    form.password = ''
-    form.password_confirm = ''
-    form.current_password = ''
+      toast.success(t('changed-password-suc'))
+      form.password = ''
+      form.password_confirm = ''
+      form.current_password = ''
+    }
+  }
+  finally {
+    isLoading.value = false
   }
 }
🤖 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 262 - 288, The
submit lock (isLoading.value) is released too early—remove the early
isLoading.value = false placed immediately after supabase.auth.updateUser and
instead set isLoading.value = false only after all post-update security steps
finish: keep the existing early release in the reauthentication branch
(needsReauthentication.value = true) but move the general error path to release
isLoading after calling setErrors('change-pass', ...) and ensure the
signOutError path releases isLoading before returning; finally, after successful
flow (resetCaptcha, optional organizationStore.fetchOrganizations,
supabase.auth.signOut, toast.success) release isLoading.value = false. Reference
functions/vars: isLoading, updateError, needsReauthentication,
setErrors('change-pass', ...), resetCaptcha,
organizationStore.fetchOrganizations, supabase.auth.signOut, toast.success.
src/pages/onboarding/set_password.vue (1)

37-52: ⚠️ Potential issue | 🟡 Minor

Keep loading active until revocation finishes.

At Line 40, isLoading is cleared before the Line 46 sign-out call completes, so users can re-submit while session revocation is still running.

Proposed fix
 async function submit(form: { password: string }) {
   isLoading.value = true
-
-  const { error: updateError } = await supabase.auth.updateUser({ password: form.password })
-  isLoading.value = false
-  if (updateError) {
-    setErrors('set-password', [updateError.message], {})
-    return
-  }
-
-  const { error: signOutError } = await supabase.auth.signOut({ scope: 'others' })
-  if (signOutError) {
-    setErrors('set-password', [signOutError.message], {})
-    return
-  }
-  toast.success(t('changed-password-suc'))
-  router.replace('/dashboard')
+  try {
+    const { error: updateError } = await supabase.auth.updateUser({ password: form.password })
+    if (updateError) {
+      setErrors('set-password', [updateError.message], {})
+      return
+    }
+
+    const { error: signOutError } = await supabase.auth.signOut({ scope: 'others' })
+    if (signOutError) {
+      setErrors('set-password', [signOutError.message], {})
+      return
+    }
+
+    toast.success(t('changed-password-suc'))
+    router.replace('/dashboard')
+  }
+  finally {
+    isLoading.value = false
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/onboarding/set_password.vue` around lines 37 - 52, Keep the loading
indicator active until the session revocation finishes by moving isLoading.value
= false out of the middle of the flow: remove the early isLoading.value = false
after await supabase.auth.updateUser, and instead set isLoading.value = false in
each return path (when updateError or signOutError occur) and after a successful
sign-out (just before calling toast.success and router.replace). Update the
block around supabase.auth.updateUser, supabase.auth.signOut, setErrors, toast,
and router.replace so loading stays true during signOut and is explicitly
cleared in both error branches and the success branch.
🤖 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/onboarding/set_password.vue`:
- Around line 37-52: Keep the loading indicator active until the session
revocation finishes by moving isLoading.value = false out of the middle of the
flow: remove the early isLoading.value = false after await
supabase.auth.updateUser, and instead set isLoading.value = false in each return
path (when updateError or signOutError occur) and after a successful sign-out
(just before calling toast.success and router.replace). Update the block around
supabase.auth.updateUser, supabase.auth.signOut, setErrors, toast, and
router.replace so loading stays true during signOut and is explicitly cleared in
both error branches and the success branch.

In `@src/pages/settings/account/ChangePassword.vue`:
- Around line 262-288: The submit lock (isLoading.value) is released too
early—remove the early isLoading.value = false placed immediately after
supabase.auth.updateUser and instead set isLoading.value = false only after all
post-update security steps finish: keep the existing early release in the
reauthentication branch (needsReauthentication.value = true) but move the
general error path to release isLoading after calling setErrors('change-pass',
...) and ensure the signOutError path releases isLoading before returning;
finally, after successful flow (resetCaptcha, optional
organizationStore.fetchOrganizations, supabase.auth.signOut, toast.success)
release isLoading.value = false. Reference functions/vars: isLoading,
updateError, needsReauthentication, setErrors('change-pass', ...), resetCaptcha,
organizationStore.fetchOrganizations, supabase.auth.signOut, toast.success.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c9dee05-c835-40a5-8ff1-a220b9892225

📥 Commits

Reviewing files that changed from the base of the PR and between 5b3c8e3 and 92d2ad1.

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

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 8bbf8bc into main Mar 16, 2026
15 checks passed
@riderx riderx deleted the riderx/pwd-session-revoke branch March 16, 2026 21:45
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.

1 participant