Skip to content

fix(auth): require explicit pending invite choice#2294

Merged
riderx merged 5 commits into
mainfrom
codex/pending-invite-choice-onboarding
May 20, 2026
Merged

fix(auth): require explicit pending invite choice#2294
riderx merged 5 commits into
mainfrom
codex/pending-invite-choice-onboarding

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 18, 2026

Summary (AI generated)

  • Added a frontend pending-invitation onboarding page for authenticated users with organization invites already present in the organization store.
  • Reused the existing frontend invite accept/deny flow: accept_invitation_to_org for joining and the existing org_users delete path for declining.
  • Redirected registration to the dashboard so the auth guard can route invited users to the invite decision page before org creation.

Motivation (AI generated)

Invited users could land on organization creation while they still had pending organization invites. They should see the invite first and explicitly choose to join or decline before continuing.

Business Impact (AI generated)

This reduces invite onboarding confusion and prevents invited users from creating unnecessary organizations while preserving user choice.

Test Plan (AI generated)

  • bun lint
  • bun typecheck
  • git diff --check
  • GitHub CI checks passed after the previous push; waiting on the latest frontend-only push rerun.

Screenshots (AI generated)

Pending invitation choice page

Checklist (AI generated)

  • My code follows the code style of this project.
  • My change does not require a change to the documentation.
  • My change uses the existing frontend invitation flow instead of adding backend endpoints.

Summary by CodeRabbit

  • New Features
    • Added organization invitation management to the onboarding flow—users can now accept or decline pending invitations individually or in bulk
    • Implemented error handling and loading states for invitation actions
    • Updated signup to redirect to dashboard upon completion

Review Change Stack

@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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a87ec29-91cb-4de9-afa1-8055ebe479a2

📥 Commits

Reviewing files that changed from the base of the PR and between d73a475 and bde8af0.

📒 Files selected for processing (2)
  • src/modules/auth.ts
  • src/pages/onboarding/invitation.vue

📝 Walkthrough

Walkthrough

This pull request implements the frontend user experience for accepting or declining pending organization invitations. The changes add route registration, auth guard redirection to detect users with pending invites, a new invitation onboarding page with accept/decline flows, supporting localization strings, and a redirect adjustment in post-signup registration.

Changes

Pending Invitations Frontend Implementation

Layer / File(s) Summary
Route registration and type definitions
src/route-map.d.ts
Routes for the new invitation page are added to the auto-route type map, enabling type-safe navigation to /onboarding/invitation.
Auth guard pending invitation detection and redirect
src/modules/auth.ts
Auth guard extends routing logic to detect pending invitations by filtering organizations with invite-like roles, gates the check until organizations are loaded, and early-redirects authenticated users to /onboarding/invitation while collapsing /onboarding/* redirect targets to /dashboard.
Invitation page state, navigation, and resolution logic
src/pages/onboarding/invitation.vue
Core page logic establishes reactive state for pending invitations, computes redirect targets that reject /onboarding/* destinations, centralizes post-resolution navigation, and implements unified accept/decline/decline-all flows with per-button loading state, Supabase operations, organization refresh, and success/error handling.
Invitation page template and user interface
src/pages/onboarding/invitation.vue
Template renders pending invitations with logo or computed initials fallback, wires buttons to resolution handler, disables controls during resolution, conditionally shows decline-all for multiple invitations, and displays loading and error states.
Localization strings and registration redirect adjustment
messages/en.json, src/pages/register.vue
English localization covers all invitation UI states and single/multiple variant wording; post-signup registration redirects to /dashboard to allow auth guard interception.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hopped through the garden of auth,
Finding invitations tied up in a bow,
Accept, decline, choose your path forth,
With proper guards and redirects to go! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(auth): require explicit pending invite choice' clearly and concisely describes the main change: adding a requirement for users with pending invitations to explicitly choose to accept or decline before continuing.
Description check ✅ Passed The PR description includes a Summary, Motivation, Business Impact, Test Plan, Screenshots, and Checklist sections that align with the template structure, though some template sections could be more detailed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/pending-invite-choice-onboarding

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 18, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/pending-invite-choice-onboarding (bde8af0) with main (7712cd5)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/functions/_backend/private/accept_invitation.ts (1)

221-221: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ensureOrgMembership return value not checked.

Same issue as in pending_invitations.ts - the function can return a quickError Response that is silently ignored. This affects lines 221, 286, and 369.

🛡️ Proposed fix pattern (apply to all three call sites)
-    await ensureOrgMembership(supabaseAdmin, userId, invitation, org)
+    const membershipResult = await ensureOrgMembership(supabaseAdmin, userId, invitation, org)
+    if (membershipResult)
+      return membershipResult
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/_backend/private/accept_invitation.ts` at line 221,
ensureOrgMembership can return a quickError Response that is currently ignored;
update each await ensureOrgMembership(...) call (the three call sites in this
file) to capture the result, check if it's a Response/quickError, and
immediately return it if so (i.e. const maybeResp = await
ensureOrgMembership(...); if (maybeResp) return maybeResp;). This ensures errors
from ensureOrgMembership are propagated instead of being silently ignored.
🧹 Nitpick comments (1)
tests/private-pending-invitations.test.ts (1)

90-90: 💤 Low value

Use it.concurrent() for parallel test execution.

Per coding guidelines, tests should use it.concurrent() instead of it() to run tests in parallel within the same file for faster CI/CD.

♻️ Proposed change
-  it('lists pending invitations without joining the organization', async () => {
+  it.concurrent('lists pending invitations without joining the organization', async () => {

Apply the same change to the other two test cases.

Note: If these tests must run sequentially due to shared state cleanup dependencies, consider isolating test data further or keeping it().

As per coding guidelines: "use it.concurrent() instead of it() to run tests in parallel within the same file for faster CI/CD".

Also applies to: 118-118, 176-176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/private-pending-invitations.test.ts` at line 90, Change the three tests
that currently use it(...) to execute in parallel by replacing it(...) with
it.concurrent(...): update the test with the description "lists pending
invitations without joining the organization" and the two other test cases (the
ones indicated by the review) to use it.concurrent, ensuring any shared state is
isolated or cleaned up so parallel execution is safe; keep using plain it(...)
only if you confirm sequential execution is required due to shared-state cleanup
dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/functions/_backend/private/pending_invitations.ts`:
- Around line 109-115: The POST handler calls getAuthenticatedEmail and
getPendingInvitations which can return Response objects; add the same runtime
checks used elsewhere: after calling getAuthenticatedEmail(auth.userId) and
after calling getPendingInvitations(c, email) verify if the result is instanceof
Response and if so immediately return that Response. Update the POST handler's
flow (the POST function in pending_invitations.ts) to perform these early-return
checks for both getAuthenticatedEmail and getPendingInvitations to avoid
treating Response objects as success values.
- Around line 175-181: The call to ensureOrgMembership (when !alreadyJoined) can
return a quickError Response which is currently ignored; update the logic around
ensureOrgMembership in pending_invitations.ts to capture its return value, check
if it is a failing Response (quickError), and if so immediately return that
Response instead of continuing to delete the invitation; keep the existing
alreadyJoined check and only call ensureOrgMembership when needed, but propagate
any error Response from ensureOrgMembership back to the caller so failures
aren’t silently ignored.
- Around line 81-85: getAuthenticatedEmail and getPendingInvitations can return
a Response (via quickError), so after calling getAuthenticatedEmail(check the
value in the variable email) and getPendingInvitations(check the value in the
variable invitations) you must detect and propagate those error Responses
instead of treating them as strings/arrays; update the code around the calls to
getAuthenticatedEmail(supabaseAdmin, auth.userId) and getPendingInvitations(c,
email) to: 1) if email is a Response (or otherwise an error Response shape)
return it immediately; 2) if email is falsy still return quickError(400,
'missing_email', ...); 3) after calling getPendingInvitations, if invitations is
a Response return it immediately before using it. Ensure you reference the exact
variables/getters: getAuthenticatedEmail, getPendingInvitations, quickError,
email, and invitations.

---

Outside diff comments:
In `@supabase/functions/_backend/private/accept_invitation.ts`:
- Line 221: ensureOrgMembership can return a quickError Response that is
currently ignored; update each await ensureOrgMembership(...) call (the three
call sites in this file) to capture the result, check if it's a
Response/quickError, and immediately return it if so (i.e. const maybeResp =
await ensureOrgMembership(...); if (maybeResp) return maybeResp;). This ensures
errors from ensureOrgMembership are propagated instead of being silently
ignored.

---

Nitpick comments:
In `@tests/private-pending-invitations.test.ts`:
- Line 90: Change the three tests that currently use it(...) to execute in
parallel by replacing it(...) with it.concurrent(...): update the test with the
description "lists pending invitations without joining the organization" and the
two other test cases (the ones indicated by the review) to use it.concurrent,
ensuring any shared state is isolated or cleaned up so parallel execution is
safe; keep using plain it(...) only if you confirm sequential execution is
required due to shared-state cleanup dependencies.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8856f0ae-abb5-4991-96da-a9186328b161

📥 Commits

Reviewing files that changed from the base of the PR and between 8daad7c and 20def59.

📒 Files selected for processing (11)
  • cloudflare_workers/api/index.ts
  • messages/en.json
  • src/modules/auth.ts
  • src/pages/onboarding/invitation.vue
  • src/pages/register.vue
  • supabase/functions/_backend/private/accept_invitation.ts
  • supabase/functions/_backend/private/invitation_membership.ts
  • supabase/functions/_backend/private/invite_new_user_to_org.ts
  • supabase/functions/_backend/private/pending_invitations.ts
  • supabase/functions/private/index.ts
  • tests/private-pending-invitations.test.ts

Comment thread supabase/functions/_backend/private/pending_invitations.ts Outdated
Comment thread supabase/functions/_backend/private/pending_invitations.ts Outdated
Comment thread supabase/functions/_backend/private/pending_invitations.ts Outdated
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/onboarding/invitation.vue`:
- Around line 45-49: The computed targetPath currently only blocks paths
starting with '/onboarding/' but allows '/onboarding' or '/onboarding?...';
update the condition in the targetPath computed (where route.query.to is parsed)
to reject any path that starts with '/onboarding' (e.g., use
!target.startsWith('/onboarding') instead of !target.startsWith('/onboarding/'))
so any onboarding root or subpath falls back to '/dashboard'.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c29ab44b-8930-4810-a75e-11618671efee

📥 Commits

Reviewing files that changed from the base of the PR and between 20def59 and 6c59c76.

⛔ Files ignored due to path filters (1)
  • docs/pr/pending-invite-choice.png is excluded by !**/*.png
📒 Files selected for processing (5)
  • messages/en.json
  • src/modules/auth.ts
  • src/pages/onboarding/invitation.vue
  • src/route-map.d.ts
  • tests/auth-sso-provisioning.unit.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/route-map.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • messages/en.json
  • src/modules/auth.ts

Comment thread src/pages/onboarding/invitation.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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pages/onboarding/invitation.vue (1)

47-48: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Also reject /onboarding#... redirect targets.

The new regex still allows to=/onboarding#..., which can send users back into onboarding after they finish resolving invites. Include hash fragments in the onboarding-target check.

Suggested fix
-  const isOnboardingTarget = /^\/onboarding(?:\/|\?|$)/.test(target)
+  const isOnboardingTarget = /^\/onboarding(?:[/?#]|$)/.test(target)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/onboarding/invitation.vue` around lines 47 - 48, The
onboarding-target check currently uses isOnboardingTarget =
/^\/onboarding(?:\/|\?|$)/.test(target) which still allows hash-only redirects
like /onboarding#...; update the regex to include hash fragments (for example
/^\/onboarding(?:[\/?#]|$)/.test(target)) or otherwise ensure the check treats
'#' the same as '/' and '?' so that target.startsWith('/') &&
!isOnboardingTarget correctly rejects /onboarding#...; modify the
isOnboardingTarget declaration accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/private-pending-invitations.test.ts`:
- Around line 103-105: The tests call createFixture(), cleanup(fixture), then
seedPendingInvitation(fixture) outside the try/finally, which can leak DB/auth
state if seeding fails; move the seedPendingInvitation(fixture) call inside the
existing try block (alongside the test actions) so that the finally always runs
cleanup(fixture). Update all three tests that use
createFixture/cleanup/seedPendingInvitation (references: createFixture, cleanup,
seedPendingInvitation) to follow this pattern.

---

Duplicate comments:
In `@src/pages/onboarding/invitation.vue`:
- Around line 47-48: The onboarding-target check currently uses
isOnboardingTarget = /^\/onboarding(?:\/|\?|$)/.test(target) which still allows
hash-only redirects like /onboarding#...; update the regex to include hash
fragments (for example /^\/onboarding(?:[\/?#]|$)/.test(target)) or otherwise
ensure the check treats '#' the same as '/' and '?' so that
target.startsWith('/') && !isOnboardingTarget correctly rejects /onboarding#...;
modify the isOnboardingTarget declaration accordingly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79c3b4f5-35bc-4875-a489-1275c09828b3

📥 Commits

Reviewing files that changed from the base of the PR and between 6c59c76 and 0ba733a.

📒 Files selected for processing (4)
  • src/pages/onboarding/invitation.vue
  • supabase/functions/_backend/private/accept_invitation.ts
  • supabase/functions/_backend/private/pending_invitations.ts
  • tests/private-pending-invitations.test.ts

Comment thread tests/private-pending-invitations.test.ts Outdated
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 78aa59f into main May 20, 2026
42 checks passed
@riderx riderx deleted the codex/pending-invite-choice-onboarding branch May 20, 2026 13:36
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