feat: add password auth fallback with VITE_MAGICLINK_AUTH#304
Conversation
Password auth is now the default (no env var needed). Set VITE_MAGICLINK_AUTH=true to switch to email OTP magic link flow. Auth service: emailAndPassword enabled when hasSmtpConfig() is false, credential added to trustedProviders for account linking. Console: password sign-in/sign-up forms shown by default, magic link shown when VITE_MAGICLINK_AUTH=true. SST: VITE_MAGICLINK_AUTH derived from SmtpHost secret — automatically true when SMTP is configured. Removes console-based OTP logger dev workaround. Co-authored-by: Heinrich Wendel <heiwen@users.noreply.github.com>
|
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)
📝 WalkthroughWalkthroughEnables email+password auth across backend and console: exports SMTP detection, conditionally enables Better Auth's emailAndPassword, adds password sign-in/sign-up UI and service methods, introduces VITE_MAGICLINK_AUTH flag, and small UI/layout/accessibility updates. Changes
Sequence DiagramssequenceDiagram
actor User
participant Console as Console UI
participant AuthService as AuthService
participant AuthClient as AuthClient
participant Backend as BetterAuth
participant DB as Database
User->>Console: Submit password sign-in form
Console->>AuthService: signInWithPassword(email, password)
AuthService->>AuthClient: authClient.signIn.email({ email, password, callbackURL })
AuthClient->>Backend: POST /sign-in/email
Backend->>DB: Verify credentials
alt Success
DB-->>Backend: user + session
Backend-->>AuthClient: success
AuthClient-->>AuthService: success
AuthService->>Console: location.replace(redirectPath)
Console->>User: Redirect to app
else Failure
DB-->>Backend: invalid credentials
Backend-->>AuthClient: error
AuthClient-->>AuthService: error
AuthService->>Console: throw Error
Console->>User: Display error
end
sequenceDiagram
actor User
participant Console as Console UI
participant AuthService as AuthService
participant AuthClient as AuthClient
participant Backend as BetterAuth
participant DB as Database
User->>Console: Submit password sign-up form
Console->>AuthService: signUpWithPassword(name, email, password)
AuthService->>AuthClient: authClient.signUp.email({ name, email, password, callbackURL })
AuthClient->>Backend: POST /sign-up/email
Backend->>DB: Hash password & create user
alt Created
DB-->>Backend: new user + session
Backend-->>AuthClient: success
AuthClient-->>AuthService: success
AuthService->>Console: location.replace(redirectPath)
Console->>User: Redirect to app
else Failure
DB-->>Backend: validation/conflict
Backend-->>AuthClient: error
AuthClient-->>AuthService: error
AuthService->>Console: throw Error
Console->>User: Display error
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Possibly related PRs
Suggested reviewers
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)
📝 Coding Plan
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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c46951a57f
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/console/app/routes/_auth.signup/route.tsx (1)
25-86: Form implementation looks good; consider addingrole="alert"for accessibility.The form correctly handles loading states, disables inputs during submission, and displays errors. The pattern is consistent with the existing password sign-in flow. One optional improvement: the error message on line 78 would benefit from
role="alert"so screen readers announce it when it appears.♿ Optional accessibility enhancement
- {error && <div className="text-sm text-destructive">{error}</div>} + {error && <div role="alert" className="text-sm text-destructive">{error}</div>}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/console/app/routes/_auth.signup/route.tsx` around lines 25 - 86, Add an accessibility role to the error message element so screen readers announce it: update the conditional rendering that displays {error} (the div currently rendered as <div className="text-sm text-destructive">{error}</div>) to include role="alert" (and optionally aria-live="assertive") so the error is announced when setError populates the error state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/auth/src/lib/email.ts`:
- Around line 98-100: The URL construction for acceptUrl uses consoleUrl before
validating it, causing new URL(...) to throw if consoleUrl is undefined; reorder
the guards so hasSmtpConfig() and the consoleUrl check run before constructing
acceptUrl. Specifically, in the function that builds the invitation email (where
acceptUrl is created), move the if (!hasSmtpConfig()) return; and if
(!consoleUrl) return console.warn(...) checks above the new
URL("/accept-invitation", consoleUrl) call—mirroring the guard order used in
sendVerificationOtpEmail—to ensure consoleUrl is present before calling new URL.
---
Nitpick comments:
In `@apps/console/app/routes/_auth.signup/route.tsx`:
- Around line 25-86: Add an accessibility role to the error message element so
screen readers announce it: update the conditional rendering that displays
{error} (the div currently rendered as <div className="text-sm
text-destructive">{error}</div>) to include role="alert" (and optionally
aria-live="assertive") so the error is announced when setError populates the
error state.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: df51d995-8467-435e-8e35-dc514c7d2931
📒 Files selected for processing (10)
apps/auth/src/better-auth.tsapps/auth/src/lib/email.tsapps/console/app/lib/auth/better-auth.tsapps/console/app/lib/auth/dummy-auth.tsapps/console/app/lib/auth/types.tsapps/console/app/lib/env.tsapps/console/app/routes/_auth.signin/password.tsxapps/console/app/routes/_auth.signin/route.tsxapps/console/app/routes/_auth.signup/route.tsxinfra/stacks/console.ts
|
@claude what do you think about the comments? |
|
Claude finished @heiwen's task in 51s —— View job Review comment analysis
Here's my take on each comment: 1. Codex P1 — SMTP config mismatch ( The console sets 2. Codex P2 — Better Auth uses 3. CodeRabbit — Guard ordering bug ( This is a real issue. Line 98 constructs - const acceptUrl = new URL("/accept-invitation", consoleUrl);
if (!hasSmtpConfig()) return;
if (!consoleUrl) return console.warn("Missing origin header, cannot send invitation email");
+ const acceptUrl = new URL("/accept-invitation", consoleUrl);4. CodeRabbit nitpick — Adding SummaryOnly comment #3 (guard ordering in Want me to push the fix for the guard ordering? |
|
@claude fix 3 and 4. for 2, I verified in the source code that "email-password" is correct, which is also in the reference docs here: https://better-auth.com/docs/reference/options |
|
Claude finished @heiwen's task in 54s —— View job Fixing review comments
All three fixes applied in commit 68c3401:
|
- Change trustedProviders slug from "credential" to "email-password" - Reorder guards before URL construction in sendOrganizationInvitationEmail - Add role="alert" to error messages for accessibility Co-authored-by: Heinrich Wendel <heiwen@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/console/app/routes/_auth.signin/route.tsx`:
- Around line 27-30: The decorative icon elements (the span with className
"relative" containing Ban and CreditCard) are being read by screen readers; mark
them as presentation-only by adding aria-hidden="true" (and role="presentation"
if your icon library requires) to the decorative container or directly to the
Ban and CreditCard components so they are ignored by assistive tech; update the
span (or the Ban and CreditCard JSX) to include aria-hidden="true" to hide these
decorative icons from screen readers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 634fe850-f99e-4e21-9b46-88d36135ec94
📒 Files selected for processing (8)
apps/auth/src/better-auth.tsapps/auth/src/lib/email.tsapps/console/app/routes/_auth.accept-invitation/route.tsxapps/console/app/routes/_auth.signin/magiclink.tsxapps/console/app/routes/_auth.signin/password.tsxapps/console/app/routes/_auth.signin/route.tsxapps/console/app/routes/_auth.signup/route.tsxapps/console/app/routes/_auth/route.tsx
✅ Files skipped from review due to trivial changes (3)
- apps/console/app/routes/_auth.signin/magiclink.tsx
- apps/console/app/routes/_auth.accept-invitation/route.tsx
- apps/console/app/routes/_auth/route.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/console/app/routes/_auth.signin/password.tsx
- apps/console/app/routes/_auth.signup/route.tsx
- apps/auth/src/lib/email.ts
- apps/auth/src/better-auth.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
@claude we still have a gap here. If no SMTP server is set, the invitation url for organization / team member invites can never be seen. how to address this? |
|
Claude finished @heiwen's task in 50s —— View job Analyzing invitation flow without SMTP
Good catch. Here's the gap: When Options to address thisOption A: Log the invitation URL to console (simplest) Option B: Return the invitation URL in the API response Option C: Show pending invitations with accept links in the members list RecommendationI'd suggest Option A as a quick fix (log the URL when SMTP isn't available — matches the pattern used in dev tools) and potentially Option B as a follow-up for a polished UX. Option A can be done in a few lines in Want me to implement one of these? If so, which approach do you prefer? |
|
@claude I like option C, it's like of a copy button for invitation links, right? I prefer it behind a button, instead of a full link visibility that spoils the UX. Maybe combine it into the existing "invited" batch? Is it possible though, or is the invitation link, not accessible anymore after it was created? |
|
Claude finished @heiwen's task in 53s —— View job Investigating invitation link feasibility
AnalysisYes, this is fully feasible! Here's why:
Suggested approachAdd a copy-link button next to the existing "Invited" badge (line 134) or in the actions column next to the "Revoke" button (line 140). Something like: // Next to the Revoke button, when SMTP is not configured (or always, for convenience)
<CopyInvitationLinkButton invitationId={inv.id} />The button would construct the URL client-side: function CopyInvitationLinkButton({ invitationId }: { invitationId: string }) {
const url = `${window.location.origin}/accept-invitation?id=${invitationId}`;
// copy to clipboard, show toast/tooltip confirmation
}However — I'd suggest this as a separate follow-up PR rather than adding it to this one, since this PR is focused on the password auth feature and is already reviewed. The invitation copy link is a distinct UX feature that benefits from its own review cycle. It also applies regardless of SMTP config (it's useful even with SMTP as a "resend" alternative). Want me to create a new issue for this, or implement it directly in this PR? |
|
@claude let's do it in this PR, otherwise we have a functional regression |
|
Claude finished @heiwen's task in 1m 26s —— View job Adding copy invitation link button
Added a copy invitation link button (link icon) next to the Revoke button for pending invitations in This ensures admins can share invitation links manually when SMTP is not configured, closing the functional gap where invitations were created but the accept URL was invisible. Pushed in commit e208e07. |
Adds a copy-to-clipboard button next to the Revoke button for pending invitations, so admins can manually share the accept URL when SMTP is not configured. Co-authored-by: Heinrich Wendel <heiwen@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@claude we have an existing CopyButton component, how about you generalize that for this use case instead of creating a new one? |
|
Claude finished @heiwen's task in 1m 31s —— View job Refactored to use existing CopyButton component
Replaced the custom Pushed in commit dd5ac8e. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/console/app/routes/_shell.agent`.$agentSlug.settings/members.tsx:
- Around line 233-239: handleCopy currently calls
navigator.clipboard.writeText(...) without handling rejections; update the
handleCopy function to catch failures and provide fallback UI/behavior: wrap the
clipboard write in a try/catch (or add .catch()) around
navigator.clipboard.writeText(url), on success continue to call setCopied(true)
and clear after timeout, on failure set an error state (e.g., setCopyError or
setCopied(false)) and surface a user-visible message or fallback copy mechanism
(like opening a prompt with the URL or selecting text for manual copy). Ensure
you reference and update the handleCopy function and use existing state setters
(setCopied) and add a new state setter (setCopyError or similar) so failures are
handled without unhandled promise rejections.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1fbbaa54-3bc8-4226-b11a-059078d11a57
📒 Files selected for processing (3)
apps/console/app/routes/_auth.signin/password.tsxapps/console/app/routes/_auth.signin/route.tsxapps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/console/app/routes/_auth.signin/route.tsx
- apps/console/app/routes/_auth.signin/password.tsx
Replace custom CopyInvitationLinkButton with the existing CopyButton component from @hebo/shared-ui, which already handles clipboard copy, check icon feedback, and tooltip. Co-authored-by: Heinrich Wendel <heiwen@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@turisanapo for final review |
Password auth is now the default (no env var needed). Set
VITE_MAGICLINK_AUTH=trueto switch to email OTP magic link flow.emailAndPasswordenabled when SMTP is not configuredVITE_MAGICLINK_AUTHderived from SmtpHost secretCloses #299
Generated with Claude Code
Summary by CodeRabbit
New Features
UI Improvements
Usability
Accessibility