🔒 feat: Idempotency Check for OAuth Flow Completion (#10468)

* 🔒 feat: Implement idempotency check for OAuth flow completion

- Added a check to prevent duplicate token exchanges if the OAuth flow has already been completed.
- Updated the OAuth callback route to redirect appropriately when a completed flow is detected.
- Refactored token storage logic to use original flow state credentials instead of updated ones.
- Enhanced tests to cover the new idempotency behavior and ensure correct handling of OAuth flow states.

* chore: add back scope for logging

* refactor: Add isFlowStale method to FlowStateManager for stale flow detection

- Implemented a new method to check if a flow is stale based on its age and status.
- Updated MCPConnectionFactory to utilize the isFlowStale method for cleaning up stale OAuth flows.
- Enhanced logging to provide more informative messages regarding flow status and age during cleanup.

* test: Add unit tests for isFlowStale method in FlowStateManager

- Implemented comprehensive tests for the isFlowStale method to verify its behavior across various flow statuses (PENDING, COMPLETED, FAILED) and age thresholds.
- Ensured correct handling of edge cases, including flows with missing timestamps and custom stale thresholds.
- Enhanced test coverage to validate the logic for determining flow staleness based on createdAt, completedAt, and failedAt timestamps.
This commit is contained in:
Danny Avila 2025-11-12 08:44:45 -05:00 committed by GitHub
parent a49c509ebc
commit dd35f42073
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 380 additions and 72 deletions

View file

@ -1,5 +1,7 @@
import { randomBytes } from 'crypto';
import { logger } from '@librechat/data-schemas';
import { FetchLike } from '@modelcontextprotocol/sdk/shared/transport';
import { OAuthMetadataSchema } from '@modelcontextprotocol/sdk/shared/auth.js';
import {
registerClient,
startAuthorization,
@ -7,7 +9,6 @@ import {
discoverAuthorizationServerMetadata,
discoverOAuthProtectedResourceMetadata,
} from '@modelcontextprotocol/sdk/client/auth.js';
import { OAuthMetadataSchema } from '@modelcontextprotocol/sdk/shared/auth.js';
import type { MCPOptions } from 'librechat-data-provider';
import type { FlowStateManager } from '~/flow/manager';
import type {
@ -18,7 +19,6 @@ import type {
OAuthMetadata,
} from './types';
import { sanitizeUrlForLogging } from '~/mcp/utils';
import { FetchLike } from '@modelcontextprotocol/sdk/shared/transport';
/** Type for the OAuth metadata from the SDK */
type SDKOAuthMetadata = Parameters<typeof registerClient>[1]['metadata'];
@ -439,9 +439,10 @@ export class MCPOAuthHandler {
fetchFn: this.createOAuthFetch(oauthHeaders),
});
logger.debug('[MCPOAuth] Raw tokens from exchange:', {
access_token: tokens.access_token ? '[REDACTED]' : undefined,
refresh_token: tokens.refresh_token ? '[REDACTED]' : undefined,
logger.debug('[MCPOAuth] Token exchange successful', {
flowId,
has_access_token: !!tokens.access_token,
has_refresh_token: !!tokens.refresh_token,
expires_in: tokens.expires_in,
token_type: tokens.token_type,
scope: tokens.scope,