diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts index 0035552..d008e4f 100644 --- a/src/app/api/analyze/route.ts +++ b/src/app/api/analyze/route.ts @@ -75,9 +75,46 @@ export async function POST(req: NextRequest): Promise { ); } - // Use headers for user info if available, otherwise fall back to session - userId = session?.user?.id || headerUser?.id || req.headers.get('x-user-id') || 'unknown'; - userEmail = session?.user?.email || headerUser?.email || req.headers.get('x-user-email') || 'unknown@example.com'; + // Get user info with lots of fallbacks and detailed logging + const sessionId = session?.user?.id; + const sessionEmail = session?.user?.email; + const headerId = headerUser?.id; + const headerEmail = headerUser?.email; + const headerXId = req.headers.get('x-user-id'); + const headerXEmail = req.headers.get('x-user-email'); + + // Log all possible sources of user data + console.log('All user data sources:', { + sessionId, + sessionEmail, + headerId, + headerEmail, + headerXId, + headerXEmail, + sessionData: session, + headerUserData: headerUser + }); + + // Use the first valid user ID we can find + userId = sessionId || headerId || headerXId || 'unknown'; + + // For email, be more strict - never use unknown@example.com unless absolutely necessary + if (sessionEmail && sessionEmail !== 'unknown@example.com') { + userEmail = sessionEmail; + } else if (headerEmail && headerEmail !== 'unknown@example.com') { + userEmail = headerEmail; + } else if (headerXEmail && headerXEmail !== 'unknown@example.com') { + userEmail = headerXEmail; + } else if (userId.includes('@')) { + // If no email but userId looks like email, use that + userEmail = userId; + console.log('Using userId as email since it looks like one:', userId); + } else { + userEmail = 'unknown@example.com'; + } + + // Log the final decision + console.log('Final user identification:', { userId, userEmail }); } // Log authentication details diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index d4296c0..b2b133c 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -52,14 +52,40 @@ const authOptions = { callbacks: { // JWT callback to persist data from the OAuth provider to the JWT async jwt({ token, user, account, profile, trigger }) { - console.log("JWT Callback:", { tokenSub: token.sub, profile, trigger }); + console.log("JWT Callback:", { + tokenSub: token.sub, + tokenEmail: token.email, + userEmail: user?.email, + profile, + trigger, + hasUser: !!user, + hasAccount: !!account + }); // Initial sign-in - add data from the OAuth provider to the token if (account && profile) { + console.log("Initial sign-in, storing profile data in token"); token.userId = token.sub; // Use sub as the primary userId token.email = profile.email; + } else if (user) { + // If we have user but no profile, make sure to keep the user data + console.log("User data available, storing in token"); + token.userId = token.sub || user.id; + token.email = token.email || user.email; + } else { + // Ensure email is always available for user identification + console.log("No profile or user, using token defaults"); + token.userId = token.sub; + // Email might already be in the token from a previous sign-in } + // Add explicit logging of the token we're returning + console.log("Returning token with data:", { + sub: token.sub, + userId: token.userId, + email: token.email + }); + return token; }, @@ -67,15 +93,32 @@ const authOptions = { async session({ session, token, user }) { console.log("Session Callback:", { sessionUserId: session?.user?.id, + sessionEmail: session?.user?.email, tokenUserId: token?.userId, - tokenSub: token?.sub + tokenSub: token?.sub, + tokenEmail: token?.email, + hasUser: !!user }); - // Ensure user ID is available in the session + // Ensure user ID and email are available in the session if (session.user) { + // First try to get ID from token.userId, then fall back to token.sub session.user.id = token.userId || token.sub; + + // Make sure we also have email - very important for points system + if (!session.user.email && token.email) { + session.user.email = token.email; + console.log("Added missing email to session from token:", token.email); + } } + // Detailed logging of the session we're returning + console.log("Returning session with user data:", { + id: session?.user?.id, + email: session?.user?.email, + name: session?.user?.name + }); + return session; } } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 4c9110d..60c68eb 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -58,15 +58,54 @@ export async function getSessionFromCookie(req: NextRequest) { }; } - // If we have a session cookie but no headers, we need to persist the session user ID + // If we have a session cookie but no headers, we need to try to extract user data from jwt if (sessionCookie) { - // For production, we'll just indicate authentication is present - // The JWT session data is handled by NextAuth in the client - console.log('Session cookie authentication is present, relying on client-side session data'); - return { - isAuthenticated: true, - sessionPresent: true - }; + try { + console.log('Session cookie authentication is present, attempting to extract user data'); + + // Try to extract the JWT payload - this is a simplified approach to get basic user data + const jwtValue = sessionCookie.value; + if (jwtValue) { + // Decode the JWT - it's base64url encoded + const parts = jwtValue.split('.'); + if (parts.length === 3) { + const payloadBase64 = parts[1]; + // Convert from base64url to regular string + const jsonStr = Buffer.from(payloadBase64, 'base64').toString(); + const payload = JSON.parse(jsonStr); + + console.log('Extracted session JWT payload:', { + sub: payload.sub, + email: payload.email, + userId: payload.userId + }); + + if (payload.sub || payload.userId || payload.email) { + return { + isAuthenticated: true, + sessionPresent: true, + user: { + id: payload.userId || payload.sub, + email: payload.email + } + }; + } + } + } + + // Fallback if JWT extraction doesn't yield useful info + console.log('Could not extract useful data from cookie, returning sessionPresent only'); + return { + isAuthenticated: true, + sessionPresent: true + }; + } catch (jwtError) { + console.log('Error parsing JWT from cookie:', jwtError); + return { + isAuthenticated: true, + sessionPresent: true + }; + } } // Fallback - this shouldn't happen often diff --git a/supabase-schema-updates.sql b/supabase-schema-updates.sql new file mode 100644 index 0000000..c3027a1 --- /dev/null +++ b/supabase-schema-updates.sql @@ -0,0 +1,152 @@ +-- Additional SQL functions to improve reliability of user points system + +-- Function to get the next transaction ID +CREATE OR REPLACE FUNCTION get_next_transaction_id() +RETURNS BIGINT AS $$ +BEGIN + RETURN nextval('point_transactions_id_seq'); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to insert a transaction with a specific ID +CREATE OR REPLACE FUNCTION insert_transaction_with_id( + id_num BIGINT, + user_id_text TEXT, + points_num INTEGER, + hash_text TEXT DEFAULT NULL +) +RETURNS BOOLEAN AS $$ +BEGIN + INSERT INTO point_transactions ( + id, + user_id, + points, + transaction_type, + image_hash, + created_at + ) + VALUES ( + id_num, + user_id_text::UUID, + points_num, + 'image_upload', + hash_text, + now() + ); + + RETURN TRUE; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Error in insert_transaction_with_id: %', SQLERRM; + RETURN FALSE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Minimal transaction insert function +CREATE OR REPLACE FUNCTION insert_minimal_transaction( + user_id_text TEXT, + points_num INTEGER +) +RETURNS BOOLEAN AS $$ +BEGIN + INSERT INTO point_transactions ( + user_id, + points, + transaction_type, + created_at + ) + VALUES ( + user_id_text::UUID, + points_num, + 'image_upload', + now() + ); + + RETURN TRUE; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Error in insert_minimal_transaction: %', SQLERRM; + RETURN FALSE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to ensure a user exists, creating if needed +CREATE OR REPLACE FUNCTION ensure_user_exists( + user_id_text TEXT, + email_text TEXT, + initial_points INTEGER DEFAULT 0 +) +RETURNS BOOLEAN AS $$ +DECLARE + user_exists BOOLEAN; +BEGIN + -- Check if user exists + SELECT EXISTS ( + SELECT 1 FROM user_points WHERE user_id = user_id_text::UUID + ) INTO user_exists; + + -- If user doesn't exist, create them + IF NOT user_exists THEN + INSERT INTO user_points ( + user_id, + email, + total_points, + created_at, + updated_at + ) + VALUES ( + user_id_text::UUID, + email_text, + initial_points, + now(), + now() + ); + END IF; + + RETURN TRUE; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Error in ensure_user_exists: %', SQLERRM; + RETURN FALSE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to force set user points to a specific value +CREATE OR REPLACE FUNCTION force_set_user_points( + user_id_text TEXT, + points_value INTEGER +) +RETURNS BOOLEAN AS $$ +BEGIN + UPDATE user_points + SET + total_points = points_value, + updated_at = now() + WHERE user_id = user_id_text::UUID; + + RETURN FOUND; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Error in force_set_user_points: %', SQLERRM; + RETURN FALSE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Function to create a user with raw SQL +CREATE OR REPLACE FUNCTION create_user_with_raw_sql( + user_id_text TEXT, + email_text TEXT +) +RETURNS BOOLEAN AS $$ +BEGIN + -- Directly execute the SQL insert + EXECUTE format(' + INSERT INTO user_points (user_id, email, total_points, created_at, updated_at) + VALUES (%L::UUID, %L, 1, now(), now()) + ON CONFLICT (user_id) DO UPDATE + SET updated_at = now() + RETURNING id + ', user_id_text, email_text); + + RETURN TRUE; +EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Error in create_user_with_raw_sql: %', SQLERRM; + RETURN FALSE; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; \ No newline at end of file