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
*/
const LOGIN_METHOD_LABELS: Record<string, string> = {
google: 'Google',
'magic-link': 'email link',
}
export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
const [email, setEmail] = useState('')
const [isLoading, setIsLoading] = useState(false)
@@ -43,6 +48,9 @@ export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
const lastMethod = authClient.getLastUsedLoginMethod?.()
const lastMethodLabel = lastMethod ? LOGIN_METHOD_LABELS[lastMethod] ?? lastMethod : null
const handleGoogleSignIn = async () => {
setError(null)
setIsGoogleLoading(true)
@@ -134,6 +142,12 @@ export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
</div>
) : (
<div className="space-y-4">
{lastMethodLabel && (
<p className="text-center text-muted-foreground text-xs">
Last signed in with {lastMethodLabel}
</p>
)}
{/* Google Sign-In */}
<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"
+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'
/**
@@ -45,7 +45,7 @@ function getAuthURL(): string {
*/
export const authClient = createAuthClient({
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 { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { magicLink } from 'better-auth/plugins'
import { lastLoginMethod, magicLink } from 'better-auth/plugins'
export interface SendMagicLinkParams {
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)
...(config.googleClientId &&
config.googleClientSecret && {
@@ -70,15 +77,11 @@ export function createAuth(config: AuthConfig): ReturnType<typeof betterAuth> {
clientSecret: config.googleClientSecret,
},
},
account: {
accountLinking: {
enabled: true,
trustedProviders: ['google'],
},
},
}),
plugins: [
...(config.additionalPlugins ?? []),
// Track which login method was last used (e.g., "google", "magic-link")
lastLoginMethod(),
// Magic link authentication
...(config.sendMagicLink
? [