diff --git a/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts b/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts index e7fdb4e6efc..603c2be09cf 100644 --- a/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts +++ b/packages/components/nodes/documentloaders/GoogleDrive/GoogleDrive.ts @@ -60,7 +60,7 @@ class GoogleDrive_DocumentLoaders implements INode { name: 'credential', type: 'credential', description: 'Google Drive OAuth2 Credential', - credentialNames: ['googleDriveOAuth2', 'googleOAuth'] + credentialNames: ['googleDriveOAuth2'] } this.inputs = [ { @@ -205,7 +205,7 @@ class GoogleDrive_DocumentLoaders implements INode { try { let credentialData = await getCredentialData(nodeData.credential ?? '', options) credentialData = await refreshOAuth2Token(nodeData.credential ?? '', credentialData, options) - const accessToken = getCredentialParam('access_token', credentialData, nodeData) || credentialData.googleAccessToken + const accessToken = getCredentialParam('access_token', credentialData, nodeData) if (!accessToken) { return returnData diff --git a/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx b/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx index 4ea97572d42..83ad31577d7 100644 --- a/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx +++ b/packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx @@ -3,6 +3,7 @@ import { useEffect, useState, useCallback, useRef } from 'react' import { Button, List, ListItem, ListItemAvatar, ListItemText, Avatar, IconButton, Stack, Alert } from '@mui/material' import useApi from '@/hooks/useApi' import credentialsApi from '@/api/credentials' +import oauth2Api from '@/api/oauth2' import { IconX, IconTrash } from '@tabler/icons-react' import { useDispatch } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions' @@ -176,6 +177,7 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre const [accessToken, setAccessToken] = useState(null) const [isTokenExpired, setIsTokenExpired] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false) + const [isReauthRequired, setIsReauthRequired] = useState(false) const enqueueSnackbar = useCallback((...args) => dispatch(enqueueSnackbarAction(...args)), [dispatch]) const closeSnackbar = useCallback((...args) => dispatch(closeSnackbarAction(...args)), [dispatch]) @@ -210,30 +212,27 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre // Reset token state setAccessToken(null) setIsTokenExpired(false) + setIsReauthRequired(false) } }, [credentialId]) // Handle credential data from prop useEffect(() => { if (credentialData?.plainDataObj) { - const expiresAt = new Date(credentialData.plainDataObj.expiresAt) - const now = new Date() - const isExpired = expiresAt < now - + const { access_token, expires_at } = credentialData.plainDataObj + const isExpired = expires_at ? new Date(expires_at) < new Date() : false setIsTokenExpired(isExpired) - setAccessToken(credentialData.plainDataObj.googleAccessToken ?? '') + setAccessToken(access_token ?? '') } }, [credentialData]) // Handle credential data from API useEffect(() => { if (getCredentialDataApi.data) { - const expiresAt = new Date(getCredentialDataApi.data?.plainDataObj.expiresAt) - const now = new Date() - const isExpired = expiresAt < now - + const { access_token, expires_at } = getCredentialDataApi.data?.plainDataObj + const isExpired = expires_at ? new Date(expires_at) < new Date() : false setIsTokenExpired(isExpired) - setAccessToken(getCredentialDataApi.data?.plainDataObj.googleAccessToken ?? '') + setAccessToken(access_token ?? '') handleCredentialDataChange?.(getCredentialDataApi.data) } }, [getCredentialDataApi.data, handleCredentialDataChange]) @@ -301,35 +300,61 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre try { setIsRefreshing(true) - // console.log('🔄 [FRONTEND] Iniciando refresh para credential:', credentialId) - - // Obtener token actual antes del refresh - // const currentCred = await credentialsApi.getSpecificCredential(credentialId) - // const oldToken = currentCred?.data?.plainDataObj?.googleAccessToken?.substring(0, 20) - // console.log('🔄 [FRONTEND] Token actual:', oldToken + '...') + await oauth2Api.refresh(credentialId) + getCredentialDataApi.request(credentialId) + setIsTokenExpired(false) + setIsReauthRequired(false) + showSnackbar('Successfully refreshed access token', 'success') + } catch (error) { + const status = error.response?.status + const errorMessage = error.response?.data?.message || error.message || 'Error refreshing access token' + + if (status === 401 || errorMessage.includes('REAUTH_REQUIRED') || errorMessage.includes('re-authenticate')) { + setIsReauthRequired(true) + showSnackbar('Re-authentication required. Please click "Re-authenticate with Google".', 'warning') + } else { + showSnackbar(errorMessage, 'error') + } + } finally { + setIsRefreshing(false) + } + }, [credentialId, getCredentialDataApi, showSnackbar]) - const response = await credentialsApi.refreshAccessToken({ credentialId }) + const handleReauthenticate = useCallback(async () => { + if (!credentialId) return - if (response.status === 200) { - getCredentialDataApi.request(credentialId) + try { + const authResponse = await oauth2Api.authorize(credentialId) + + if (authResponse.data?.authorizationUrl) { + const authWindow = window.open( + authResponse.data.authorizationUrl, + '_blank', + 'width=600,height=700,scrollbars=yes,resizable=yes' + ) + + if (!authWindow) { + showSnackbar('Popup blocked. Please allow popups for this site and try again.', 'error') + return + } - // Verificar que el token cambió - setTimeout(async () => { - // const newCred = await credentialsApi.getSpecificCredential(credentialId) - // const newToken = newCred?.data?.plainDataObj?.googleAccessToken?.substring(0, 20) - // console.log('✅ [FRONTEND] Token nuevo:', newToken + '...') - // console.log('🔍 [FRONTEND] ¿Token cambió?', oldToken !== newToken) - }, 1000) + const handleMessage = (event) => { + if (event.data?.type === 'OAUTH2_SUCCESS') { + window.removeEventListener('message', handleMessage) + getCredentialDataApi.request(credentialId) + setIsTokenExpired(false) + setIsReauthRequired(false) + showSnackbar('Successfully re-authenticated with Google', 'success') + } else if (event.data?.type === 'OAUTH2_ERROR') { + window.removeEventListener('message', handleMessage) + showSnackbar(event.data.error || 'Re-authentication failed', 'error') + } + } - setIsTokenExpired(false) - showSnackbar('Successfully refreshed access token', 'success') + window.addEventListener('message', handleMessage) } } catch (error) { - console.error('❌ [FRONTEND] Error refreshing access token:', error) - const errorMessage = error.response?.data?.message || 'Error refreshing access token' - showSnackbar(errorMessage, 'error') - } finally { - setIsRefreshing(false) + showSnackbar(error.response?.data?.message || 'Failed to start re-authentication', 'error') } }, [credentialId, getCredentialDataApi, showSnackbar]) @@ -359,7 +384,7 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre )} - {isTokenExpired && ( + {isTokenExpired && !isReauthRequired && ( + )} - {isTokenExpired && ( + {isTokenExpired && !isReauthRequired && ( - Access token has expired. Please re-authenticate or refresh the access token. + Access token has expired. Click "Refresh Access Token" to renew it. + + )} + + {isReauthRequired && ( + + Google authorization has expired or been revoked. Click "Re-authenticate with Google" to reconnect. )}