Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -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
Expand Down
109 changes: 73 additions & 36 deletions packages/ui/src/ui-component/drive/GoogleDrivePicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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])

Expand Down Expand Up @@ -359,7 +384,7 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre
</Button>
)}

{isTokenExpired && (
{isTokenExpired && !isReauthRequired && (
<Button
variant='outlined'
onClick={handleRefreshAccessToken}
Expand All @@ -374,11 +399,23 @@ export const GoogleDrivePicker = ({ onChange, value, disabled, credentialId, cre
{isRefreshing ? 'Refreshing...' : 'Refresh Access Token'}
</Button>
)}

{isReauthRequired && (
<Button variant='outlined' color='warning' onClick={handleReauthenticate} disabled={!credentialId}>
Re-authenticate with Google
</Button>
)}
</Stack>

{isTokenExpired && (
{isTokenExpired && !isReauthRequired && (
<Alert severity='warning' sx={{ mb: 1 }}>
Access token has expired. Please re-authenticate or refresh the access token.
Access token has expired. Click &quot;Refresh Access Token&quot; to renew it.
</Alert>
)}

{isReauthRequired && (
<Alert severity='error' sx={{ mb: 1 }}>
Google authorization has expired or been revoked. Click &quot;Re-authenticate with Google&quot; to reconnect.
</Alert>
)}

Expand Down