Implement direct standalone Google OAuth 2.0 flow for live Google Sheets and Drive sync

This commit is contained in:
2026-07-22 19:48:50 -04:00
parent ec612e3d54
commit 9d6aa88f29
2 changed files with 103 additions and 80 deletions
+16 -6
View File
@@ -110,13 +110,23 @@ export const GoogleWorkspaceHub: React.FC<GoogleWorkspaceHubProps> = ({
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' };
try {
showToast('Opening Google Account login window...');
const res = await googleWorkspaceSignIn();
if (res) {
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)!');
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 = () => {
+86 -73
View File
@@ -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<void> => {
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(