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:
co-authored by
Claude Opus 4.6
parent
a0ac904dfc
commit
6f7613818b
@@ -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"
|
||||||
|
|||||||
@@ -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()],
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
? [
|
? [
|
||||||
|
|||||||
Reference in New Issue
Block a user