mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
Cleanup
This commit is contained in:
parent
96bb45ede0
commit
797c5b7154
99
src/app/api/auth/session-signers/route.ts
Normal file
99
src/app/api/auth/session-signers/route.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getServerSession } from 'next-auth';
|
||||||
|
import { authOptions } from '~/auth';
|
||||||
|
import { getNeynarClient } from '~/lib/neynar';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.fid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'No authenticated session found' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const message = searchParams.get('message');
|
||||||
|
const signature = searchParams.get('signature');
|
||||||
|
|
||||||
|
if (!message || !signature) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Message and signature are required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = getNeynarClient();
|
||||||
|
const data = await client.fetchSigners({ message, signature });
|
||||||
|
const signers = data.signers;
|
||||||
|
|
||||||
|
// Fetch user data if signers exist
|
||||||
|
let user = null;
|
||||||
|
if (signers && signers.length > 0) {
|
||||||
|
try {
|
||||||
|
const userResponse = await fetch(
|
||||||
|
`${process.env.NEXTAUTH_URL}/api/users?fids=${signers[0].fid}`
|
||||||
|
);
|
||||||
|
if (userResponse.ok) {
|
||||||
|
const userDataResponse = await userResponse.json();
|
||||||
|
user = userDataResponse.users?.[0] || null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user data:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
signers,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in session-signers API:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to fetch signers' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.fid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'No authenticated session found' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
const { message, signature, signers, user } = body;
|
||||||
|
|
||||||
|
if (!message || !signature || !signers) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Message, signature, and signers are required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we can't directly modify the session token here,
|
||||||
|
// we'll return the data and let the client trigger a session update
|
||||||
|
// The client will need to call getSession() to refresh the session
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Session data prepared for update',
|
||||||
|
signers,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating session signers:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to update session' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/app/api/auth/update-session/route.ts
Normal file
46
src/app/api/auth/update-session/route.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getServerSession } from 'next-auth';
|
||||||
|
import { authOptions } from '~/auth';
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.fid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'No authenticated session found' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
const { signers, user } = body;
|
||||||
|
|
||||||
|
if (!signers || !user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Signers and user are required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For NextAuth to update the session, we need to trigger the JWT callback
|
||||||
|
// This is typically done by calling the session endpoint with updated data
|
||||||
|
// However, we can't directly modify the session token from here
|
||||||
|
|
||||||
|
// Instead, we'll store the data temporarily and let the client refresh the session
|
||||||
|
// The session will be updated when the JWT callback is triggered
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Session update prepared',
|
||||||
|
signers,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error preparing session update:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to prepare session update' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
236
src/auth.ts
236
src/auth.ts
@ -4,16 +4,198 @@ import { createAppClient, viemConnector } from '@farcaster/auth-client';
|
|||||||
|
|
||||||
declare module 'next-auth' {
|
declare module 'next-auth' {
|
||||||
interface Session {
|
interface Session {
|
||||||
user: {
|
provider?: string;
|
||||||
|
user?: {
|
||||||
fid: number;
|
fid: number;
|
||||||
provider?: string;
|
object?: 'user';
|
||||||
username?: string;
|
username?: string;
|
||||||
|
display_name?: string;
|
||||||
|
pfp_url?: string;
|
||||||
|
custody_address?: string;
|
||||||
|
profile?: {
|
||||||
|
bio: {
|
||||||
|
text: string;
|
||||||
|
mentioned_profiles?: Array<{
|
||||||
|
object: 'user_dehydrated';
|
||||||
|
fid: number;
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
pfp_url: string;
|
||||||
|
custody_address: string;
|
||||||
|
}>;
|
||||||
|
mentioned_profiles_ranges?: Array<{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
location?: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address: {
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
country: string;
|
||||||
|
country_code: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
follower_count?: number;
|
||||||
|
following_count?: number;
|
||||||
|
verifications?: string[];
|
||||||
|
verified_addresses?: {
|
||||||
|
eth_addresses: string[];
|
||||||
|
sol_addresses: string[];
|
||||||
|
primary: {
|
||||||
|
eth_address: string;
|
||||||
|
sol_address: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
verified_accounts?: Array<Record<string, unknown>>;
|
||||||
|
power_badge?: boolean;
|
||||||
|
url?: string;
|
||||||
|
experimental?: {
|
||||||
|
neynar_user_score: number;
|
||||||
|
deprecation_notice: string;
|
||||||
|
};
|
||||||
|
score?: number;
|
||||||
};
|
};
|
||||||
|
signers?: {
|
||||||
|
object: 'signer';
|
||||||
|
signer_uuid: string;
|
||||||
|
public_key: string;
|
||||||
|
status: 'approved';
|
||||||
|
fid: number;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
username?: string;
|
signers?: Array<{
|
||||||
|
object: 'signer';
|
||||||
|
signer_uuid: string;
|
||||||
|
public_key: string;
|
||||||
|
status: 'approved';
|
||||||
|
fid: number;
|
||||||
|
}>;
|
||||||
|
user?: {
|
||||||
|
object: 'user';
|
||||||
|
fid: number;
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
pfp_url: string;
|
||||||
|
custody_address: string;
|
||||||
|
profile: {
|
||||||
|
bio: {
|
||||||
|
text: string;
|
||||||
|
mentioned_profiles?: Array<{
|
||||||
|
object: 'user_dehydrated';
|
||||||
|
fid: number;
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
pfp_url: string;
|
||||||
|
custody_address: string;
|
||||||
|
}>;
|
||||||
|
mentioned_profiles_ranges?: Array<{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
location?: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address: {
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
country: string;
|
||||||
|
country_code: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
follower_count: number;
|
||||||
|
following_count: number;
|
||||||
|
verifications: string[];
|
||||||
|
verified_addresses: {
|
||||||
|
eth_addresses: string[];
|
||||||
|
sol_addresses: string[];
|
||||||
|
primary: {
|
||||||
|
eth_address: string;
|
||||||
|
sol_address: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
verified_accounts: Array<Record<string, unknown>>;
|
||||||
|
power_badge: boolean;
|
||||||
|
url?: string;
|
||||||
|
experimental?: {
|
||||||
|
neynar_user_score: number;
|
||||||
|
deprecation_notice: string;
|
||||||
|
};
|
||||||
|
score: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JWT {
|
||||||
|
provider?: string;
|
||||||
|
signers?: Array<{
|
||||||
|
object: 'signer';
|
||||||
|
signer_uuid: string;
|
||||||
|
public_key: string;
|
||||||
|
status: 'approved';
|
||||||
|
fid: number;
|
||||||
|
}>;
|
||||||
|
user?: {
|
||||||
|
object: 'user';
|
||||||
|
fid: number;
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
pfp_url: string;
|
||||||
|
custody_address: string;
|
||||||
|
profile: {
|
||||||
|
bio: {
|
||||||
|
text: string;
|
||||||
|
mentioned_profiles?: Array<{
|
||||||
|
object: 'user_dehydrated';
|
||||||
|
fid: number;
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
pfp_url: string;
|
||||||
|
custody_address: string;
|
||||||
|
}>;
|
||||||
|
mentioned_profiles_ranges?: Array<{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
location?: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address: {
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
country: string;
|
||||||
|
country_code: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
follower_count: number;
|
||||||
|
following_count: number;
|
||||||
|
verifications: string[];
|
||||||
|
verified_addresses: {
|
||||||
|
eth_addresses: string[];
|
||||||
|
sol_addresses: string[];
|
||||||
|
primary: {
|
||||||
|
eth_address: string;
|
||||||
|
sol_address: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
verified_accounts?: Array<Record<string, unknown>>;
|
||||||
|
power_badge?: boolean;
|
||||||
|
url?: string;
|
||||||
|
experimental?: {
|
||||||
|
neynar_user_score: number;
|
||||||
|
deprecation_notice: string;
|
||||||
|
};
|
||||||
|
score?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,20 +309,15 @@ export const authOptions: AuthOptions = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: '0',
|
placeholder: '0',
|
||||||
},
|
},
|
||||||
username: {
|
signers: {
|
||||||
label: 'Username',
|
label: 'Signers',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: 'username',
|
placeholder: 'JSON string of signers',
|
||||||
},
|
},
|
||||||
displayName: {
|
user: {
|
||||||
label: 'Display Name',
|
label: 'User Data',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: 'Display Name',
|
placeholder: 'JSON string of user data',
|
||||||
},
|
|
||||||
pfpUrl: {
|
|
||||||
label: 'Profile Picture URL',
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'https://...',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
@ -182,13 +359,11 @@ export const authOptions: AuthOptions = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: fid.toString(),
|
id: fid.toString(),
|
||||||
name:
|
|
||||||
credentials?.displayName ||
|
|
||||||
credentials?.username ||
|
|
||||||
`User ${fid}`,
|
|
||||||
image: credentials?.pfpUrl || null,
|
|
||||||
provider: 'neynar',
|
provider: 'neynar',
|
||||||
username: credentials?.username || undefined,
|
signers: credentials?.signers
|
||||||
|
? JSON.parse(credentials.signers)
|
||||||
|
: undefined,
|
||||||
|
user: credentials?.user ? JSON.parse(credentials.user) : undefined,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in Neynar auth:', error);
|
console.error('Error in Neynar auth:', error);
|
||||||
@ -199,18 +374,27 @@ export const authOptions: AuthOptions = {
|
|||||||
],
|
],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
session: async ({ session, token }) => {
|
session: async ({ session, token }) => {
|
||||||
if (session?.user) {
|
// Set provider at the root level
|
||||||
session.user.fid = parseInt(token.sub ?? '');
|
session.provider = token.provider as string;
|
||||||
// Add provider information to session
|
|
||||||
session.user.provider = token.provider as string;
|
if (token.provider === 'farcaster') {
|
||||||
session.user.username = token.username as string;
|
// For Farcaster, simple structure
|
||||||
|
session.user = {
|
||||||
|
fid: parseInt(token.sub ?? ''),
|
||||||
|
};
|
||||||
|
} else if (token.provider === 'neynar') {
|
||||||
|
// For Neynar, use full user data structure from user
|
||||||
|
session.user = token.user as typeof session.user;
|
||||||
|
session.signers = token.signers as typeof session.signers;
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
jwt: async ({ token, user }) => {
|
jwt: async ({ token, user }) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
token.provider = user.provider;
|
token.provider = user.provider;
|
||||||
token.username = user.username;
|
token.signers = user.signers;
|
||||||
|
token.user = user.user;
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -137,6 +137,36 @@ export function NeynarAuthButton() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Helper function to update session with signers (backend flow only)
|
||||||
|
const updateSessionWithSigners = useCallback(
|
||||||
|
async (
|
||||||
|
signers: StoredAuthState['signers'],
|
||||||
|
user: StoredAuthState['user']
|
||||||
|
) => {
|
||||||
|
if (!useBackendFlow) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// For backend flow, we need to sign in again with the additional data
|
||||||
|
if (message && signature) {
|
||||||
|
const signInData = {
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
redirect: false,
|
||||||
|
nonce: nonce || '',
|
||||||
|
fid: user?.fid?.toString() || '',
|
||||||
|
signers: JSON.stringify(signers),
|
||||||
|
user: JSON.stringify(user),
|
||||||
|
};
|
||||||
|
|
||||||
|
await backendSignIn('neynar', signInData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error updating session with signers:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[useBackendFlow, message, signature, nonce]
|
||||||
|
);
|
||||||
|
|
||||||
// Helper function to fetch user data from Neynar API
|
// Helper function to fetch user data from Neynar API
|
||||||
const fetchUserData = useCallback(
|
const fetchUserData = useCallback(
|
||||||
async (fid: number): Promise<User | null> => {
|
async (fid: number): Promise<User | null> => {
|
||||||
@ -201,33 +231,51 @@ export function NeynarAuthButton() {
|
|||||||
try {
|
try {
|
||||||
setSignersLoading(true);
|
setSignersLoading(true);
|
||||||
|
|
||||||
const response = await fetch(
|
const endpoint = useBackendFlow
|
||||||
`/api/auth/signers?message=${encodeURIComponent(
|
? `/api/auth/session-signers?message=${encodeURIComponent(
|
||||||
message
|
message
|
||||||
)}&signature=${signature}`
|
)}&signature=${signature}`
|
||||||
);
|
: `/api/auth/signers?message=${encodeURIComponent(
|
||||||
|
message
|
||||||
|
)}&signature=${signature}`;
|
||||||
|
|
||||||
|
const response = await fetch(endpoint);
|
||||||
const signerData = await response.json();
|
const signerData = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let user: StoredAuthState['user'] | null = null;
|
if (useBackendFlow) {
|
||||||
|
// For backend flow, update session with signers
|
||||||
|
if (signerData.signers && signerData.signers.length > 0) {
|
||||||
|
const user =
|
||||||
|
signerData.user ||
|
||||||
|
(await fetchUserData(signerData.signers[0].fid));
|
||||||
|
await updateSessionWithSigners(signerData.signers, user);
|
||||||
|
}
|
||||||
|
return signerData.signers;
|
||||||
|
} else {
|
||||||
|
// For frontend flow, store in localStorage
|
||||||
|
let user: StoredAuthState['user'] | null = null;
|
||||||
|
|
||||||
if (signerData.signers && signerData.signers.length > 0) {
|
if (signerData.signers && signerData.signers.length > 0) {
|
||||||
user = await fetchUserData(signerData.signers[0].fid);
|
const fetchedUser = (await fetchUserData(
|
||||||
|
signerData.signers[0].fid
|
||||||
|
)) as StoredAuthState['user'];
|
||||||
|
user = fetchedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store signers in localStorage, preserving existing auth data
|
||||||
|
const existingAuth = getItem<StoredAuthState>(STORAGE_KEY);
|
||||||
|
const updatedState: StoredAuthState = {
|
||||||
|
...existingAuth,
|
||||||
|
isAuthenticated: !!user,
|
||||||
|
signers: signerData.signers || [],
|
||||||
|
user,
|
||||||
|
};
|
||||||
|
setItem<StoredAuthState>(STORAGE_KEY, updatedState);
|
||||||
|
setStoredAuth(updatedState);
|
||||||
|
|
||||||
|
return signerData.signers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store signers in localStorage, preserving existing auth data
|
|
||||||
const existingAuth = getItem<StoredAuthState>(STORAGE_KEY);
|
|
||||||
const updatedState: StoredAuthState = {
|
|
||||||
...existingAuth,
|
|
||||||
isAuthenticated: !!user,
|
|
||||||
signers: signerData.signers || [],
|
|
||||||
user,
|
|
||||||
};
|
|
||||||
setItem<StoredAuthState>(STORAGE_KEY, updatedState);
|
|
||||||
setStoredAuth(updatedState);
|
|
||||||
|
|
||||||
return signerData.signers;
|
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ Failed to fetch signers');
|
console.error('❌ Failed to fetch signers');
|
||||||
// throw new Error('Failed to fetch signers');
|
// throw new Error('Failed to fetch signers');
|
||||||
@ -239,7 +287,7 @@ export function NeynarAuthButton() {
|
|||||||
setSignersLoading(false);
|
setSignersLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[useBackendFlow, fetchUserData, updateSessionWithSigners]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Helper function to poll signer status
|
// Helper function to poll signer status
|
||||||
@ -305,26 +353,34 @@ export function NeynarAuthButton() {
|
|||||||
generateNonce();
|
generateNonce();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Load stored auth state on mount
|
// Load stored auth state on mount (only for frontend flow)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stored = getItem<StoredAuthState>(STORAGE_KEY);
|
if (!useBackendFlow) {
|
||||||
if (stored && stored.isAuthenticated) {
|
const stored = getItem<StoredAuthState>(STORAGE_KEY);
|
||||||
setStoredAuth(stored);
|
if (stored && stored.isAuthenticated) {
|
||||||
|
setStoredAuth(stored);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, [useBackendFlow]);
|
||||||
|
|
||||||
// Success callback - this is critical!
|
// Success callback - this is critical!
|
||||||
const onSuccessCallback = useCallback((res: unknown) => {
|
const onSuccessCallback = useCallback(
|
||||||
const existingAuth = getItem<StoredAuthState>(STORAGE_KEY);
|
(res: unknown) => {
|
||||||
const authState: StoredAuthState = {
|
if (!useBackendFlow) {
|
||||||
isAuthenticated: true,
|
// Only handle localStorage for frontend flow
|
||||||
user: res as StoredAuthState['user'],
|
const existingAuth = getItem<StoredAuthState>(STORAGE_KEY);
|
||||||
signers: existingAuth?.signers || [], // Preserve existing signers
|
const authState: StoredAuthState = {
|
||||||
};
|
isAuthenticated: true,
|
||||||
setItem<StoredAuthState>(STORAGE_KEY, authState);
|
user: res as StoredAuthState['user'],
|
||||||
setStoredAuth(authState);
|
signers: existingAuth?.signers || [], // Preserve existing signers
|
||||||
// setShowDialog(false);
|
};
|
||||||
}, []);
|
setItem<StoredAuthState>(STORAGE_KEY, authState);
|
||||||
|
setStoredAuth(authState);
|
||||||
|
}
|
||||||
|
// For backend flow, the session will be handled by NextAuth
|
||||||
|
},
|
||||||
|
[useBackendFlow]
|
||||||
|
);
|
||||||
|
|
||||||
// Error callback
|
// Error callback
|
||||||
const onErrorCallback = useCallback((error?: Error | null) => {
|
const onErrorCallback = useCallback((error?: Error | null) => {
|
||||||
@ -368,12 +424,6 @@ export function NeynarAuthButton() {
|
|||||||
if (message && signature) {
|
if (message && signature) {
|
||||||
const handleSignerFlow = async () => {
|
const handleSignerFlow = async () => {
|
||||||
try {
|
try {
|
||||||
// // Ensure we have message and signature
|
|
||||||
// if (!message || !signature) {
|
|
||||||
// console.error('❌ Missing message or signature');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Step 1: Change to loading state
|
// Step 1: Change to loading state
|
||||||
setDialogStep('loading');
|
setDialogStep('loading');
|
||||||
setSignersLoading(true);
|
setSignersLoading(true);
|
||||||
@ -403,10 +453,13 @@ export function NeynarAuthButton() {
|
|||||||
setDebugState('Setting signer approval URL...');
|
setDebugState('Setting signer approval URL...');
|
||||||
setSignerApprovalUrl(signedKeyData.signer_approval_url);
|
setSignerApprovalUrl(signedKeyData.signer_approval_url);
|
||||||
setSignersLoading(false); // Stop loading, show QR code
|
setSignersLoading(false); // Stop loading, show QR code
|
||||||
if (
|
// Check if we're in a mobile context
|
||||||
context?.client?.platformType === 'mobile' &&
|
const clientContext = context?.client as Record<string, unknown>;
|
||||||
context?.client?.clientFid === FARCASTER_FID
|
const isMobileContext =
|
||||||
) {
|
clientContext?.platformType === 'mobile' &&
|
||||||
|
clientContext?.clientFid === FARCASTER_FID;
|
||||||
|
|
||||||
|
if (isMobileContext) {
|
||||||
setDebugState('Opening mobile app...');
|
setDebugState('Opening mobile app...');
|
||||||
setShowDialog(false);
|
setShowDialog(false);
|
||||||
await sdk.actions.openUrl(
|
await sdk.actions.openUrl(
|
||||||
@ -418,16 +471,11 @@ export function NeynarAuthButton() {
|
|||||||
} else {
|
} else {
|
||||||
setDebugState(
|
setDebugState(
|
||||||
'Opening access dialog...' +
|
'Opening access dialog...' +
|
||||||
` ${context?.client?.platformType}` +
|
` ${clientContext?.platformType}` +
|
||||||
` ${context?.client?.clientFid}`
|
` ${clientContext?.clientFid}`
|
||||||
);
|
);
|
||||||
setDialogStep('access');
|
setDialogStep('access');
|
||||||
setShowDialog(true);
|
setShowDialog(true);
|
||||||
setDebugState(
|
|
||||||
'Opening access dialog...2' +
|
|
||||||
` ${dialogStep}` +
|
|
||||||
` ${showDialog}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Start polling for signer approval
|
// Step 4: Start polling for signer approval
|
||||||
@ -514,15 +562,17 @@ export function NeynarAuthButton() {
|
|||||||
|
|
||||||
if (useBackendFlow) {
|
if (useBackendFlow) {
|
||||||
// Only sign out from NextAuth if the current session is from Neynar provider
|
// Only sign out from NextAuth if the current session is from Neynar provider
|
||||||
if (session?.user?.provider === 'neynar') {
|
if (session?.provider === 'neynar') {
|
||||||
await backendSignOut({ redirect: false });
|
await backendSignOut({ redirect: false });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Frontend flow sign out
|
||||||
frontendSignOut();
|
frontendSignOut();
|
||||||
|
removeItem(STORAGE_KEY);
|
||||||
|
setStoredAuth(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem(STORAGE_KEY);
|
// Common cleanup for both flows
|
||||||
setStoredAuth(null);
|
|
||||||
setShowDialog(false);
|
setShowDialog(false);
|
||||||
setDialogStep('signin');
|
setDialogStep('signin');
|
||||||
setSignerApprovalUrl(null);
|
setSignerApprovalUrl(null);
|
||||||
@ -543,15 +593,27 @@ export function NeynarAuthButton() {
|
|||||||
}
|
}
|
||||||
}, [useBackendFlow, frontendSignOut, pollingInterval, session]);
|
}, [useBackendFlow, frontendSignOut, pollingInterval, session]);
|
||||||
|
|
||||||
// The key fix: match the original library's authentication logic exactly
|
const authenticated = useBackendFlow
|
||||||
const authenticated =
|
? !!(
|
||||||
((isSuccess && validSignature) || storedAuth?.isAuthenticated) &&
|
session?.provider === 'neynar' &&
|
||||||
!!(storedAuth?.signers && storedAuth.signers.length > 0);
|
session?.user?.fid &&
|
||||||
const userData = {
|
session?.signers &&
|
||||||
fid: storedAuth?.user?.fid,
|
session.signers.length > 0
|
||||||
username: storedAuth?.user?.username || '',
|
)
|
||||||
pfpUrl: storedAuth?.user?.pfp_url || '',
|
: ((isSuccess && validSignature) || storedAuth?.isAuthenticated) &&
|
||||||
};
|
!!(storedAuth?.signers && storedAuth.signers.length > 0);
|
||||||
|
|
||||||
|
const userData = useBackendFlow
|
||||||
|
? {
|
||||||
|
fid: session?.user?.fid,
|
||||||
|
username: session?.user?.username || '',
|
||||||
|
pfpUrl: session?.user?.pfp_url || '',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
fid: storedAuth?.user?.fid,
|
||||||
|
username: storedAuth?.user?.username || '',
|
||||||
|
pfpUrl: storedAuth?.user?.pfp_url || '',
|
||||||
|
};
|
||||||
|
|
||||||
// Show loading state while nonce is being fetched or signers are loading
|
// Show loading state while nonce is being fetched or signers are loading
|
||||||
if (!nonce || signersLoading) {
|
if (!nonce || signersLoading) {
|
||||||
@ -589,12 +651,15 @@ export function NeynarAuthButton() {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span>{debugState || 'Sign in with Neynar'}</span>
|
<span>Sign in with Neynar</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<p>LocalStorage state</p>
|
||||||
|
{window && JSON.stringify(window.localStorage.getItem(STORAGE_KEY))}
|
||||||
|
|
||||||
{/* Unified Auth Dialog */}
|
{/* Unified Auth Dialog */}
|
||||||
{
|
{
|
||||||
<AuthDialog
|
<AuthDialog
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export function SignIn() {
|
|||||||
try {
|
try {
|
||||||
setAuthState((prev) => ({ ...prev, signingOut: true }));
|
setAuthState((prev) => ({ ...prev, signingOut: true }));
|
||||||
// Only sign out if the current session is from Farcaster provider
|
// Only sign out if the current session is from Farcaster provider
|
||||||
if (session?.user?.provider === 'farcaster') {
|
if (session?.provider === 'farcaster') {
|
||||||
await signOut({ redirect: false });
|
await signOut({ redirect: false });
|
||||||
}
|
}
|
||||||
setSignInResult(undefined);
|
setSignInResult(undefined);
|
||||||
@ -118,18 +118,16 @@ export function SignIn() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Authentication Buttons */}
|
{/* Authentication Buttons */}
|
||||||
{(status !== 'authenticated' ||
|
{(status !== 'authenticated' || session?.provider !== 'farcaster') && (
|
||||||
session?.user?.provider !== 'farcaster') && (
|
|
||||||
<Button onClick={handleSignIn} disabled={authState.signingIn}>
|
<Button onClick={handleSignIn} disabled={authState.signingIn}>
|
||||||
Sign In with Farcaster
|
Sign In with Farcaster
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{status === 'authenticated' &&
|
{status === 'authenticated' && session?.provider === 'farcaster' && (
|
||||||
session?.user?.provider === 'farcaster' && (
|
<Button onClick={handleSignOut} disabled={authState.signingOut}>
|
||||||
<Button onClick={handleSignOut} disabled={authState.signingOut}>
|
Sign out
|
||||||
Sign out
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Session Information */}
|
{/* Session Information */}
|
||||||
{session && (
|
{session && (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user