mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-18 12:13:46 +00:00
(fix:auth) refresh drive token
This commit is contained in:
1 parent
0ee875bee4
commit
896612a5a3
3 files changed
+176
-139
No files matched your search
@@ -441,9 +441,29 @@ class ConnectorValidateSession(Resource):
|
|||||||
auth = ConnectorCreator.create_auth(provider)
|
auth = ConnectorCreator.create_auth(provider)
|
||||||
is_expired = auth.is_token_expired(token_info)
|
is_expired = auth.is_token_expired(token_info)
|
||||||
|
|
||||||
|
if is_expired and token_info.get('refresh_token'):
|
||||||
|
try:
|
||||||
|
new_token_info = auth.refresh_access_token(token_info.get('refresh_token'))
|
||||||
|
|
||||||
|
sessions_collection.update_one(
|
||||||
|
{"session_token": session_token},
|
||||||
|
{"$set": {"token_info": new_token_info}}
|
||||||
|
)
|
||||||
|
token_info = new_token_info
|
||||||
|
is_expired = False
|
||||||
|
except Exception as refresh_error:
|
||||||
|
current_app.logger.error(f"Failed to refresh token: {refresh_error}")
|
||||||
|
|
||||||
|
if is_expired:
|
||||||
|
return make_response(jsonify({
|
||||||
|
"success": False,
|
||||||
|
"expired": True,
|
||||||
|
"error": "Session token has expired. Please reconnect."
|
||||||
|
}), 401)
|
||||||
|
|
||||||
return make_response(jsonify({
|
return make_response(jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"expired": is_expired,
|
"expired": False,
|
||||||
"user_email": session.get('user_email', 'Connected User'),
|
"user_email": session.get('user_email', 'Connected User'),
|
||||||
"access_token": token_info.get('access_token')
|
"access_token": token_info.get('access_token')
|
||||||
}), 200)
|
}), 200)
|
||||||
|
|||||||
@@ -7,20 +7,21 @@ interface ConnectorAuthProps {
|
|||||||
onSuccess: (data: { session_token: string; user_email: string }) => void;
|
onSuccess: (data: { session_token: string; user_email: string }) => void;
|
||||||
onError: (error: string) => void;
|
onError: (error: string) => void;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
isConnected?: boolean;
|
||||||
|
userEmail?: string;
|
||||||
|
onDisconnect?: () => void;
|
||||||
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerLabel = (provider: string) => {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
google_drive: 'Google Drive',
|
|
||||||
};
|
|
||||||
return map[provider] || provider.replace(/_/g, ' ');
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
||||||
provider,
|
provider,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
label,
|
label,
|
||||||
|
isConnected = false,
|
||||||
|
userEmail = '',
|
||||||
|
onDisconnect,
|
||||||
|
errorMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const token = useSelector(selectToken);
|
const token = useSelector(selectToken);
|
||||||
const completedRef = useRef(false);
|
const completedRef = useRef(false);
|
||||||
@@ -36,12 +37,8 @@ const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
|||||||
|
|
||||||
const handleAuthMessage = (event: MessageEvent) => {
|
const handleAuthMessage = (event: MessageEvent) => {
|
||||||
const successGeneric = event.data?.type === 'connector_auth_success';
|
const successGeneric = event.data?.type === 'connector_auth_success';
|
||||||
const successProvider =
|
const successProvider = event.data?.type === `${provider}_auth_success`;
|
||||||
event.data?.type === `${provider}_auth_success` ||
|
const errorProvider = event.data?.type === `${provider}_auth_error`;
|
||||||
event.data?.type === 'google_drive_auth_success';
|
|
||||||
const errorProvider =
|
|
||||||
event.data?.type === `${provider}_auth_error` ||
|
|
||||||
event.data?.type === 'google_drive_auth_error';
|
|
||||||
|
|
||||||
if (successGeneric || successProvider) {
|
if (successGeneric || successProvider) {
|
||||||
completedRef.current = true;
|
completedRef.current = true;
|
||||||
@@ -109,21 +106,59 @@ const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonLabel = label || `Connect ${providerLabel(provider)}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<>
|
||||||
onClick={handleAuth}
|
{errorMessage && (
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-500 px-4 py-3 text-white transition-colors hover:bg-blue-600"
|
<div className="mb-4 flex items-center gap-2 rounded-md border border-[#E60000] bg-[#FFEBEB] p-3">
|
||||||
>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg className="h-5 w-5" viewBox="0 0 24 24">
|
<path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v4h2v-4h-2zm0-6v2h2V5h-2z" fill="#E60000"/>
|
||||||
<path
|
</svg>
|
||||||
fill="currentColor"
|
<span style={{
|
||||||
d="M6.28 3l5.72 10H24l-5.72-10H6.28zm11.44 0L12 13l5.72 10H24L18.28 3h-.56zM0 13l5.72 10h5.72L5.72 13H0z"
|
fontFamily: 'Inter',
|
||||||
/>
|
fontWeight: 400,
|
||||||
</svg>
|
fontSize: '15px',
|
||||||
{buttonLabel}
|
lineHeight: '100%',
|
||||||
</button>
|
color: '#E60000'
|
||||||
|
}}>
|
||||||
|
{errorMessage}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isConnected ? (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="w-full flex items-center justify-between rounded-[10px] bg-[#8FDD51] px-4 py-2 text-[#212121] font-medium text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg className="h-4 w-4" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
||||||
|
</svg>
|
||||||
|
<span>Connected as {userEmail}</span>
|
||||||
|
</div>
|
||||||
|
{onDisconnect && (
|
||||||
|
<button
|
||||||
|
onClick={onDisconnect}
|
||||||
|
className="text-[#212121] hover:text-gray-700 font-medium text-xs underline"
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleAuth}
|
||||||
|
className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-500 px-4 py-3 text-white transition-colors hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
<svg className="h-5 w-5" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M6.28 3l5.72 10H24l-5.72-10H6.28zm11.44 0L12 13l5.72 10H24L18.28 3h-.56zM0 13l5.72 10h5.72L5.72 13H0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -119,9 +119,10 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
|||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
developerKey: developerKey,
|
developerKey: developerKey,
|
||||||
appId: appId,
|
appId: appId,
|
||||||
|
setSelectFolderEnabled: true,
|
||||||
viewId: "DOCS",
|
viewId: "DOCS",
|
||||||
showUploadView: false,
|
showUploadView: false,
|
||||||
showUploadFolders: false,
|
showUploadFolders: true,
|
||||||
supportDrives: false,
|
supportDrives: false,
|
||||||
multiselect: true,
|
multiselect: true,
|
||||||
token: accessToken,
|
token: accessToken,
|
||||||
@@ -201,122 +202,103 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
|||||||
onSelectionChange([], []);
|
onSelectionChange([], []);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isConnected) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{authError && (
|
|
||||||
<div className="text-red-500 text-sm mb-4 text-center">{authError}</div>
|
|
||||||
)}
|
|
||||||
<ConnectorAuth
|
|
||||||
provider="google_drive"
|
|
||||||
onSuccess={(data) => {
|
|
||||||
setUserEmail(data.user_email || 'Connected User');
|
|
||||||
setIsConnected(true);
|
|
||||||
setAuthError('');
|
|
||||||
|
|
||||||
if (data.session_token) {
|
|
||||||
setSessionToken('google_drive', data.session_token);
|
|
||||||
validateSession(data.session_token);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onError={(error) => {
|
|
||||||
setAuthError(error);
|
|
||||||
setIsConnected(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-4">
|
<ConnectorAuth
|
||||||
<div className="w-full flex items-center justify-between rounded-[10px] bg-[#8FDD51] px-4 py-2 text-[#212121] font-medium text-sm">
|
provider="google_drive"
|
||||||
<div className="flex items-center gap-2">
|
label="Connect to Google Drive"
|
||||||
<svg className="h-4 w-4" viewBox="0 0 24 24">
|
onSuccess={(data) => {
|
||||||
<path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
setUserEmail(data.user_email || 'Connected User');
|
||||||
</svg>
|
setIsConnected(true);
|
||||||
<span>Connected as {userEmail}</span>
|
setAuthError('');
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={handleDisconnect}
|
|
||||||
className="text-[#212121] hover:text-gray-700 font-medium text-xs underline"
|
|
||||||
>
|
|
||||||
Disconnect
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border border-[#EEE6FF78] rounded-lg dark:border-[#6A6A6A]">
|
if (data.session_token) {
|
||||||
<div className="p-4">
|
setSessionToken('google_drive', data.session_token);
|
||||||
<div className="flex justify-between items-center mb-4">
|
validateSession(data.session_token);
|
||||||
<h3 className="text-sm font-medium">Selected Files</h3>
|
}
|
||||||
<button
|
}}
|
||||||
onClick={() => handleOpenPicker()}
|
onError={(error) => {
|
||||||
className="bg-[#A076F6] hover:bg-[#8A5FD4] text-white text-sm py-1 px-3 rounded-md"
|
setAuthError(error);
|
||||||
disabled={isLoading}
|
setIsConnected(false);
|
||||||
>
|
}}
|
||||||
{isLoading ? 'Loading...' : 'Select Files'}
|
isConnected={isConnected}
|
||||||
</button>
|
userEmail={userEmail}
|
||||||
</div>
|
onDisconnect={handleDisconnect}
|
||||||
|
errorMessage={authError}
|
||||||
|
/>
|
||||||
|
|
||||||
{selectedFiles.length === 0 && selectedFolders.length === 0 ? (
|
{isConnected && (
|
||||||
<p className="text-gray-600 dark:text-gray-400 text-sm">No files or folders selected</p>
|
<div className="border border-[#EEE6FF78] rounded-lg dark:border-[#6A6A6A]">
|
||||||
) : (
|
<div className="p-4">
|
||||||
<div className="max-h-60 overflow-y-auto">
|
<div className="flex justify-between items-center mb-4">
|
||||||
{selectedFolders.length > 0 && (
|
<h3 className="text-sm font-medium">Selected Files</h3>
|
||||||
<div className="mb-2">
|
<button
|
||||||
<h4 className="text-xs font-medium text-gray-500 mb-1">Folders</h4>
|
onClick={() => handleOpenPicker()}
|
||||||
{selectedFolders.map((folder) => (
|
className="bg-[#A076F6] hover:bg-[#8A5FD4] text-white text-sm py-1 px-3 rounded-md"
|
||||||
<div key={folder.id} className="flex items-center p-2 border-b border-gray-200 dark:border-gray-700">
|
disabled={isLoading}
|
||||||
<img src={folder.iconUrl} alt="Folder" className="w-5 h-5 mr-2" />
|
>
|
||||||
<span className="text-sm truncate flex-1">{folder.name}</span>
|
{isLoading ? 'Loading...' : 'Select Files'}
|
||||||
<button
|
</button>
|
||||||
onClick={() => {
|
|
||||||
const newSelectedFolders = selectedFolders.filter(f => f.id !== folder.id);
|
|
||||||
setSelectedFolders(newSelectedFolders);
|
|
||||||
onSelectionChange(
|
|
||||||
selectedFiles.map(f => f.id),
|
|
||||||
newSelectedFolders.map(f => f.id)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className="text-red-500 hover:text-red-700 text-sm ml-2"
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedFiles.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<h4 className="text-xs font-medium text-gray-500 mb-1">Files</h4>
|
|
||||||
{selectedFiles.map((file) => (
|
|
||||||
<div key={file.id} className="flex items-center p-2 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<img src={file.iconUrl} alt="File" className="w-5 h-5 mr-2" />
|
|
||||||
<span className="text-sm truncate flex-1">{file.name}</span>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
const newSelectedFiles = selectedFiles.filter(f => f.id !== file.id);
|
|
||||||
setSelectedFiles(newSelectedFiles);
|
|
||||||
onSelectionChange(
|
|
||||||
newSelectedFiles.map(f => f.id),
|
|
||||||
selectedFolders.map(f => f.id)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className="text-red-500 hover:text-red-700 text-sm ml-2"
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{selectedFiles.length === 0 && selectedFolders.length === 0 ? (
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 text-sm">No files or folders selected</p>
|
||||||
|
) : (
|
||||||
|
<div className="max-h-60 overflow-y-auto">
|
||||||
|
{selectedFolders.length > 0 && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<h4 className="text-xs font-medium text-gray-500 mb-1">Folders</h4>
|
||||||
|
{selectedFolders.map((folder) => (
|
||||||
|
<div key={folder.id} className="flex items-center p-2 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<img src={folder.iconUrl} alt="Folder" className="w-5 h-5 mr-2" />
|
||||||
|
<span className="text-sm truncate flex-1">{folder.name}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const newSelectedFolders = selectedFolders.filter(f => f.id !== folder.id);
|
||||||
|
setSelectedFolders(newSelectedFolders);
|
||||||
|
onSelectionChange(
|
||||||
|
selectedFiles.map(f => f.id),
|
||||||
|
newSelectedFolders.map(f => f.id)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="text-red-500 hover:text-red-700 text-sm ml-2"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedFiles.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-medium text-gray-500 mb-1">Files</h4>
|
||||||
|
{selectedFiles.map((file) => (
|
||||||
|
<div key={file.id} className="flex items-center p-2 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<img src={file.iconUrl} alt="File" className="w-5 h-5 mr-2" />
|
||||||
|
<span className="text-sm truncate flex-1">{file.name}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const newSelectedFiles = selectedFiles.filter(f => f.id !== file.id);
|
||||||
|
setSelectedFiles(newSelectedFiles);
|
||||||
|
onSelectionChange(
|
||||||
|
newSelectedFiles.map(f => f.id),
|
||||||
|
selectedFolders.map(f => f.id)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="text-red-500 hover:text-red-700 text-sm ml-2"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user