Files
Chris Coutinho f43343356e fix: address review feedback — security, caching, CI 429 retry
- Add 429 retry with exponential backoff to register_client() (fixes CI
  oauth matrix failures from parallel DCR requests)
- Make client_id, redirect_uri, and PKCE mandatory at token endpoint
- Add null-checks for discovery_url and OAuth credentials in proxy flows
- Add OIDC discovery document caching with 5-min TTL
- Add per-IP rate limiting on /oauth/register DCR proxy
- Discover DCR endpoint from OIDC discovery instead of hardcoding
- Extract extract_user_id_from_token to auth/token_utils.py (breaks
  circular imports between server/ and auth/ layers)
- Add TTL scope cache in scope_authorization.py (avoids DB hit per tool)
- Add defense-in-depth scope validation in storage layer
- Broaden elicitation exception handling with graceful fallback
- Add idempotentHint to nc_auth_check_status, return "pending" status
  after accepted elicitation, add polling interval to description
- Change ALL_SUPPORTED_SCOPES from tuple to frozenset for O(1) lookups
- Replace Optional[str] with str | None throughout config.py
- Use default_factory for ProxyCodeEntry/ASProxySession dataclasses
- Add proxy code/session cleanup to background loop
- Fix OIDC verification CI step to only run for oauth/login-flow modes
- Add unit tests for access.py REST endpoints (10 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:22:23 +01:00

79 lines
2.5 KiB
Python

"""Pydantic response models for Login Flow v2 auth tools."""
from pydantic import Field
from nextcloud_mcp_server.models.base import BaseResponse
class ProvisionAccessResponse(BaseResponse):
"""Response from nc_auth_provision_access tool."""
status: str = Field(
description="Provisioning status: 'login_required', 'already_provisioned', 'declined', 'cancelled', 'error'"
)
login_url: str | None = Field(
None, description="URL to open in browser for Nextcloud login"
)
message: str = Field(description="Human-readable status message")
user_id: str | None = Field(None, description="MCP user ID")
requested_scopes: list[str] | None = Field(
None, description="Scopes requested in this provisioning flow"
)
class ProvisionStatusResponse(BaseResponse):
"""Response from nc_auth_check_status tool."""
status: str = Field(
description="Status: 'provisioned', 'pending', 'not_initiated', 'error'"
)
message: str = Field(description="Human-readable status message")
user_id: str | None = Field(None, description="MCP user ID")
scopes: list[str] | None = Field(
None, description="Granted scopes (None = all scopes)"
)
username: str | None = Field(None, description="Nextcloud username (loginName)")
class UpdateScopesResponse(BaseResponse):
"""Response from nc_auth_update_scopes tool."""
status: str = Field(
description="Status: 'login_required', 'unchanged', 'declined', 'cancelled', 'error'"
)
login_url: str | None = Field(
None, description="URL for re-provisioning with new scopes"
)
message: str = Field(description="Human-readable status message")
previous_scopes: list[str] | None = Field(
None, description="Previously granted scopes"
)
new_scopes: list[str] | None = Field(None, description="Updated scope set")
# All supported application-level scopes (frozenset for O(1) membership tests)
ALL_SUPPORTED_SCOPES: frozenset[str] = frozenset(
{
"notes:read",
"notes:write",
"calendar:read",
"calendar:write",
"todo:read",
"todo:write",
"contacts:read",
"contacts:write",
"files:read",
"files:write",
"tables:read",
"tables:write",
"deck:read",
"deck:write",
"cookbook:read",
"cookbook:write",
"sharing:read",
"sharing:write",
"news:read",
"news:write",
}
)