fix(auth): require explicit pending invite choice#2294
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis 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. ChangesPending Invitations Frontend Implementation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
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
ensureOrgMembershipreturn value not checked.Same issue as in
pending_invitations.ts- the function can return aquickErrorResponse 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 valueUse
it.concurrent()for parallel test execution.Per coding guidelines, tests should use
it.concurrent()instead ofit()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
📒 Files selected for processing (11)
cloudflare_workers/api/index.tsmessages/en.jsonsrc/modules/auth.tssrc/pages/onboarding/invitation.vuesrc/pages/register.vuesupabase/functions/_backend/private/accept_invitation.tssupabase/functions/_backend/private/invitation_membership.tssupabase/functions/_backend/private/invite_new_user_to_org.tssupabase/functions/_backend/private/pending_invitations.tssupabase/functions/private/index.tstests/private-pending-invitations.test.ts
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
docs/pr/pending-invite-choice.pngis excluded by!**/*.png
📒 Files selected for processing (5)
messages/en.jsonsrc/modules/auth.tssrc/pages/onboarding/invitation.vuesrc/route-map.d.tstests/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
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/pages/onboarding/invitation.vue (1)
47-48:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAlso 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
📒 Files selected for processing (4)
src/pages/onboarding/invitation.vuesupabase/functions/_backend/private/accept_invitation.tssupabase/functions/_backend/private/pending_invitations.tstests/private-pending-invitations.test.ts
|



Summary (AI generated)
accept_invitation_to_orgfor joining and the existingorg_usersdelete path for declining.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 lintbun typecheckgit diff --checkScreenshots (AI generated)
Checklist (AI generated)
Summary by CodeRabbit