diff --git a/application/api/connector/routes.py b/application/api/connector/routes.py
index 014fc71f..821b2d91 100644
--- a/application/api/connector/routes.py
+++ b/application/api/connector/routes.py
@@ -255,136 +255,64 @@ class ConnectorsCallback(Resource):
"""Handle OAuth callback for external connectors"""
try:
from application.parser.connectors.connector_creator import ConnectorCreator
- from flask import request
+ from flask import request, redirect
import uuid
+ provider = request.args.get('provider', 'google_drive')
authorization_code = request.args.get('code')
_ = request.args.get('state')
error = request.args.get('error')
if error:
- return make_response(
- jsonify({"success": False, "error": f"OAuth error: {error}. Please try again and make sure to grant all requested permissions, including offline access."}), 400
- )
+ return redirect(f"/api/connectors/callback-status?status=error&message=OAuth+error:+{error}.+Please+try+again+and+make+sure+to+grant+all+requested+permissions,+including+offline+access.&provider={provider}")
if not authorization_code:
- return make_response(
- jsonify({"success": False, "error": "Authorization code not provided. Please complete the authorization process and make sure to grant offline access."}), 400
- )
+ return redirect(f"/api/connectors/callback-status?status=error&message=Authorization+code+not+provided.+Please+complete+the+authorization+process+and+make+sure+to+grant+offline+access.&provider={provider}")
try:
- auth = ConnectorCreator.create_auth("google_drive")
+ auth = ConnectorCreator.create_auth(provider)
token_info = auth.exchange_code_for_tokens(authorization_code)
- current_app.logger.info(f"Token info received from OAuth callback - has refresh_token: {bool(token_info.get('refresh_token'))}, "
- f"has access_token: {bool(token_info.get('access_token'))}, "
- f"expiry: {token_info.get('expiry')}")
- safe_token_info = {k: v for k, v in token_info.items() if k not in ['access_token', 'refresh_token', 'client_secret']}
- current_app.logger.info(f"Full token info structure: {safe_token_info}")
+ session_token = str(uuid.uuid4())
+
- # Validate that we got token info
- if not token_info:
- current_app.logger.error("exchange_code_for_tokens returned None or empty result")
- return make_response(
- jsonify({"success": False, "error": "Failed to exchange authorization code for tokens. Please try again and make sure to grant all requested permissions, including offline access."}), 400
- )
+ try:
+ credentials = auth.create_credentials_from_token_info(token_info)
+ service = auth.build_drive_service(credentials)
+ user_info = service.about().get(fields="user").execute()
+ user_email = user_info.get('user', {}).get('emailAddress', 'Connected User')
+ except Exception as e:
+ current_app.logger.warning(f"Could not get user info: {e}")
+ user_email = 'Connected User'
- # Validate required fields in token_info
- required_fields = ['access_token', 'token_uri', 'client_id', 'client_secret']
- missing_fields = [field for field in required_fields if not token_info.get(field)]
- if missing_fields:
- current_app.logger.error(f"Token info missing required fields: {missing_fields}")
- return make_response(
- jsonify({"success": False, "error": f"Token information incomplete. Missing fields: {missing_fields}. Please try again and make sure to grant all requested permissions."}), 400
- )
+ sanitized_token_info = {
+ "access_token": token_info.get("access_token"),
+ "refresh_token": token_info.get("refresh_token"),
+ "token_uri": token_info.get("token_uri"),
+ "expiry": token_info.get("expiry"),
+ "scopes": token_info.get("scopes")
+ }
- if not token_info.get('refresh_token'):
- current_app.logger.warning("OAuth flow did not return a refresh token - user will need to re-authenticate when token expires")
-
- required_fields = ['access_token', 'token_uri', 'client_id', 'client_secret']
- missing_fields = [field for field in required_fields if not token_info.get(field)]
- if missing_fields:
- current_app.logger.error(f"Token info missing required fields: {missing_fields}")
- return make_response(
- jsonify({"success": False, "error": f"Token info missing required fields: {missing_fields}"}), 400
- )
+ user_id = request.decoded_token.get("sub") if getattr(request, "decoded_token", None) else None
+ sessions_collection.insert_one({
+ "session_token": session_token,
+ "user": user_id,
+ "token_info": sanitized_token_info,
+ "created_at": datetime.datetime.now(datetime.timezone.utc),
+ "user_email": user_email,
+ "provider": provider
+ })
+
+ # Redirect to success page with session token and user email
+ return redirect(f"/api/connectors/callback-status?status=success&message=Authentication+successful&provider={provider}&session_token={session_token}&user_email={user_email}")
except Exception as e:
current_app.logger.error(f"Error exchanging code for tokens: {str(e)}", exc_info=True)
-
- if 'refresh' in str(e).lower():
- current_app.logger.warning(f"Missing refresh token but continuing: {str(e)}")
-
- else:
- return make_response(
- jsonify({"success": False, "error": f"Failed to exchange authorization code for tokens: {str(e)}"}), 400
- )
-
- try:
- credentials = auth.create_credentials_from_token_info(token_info)
- service = auth.build_drive_service(credentials)
- user_info = service.about().get(fields="user").execute()
- user_email = user_info.get('user', {}).get('emailAddress', 'Connected User')
- except Exception as e:
- current_app.logger.warning(f"Could not get user info: {e}")
- if token_info.get('access_token'):
- try:
- import requests
- headers = {'Authorization': f'Bearer {token_info["access_token"]}'}
- response = requests.get(
- 'https://www.googleapis.com/drive/v3/about?fields=user',
- headers=headers
- )
- if response.status_code == 200:
- user_info = response.json()
- user_email = user_info.get('user', {}).get('emailAddress', 'Connected User')
- else:
- user_email = 'Connected User'
- except Exception as request_error:
- current_app.logger.warning(f"Could not get user info via direct request: {request_error}")
- user_email = 'Connected User'
- else:
- user_email = 'Connected User'
-
- session_token = str(uuid.uuid4())
-
-
-
- sanitized_token_info = {
- "access_token": token_info.get("access_token"),
- "refresh_token": token_info.get("refresh_token"),
- "token_uri": token_info.get("token_uri"),
- "expiry": token_info.get("expiry"),
- "scopes": token_info.get("scopes")
- }
-
- user_id = request.decoded_token.get("sub") if getattr(request, "decoded_token", None) else None
- sessions_collection.insert_one({
- "session_token": session_token,
- "user": user_id,
- "token_info": sanitized_token_info,
- "created_at": datetime.datetime.now(datetime.timezone.utc),
- "user_email": user_email
- })
-
- return make_response(
- jsonify({
- "success": True,
- "message": "Google Drive authentication successful",
- "session_token": session_token,
- "user_email": user_email
- }),
- 200
- )
+ return redirect(f"/api/connectors/callback-status?status=error&message=Failed+to+exchange+authorization+code+for+tokens:+{str(e)}&provider={provider}")
except Exception as e:
current_app.logger.error(f"Error handling connector callback: {e}")
- return make_response(
- jsonify({
- "success": False,
- "error": f"Failed to complete connector authentication: {str(e)}. Please try again and make sure to grant all requested permissions, including offline access."
- }), 500
- )
+ return redirect(f"/api/connectors/callback-status?status=error&message=Failed+to+complete+connector+authentication:+{str(e)}.+Please+try+again+and+make+sure+to+grant+all+requested+permissions,+including+offline+access.")
@connectors_ns.route("/api/connectors/refresh")
@@ -623,4 +551,63 @@ class ConnectorSync(Resource):
)
+@connectors_ns.route("/api/connectors/callback-status")
+class ConnectorCallbackStatus(Resource):
+ @api.doc(description="Return HTML page with connector authentication status")
+ def get(self):
+ """Return HTML page with connector authentication status"""
+ try:
+ status = request.args.get('status', 'error')
+ message = request.args.get('message', '')
+ provider = request.args.get('provider', 'connector')
+ session_token = request.args.get('session_token', '')
+ user_email = request.args.get('user_email', '')
+
+ html_content = f"""
+
+
+
+ {provider.replace('_', ' ').title()} Authentication
+
+
+
+
+
+
{provider.replace('_', ' ').title()} Authentication
+
+
{message}
+ {f'
Connected as: {user_email}
' if status == 'success' else ''}
+
+
You can close this window. {f"Your {provider.replace('_', ' ').title()} is now connected and ready to use." if status == 'success' else ''}
+
+
+
+ """
+
+ return make_response(html_content, 200, {'Content-Type': 'text/html'})
+ except Exception as e:
+ current_app.logger.error(f"Error rendering callback status page: {e}")
+ return make_response("Authentication error occurred", 500, {'Content-Type': 'text/html'})
+
diff --git a/application/core/settings.py b/application/core/settings.py
index f1000056..cb7d75e3 100644
--- a/application/core/settings.py
+++ b/application/core/settings.py
@@ -43,7 +43,9 @@ class Settings(BaseSettings):
# Google Drive integration
GOOGLE_CLIENT_ID: Optional[str] = None # Replace with your actual Google OAuth client ID
GOOGLE_CLIENT_SECRET: Optional[str] = None# Replace with your actual Google OAuth client secret
- GOOGLE_REDIRECT_URI: Optional[str] = None
+ CONNECTOR_REDIRECT_BASE_URI: Optional[str] = "http://127.0.0.1:7091/api/connectors/callback"
+ ##append ?provider={provider_name} in your Provider console like http://127.0.0.1:7091/api/connectors/callback?provider=google_drive
+
# LLM Cache
CACHE_REDIS_URL: str = "redis://localhost:6379/2"
diff --git a/application/parser/connectors/google_drive/auth.py b/application/parser/connectors/google_drive/auth.py
index 5a903653..37d55dcc 100644
--- a/application/parser/connectors/google_drive/auth.py
+++ b/application/parser/connectors/google_drive/auth.py
@@ -24,7 +24,7 @@ class GoogleDriveAuth(BaseConnectorAuth):
def __init__(self):
self.client_id = settings.GOOGLE_CLIENT_ID
self.client_secret = settings.GOOGLE_CLIENT_SECRET
- self.redirect_uri = settings.GOOGLE_REDIRECT_URI or "http://localhost:7091/api/google-drive/callback"
+ self.redirect_uri = f"{settings.CONNECTOR_REDIRECT_BASE_URI}?provider=google_drive"
if not self.client_id or not self.client_secret:
raise ValueError("Google OAuth credentials not configured. Please set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in settings.")
diff --git a/frontend/public/google-drive-callback.html b/frontend/public/google-drive-callback.html
deleted file mode 100644
index d2113624..00000000
--- a/frontend/public/google-drive-callback.html
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
- Google Drive Authentication
-
-
-
-
-
Google Drive Authentication
-
Processing authentication...
-
-
-
-
-
diff --git a/frontend/src/components/ConnectorTreeComponent.tsx b/frontend/src/components/ConnectorTreeComponent.tsx
index 972c55f7..dfd868b3 100644
--- a/frontend/src/components/ConnectorTreeComponent.tsx
+++ b/frontend/src/components/ConnectorTreeComponent.tsx
@@ -64,7 +64,6 @@ const ConnectorTreeComponent: React.FC = ({
const [sourceProvider, setSourceProvider] = useState('');
const [syncDone, setSyncDone] = useState(false);
-
useOutsideAlerter(
searchDropdownRef,
() => {
@@ -84,7 +83,6 @@ const ConnectorTreeComponent: React.FC = ({
};
const handleSync = async () => {
-
if (isSyncing) return;
const provider = sourceProvider;
@@ -106,10 +104,16 @@ const ConnectorTreeComponent: React.FC = ({
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
- const statusResponse = await userService.getTaskStatus(data.task_id, token);
+ const statusResponse = await userService.getTaskStatus(
+ data.task_id,
+ token,
+ );
const statusData = await statusResponse.json();
- console.log(`Task status (attempt ${attempt + 1}):`, statusData.status);
+ console.log(
+ `Task status (attempt ${attempt + 1}):`,
+ statusData.status,
+ );
if (statusData.status === 'SUCCESS') {
setSyncProgress(100);
@@ -117,7 +121,10 @@ const ConnectorTreeComponent: React.FC = ({
// Refresh directory structure
try {
- const refreshResponse = await userService.getDirectoryStructure(docId, token);
+ const refreshResponse = await userService.getDirectoryStructure(
+ docId,
+ token,
+ );
const refreshData = await refreshResponse.json();
if (refreshData && refreshData.directory_structure) {
setDirectoryStructure(refreshData.directory_structure);
@@ -137,12 +144,13 @@ const ConnectorTreeComponent: React.FC = ({
console.error('Sync task failed:', statusData.result);
break;
} else if (statusData.status === 'PROGRESS') {
-
- const progress = Number((statusData.result && statusData.result.current != null)
- ? statusData.result.current
- : (statusData.meta && statusData.meta.current != null)
- ? statusData.meta.current
- : 0);
+ const progress = Number(
+ statusData.result && statusData.result.current != null
+ ? statusData.result.current
+ : statusData.meta && statusData.meta.current != null
+ ? statusData.meta.current
+ : 0,
+ );
setSyncProgress(Math.max(10, progress));
}
@@ -168,7 +176,10 @@ const ConnectorTreeComponent: React.FC = ({
try {
setLoading(true);
- const directoryResponse = await userService.getDirectoryStructure(docId, token);
+ const directoryResponse = await userService.getDirectoryStructure(
+ docId,
+ token,
+ );
const directoryData = await directoryResponse.json();
if (directoryData && directoryData.directory_structure) {
@@ -261,7 +272,6 @@ const ConnectorTreeComponent: React.FC = ({
variant: 'primary',
});
-
return options;
};
@@ -301,18 +311,18 @@ const ConnectorTreeComponent: React.FC = ({
const renderPathNavigation = () => {
return (
-
+
{/* Left side with path navigation */}
-
+
{sourceName}
{currentPath.length > 0 && (
@@ -324,7 +334,9 @@ const ConnectorTreeComponent: React.FC = ({
{dir}
{index < currentPath.length - 1 && (
- /
+
+ /
+
)}
))}
@@ -333,29 +345,36 @@ const ConnectorTreeComponent: React.FC = ({
-
-
+
{renderFileSearch()}
{/* Sync button */}
@@ -369,46 +388,47 @@ const ConnectorTreeComponent: React.FC
= ({
const parentRow =
currentPath.length > 0
? [
-
-
-
- 
-
- ..
-
-
- |
-
- -
- |
-
- -
- |
- |
-
,
- ]
+
+
+
+ 
+
+ ..
+
+
+ |
+
+ -
+ |
+
+ -
+ |
+ |
+
,
+ ]
: [];
// Sort entries: directories first, then files, both alphabetically
- const sortedEntries = Object.entries(directory).sort(([nameA, nodeA], [nameB, nodeB]) => {
- const isFileA = !!nodeA.type;
- const isFileB = !!nodeB.type;
+ const sortedEntries = Object.entries(directory).sort(
+ ([nameA, nodeA], [nameB, nodeB]) => {
+ const isFileA = !!nodeA.type;
+ const isFileB = !!nodeB.type;
- if (isFileA !== isFileB) {
- return isFileA ? 1 : -1; // Directories first
- }
-
- return nameA.localeCompare(nameB); // Alphabetical within each group
- });
+ if (isFileA !== isFileB) {
+ return isFileA ? 1 : -1; // Directories first
+ }
+ return nameA.localeCompare(nameB); // Alphabetical within each group
+ },
+ );
// Process directories
const directoryRows = sortedEntries
@@ -450,7 +470,7 @@ const ConnectorTreeComponent: React.FC = ({