diff --git a/src/components/GoogleWorkspaceHub.tsx b/src/components/GoogleWorkspaceHub.tsx index ec10752..c0417c0 100644 --- a/src/components/GoogleWorkspaceHub.tsx +++ b/src/components/GoogleWorkspaceHub.tsx @@ -110,13 +110,23 @@ export const GoogleWorkspaceHub: React.FC = ({ const [customTokenInput, setCustomTokenInput] = useState(''); const handleConnectGoogle = async () => { - // 100% Pure Local Execution Mode: Connect directly without external Firebase Auth popup dependency - const userObj = { email: 'dkifer19@gmail.com', name: 'David Kifer' }; - setIsConnected(true); - setConnectedUser(userObj); - localStorage.setItem('ai_job_portal_google_connected', 'true'); - localStorage.setItem('ai_job_portal_google_user', JSON.stringify(userObj)); - showToast('✓ Google Workspace connected for David Kifer (dkifer19@gmail.com)!'); + try { + showToast('Opening Google Account login window...'); + const res = await googleWorkspaceSignIn(); + if (res) { + setIsConnected(true); + setAccessToken(res.accessToken); + setConnectedUser({ + email: res.email || 'dkifer19@gmail.com', + name: res.name || 'David Kifer' + }); + showToast('✓ Google Workspace connected! Live Google Drive & Sheets enabled.'); + loadDriveFiles(res.accessToken); + } + } catch (err: any) { + console.error("Google Direct OAuth Error:", err); + showToast(`Google Sign-In Notice: ${err.message || String(err)}`); + } }; const handleApplyCustomToken = () => { diff --git a/src/lib/googleAuth.ts b/src/lib/googleAuth.ts index 43336c3..9ddafa2 100644 --- a/src/lib/googleAuth.ts +++ b/src/lib/googleAuth.ts @@ -1,16 +1,9 @@ -import { initializeApp, getApps, getApp } from 'firebase/app'; -import { - getAuth, - signInWithPopup, - GoogleAuthProvider, - onAuthStateChanged, - User, - signOut -} from 'firebase/auth'; -import rawFirebaseConfig from '../../firebase-applet-config.json'; import { JobApplication } from '../types'; -// Load config dynamically from environment variables with json fallback +let cachedAccessToken: string | null = localStorage.getItem('ai_job_portal_google_token'); + +export const getCachedAccessToken = () => cachedAccessToken; + const getEnvVar = (key: string) => { try { return (import.meta as any).env?.[key]; @@ -19,80 +12,100 @@ const getEnvVar = (key: string) => { } }; -const firebaseConfig = { - projectId: getEnvVar('VITE_FIREBASE_PROJECT_ID') || rawFirebaseConfig.projectId, - appId: getEnvVar('VITE_FIREBASE_APP_ID') || rawFirebaseConfig.appId, - apiKey: getEnvVar('VITE_FIREBASE_API_KEY') || rawFirebaseConfig.apiKey, - authDomain: getEnvVar('VITE_FIREBASE_AUTH_DOMAIN') || rawFirebaseConfig.authDomain, - storageBucket: getEnvVar('VITE_FIREBASE_STORAGE_BUCKET') || rawFirebaseConfig.storageBucket, - messagingSenderId: getEnvVar('VITE_FIREBASE_MESSAGING_SENDER_ID') || rawFirebaseConfig.messagingSenderId, - oAuthClientId: getEnvVar('VITE_GOOGLE_OAUTH_CLIENT_ID') || rawFirebaseConfig.oAuthClientId -}; +const OAUTH_CLIENT_ID = getEnvVar('VITE_GOOGLE_OAUTH_CLIENT_ID') || '488548028456-tal52pta8fm2uc9t4o2u82itqvi91rp7.apps.googleusercontent.com'; -const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp(); -export const auth = getAuth(app); +/** + * Direct Google OAuth 2.0 Flow via Google Identity Services + * Obtains real access token directly from accounts.google.com for Google Drive/Sheets API calls. + */ +export const googleWorkspaceSignIn = async (): Promise<{ email: string; name: string; accessToken: string }> => { + return new Promise((resolve, reject) => { + try { + const scope = encodeURIComponent('https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/documents'); + const redirectUri = encodeURIComponent(`${window.location.origin}`); + const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${OAUTH_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=token&scope=${scope}&prompt=select_account`; -const provider = new GoogleAuthProvider(); -// Workspace Scopes -provider.addScope('https://www.googleapis.com/auth/spreadsheets'); -provider.addScope('https://www.googleapis.com/auth/drive'); -provider.addScope('https://www.googleapis.com/auth/documents'); + const width = 500; + const height = 650; + const left = window.screenX + (window.outerWidth - width) / 2; + const top = window.screenY + (window.outerHeight - height) / 2; -let isSigningIn = false; -let cachedAccessToken: string | null = null; + const popup = window.open( + authUrl, + 'Google Workspace OAuth Sign-In', + `width=${width},height=${height},left=${left},top=${top},scrollbars=yes` + ); -export const initAuthListener = ( - onSuccess: (user: User, token: string) => void, - onFailure: () => void -) => { - return onAuthStateChanged(auth, async (user) => { - if (user) { - if (cachedAccessToken) { - onSuccess(user, cachedAccessToken); - } else if (!isSigningIn) { - // If logged in without token in memory (e.g., page refresh), trigger quick re-auth if needed - onFailure(); + if (!popup) { + throw new Error('Popup window was blocked by your browser. Please allow popups for http://localhost:3000.'); } - } else { - cachedAccessToken = null; - onFailure(); + + const interval = setInterval(async () => { + try { + if (popup.closed) { + clearInterval(interval); + if (!cachedAccessToken) { + reject(new Error('Sign-in window closed before completing authentication.')); + } + return; + } + + const currentUrl = popup.location.href; + if (currentUrl && currentUrl.includes(window.location.origin)) { + const hash = popup.location.hash; + if (hash && hash.includes('access_token=')) { + clearInterval(interval); + popup.close(); + + const params = new URLSearchParams(hash.substring(1)); + const token = params.get('access_token'); + + if (token) { + cachedAccessToken = token; + localStorage.setItem('ai_job_portal_google_token', token); + localStorage.setItem('ai_job_portal_google_connected', 'true'); + + try { + const userRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { + headers: { Authorization: `Bearer ${token}` } + }); + if (userRes.ok) { + const userData = await userRes.json(); + resolve({ + email: userData.email || 'dkifer19@gmail.com', + name: userData.name || 'David Kifer', + accessToken: token + }); + return; + } + } catch (e) {} + + resolve({ + email: 'dkifer19@gmail.com', + name: 'David Kifer', + accessToken: token + }); + } + } + } + } catch (e) {} + }, 400); + + } catch (err: any) { + reject(err); } }); }; -export const googleWorkspaceSignIn = async (): Promise<{ user: User; accessToken: string } | null> => { - try { - isSigningIn = true; - const result = await signInWithPopup(auth, provider); - const credential = GoogleAuthProvider.credentialFromResult(result); - if (!credential?.accessToken) { - throw new Error('Failed to obtain Google access token'); - } - cachedAccessToken = credential.accessToken; - return { user: result.user, accessToken: cachedAccessToken }; - } catch (error: any) { - console.error('Google Workspace Sign-in Error:', error); - - // Check if error is due to domain restriction or cross-origin popup block - const errCode = error?.code || ''; - if (errCode.includes('unauthorized-domain') || errCode.includes('popup-closed') || errCode.includes('auth/invalid-api-key')) { - throw new Error(`Google Auth Domain Restriction: Your local origin (localhost:3000) is not listed in the Firebase/Google Auth authorized domains. Error Code: ${errCode}`); - } - throw error; - } finally { - isSigningIn = false; - } -}; - -export const getCachedAccessToken = (): string | null => { - return cachedAccessToken; -}; - -export const googleSignOut = async (): Promise => { - await signOut(auth); +export const googleSignOut = async () => { cachedAccessToken = null; + localStorage.removeItem('ai_job_portal_google_token'); + localStorage.removeItem('ai_job_portal_google_connected'); + localStorage.removeItem('ai_job_portal_google_user'); }; + + // Workspace Action Handlers export async function exportApplicationsToGoogleSheets(