From f8c455fa39e0fd61b09b3731005df4ea79d1b470 Mon Sep 17 00:00:00 2001 From: Saumya Date: Tue, 22 Jul 2025 22:14:44 +0530 Subject: [PATCH] attempt to fix google oauth --- docs/GOOGLE_OAUTH_SETUP.md | 156 +++++++++++ frontend/src/components/GoogleSignIn.tsx | 325 +++++++++++------------ 2 files changed, 313 insertions(+), 168 deletions(-) create mode 100644 docs/GOOGLE_OAUTH_SETUP.md diff --git a/docs/GOOGLE_OAUTH_SETUP.md b/docs/GOOGLE_OAUTH_SETUP.md new file mode 100644 index 00000000..92360315 --- /dev/null +++ b/docs/GOOGLE_OAUTH_SETUP.md @@ -0,0 +1,156 @@ +# Google OAuth Setup Guide + +This guide will help you configure Google Sign-In for your Suna application to avoid common errors like "Access blocked: This app's request is invalid". + +## Prerequisites + +- A Google Cloud Console account +- Your Supabase project URL and anon key + +## Step 1: Create a Google Cloud Project + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Click "Select a project" → "New Project" +3. Enter a project name (e.g., "Suna App") +4. Click "Create" + +## Step 2: Configure OAuth Consent Screen + +1. In the Google Cloud Console, go to "APIs & Services" → "OAuth consent screen" +2. Select "External" user type (unless you have a Google Workspace account) +3. Click "Create" +4. Fill in the required fields: + - **App name**: Your application name (e.g., "Suna") + - **User support email**: Your email address + - **App logo**: Optional, but recommended + - **App domain**: Your domain (for local dev, skip this) + - **Authorized domains**: Add your domain(s) + - **Developer contact information**: Your email address +5. Click "Save and Continue" +6. **Scopes**: Click "Add or Remove Scopes" + - Select `.../auth/userinfo.email` + - Select `.../auth/userinfo.profile` + - Select `openid` + - Click "Update" +7. Click "Save and Continue" +8. **Test users**: Add test email addresses if in testing mode +9. Click "Save and Continue" +10. Review and click "Back to Dashboard" + +## Step 3: Create OAuth 2.0 Credentials + +1. Go to "APIs & Services" → "Credentials" +2. Click "Create Credentials" → "OAuth client ID" +3. Select "Web application" as the application type +4. Configure the client: + - **Name**: "Suna Web Client" (or any name you prefer) + - **Authorized JavaScript origins**: + - Add `http://localhost:3000` (for local development) + - Add `https://yourdomain.com` (for production) + - Add your Supabase URL (e.g., `https://yourproject.supabase.co`) + - **Authorized redirect URIs**: + - Add `http://localhost:3000/auth/callback` (for local development) + - Add `https://yourdomain.com/auth/callback` (for production) + - Add your Supabase auth callback URL: `https://yourproject.supabase.co/auth/v1/callback` +5. Click "Create" +6. **Important**: Copy the "Client ID" - you'll need this + +## Step 4: Configure Supabase + +1. Go to your [Supabase Dashboard](https://app.supabase.com) +2. Select your project +3. Go to "Authentication" → "Providers" +4. Find "Google" and enable it +5. Add your Google OAuth credentials: + - **Client ID**: Paste the Client ID from Step 3 + - **Client Secret**: Leave empty (not needed for web applications) +6. **Authorized Client IDs**: Add your Client ID here as well +7. Click "Save" + +## Step 5: Configure Your Application + +1. Add the Google Client ID to your environment variables: + + **Frontend** (`frontend/.env.local`): + ```env + NEXT_PUBLIC_GOOGLE_CLIENT_ID=your-client-id-here + ``` + +2. Restart your development server + +## Step 6: Test Your Setup + +1. Open your application in a browser +2. Click the "Continue with Google" button +3. You should see the Google sign-in popup +4. Select an account and authorize the application +5. You should be redirected back to your application and logged in + +## Common Issues and Solutions + +### "Access blocked: This app's request is invalid" + +This error usually means: +- **Missing redirect URI**: Make sure all your redirect URIs are added in Google Cloud Console +- **Wrong Client ID**: Verify you're using the correct Client ID +- **OAuth consent screen not configured**: Complete all required fields in the consent screen + +### "redirect_uri_mismatch" + +- Check that your redirect URIs in Google Cloud Console exactly match your application URLs +- Include the protocol (`http://` or `https://`) +- Don't include trailing slashes +- For local development, use `http://localhost:3000`, not `http://127.0.0.1:3000` + +### "invalid_client" + +- Verify your Client ID is correct in the environment variables +- Make sure you're using the Web application client ID, not a different type +- Check that the OAuth client hasn't been deleted in Google Cloud Console + +### Google button doesn't appear + +- Check browser console for errors +- Verify `NEXT_PUBLIC_GOOGLE_CLIENT_ID` is set in your environment +- Make sure the Google Identity Services script is loading + +## Production Deployment + +When deploying to production: + +1. Update Google Cloud Console: + - Add your production domain to "Authorized JavaScript origins" + - Add your production callback URL to "Authorized redirect URIs" + - Update the OAuth consent screen with production information + +2. Update your production environment variables: + - Set `NEXT_PUBLIC_GOOGLE_CLIENT_ID` in your deployment platform + +3. Verify Supabase settings: + - Ensure Google provider is enabled + - Confirm the Client ID is set correctly + +## Security Best Practices + +1. **Never commit your Client ID to version control** - always use environment variables +2. **Use HTTPS in production** - Google requires secure connections for OAuth +3. **Restrict your OAuth client** - Only add the domains you actually use +4. **Review permissions regularly** - Remove unused test users and unnecessary scopes +5. **Monitor usage** - Check Google Cloud Console for unusual activity + +## Publishing Your App + +If you want to remove the "unverified app" warning: + +1. Go to "OAuth consent screen" in Google Cloud Console +2. Click "Publish App" +3. Google may require verification for certain scopes +4. Follow the verification process if required + +## Need Help? + +If you're still experiencing issues: +1. Check the browser console for detailed error messages +2. Verify all URLs and IDs are correctly copied +3. Ensure your Supabase project is properly configured +4. Try using an incognito/private browser window to rule out cache issues \ No newline at end of file diff --git a/frontend/src/components/GoogleSignIn.tsx b/frontend/src/components/GoogleSignIn.tsx index bd66036a..a4692010 100644 --- a/frontend/src/components/GoogleSignIn.tsx +++ b/frontend/src/components/GoogleSignIn.tsx @@ -1,231 +1,220 @@ 'use client'; -import { useEffect, useCallback, useState } from 'react'; +import { useEffect, useState } from 'react'; import Script from 'next/script'; import { createClient } from '@/lib/supabase/client'; import { useAuthMethodTracking } from '@/lib/stores/auth-tracking'; +import { toast } from 'sonner'; import { FcGoogle } from "react-icons/fc"; import { Loader2 } from 'lucide-react'; -import { toast } from 'sonner'; declare global { interface Window { - handleGoogleSignIn?: (response: GoogleSignInResponse) => void; - google: { + google?: { accounts: { id: { - initialize: (config: GoogleInitializeConfig) => void; - prompt: ( - callback?: (notification: GoogleNotification) => void, - ) => void; - cancel: () => void; + initialize: (config: any) => void; + renderButton: (element: HTMLElement, config: any) => void; + prompt: (notification?: (notification: any) => void) => void; }; }; }; } } -interface GoogleSignInResponse { - credential: string; - clientId?: string; - select_by?: string; -} - -interface GoogleInitializeConfig { - client_id: string | undefined; - callback: ((response: GoogleSignInResponse) => void) | undefined; - nonce?: string; - use_fedcm?: boolean; - context?: string; - itp_support?: boolean; -} - -interface GoogleNotification { - isNotDisplayed: () => boolean; - getNotDisplayedReason: () => string; - isSkippedMoment: () => boolean; - getSkippedReason: () => string; - isDismissedMoment: () => boolean; - getDismissedReason: () => string; -} - interface GoogleSignInProps { returnUrl?: string; } export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) { - const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; const [isLoading, setIsLoading] = useState(false); const [isGoogleLoaded, setIsGoogleLoaded] = useState(false); - const { wasLastMethod, markAsUsed } = useAuthMethodTracking('google'); const supabase = createClient(); - const handleGoogleSignIn = useCallback( - async (response: GoogleSignInResponse) => { - try { - setIsLoading(true); + const handleGoogleResponse = async (response: any) => { + try { + setIsLoading(true); + markAsUsed(); - console.log('Starting Google sign in process'); - markAsUsed(); + const { data, error } = await supabase.auth.signInWithIdToken({ + provider: 'google', + token: response.credential, + }); - const { error } = await supabase.auth.signInWithIdToken({ + if (error) { + const redirectTo = `${window.location.origin}/auth/callback${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ''}`; + console.log('OAuth redirect URI:', redirectTo); + + const { error: oauthError } = await supabase.auth.signInWithOAuth({ provider: 'google', - token: response.credential, + options: { + redirectTo, + }, }); - if (error) throw error; - - console.log( - 'Google sign in successful, preparing redirect to:', - returnUrl || '/dashboard', - ); - - setTimeout(() => { - console.log('Executing redirect now to:', returnUrl || '/dashboard'); - window.location.href = returnUrl || '/dashboard'; - }, 500); - } catch (error) { - console.error('Error signing in with Google:', error); - setIsLoading(false); - toast.error('Google sign-in failed. Please try again.'); + if (oauthError) { + throw oauthError; + } + } else { + window.location.href = returnUrl || '/dashboard'; } - }, - [returnUrl, markAsUsed], - ); + } catch (error: any) { + console.error('Google sign-in error:', error); + + if (error.message?.includes('redirect_uri_mismatch')) { + const redirectUri = `${window.location.origin}/auth/callback`; + toast.error( + `Google OAuth configuration error. Add this exact URL to your Google Cloud Console: ${redirectUri}`, + { duration: 10000 } + ); + } else { + toast.error(error.message || 'Failed to sign in with Google'); + } + + setIsLoading(false); + } + }; - const handleCustomGoogleSignIn = useCallback(() => { - if (isLoading) return; + useEffect(() => { + const initializeGoogleSignIn = () => { + if (!window.google || !process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) return; - if (!window.google || !googleClientId || !isGoogleLoaded) { - console.error('Google sign-in not properly initialized'); - toast.error('Google sign-in not ready. Please try again.'); + window.google.accounts.id.initialize({ + client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, + callback: handleGoogleResponse, + auto_select: false, + cancel_on_tap_outside: false, + }); + + setIsGoogleLoaded(true); + }; + + if (window.google) { + initializeGoogleSignIn(); + } + }, [returnUrl, markAsUsed, supabase]); + + const handleScriptLoad = () => { + if (window.google && process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) { + window.google.accounts.id.initialize({ + client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, + callback: handleGoogleResponse, + auto_select: false, + cancel_on_tap_outside: false, + }); + + setIsGoogleLoaded(true); + } + }; + + const handleGoogleSignIn = () => { + if (!window.google || !isGoogleLoaded) { + toast.error('Google Sign-In is still loading. Please try again.'); return; } try { - setIsLoading(true); - - const timeoutId = setTimeout(() => { - console.log('Google sign-in timeout - resetting loading state'); - setIsLoading(false); - toast.error('Google sign-in popup failed to load. Please try again.'); - }, 5000); - window.google.accounts.id.prompt(async (notification) => { - clearTimeout(timeoutId); - + window.google.accounts.id.prompt((notification: any) => { if (notification.isNotDisplayed() || notification.isSkippedMoment()) { - console.log('Google sign-in was not displayed or skipped:', - notification.isNotDisplayed() ? notification.getNotDisplayedReason() : notification.getSkippedReason() - ); - await supabase.auth.signInWithOAuth({ + console.log('One Tap not displayed, using OAuth flow'); + setIsLoading(true); + + const redirectTo = `${window.location.origin}/auth/callback${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ''}`; + console.log('OAuth redirect URI:', redirectTo); + + supabase.auth.signInWithOAuth({ provider: 'google', options: { - queryParams: { - prompt: 'select_account', - }, - redirectTo: `${window.location.origin}/dashboard`, + redirectTo, }, + }).then(({ error }) => { + if (error) { + console.error('OAuth error:', error); + + if (error.message?.includes('redirect_uri_mismatch')) { + const redirectUri = `${window.location.origin}/auth/callback`; + toast.error( + `Google OAuth configuration error. Add this exact URL to your Google Cloud Console: ${redirectUri}`, + { duration: 10000 } + ); + } else { + toast.error(error.message || 'Failed to sign in with Google'); + } + + setIsLoading(false); + } }); - setIsLoading(false); - } else if (notification.isDismissedMoment()) { - console.log('Google sign-in was dismissed:', notification.getDismissedReason()); - setIsLoading(false); } }); } catch (error) { - console.error('Error showing Google sign-in prompt:', error); - setIsLoading(false); - toast.error('Failed to start Google sign-in. Please try again.'); - } - }, [googleClientId, isGoogleLoaded, isLoading]); - - useEffect(() => { - window.handleGoogleSignIn = handleGoogleSignIn; - - if (window.google && googleClientId && !isGoogleLoaded) { - window.google.accounts.id.initialize({ - client_id: googleClientId, - callback: handleGoogleSignIn, - use_fedcm: true, - context: 'signin', - itp_support: true, + console.error('Error triggering Google sign-in:', error); + setIsLoading(true); + + const redirectTo = `${window.location.origin}/auth/callback${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ''}`; + supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo, + }, + }).then(({ error }) => { + if (error) { + console.error('OAuth error:', error); + + if (error.message?.includes('redirect_uri_mismatch')) { + const redirectUri = `${window.location.origin}/auth/callback`; + toast.error( + `Google OAuth configuration error. Add this exact URL to your Google Cloud Console: ${redirectUri}`, + { duration: 10000 } + ); + } else { + toast.error(error.message || 'Failed to sign in with Google'); + } + + setIsLoading(false); + } }); - setIsGoogleLoaded(true); } + }; - return () => { - delete window.handleGoogleSignIn; - }; - }, [googleClientId, handleGoogleSignIn, isGoogleLoaded]); - - if (!googleClientId) { + if (!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) { return ( - +
+ Google Sign-In not configured +
); } return ( - <> -
-
- - - {wasLastMethod && ( -
-
-
+
+
- + + {isLoading ? 'Signing in...' : 'Continue with Google'} + + + + {wasLastMethod && ( +
+
+
+ )} +