Skip to content

Commit 8a7960d

Browse files
committed
fix connector issues
1 parent 787d192 commit 8a7960d

File tree

5 files changed

+102
-44
lines changed

5 files changed

+102
-44
lines changed

ee/config.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,36 @@ def get_ee_settings() -> EESettings:
6969
if config_from_toml.get("GOOGLE_TOKEN_STORAGE_PATH"):
7070
settings_kwargs["GOOGLE_TOKEN_STORAGE_PATH"] = config_from_toml["GOOGLE_TOKEN_STORAGE_PATH"]
7171

72+
# -------------------------------------------------------------------------
73+
# Derive a sensible default GOOGLE_REDIRECT_URI based on the *core* config.
74+
# • If the value has already been provided via env or ee.toml we leave it
75+
# untouched (handled above).
76+
# • Otherwise we look at the global Morphik MODE setting. In **cloud**
77+
# mode the public API is expected to be served from the configured
78+
# `API_DOMAIN` (default: api.morphik.ai). For **self_hosted** mode we
79+
# fall back to the traditional localhost development URI.
80+
# -------------------------------------------------------------------------
81+
if "GOOGLE_REDIRECT_URI" not in settings_kwargs:
82+
try:
83+
# Import lazily to avoid any potential circular dependency issues
84+
from core.config import get_settings # pylint: disable=import-error
85+
86+
core_settings = get_settings()
87+
88+
if getattr(core_settings, "MODE", "self_hosted") == "cloud":
89+
api_domain = getattr(core_settings, "API_DOMAIN", "api.morphik.ai")
90+
derived_redirect = f"https://{api_domain}/ee/connectors/google_drive/oauth2callback"
91+
else:
92+
# Default for local/self-hosted development
93+
derived_redirect = "http://localhost:8000/ee/connectors/google_drive/oauth2callback"
94+
95+
settings_kwargs["GOOGLE_REDIRECT_URI"] = derived_redirect
96+
except Exception:
97+
# In case the core settings cannot be loaded for any reason we keep
98+
# the safe localhost default to avoid breaking startup.
99+
settings_kwargs.setdefault(
100+
"GOOGLE_REDIRECT_URI",
101+
"http://localhost:8000/ee/connectors/google_drive/oauth2callback",
102+
)
103+
72104
return EESettings(**settings_kwargs)

ee/routers/connectors_router.py

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,20 @@ async def get_auth_status_for_connector(
8282
raise HTTPException(status_code=500, detail="An internal server error occurred.")
8383

8484

85-
@router.get("/{connector_type}/auth/initiate") # No specific response model, as it redirects
86-
async def initiate_connector_authentication(
87-
request: Request, # FastAPI Request object to access session
85+
@router.get("/{connector_type}/auth/initiate_url", response_model=Dict[str, str])
86+
async def get_initiate_auth_url(
87+
request: Request,
8888
connector_type: str,
89-
app_redirect_uri: Optional[str] = None, # New parameter for frontend redirect
89+
app_redirect_uri: Optional[str] = None,
9090
service: ConnectorService = Depends(get_connector_service),
9191
):
92+
"""Return the provider's *authorization_url* for the given connector.
93+
94+
The method mirrors the logic of the `/auth/initiate` endpoint but sends a
95+
JSON payload instead of a redirect so that browsers can stay on the same
96+
origin until they intentionally navigate away.
9297
"""
93-
Initiates the OAuth 2.0 authentication flow for the specified connector.
94-
Stores state in session and redirects user to the provider's authorization URL.
95-
"""
98+
9699
try:
97100
connector = await service.get_connector(connector_type)
98101
auth_details = await connector.initiate_auth()
@@ -102,42 +105,32 @@ async def initiate_connector_authentication(
102105

103106
if not authorization_url or not state:
104107
logger.error(
105-
f"Connector '{connector_type}' did not return authorization URL or state "
106-
f"for user '{service.user_identifier}'."
108+
"Connector '%s' did not return authorization URL or state for user '%s'.",
109+
connector_type,
110+
service.user_identifier,
107111
)
108112
raise HTTPException(status_code=500, detail="Failed to initiate authentication with the provider.")
109113

110-
# Store state and connector type in session for later validation in the callback
114+
# Store state and connector type in session for later validation.
111115
request.session["oauth_state"] = state
112116
request.session["connector_type_for_callback"] = connector_type
113117
if app_redirect_uri:
114118
request.session["app_redirect_uri"] = app_redirect_uri
115-
logger.info(f"Stored app_redirect_uri in session: {app_redirect_uri}")
116119

117-
logger.info(
118-
f"Initiating auth for '{connector_type}' for user '{service.user_identifier}'. "
119-
f"Redirecting to: {authorization_url[:70]}..."
120-
)
121-
return RedirectResponse(url=authorization_url)
120+
logger.info("Prepared auth URL for '%s' for user '%s'.", connector_type, service.user_identifier)
121+
122+
return {"authorization_url": authorization_url}
122123

123-
except ValueError as ve: # Raised by get_connector for unsupported type or by connector for config issues
124-
logger.warning(f"Auth initiation for '{connector_type}' failed: {ve} for user '{service.user_identifier}'")
125-
# Determine if it's a 404 (unsupported) or 500 (config error within connector)
124+
except ValueError as ve:
125+
logger.warning("Auth URL preparation for '%s' failed: %s", connector_type, ve)
126126
if "Unsupported connector type" in str(ve):
127127
raise HTTPException(status_code=404, detail=str(ve))
128-
else:
129-
# E.g. Google Client ID/Secret not configured in GoogleDriveConnector
130-
raise HTTPException(
131-
status_code=500, detail=f"Configuration error for connector '{connector_type}': {str(ve)}"
132-
)
128+
raise HTTPException(status_code=500, detail=str(ve))
133129
except NotImplementedError:
134-
logger.error(f"Connector '{connector_type}' is not fully implemented for auth initiation.")
135130
raise HTTPException(status_code=501, detail=f"Connector '{connector_type}' not fully implemented.")
136-
except Exception as e:
137-
logger.exception(
138-
f"Error initiating auth for connector '{connector_type}' for user '{service.user_identifier}': {e}"
139-
)
140-
raise HTTPException(status_code=500, detail="Internal server error initiating authentication.")
131+
except Exception as exc:
132+
logger.exception("Error preparing auth URL for '%s': %s", connector_type, exc)
133+
raise HTTPException(status_code=500, detail="Internal server error preparing authentication URL.")
141134

142135

143136
@router.get("/{connector_type}/oauth2callback")

ee/ui-component/components/connectors/ConnectorCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function ConnectorCard({
6969
const connectionsSectionUrl = new URL(window.location.origin);
7070
connectionsSectionUrl.pathname = "/"; // Ensure we are at the root path
7171
connectionsSectionUrl.searchParams.set("section", "connections");
72-
initiateConnectorAuth(apiBaseUrl, connectorType, connectionsSectionUrl.toString());
72+
initiateConnectorAuth(apiBaseUrl, connectorType, connectionsSectionUrl.toString(), authToken);
7373
} catch (err) {
7474
setError(err instanceof Error ? err.message : "Failed to initiate connection.");
7575
setIsSubmitting(false);

ee/ui-component/lib/connectorsApi.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,53 @@ export async function getConnectorAuthStatus(
2727

2828
// Initiates the authentication process by redirecting the user
2929
// The backend will handle the actual redirect to the OAuth provider
30-
export function initiateConnectorAuth(
30+
export async function initiateConnectorAuth(
3131
apiBaseUrl: string,
3232
connectorType: string,
33-
appRedirectUri: string
34-
// authToken is not typically needed for the initiation step if it's a redirect-based flow
35-
// and the backend establishes session/cookie upon callback.
36-
// If token IS needed by this specific /initiate endpoint, it would be added here.
37-
): void {
38-
// The backend /auth/initiate endpoint itself performs a redirect.
39-
// So, navigating to it will trigger the OAuth flow.
40-
// We add the app_redirect_uri for the backend to use after successful callback.
41-
const initiateUrl = new URL(`${apiBaseUrl}/ee/connectors/${connectorType}/auth/initiate`);
42-
initiateUrl.searchParams.append("app_redirect_uri", appRedirectUri);
43-
window.location.href = initiateUrl.toString();
33+
appRedirectUri: string,
34+
authToken: string | null
35+
): Promise<void> {
36+
// Use the *initiate_url* helper which returns a JSON payload containing the
37+
// provider's authorization_url. This avoids CORS issues with opaque
38+
// redirects when the backend directly issues a 30x to a third-party domain.
39+
40+
const helperUrl = new URL(`${apiBaseUrl}/ee/connectors/${connectorType}/auth/initiate_url`);
41+
helperUrl.searchParams.append("app_redirect_uri", appRedirectUri);
42+
43+
try {
44+
const resp = await fetch(helperUrl.toString(), {
45+
method: "GET",
46+
headers: {
47+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
48+
},
49+
credentials: "include",
50+
});
51+
52+
if (!resp.ok) {
53+
let detail = "";
54+
try {
55+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
56+
const errBody = await resp.json();
57+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
58+
detail = (errBody as any)?.detail || "";
59+
} catch {
60+
/* ignore */
61+
}
62+
throw new Error(`Failed to initiate auth flow: ${resp.status} ${resp.statusText} ${detail}`);
63+
}
64+
65+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
66+
const data: { authorization_url: string } = await resp.json();
67+
if (!data.authorization_url) {
68+
throw new Error("Backend did not return authorization_url");
69+
}
70+
71+
// Finally navigate to the provider's OAuth consent page
72+
window.location.href = data.authorization_url;
73+
} catch (err) {
74+
console.error("Error initiating connector auth:", err);
75+
throw err;
76+
}
4477
}
4578

4679
// Disconnects a connector for the current user

ee/ui-component/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@morphik/ui",
3-
"version": "0.2.10",
3+
"version": "0.2.11",
44
"private": true,
55
"description": "Modern UI component for Morphik - A powerful document processing and querying system",
66
"author": "Morphik Team",

0 commit comments

Comments
 (0)