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
7 changes: 6 additions & 1 deletion mcpgateway/services/oauth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,13 +573,18 @@ async def complete_authorization_code_flow(self, gateway_id: str, code: str, sta

# Store tokens if storage service is available
if self.token_storage:
# Use provider's expires_in if present; None means the provider
# does not specify token expiration (e.g. GitHub OAuth Apps).
raw_expires = token_response.get("expires_in")
expires_in = int(raw_expires) if raw_expires is not None else None

token_record = await self.token_storage.store_tokens(
gateway_id=gateway_id,
user_id=user_id,
app_user_email=app_user_email, # User from state
access_token=token_response["access_token"],
refresh_token=token_response.get("refresh_token"),
expires_in=token_response.get("expires_in", self.settings.oauth_default_timeout),
expires_in=expires_in,
scopes=token_response.get("scope", "").split(),
)

Expand Down
12 changes: 8 additions & 4 deletions mcpgateway/services/token_storage_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def __init__(self, db: Session):
logger.warning("OAuth encryption not available, using plain text storage")
self.encryption = None

async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str, access_token: str, refresh_token: Optional[str], expires_in: int, scopes: List[str]) -> OAuthToken:
async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str, access_token: str, refresh_token: Optional[str], expires_in: Optional[int], scopes: List[str]) -> OAuthToken:
"""Store OAuth tokens for a gateway-user combination.

Args:
Expand All @@ -83,7 +83,7 @@ async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str,
app_user_email: ContextForge user email (required)
access_token: Access token from OAuth provider
refresh_token: Refresh token from OAuth provider (optional)
expires_in: Token expiration time in seconds
expires_in: Token expiration time in seconds, or None if the provider does not specify expiration
scopes: List of OAuth scopes granted

Returns:
Expand All @@ -102,8 +102,12 @@ async def store_tokens(self, gateway_id: str, user_id: str, app_user_email: str,
if refresh_token:
encrypted_refresh = await self.encryption.encrypt_secret_async(refresh_token)

# Calculate expiration
expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(expires_in))
# Calculate expiration (None if provider does not specify expires_in)
if expires_in is not None:
expires_at = datetime.now(timezone.utc) + timedelta(seconds=int(expires_in))
else:
logger.info(f"No expires_in from OAuth provider for gateway {SecurityValidator.sanitize_log_message(gateway_id)} β€” token will not auto-expire")
expires_at = None
# Create or update token record - now scoped by app_user_email
token_record = self.db.execute(select(OAuthToken).where(OAuthToken.gateway_id == gateway_id, OAuthToken.app_user_email == app_user_email)).scalar_one_or_none()

Expand Down