Skip to content

fix(share): auto-join drive after OAuth sign-in/up - #1344

Merged
2witstudios merged 7 commits into
masterfrom
pu/invite-broken
May 13, 2026
Merged

2witstudios merged 7 commits into
masterfrom
pu/invite-broken

Conversation

@2witstudios

@2witstudios 2witstudios commented May 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Root cause 1: `useOAuthSignIn` never passed a `returnUrl` to the OAuth state, so after Google/Apple sign-in the user always landed on `/dashboard` instead of back on `/s/[token]` — they never saw the share accept page again.
  • Root cause 2: New-user getting-started drive provisioning in the Google and Apple OAuth callbacks overwrote `returnUrl`, sending brand-new sign-ups to their fresh drive rather than back to the share link.
  • Root cause 3: `DriveShareAccept` required a manual button click to join. `PageShareAccept` already auto-accepts via `useEffect` — drive shares now match that behaviour.
  • Root cause 4: `DriveShareAccept` only destructured `csrfToken` from `useCSRFToken`, so a CSRF fetch failure left the component in an infinite spinner with no escape.

Changes:

  • `useOAuthSignIn` — adds `returnUrl` option, threads it into `buildOAuthSigninBody` (both Google and Apple signin routes already accept it); also threads `returnUrl` through native iOS sign-in calls
  • `signin/page.tsx` — passes `nextPath` as `returnUrl` to `useOAuthSignIn`; "Sign up" link preserves `?next=` param
  • `signup/page.tsx` + `SignUpClient.tsx` — reads and validates `?next=`, forwards it through to `useOAuthSignIn` and `MagicLinkForm`; "Log in" link preserves `?next=` param
  • `google/callback/route.ts` + `apple/callback/route.ts` — skips getting-started drive redirect when `returnUrl` is a share link (`/s/…`)
  • `DriveShareAccept.tsx` — rewired to auto-accept on auth via `useEffect`; CSRF loading state uses `csrfLoading` (not `!csrfToken`) so a CSRF failure shows error UI rather than an infinite spinner
  • `ios-google-auth.ts` + `ios-apple-auth.ts` — accept `returnUrl` in options, pass it to backend, surface it in result
  • `api/auth/google/native` + `api/auth/apple/native` — accept `returnUrl` in Zod schema, validate with `isSafeReturnUrl`, echo back in response
  • `buildPostNativeAuthRedirect` — `returnUrl` takes precedence over new-user welcome (invite-consumed drive still wins)
  • `PasskeySignupButton` — threads `nextPath` so passkey registration also returns to the share link

Test plan

  • Sign out, visit a drive share link → click "Sign in to join" → sign in with Google → lands back on `/s/[token]` → auto-joins → redirected to drive
  • Same flow with Apple sign-in
  • Same flow with magic link
  • Same flow with passkey (sign in + sign up)
  • Brand-new account via Google OAuth from a share link → getting-started drive is created but user still ends up in the invited drive
  • "Sign up" link on signin page with `?next=/s/[token]` preserves the next param through to signup
  • "Log in" link on signup page with `returnUrl` set preserves it back to signin
  • Already-authenticated user visiting a share link is immediately auto-joined (no button click)
  • Authenticated user with CSRF failure sees error state and "Go to dashboard" link, not an infinite spinner
  • Page share links (`PageShareAccept`) still work (no regression)
  • Email invite flow unaffected

🤖 Generated with Claude Code

- Thread returnUrl through useOAuthSignIn so OAuth flows redirect back to /s/[token] after auth instead of /dashboard

- Skip getting-started drive overwrite of returnUrl when destination is a share link, so new users still land on /s/[token]

- DriveShareAccept now auto-accepts via useEffect on auth (matching PageShareAccept)

- signin page passes nextPath as returnUrl to OAuth; signup page reads and forwards ?next param

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 13, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 57 minutes and 5 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 642d585c-28b4-4de0-a097-0d49afa9eda6

📥 Commits

Reviewing files that changed from the base of the PR and between 47950fa and 50391e1.

📒 Files selected for processing (10)
  • apps/web/src/app/api/auth/apple/native/route.ts
  • apps/web/src/app/api/auth/google/native/route.ts
  • apps/web/src/app/auth/signup/SignUpClient.tsx
  • apps/web/src/app/s/[token]/DriveShareAccept.tsx
  • apps/web/src/app/s/[token]/__tests__/DriveShareAccept.test.tsx
  • apps/web/src/components/auth/PasskeySignupButton.tsx
  • apps/web/src/hooks/__tests__/useOAuthSignIn.test.ts
  • apps/web/src/hooks/useOAuthSignIn.ts
  • apps/web/src/lib/ios-apple-auth.ts
  • apps/web/src/lib/ios-google-auth.ts
📝 Walkthrough

Walkthrough

This PR extends the authentication flow to support post-auth redirect URLs while preserving share-invitation links. It adds optional returnUrl threading through OAuth callbacks, sign-in/sign-up pages, and replaces manual share-acceptance with automatic invitation acceptance.

Changes

OAuth post-auth redirect flow with conditional share-invitation preservation

Layer / File(s) Summary
OAuth hook returnUrl support
apps/web/src/hooks/useOAuthSignIn.ts
useOAuthSignIn hook and buildOAuthSigninBody now accept optional returnUrl parameter and conditionally include it in the generated OAuth request body payload.
OAuth callback conditional returnUrl rewriting
apps/web/src/app/api/auth/apple/callback/route.ts, apps/web/src/app/api/auth/google/callback/route.ts
Apple and Google OAuth callbacks now conditionally rewrite returnUrl to /dashboard/<driveId> only when the existing returnUrl does not start with /s/, preserving share-invitation redirect targets during Getting Started drive provisioning.
Sign-in page OAuth returnUrl integration
apps/web/src/app/auth/signin/page.tsx
Sign-in page validates the next query parameter and passes it as optional returnUrl to useOAuthSignIn, and the Sign-up footer link now preserves the next parameter as an encoded query string.
Sign-up page and client returnUrl threading
apps/web/src/app/auth/signup/page.tsx, apps/web/src/app/auth/signup/SignUpClient.tsx
Sign-up page validates next parameter and conditionally passes it as returnUrl to SignUpClient; SignUpClient extends to accept returnUrl and threads it through both OAuth and magic-link signup paths via useOAuthSignIn hook and MagicLinkForm component.
Drive share auto-accept with redirect flow
apps/web/src/app/s/[token]/DriveShareAccept.tsx
DriveShareAccept now auto-accepts share invitations via useEffect when user is authenticated and CSRF token is available; replaces manual join button with automatic POST to /api/share/{token}/accept, handles pending/error/unauthenticated states, and navigates to dashboard on success.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 2witstudios/PageSpace#833: Both PRs modify the shared OAuth client logic in apps/web/src/hooks/useOAuthSignIn.ts to extend authentication flow behavior.
  • 2witstudios/PageSpace#89: Both PRs modify OAuth callback redirect logic and drive provisioning to handle post-auth destination URLs.
  • 2witstudios/PageSpace#1273: Both PRs touch the OAuth redirect logic in auth callback routes and OAuth hook to handle invite-driven redirect targets.

Poem

🐰 A rabbit hops through signin's door,
with next in tow, to redirect once more—
Safe from /s/, the shares stay true,
while auto-joins bring dashboards into view!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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(share): auto-join drive after OAuth sign-in/up' clearly and specifically describes the main change: enabling automatic join functionality for drive shares after OAuth authentication. It directly aligns with the primary objective of the PR.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/invite-broken

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.

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

Copy link
Copy Markdown

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: 47950facbd

ℹ️ 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".

Comment thread apps/web/src/app/s/[token]/DriveShareAccept.tsx
When authenticated but csrfToken is null, show loading state instead of null (blank page regression).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 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 `@apps/web/src/app/auth/signup/SignUpClient.tsx`:
- Line 27: The "Already have an account?" link in SignUpClient is hardcoded to
'/auth/signin' and drops the passed returnUrl; update the link generation inside
SignUpClient (the code that renders the "Already have an account?" path) to
include the existing returnUrl when present (e.g., append as a query param or
build the route using returnUrl) so the redirect target is preserved when users
switch to Log in; ensure both occurrences around the current sign-in link use
the inviteToken/inviteContext logic if needed and fall back safely when
returnUrl is undefined.

In `@apps/web/src/app/s/`[token]/DriveShareAccept.tsx:
- Around line 24-26: The component DriveShareAccept currently exits the
useEffect and renders null when isAuthenticated is true but csrfToken is
missing, causing a blank UI; update the useEffect to only skip starting the
accept flow when the user is unauthenticated (i.e., if (!isAuthenticated)
return) and handle the authenticated+no-csrf state by rendering a
loading/placeholder instead of null (replace the null return at the render path
with a spinner or retry UI), and optionally trigger or retry CSRF retrieval when
csrfToken is absent so the accept logic (the effect) runs once csrfToken becomes
available.
- Around line 97-99: The sign-in redirect currently interpolates the raw token
into the next query (Button onClick →
router.push(`/auth/signin?next=/s/${token}`)), which can produce invalid URLs
for tokens containing reserved characters; change the push to URL-encode the
next value (e.g., use encodeURIComponent on the path or token) so the
constructed query becomes
router.push(`/auth/signin?next=${encodeURIComponent(`/s/${token}`)}`) — update
the Button onClick handler accordingly to ensure safe redirects.
🪄 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: 33b25eb0-f420-4261-a45f-6527f2f9ebbb

📥 Commits

Reviewing files that changed from the base of the PR and between 75bd66a and 47950fa.

📒 Files selected for processing (7)
  • apps/web/src/app/api/auth/apple/callback/route.ts
  • apps/web/src/app/api/auth/google/callback/route.ts
  • apps/web/src/app/auth/signin/page.tsx
  • apps/web/src/app/auth/signup/SignUpClient.tsx
  • apps/web/src/app/auth/signup/page.tsx
  • apps/web/src/app/s/[token]/DriveShareAccept.tsx
  • apps/web/src/hooks/useOAuthSignIn.ts

Comment thread apps/web/src/app/auth/signup/SignUpClient.tsx
Comment thread apps/web/src/app/s/[token]/DriveShareAccept.tsx
Comment thread apps/web/src/app/s/[token]/DriveShareAccept.tsx Outdated
2witstudios and others added 2 commits May 13, 2026 17:14
- encodeURIComponent the /s/[token] path in DriveShareAccept signin redirect

- SignUpClient Log in link now preserves returnUrl as ?next= param

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spinner replaces disabled button during auth load; sign-in URL
is now URL-encoded per CodeRabbit fix in 45023b4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

CI fix: Unit Tests failed on the existing DriveShareAccept test suite (2 tests).

  • Test 1 ("auth is loading") was asserting a disabled "Join" button, but the component now shows a loading spinner instead — updated to assert the spinner text and absence of the button.
  • Test 3 ("redirect to signin") expected un-encoded URL /auth/signin?next=/s/tok-abc but our encodeURIComponent fix produces /auth/signin?next=%2Fs%2Ftok-abc — updated the expected value.

Fixed in 65bec36.

New users registering via passkey from a share link were landing
on /dashboard instead of back at /s/[token]. Add nextPath prop
to PasskeySignupButton (mirrors PasskeyLoginButton) and wire it
from SignUpClient. Also fix onEmailExists to preserve returnUrl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

Additional fix: passkey signup didn't thread returnUrl back to the share link.

Root cause: PasskeySignupButton had no nextPath prop — after registering, users landed on /dashboard instead of /s/[token]. Passkey login was already correct (PasskeyLoginButton uses nextPath ?? redirectUrl).

Fix (13684a5):

  • Added nextPath?: string to PasskeySignupButton props — mirrors the existing pattern in PasskeyLoginButton
  • SignUpClient now passes nextPath={returnUrl} to PasskeySignupButton
  • onEmailExists callback now preserves returnUrl as ?next= param when bouncing to signin

Auth method coverage after all fixes:

Method Signin flow Signup flow
OAuth (Google/Apple) ✅ ✅
Magic link ✅ ✅
Passkey login ✅ —
Passkey signup — ✅ (this fix)

…ative iOS OAuth

- DriveShareAccept: destructure csrfLoading/csrfError from useCSRFToken so a
  CSRF fetch failure shows the error state instead of an infinite spinner
- useOAuthSignIn: add returnUrl to PostNativeAuthRedirectInput so native iOS
  auth uses the share link as the post-auth destination (invite-consumed drive
  still wins)
- ios-google-auth / ios-apple-auth: pass returnUrl in request body and surface
  it in the result type so the hook can act on it
- api/auth/google/native + api/auth/apple/native: accept returnUrl in the Zod
  schema, validate it with isSafeReturnUrl, and echo it back in the response
- tests: add 4 new DriveShareAccept cases (CSRF error, happy path, non-ok,
  throws) and 5 useOAuthSignIn cases (returnUrl inclusion/omission in body,
  buildPostNativeAuthRedirect returnUrl precedence)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

Additional hardening commit pushed (b1b3df191):

CSRF error → infinite spinner (critical, found in self-review):

  • DriveShareAccept previously only destructured csrfToken from useCSRFToken, so a CSRF fetch failure kept csrfToken=null and localError=null — the spinner condition stayed true indefinitely. Now destructures csrfLoading and csrfError; spinner condition uses csrfLoading (not !csrfToken) and a CSRF error falls through to the error UI with "Go to dashboard".

Missing test coverage (significant, found in self-review):

  • Added 4 new DriveShareAccept test cases: CSRF error shows error state (not spinner), authenticated auto-accept happy path, non-ok response shows server error message, fetch throw shows generic error.

Native iOS returnUrl gap (minor, found in self-review):

  • Completed the full chain: useOAuthSignIn → ios-google-auth / ios-apple-auth → native backend routes. Both routes now accept returnUrl in the Zod schema, validate it with isSafeReturnUrl, and echo it in the response. buildPostNativeAuthRedirect updated so returnUrl takes precedence over the new-user welcome redirect (invite-consumed drive still wins). Added 5 new useOAuthSignIn test cases covering the new buildPostNativeAuthRedirect precedence.

All 21 new/updated tests pass.

Replace dynamic await import() with the static import that is already
present via the getClientIP import from @/lib/auth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios
2witstudios merged commit 20ea384 into master May 13, 2026
9 checks passed
@2witstudios
2witstudios deleted the pu/invite-broken branch May 14, 2026 17:35
2witstudios added a commit that referenced this pull request May 15, 2026
* fix(share): auto-join drive after OAuth sign-in/up

- Thread returnUrl through useOAuthSignIn so OAuth flows redirect back to /s/[token] after auth instead of /dashboard

- Skip getting-started drive overwrite of returnUrl when destination is a share link, so new users still land on /s/[token]

- DriveShareAccept now auto-accepts via useEffect on auth (matching PageShareAccept)

- signin page passes nextPath as returnUrl to OAuth; signup page reads and forwards ?next param

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(share): show spinner while CSRF token loads

When authenticated but csrfToken is null, show loading state instead of null (blank page regression).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(share): encode next param and preserve returnUrl on login link

- encodeURIComponent the /s/[token] path in DriveShareAccept signin redirect

- SignUpClient Log in link now preserves returnUrl as ?next= param

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(share): update DriveShareAccept tests for auto-accept

Spinner replaces disabled button during auth load; sign-in URL
is now URL-encoded per CodeRabbit fix in 45023b4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(share): thread nextPath through PasskeySignupButton

New users registering via passkey from a share link were landing
on /dashboard instead of back at /s/[token]. Add nextPath prop
to PasskeySignupButton (mirrors PasskeyLoginButton) and wire it
from SignUpClient. Also fix onEmailExists to preserve returnUrl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(share): harden CSRF error handling and thread returnUrl through native iOS OAuth

- DriveShareAccept: destructure csrfLoading/csrfError from useCSRFToken so a
  CSRF fetch failure shows the error state instead of an infinite spinner
- useOAuthSignIn: add returnUrl to PostNativeAuthRedirectInput so native iOS
  auth uses the share link as the post-auth destination (invite-consumed drive
  still wins)
- ios-google-auth / ios-apple-auth: pass returnUrl in request body and surface
  it in the result type so the hook can act on it
- api/auth/google/native + api/auth/apple/native: accept returnUrl in the Zod
  schema, validate it with isSafeReturnUrl, and echo it back in the response
- tests: add 4 new DriveShareAccept cases (CSRF error, happy path, non-ok,
  throws) and 5 useOAuthSignIn cases (returnUrl inclusion/omission in body,
  buildPostNativeAuthRedirect returnUrl precedence)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(share): use static import for isSafeReturnUrl in native routes

Replace dynamic await import() with the static import that is already
present via the getClientIP import from @/lib/auth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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