Add dedicated /auth sign-in page with callback routing - #139
Conversation
Route homepage and device authorization through /auth, add Google and magic-link providers with Resend-backed delivery, and cover callback/dev-mode behavior with web/server tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Verify the sign-in/magic-link endpoint accepts requests without relying on intercepting internal plugin redirects or external Resend calls. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR adds Google OAuth and Resend-backed magic links on the server. The web app adds callback-safe authentication routes, password and magic-link forms, device authorization guards, two-factor method persistence, and updated TanStack Router wiring. ChangesAuthentication overhaul
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Browser
participant AuthRoute
participant LoginForm
participant BetterAuth
participant Resend
Browser->>AuthRoute: Open auth route with callbackUrl
AuthRoute->>LoginForm: Pass normalized callback URL
LoginForm->>BetterAuth: Submit password or magic-link sign-in
BetterAuth->>Resend: Send magic-link email
BetterAuth-->>Browser: Continue to callback or two-factor route
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Consolidate Resend + MagicLinkEmail rendering under apps/server/src/emails, and align react/@better-auth-ui/react versions via workspace catalogs. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep core and react on the same cataloged version so web auth UI imports stay compatible. Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh wrangler-generated bindings for Google/Resend secrets, and pass Better Auth's magic-link callback args straight through to the email sender. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
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/server/src/emails/magic-email.ts`:
- Around line 60-64: Update the email-sending flow around resend.emails.send to
capture its { data, error } result and explicitly propagate API-level failures
through the existing magic-link request path. Preserve the current send
arguments and successful behavior, but ensure a returned error is surfaced
instead of treating the request as successful.
In `@apps/web/src/auth/callback-url.ts`:
- Around line 8-16: Update toAbsoluteCallbackUrl to reject callback paths
containing backslashes before constructing the URL, while preserving the
existing same-origin relative-path validation from the callback URL flow. Ensure
backslash-based inputs cannot resolve to an off-origin URL.
In `@apps/web/src/auth/post-sign-in-redirect.ts`:
- Around line 7-9: Update the callback handling around normalizeCallbackPath to
reject any callbackUrl containing backslashes before normalization, and validate
absolute redirect URLs remain same-origin before returning them; otherwise use
defaultRedirect. Add a regression test covering a backslash-based bypass such as
/\\evil.example and preserve valid same-origin callback behavior.
🪄 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 Plus
Run ID: 57b0e313-544b-47e9-9c8f-30a8c1344fbe
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (27)
apps/server/.env.exampleapps/server/package.jsonapps/server/src/auth/index.test.tsapps/server/src/auth/options.tsapps/server/src/config/env.tsapps/server/src/emails/index.tsapps/server/src/emails/magic-email.test.tsapps/server/src/emails/magic-email.tsapps/server/src/worker-configuration.d.tsapps/web/package.jsonapps/web/src/auth/auth-flows.test.tsapps/web/src/auth/callback-url.tsapps/web/src/auth/device-callback.tsapps/web/src/auth/post-sign-in-redirect.tsapps/web/src/auth/use-sign-in-continuation.tsapps/web/src/components/auth/magic-link-button.tsxapps/web/src/components/auth/magic-link.tsxapps/web/src/components/auth/provider-buttons.tsxapps/web/src/components/auth/sign-in.tsxapps/web/src/components/home/hero.tsxapps/web/src/lib/env.tsapps/web/src/routeTree.gen.tsapps/web/src/routes/auth/device.tsxapps/web/src/routes/auth/index.tsxapps/web/src/routes/auth/magic-link.tsxpackage.jsontooling/test/mocks/auth-env.ts
Split auth helpers into hooks/stores/utils/lib and drop local copies of isTwoFactorRedirect and parseTwoFactorMethods from @better-auth-ui/core. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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 `@apps/web/src/utils/callback.ts`:
- Around line 4-5: Update the callback path validation near
normalizeCallbackPath to reject paths containing backslashes before constructing
the callback link, while preserving the existing slash and double-slash checks.
Add coverage verifying normalizeCallbackPath("/\\evil.example") returns null.
🪄 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 Plus
Run ID: dcb215cd-438f-41e8-93bb-469338d6f54d
📒 Files selected for processing (25)
apps/server/src/auth/options.tsapps/server/src/emails/magic-email.test.tsapps/server/src/emails/magic-email.tsapps/server/src/worker-configuration.d.tsapps/web/src/auth/two-factor-methods.tsapps/web/src/auth/use-sign-in-continuation.tsapps/web/src/components/auth/magic-link-button.tsxapps/web/src/components/auth/magic-link-sent.tsxapps/web/src/components/auth/magic-link.tsxapps/web/src/components/auth/sign-in.test.tsapps/web/src/components/auth/sign-in.tsxapps/web/src/constants/storage-keys.tsapps/web/src/hooks/auth/use-sign-in-continuation.tsapps/web/src/lib/auth/index.tsapps/web/src/lib/auth/plugins/auth-plugin.tsapps/web/src/lib/auth/plugins/magic-link-plugin.tsapps/web/src/routeTree.gen.tsapps/web/src/routes/auth/device.tsxapps/web/src/routes/auth/index.tsxapps/web/src/routes/auth/magic-link.tsxapps/web/src/stores/stores.test.tsapps/web/src/stores/two-factor-methods.tsapps/web/src/utils/callback.test.tsapps/web/src/utils/callback.tsknip.json
💤 Files with no reviewable changes (2)
- apps/web/src/auth/use-sign-in-continuation.ts
- apps/web/src/auth/two-factor-methods.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- apps/web/src/components/auth/magic-link-button.tsx
- apps/web/src/routes/auth/magic-link.tsx
- apps/server/src/worker-configuration.d.ts
- apps/server/src/emails/magic-email.test.ts
- apps/web/src/routes/auth/device.tsx
- apps/web/src/components/auth/magic-link.tsx
- apps/web/src/components/auth/sign-in.tsx
- apps/web/src/routes/auth/index.tsx
Resend returns { error } instead of throwing, and WHATWG URL parsing treats /\evil as an authority bypass.
Co-authored-by: Cursor <cursoragent@cursor.com>
Propagating Resend API errors made the previous silent invalid-key path return 500. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep both the auth index route and settings accounts route in the generated route tree. Co-authored-by: Cursor <cursoragent@cursor.com>
…ling. Register magic-link/social providers on AuthProvider, use Magic Link as the production /auth surface, align primary CTAs with the hero, and tighten auth card layout. Co-authored-by: Cursor <cursoragent@cursor.com>
Combine password and magic-link flows into one black card layout with FlickeringGrid behind OrbitingCircles, and lift provider buttons so they read against the dark surface. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/routes/auth/index.tsx (1)
15-22: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winPreserve
callbackUrlthrough password sign-in continuations.Pass the route
callbackUrlprop through to password flows or update the shared route query inhandlePasswordSubmit; otherwiseLoginForm.callbackUrlis passed to social/magic-link only, whileuseSignInContinuationfalls back toredirectTo.🤖 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 `@apps/web/src/routes/auth/index.tsx` around lines 15 - 22, Update AuthPage and the password sign-in flow so the normalized callbackUrl remains available during password continuations, either by passing it through the relevant password-flow props or by updating the shared route query in handlePasswordSubmit. Ensure useSignInContinuation receives the callback URL rather than falling back to redirectTo, while preserving existing social and magic-link behavior.
🧹 Nitpick comments (3)
apps/web/src/components/ui/orbiting-circles.tsx (1)
33-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHide the decorative orbit path from assistive technology.
The
<title>element at Line 39 is announced by screen readers as "Orbit path". The circle is decorative. Addaria-hidden="true"to thesvgand remove the<title>.🔧 Proposed fix
<svg + aria-hidden="true" className="pointer-events-none absolute inset-0 size-full" version="1.1" xmlns="http://www.w3.org/2000/svg" > - <title>Orbit path</title> <circle🤖 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 `@apps/web/src/components/ui/orbiting-circles.tsx` around lines 33 - 48, Add aria-hidden="true" to the decorative SVG rendered by the path conditional and remove its title element so assistive technology ignores the orbit path; leave the circle’s visual rendering unchanged.apps/web/src/components/ui/flickering-grid.tsx (1)
129-134: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueInitialize
lastTimeon the first frame.
lastTimestarts at0. On the firstanimatecall,timeis the timestamp since page load.deltaTimetherefore becomes that whole elapsed time in seconds.flickerChance * deltaTimethen exceeds1, so every square is re-randomized on the first frame. The effect is a single visible flash. It corrects itself on the next frame.🔧 Proposed fix
- let lastTime = 0; + let lastTime: number | null = null; const animate = (time: number) => { if (!(isInView && gridParams)) return; - const deltaTime = (time - lastTime) / 1000; + const deltaTime = lastTime === null ? 0 : (time - lastTime) / 1000; lastTime = time;🤖 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 `@apps/web/src/components/ui/flickering-grid.tsx` around lines 129 - 134, Update the animation loop around the local lastTime variable in animate so the first frame initializes lastTime to the current time and skips elapsed-time processing. Subsequent frames should calculate deltaTime from the previous frame timestamp, preventing the initial page-load timestamp from causing a full-grid flicker.apps/web/src/components/login-form.tsx (1)
125-272: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftThe email/password form is implemented twice. This PR adds
PasswordAuthFieldsinapps/web/src/components/login-form.tsxwhileapps/web/src/components/auth/sign-in.tsxkeeps its own copy of the same form. The markup, the Tailwind classes, theonInvalidvalidation messages, and theauthButtonscomposition match in both places. The two copies will diverge.
apps/web/src/components/login-form.tsx#L125-L272: promotePasswordAuthFieldsinto a shared module, for exampleapps/web/src/components/auth/password-auth-fields.tsx, and import it here.apps/web/src/components/auth/sign-in.tsx#L124-L330: replace the inline form and theauthButtonscomposition with the shared component.🤖 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 `@apps/web/src/components/login-form.tsx` around lines 125 - 272, Extract PasswordAuthFields from apps/web/src/components/login-form.tsx:125-272 into a shared module and import it here, preserving its props and behavior. In apps/web/src/components/auth/sign-in.tsx:124-330, replace the duplicated inline form and authButtons composition with the shared PasswordAuthFields component; no direct changes are otherwise required at the extracted anchor site beyond the move and import.
🤖 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/components/auth/magic-link.tsx`:
- Line 136: Update the form control styling in the magic-link component,
including the input class at the referenced location and the button hover
classes around the additional locations, to use theme-aware background and
foreground tokens instead of hard-coded black, white, or light-text colors.
Preserve the existing control states while ensuring both light and dark themes
maintain readable contrast.
In `@apps/web/src/components/auth/sign-in.tsx`:
- Line 290: Update the remember-me conditional near the sign-in form to use
optional chaining when accessing emailAndPassword, changing the direct
rememberMe access to emailAndPassword?.rememberMe while preserving the existing
conditional rendering behavior.
In `@apps/web/src/components/login-form.tsx`:
- Around line 408-416: Update the magic-link flow around useSignInMagicLink and
AuthPrompts so failed requests surface an actionable error to the user in
production as well as password-enabled mode. Either render AuthPrompts
regardless of canUseEmailAndPassword, or add an onError handler to
signInMagicLink that displays the failure through the existing feedback
mechanism; preserve the current success navigation behavior.
In `@apps/web/src/components/ui/orbiting-circles.tsx`:
- Around line 49-71: Update the orbiting circles component around the
Children.map render so HTML props are not spread onto every child wrapper.
Prefer adding a single container that receives props and className while keeping
per-child wrappers limited to orbit styling, or narrow the component’s prop type
to prevent container attributes such as id and onClick; preserve the existing
OrbitingCircles child layout and animation behavior.
---
Outside diff comments:
In `@apps/web/src/routes/auth/index.tsx`:
- Around line 15-22: Update AuthPage and the password sign-in flow so the
normalized callbackUrl remains available during password continuations, either
by passing it through the relevant password-flow props or by updating the shared
route query in handlePasswordSubmit. Ensure useSignInContinuation receives the
callback URL rather than falling back to redirectTo, while preserving existing
social and magic-link behavior.
---
Nitpick comments:
In `@apps/web/src/components/login-form.tsx`:
- Around line 125-272: Extract PasswordAuthFields from
apps/web/src/components/login-form.tsx:125-272 into a shared module and import
it here, preserving its props and behavior. In
apps/web/src/components/auth/sign-in.tsx:124-330, replace the duplicated inline
form and authButtons composition with the shared PasswordAuthFields component;
no direct changes are otherwise required at the extracted anchor site beyond the
move and import.
In `@apps/web/src/components/ui/flickering-grid.tsx`:
- Around line 129-134: Update the animation loop around the local lastTime
variable in animate so the first frame initializes lastTime to the current time
and skips elapsed-time processing. Subsequent frames should calculate deltaTime
from the previous frame timestamp, preventing the initial page-load timestamp
from causing a full-grid flicker.
In `@apps/web/src/components/ui/orbiting-circles.tsx`:
- Around line 33-48: Add aria-hidden="true" to the decorative SVG rendered by
the path conditional and remove its title element so assistive technology
ignores the orbit path; leave the circle’s visual rendering unchanged.
🪄 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 Plus
Run ID: 08cb9dcb-2e40-433a-a453-99e138590f22
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (20)
apps/web/src/components/auth/magic-link-button.tsxapps/web/src/components/auth/magic-link-sent.tsxapps/web/src/components/auth/magic-link.tsxapps/web/src/components/auth/provider-button.tsxapps/web/src/components/auth/sign-in.tsxapps/web/src/components/home/hero.tsxapps/web/src/components/login-form.tsxapps/web/src/components/ui/flickering-grid.tsxapps/web/src/components/ui/input-group.tsxapps/web/src/components/ui/orbiting-circles.tsxapps/web/src/index.cssapps/web/src/lib/auth/index.tsapps/web/src/main.tsxapps/web/src/routeTree.gen.tsapps/web/src/routes/auth/index.tsxapps/web/src/routes/auth/magic-link-sent.tsxapps/web/src/routes/auth/route.tsxapps/web/src/utils/callback.tspackage.jsonturbo.json
🚧 Files skipped from review as they are similar to previous changes (6)
- package.json
- apps/web/src/components/auth/magic-link-button.tsx
- apps/web/src/components/home/hero.tsx
- apps/web/src/components/auth/magic-link-sent.tsx
- apps/web/src/utils/callback.ts
- apps/web/src/routeTree.gen.ts
Always mount AuthPrompts for magic-link errors, optional-chain rememberMe, and stop spreading container props onto every orbiting child. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
/authroute that presents GitHub, Google, and Magic Link sign-in methods, with Email+Password gated behindVITE_IS_DEV_MODE/auth/devicethrough/authusing validatedcallbackUrlhandling so successful sign-in returns users to/workersor back to device approval withuser_codepreserveddisableSignUp: false) and add Resend-backed magic-link email delivery with focused web/server tests for callback, visibility, and magic-link request flowTest plan
bun --filter @cyrus/web check:typesbun --filter @cyrus/server check:typesbunx vitest run --project @cyrus/webbunx vitest run --project @cyrus/serverMade with Cursor
Summary by CodeRabbit
New Features
Bug Fixes