Skip to content
Open
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
3 changes: 2 additions & 1 deletion frontend/app/api/mutations/useConnectConnectorMutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export const useConnectConnectorMutation = () => {
result.oauth_config.redirect_uri,
)}&` +
`access_type=offline&` +
`prompt=select_account&` +
`include_granted_scopes=true&` +
`prompt=consent&` +
`state=${result.connection_id}`;
Comment on lines 79 to 82

window.location.href = authUrl;
Expand Down
3 changes: 2 additions & 1 deletion frontend/contexts/auth-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export function AuthProvider({ children }: AuthProviderProps) {
`scope=${result.oauth_config.scopes.join(" ")}&` +
`redirect_uri=${encodeURIComponent(result.oauth_config.redirect_uri)}&` +
`access_type=offline&` +
`prompt=select_account&` +
`include_granted_scopes=true&` +
`prompt=consent&` +
`state=${result.connection_id}`;

console.log("Redirecting to OAuth URL:", authUrl);
Expand Down
16 changes: 11 additions & 5 deletions src/connectors/google_drive/oauth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import json
from typing import Optional
from datetime import datetime, timezone
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import Flow
Expand Down Expand Up @@ -50,13 +51,18 @@ async def load_credentials(self) -> Optional[Credentials]:
scopes=token_data.get("scopes", self.SCOPES),
)

# Set expiry if available (ensure timezone-naive for Google auth compatibility)
# Set expiry if available.
# Backward compatibility: older tokens were saved as naive local datetimes.
if token_data.get("expiry"):
from datetime import datetime

expiry_dt = datetime.fromisoformat(token_data["expiry"])
# Remove timezone info to make it naive (Google auth expects naive datetimes)
self.creds.expiry = expiry_dt.replace(tzinfo=None)
if expiry_dt.tzinfo is None:
local_tz = datetime.now().astimezone().tzinfo
expiry_dt = expiry_dt.replace(tzinfo=local_tz)

# google-auth compares against a naive UTC timestamp internally.
self.creds.expiry = (
expiry_dt.astimezone(timezone.utc).replace(tzinfo=None)
)
Comment on lines 56 to +65

# If credentials are expired, refresh them
if self.creds and self.creds.expired and self.creds.refresh_token:
Expand Down
24 changes: 19 additions & 5 deletions src/services/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import httpx
import aiofiles
import logging
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Optional
import asyncio

Expand Down Expand Up @@ -92,7 +92,6 @@ async def init_oauth(
)

# Get OAuth configuration from connector and OAuth classes
import os

# Map connector types to their connector and OAuth classes
connector_class_map = {
Expand Down Expand Up @@ -236,21 +235,36 @@ async def handle_oauth_callback(
else granted_scopes
)

refresh_token = token_data.get("refresh_token")
token_file_path = connection_config.config["token_file"]

# Some OAuth providers omit refresh_token on re-consent. Preserve an
# existing one so background token refresh keeps working.
if not refresh_token and os.path.exists(token_file_path):
try:
async with aiofiles.open(token_file_path, "r") as f:
existing = json.loads(await f.read())
refresh_token = existing.get("refresh_token")
except Exception:
refresh_token = None

token_file_data = {
"token": token_data["access_token"],
"refresh_token": token_data.get("refresh_token"),
"refresh_token": refresh_token,
"scopes": scopes,
}

if token_data.get("id_token"):
token_file_data["id_token"] = token_data["id_token"]

# Add expiry if provided
if token_data.get("expires_in"):
expiry = datetime.now() + timedelta(
expiry = datetime.now(timezone.utc) + timedelta(
seconds=int(token_data["expires_in"])
)
token_file_data["expiry"] = expiry.isoformat()

# Save tokens to file
token_file_path = connection_config.config["token_file"]
async with aiofiles.open(token_file_path, "w") as f:
await f.write(json.dumps(token_file_data, indent=2))

Expand Down
Loading