From 96bb45ede0d9538fadef8de7a9db54f0ee8673a6 Mon Sep 17 00:00:00 2001 From: Shreyaschorge Date: Thu, 10 Jul 2025 18:45:07 +0530 Subject: [PATCH] Seperate SIWN and SIWF --- src/auth.ts | 118 ++++++++++++++++++- src/components/ui/NeynarAuthButton/index.tsx | 19 ++- src/components/ui/wallet/SignIn.tsx | 45 +++---- 3 files changed, 156 insertions(+), 26 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 4d06956..a8e6ca1 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -6,8 +6,15 @@ declare module 'next-auth' { interface Session { user: { fid: number; + provider?: string; + username?: string; }; } + + interface User { + provider?: string; + username?: string; + } } function getDomainFromUrl(urlString: string | undefined): string { @@ -29,6 +36,7 @@ export const authOptions: AuthOptions = { // Configure one or more authentication providers providers: [ CredentialsProvider({ + id: 'farcaster', name: 'Sign in with Farcaster', credentials: { message: { @@ -62,9 +70,7 @@ export const authOptions: AuthOptions = { }, }, async authorize(credentials, req) { - const csrfToken = req?.body?.csrfToken; - - const nonce = credentials?.nonce || csrfToken; + const nonce = req?.body?.csrfToken; if (!nonce) { console.error('No nonce or CSRF token provided'); @@ -91,17 +97,123 @@ export const authOptions: AuthOptions = { return { id: fid.toString(), + name: credentials?.name || `User ${fid}`, + image: credentials?.pfp || null, + provider: 'farcaster', }; }, }), + CredentialsProvider({ + id: 'neynar', + name: 'Sign in with Neynar', + credentials: { + message: { + label: 'Message', + type: 'text', + placeholder: '0x0', + }, + signature: { + label: 'Signature', + type: 'text', + placeholder: '0x0', + }, + nonce: { + label: 'Nonce', + type: 'text', + placeholder: 'Custom nonce (optional)', + }, + fid: { + label: 'FID', + type: 'text', + placeholder: '0', + }, + username: { + label: 'Username', + type: 'text', + placeholder: 'username', + }, + displayName: { + label: 'Display Name', + type: 'text', + placeholder: 'Display Name', + }, + pfpUrl: { + label: 'Profile Picture URL', + type: 'text', + placeholder: 'https://...', + }, + }, + async authorize(credentials) { + const nonce = credentials?.nonce; + + if (!nonce) { + console.error('No nonce or CSRF token provided for Neynar auth'); + return null; + } + + // For Neynar, we can use a different validation approach + // This could involve validating against Neynar's API or using their SDK + try { + // Validate the signature using Farcaster's auth client (same as Farcaster provider) + const appClient = createAppClient({ + ethereum: viemConnector(), + }); + + const domain = getDomainFromUrl(process.env.NEXTAUTH_URL); + + const verifyResponse = await appClient.verifySignInMessage({ + message: credentials?.message as string, + signature: credentials?.signature as `0x${string}`, + domain, + nonce, + }); + + const { success, fid } = verifyResponse; + + if (!success) { + return null; + } + + // Validate that the provided FID matches the verified FID + if (credentials?.fid && parseInt(credentials.fid) !== fid) { + console.error('FID mismatch in Neynar auth'); + return null; + } + + return { + id: fid.toString(), + name: + credentials?.displayName || + credentials?.username || + `User ${fid}`, + image: credentials?.pfpUrl || null, + provider: 'neynar', + username: credentials?.username || undefined, + }; + } catch (error) { + console.error('Error in Neynar auth:', error); + return null; + } + }, + }), ], callbacks: { session: async ({ session, token }) => { if (session?.user) { session.user.fid = parseInt(token.sub ?? ''); + // Add provider information to session + session.user.provider = token.provider as string; + session.user.username = token.username as string; } return session; }, + jwt: async ({ token, user }) => { + if (user) { + token.provider = user.provider; + token.username = user.username; + } + return token; + }, }, cookies: { sessionToken: { diff --git a/src/components/ui/NeynarAuthButton/index.tsx b/src/components/ui/NeynarAuthButton/index.tsx index 874ea81..1fec0b1 100644 --- a/src/components/ui/NeynarAuthButton/index.tsx +++ b/src/components/ui/NeynarAuthButton/index.tsx @@ -13,9 +13,18 @@ import { useMiniApp } from '@neynar/react'; import { signIn as backendSignIn, signOut as backendSignOut, + useSession, } from 'next-auth/react'; import sdk, { SignIn as SignInCore } from '@farcaster/frame-sdk'; +type User = { + fid: number; + username: string; + display_name: string; + pfp_url: string; + // Add other user properties as needed +}; + const STORAGE_KEY = 'neynar_authenticated_user'; const FARCASTER_FID = 9152; @@ -90,6 +99,7 @@ export function NeynarAuthButton() { const [storedAuth, setStoredAuth] = useState(null); const [signersLoading, setSignersLoading] = useState(false); const { context } = useMiniApp(); + const { data: session } = useSession(); // New state for unified dialog flow const [showDialog, setShowDialog] = useState(false); const [dialogStep, setDialogStep] = useState<'signin' | 'access' | 'loading'>( @@ -468,7 +478,7 @@ export function NeynarAuthButton() { nonce: nonce, }; - const nextAuthResult = await backendSignIn('credentials', signInData); + const nextAuthResult = await backendSignIn('neynar', signInData); if (nextAuthResult?.ok) { setMessage(result.message); setSignature(result.signature); @@ -503,7 +513,10 @@ export function NeynarAuthButton() { setSignersLoading(true); if (useBackendFlow) { - await backendSignOut({ redirect: false }); + // Only sign out from NextAuth if the current session is from Neynar provider + if (session?.user?.provider === 'neynar') { + await backendSignOut({ redirect: false }); + } } else { frontendSignOut(); } @@ -528,7 +541,7 @@ export function NeynarAuthButton() { } finally { setSignersLoading(false); } - }, [useBackendFlow, frontendSignOut, pollingInterval]); + }, [useBackendFlow, frontendSignOut, pollingInterval, session]); // The key fix: match the original library's authentication logic exactly const authenticated = diff --git a/src/components/ui/wallet/SignIn.tsx b/src/components/ui/wallet/SignIn.tsx index 890ab9f..3e489d7 100644 --- a/src/components/ui/wallet/SignIn.tsx +++ b/src/components/ui/wallet/SignIn.tsx @@ -77,7 +77,7 @@ export function SignIn() { const nonce = await getNonce(); const result = await sdk.actions.signIn({ nonce }); setSignInResult(result); - await signIn('credentials', { + await signIn('farcaster', { message: result.message, signature: result.signature, redirect: false, @@ -96,41 +96,46 @@ export function SignIn() { /** * Handles the sign-out process. * - * This function clears the NextAuth session and resets the local - * sign-in result state to complete the sign-out flow. + * This function clears the NextAuth session only if the current session + * is using the Farcaster provider, and resets the local sign-in result state. * * @returns Promise */ const handleSignOut = useCallback(async () => { try { setAuthState((prev) => ({ ...prev, signingOut: true })); - await signOut({ redirect: false }); + // Only sign out if the current session is from Farcaster provider + if (session?.user?.provider === 'farcaster') { + await signOut({ redirect: false }); + } setSignInResult(undefined); } finally { setAuthState((prev) => ({ ...prev, signingOut: false })); } - }, []); + }, [session]); // --- Render --- return ( <> {/* Authentication Buttons */} - {status !== 'authenticated' && ( + {(status !== 'authenticated' || + session?.user?.provider !== 'farcaster') && ( )} - {status === 'authenticated' && ( - - )} + {status === 'authenticated' && + session?.user?.provider === 'farcaster' && ( + + )} {/* Session Information */} {session && ( -
-
Session
-
+
+
Session
+
{JSON.stringify(session, null, 2)}
@@ -138,17 +143,17 @@ export function SignIn() { {/* Error Display */} {signInFailure && !authState.signingIn && ( -
-
SIWF Result
-
{signInFailure}
+
+
SIWF Result
+
{signInFailure}
)} {/* Success Result Display */} {signInResult && !authState.signingIn && ( -
-
SIWF Result
-
+
+
SIWF Result
+
{JSON.stringify(signInResult, null, 2)}