feat: Complete ADR-004 Progressive Consent OAuth flows implementation
Implement dual OAuth flows for Progressive Consent architecture: Flow 1 (Client Authentication): - Client authenticates directly to IdP with its own client_id - Server validates client_id against ALLOWED_MCP_CLIENTS whitelist - Issues tokens with aud: "mcp-server" for MCP authentication only - Progressive mode detected via ENABLE_PROGRESSIVE_CONSENT env var Flow 2 (Resource Provisioning): - New endpoints: /oauth/authorize-nextcloud, /oauth/callback-nextcloud - MCP server acts as OAuth client for delegated Nextcloud access - Stores master refresh tokens with flow_type and audience metadata - Returns success HTML page after provisioning completion Scope Authorization Updates: - Added ProvisioningRequiredError for missing Flow 2 provisioning - Decorator checks if Nextcloud scopes require provisioning in Progressive mode - Validates token has Nextcloud scopes before allowing access Storage Schema Enhancements: - Added flow_type, is_provisioning, requested_scopes to oauth_sessions - Enhanced store_oauth_session to support Progressive Consent metadata - Maintains backward compatibility with hybrid flow This completes the Progressive Consent implementation, enabling: - Explicit user consent for resource access - Stateless server by default (no automatic provisioning) - Clear separation between authentication and resource access - Defense in depth with audience-specific tokens 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -711,10 +711,14 @@ class RefreshTokenStorage:
|
||||
code_challenge: Optional[str] = None,
|
||||
code_challenge_method: Optional[str] = None,
|
||||
mcp_authorization_code: Optional[str] = None,
|
||||
client_id: Optional[str] = None,
|
||||
flow_type: str = "hybrid",
|
||||
is_provisioning: bool = False,
|
||||
requested_scopes: Optional[str] = None,
|
||||
ttl_seconds: int = 600, # 10 minutes
|
||||
) -> None:
|
||||
"""
|
||||
Store OAuth session for Hybrid Flow (ADR-004).
|
||||
Store OAuth session for ADR-004 Progressive Consent.
|
||||
|
||||
Args:
|
||||
session_id: Unique session identifier
|
||||
@@ -723,6 +727,10 @@ class RefreshTokenStorage:
|
||||
code_challenge: PKCE code challenge
|
||||
code_challenge_method: PKCE method (S256)
|
||||
mcp_authorization_code: Pre-generated MCP authorization code
|
||||
client_id: Client identifier (for Flow 1)
|
||||
flow_type: Type of flow ('hybrid', 'flow1', 'flow2')
|
||||
is_provisioning: Whether this is a Flow 2 provisioning session
|
||||
requested_scopes: Requested OAuth scopes
|
||||
ttl_seconds: Session TTL in seconds
|
||||
"""
|
||||
if not self._initialized:
|
||||
@@ -735,17 +743,22 @@ class RefreshTokenStorage:
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO oauth_sessions
|
||||
(session_id, client_redirect_uri, state, code_challenge,
|
||||
code_challenge_method, mcp_authorization_code, created_at, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(session_id, client_id, client_redirect_uri, state, code_challenge,
|
||||
code_challenge_method, mcp_authorization_code, flow_type,
|
||||
is_provisioning, requested_scopes, created_at, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
session_id,
|
||||
client_id,
|
||||
client_redirect_uri,
|
||||
state,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
mcp_authorization_code,
|
||||
flow_type,
|
||||
is_provisioning,
|
||||
requested_scopes,
|
||||
now,
|
||||
expires_at,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user