feat: account linking for magic link + Google, last login method hint

- Move account linking config out of Google conditional so it's always active
- Trust both 'google' and 'email' providers for automatic account linking
- Add lastLoginMethod plugin to track how users last signed in
- Show "Last signed in with Google/email link" hint in sign-in dialog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aymeric Rabot
2026-02-19 01:29:30 -05:00
co-authored by Claude Opus 4.6
parent a0ac904dfc
commit 6f7613818b
3 changed files with 26 additions and 9 deletions
@@ -36,6 +36,11 @@ function GoogleIcon({ className }: { className?: string }) {
/** /**
* SignInDialog - Authentication dialog with Google OAuth and magic link * SignInDialog - Authentication dialog with Google OAuth and magic link
*/ */
const LOGIN_METHOD_LABELS: Record<string, string> = {
google: 'Google',
'magic-link': 'email link',
}
export function SignInDialog({ open, onOpenChange }: SignInDialogProps) { export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@@ -43,6 +48,9 @@ export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false) const [success, setSuccess] = useState(false)
const lastMethod = authClient.getLastUsedLoginMethod?.()
const lastMethodLabel = lastMethod ? LOGIN_METHOD_LABELS[lastMethod] ?? lastMethod : null
const handleGoogleSignIn = async () => { const handleGoogleSignIn = async () => {
setError(null) setError(null)
setIsGoogleLoading(true) setIsGoogleLoading(true)
@@ -134,6 +142,12 @@ export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{lastMethodLabel && (
<p className="text-center text-muted-foreground text-xs">
Last signed in with {lastMethodLabel}
</p>
)}
{/* Google Sign-In */} {/* Google Sign-In */}
<button <button
className="flex w-full items-center justify-center gap-2 rounded-md border border-input bg-background px-4 py-2.5 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground disabled:opacity-50" className="flex w-full items-center justify-center gap-2 rounded-md border border-input bg-background px-4 py-2.5 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
+2 -2
View File
@@ -1,4 +1,4 @@
import { magicLinkClient } from 'better-auth/client/plugins' import { lastLoginMethodClient, magicLinkClient } from 'better-auth/client/plugins'
import { createAuthClient } from 'better-auth/react' import { createAuthClient } from 'better-auth/react'
/** /**
@@ -45,7 +45,7 @@ function getAuthURL(): string {
*/ */
export const authClient = createAuthClient({ export const authClient = createAuthClient({
baseURL: getAuthURL(), baseURL: getAuthURL(),
plugins: [magicLinkClient()], plugins: [magicLinkClient(), lastLoginMethodClient()],
}) })
/** /**
+10 -7
View File
@@ -3,7 +3,7 @@ import { schema } from '@pascal-app/db'
import type { BetterAuthOptions } from 'better-auth' import type { BetterAuthOptions } from 'better-auth'
import { betterAuth } from 'better-auth' import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle' import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { magicLink } from 'better-auth/plugins' import { lastLoginMethod, magicLink } from 'better-auth/plugins'
export interface SendMagicLinkParams { export interface SendMagicLinkParams {
email: string email: string
@@ -61,6 +61,13 @@ export function createAuth(config: AuthConfig): ReturnType<typeof betterAuth> {
}, },
}, },
}, },
// Account linking — always enabled so magic link + Google users can share accounts
account: {
accountLinking: {
enabled: true,
trustedProviders: ['google', 'email'],
},
},
// Google OAuth provider (only enabled when credentials are provided) // Google OAuth provider (only enabled when credentials are provided)
...(config.googleClientId && ...(config.googleClientId &&
config.googleClientSecret && { config.googleClientSecret && {
@@ -70,15 +77,11 @@ export function createAuth(config: AuthConfig): ReturnType<typeof betterAuth> {
clientSecret: config.googleClientSecret, clientSecret: config.googleClientSecret,
}, },
}, },
account: {
accountLinking: {
enabled: true,
trustedProviders: ['google'],
},
},
}), }),
plugins: [ plugins: [
...(config.additionalPlugins ?? []), ...(config.additionalPlugins ?? []),
// Track which login method was last used (e.g., "google", "magic-link")
lastLoginMethod(),
// Magic link authentication // Magic link authentication
...(config.sendMagicLink ...(config.sendMagicLink
? [ ? [