Many identity providers (AWS Cognito, Okta, Azure AD) reject or mishandle
colons in OAuth scope names. This migrates all custom scopes from
`resource:action` to `resource.action` format (e.g., `notes:read` →
`notes.read`), which is universally accepted and aligns with industry
conventions (Microsoft, Google).
Includes Alembic migration 004 for stored scope strings and ADR-024
documenting the rationale and RFC references.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document wildcard scope policy in ClientRegistry class docstring
- Add hostname None guard and IPv6 loopback (::1) to redirect URI validation
- Simplify redirect URI scheme validation into single guard clause
- Add try/finally cleanup to DCR client deletion test
- Validate 302 Location header in unknown client rejection test
- Add unit tests for IPv6 loopback, malformed URIs, and DCR proxy paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the unused ALLOWED_MCP_CLOUD_CLIENTS env var — all clients are
defined via ALLOWED_MCP_CLIENTS or the static well-known defaults.
Add keycloak as an integration test profile in CI now that login-flow
replaces the old bearer token approach for external IdPs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge ALLOWED_MCP_CLOUD_CLIENTS into a single ALLOWED_MCP_CLIENTS env var
that supports both simple client IDs and pipe-separated client_id|redirect_uri
entries. Enforce HTTPS for non-localhost redirect URIs, warn on malformed
entries, and use wildcard scopes for all static clients (upstream IdP enforces
actual scopes). Add deprecation warning for the old env var.
Also fixes DCR proxy error messages to reference only ALLOWED_MCP_CLIENTS and
use "Upstream" instead of "Nextcloud" for IdP-agnostic language. Enables
Login Flow v2 + DCR on the mcp-keycloak docker-compose service.
Adds 17 unit tests for ClientRegistry parsing/validation and 7 keycloak
integration tests for DCR lifecycle, AS metadata, and client authorization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude AI (web) sends a Cognito-issued client_id with an HTTPS redirect
URI, but the client registry only supported localhost redirect URIs via
ALLOWED_MCP_CLIENTS. Add ALLOWED_MCP_CLOUD_CLIENTS env var for web-based
clients with format "client_id|redirect_uri".
Also fix the DCR proxy to return a clear error when the upstream IdP
(e.g. Cognito) doesn't support dynamic client registration, instead of
silently falling back to a Nextcloud-specific endpoint that fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove all RFC 8693 token exchange tests (integration, manual, keycloak)
since Nextcloud doesn't support bearer tokens without upstream patches
- Remove manual impersonation/ADR-004 scripts and their docs
- Clean up token_exchange singleton from integration conftest
- Improve logging in _complete_login_flow_v2_as_user with step-by-step
[username] prefixed messages matching _complete_login_flow_v2 style
- Remove unnecessary time staggering from all_login_flow_user_tokens;
concurrent token acquisition works without artificial delays
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OAuth profile removal dropped cross-user permission tests (deck, files,
notes) that validated Nextcloud sharing/ACL enforcement through MCP tools.
These tested general functionality, not OAuth-specific behavior.
Restores coverage with login-flow fixtures and 9 tests covering file share
read/write enforcement, folder sharing, Deck board ACL view/edit, and
per-user resource isolation for files, boards, and notes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace 4 stale mcp-oauth references in CLAUDE.md with mcp-login-flow
- Import and catch playwright.async_api.TimeoutError in consent retry loop
(Playwright's TimeoutError doesn't inherit from Python's built-in)
- Replace unreachable `return True` with explicit RuntimeError raise
- Add clarifying comment for hardcoded login-flow port default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The login-flow MCP server exposes 3 additional auth tools
(nc_auth_provision_access, nc_auth_check_status, nc_auth_update_scopes)
from ADR-022 that require only 'openid' scope. Update the
no-custom-scopes test to expect 7 auth tools instead of 4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Discourage raw docker exec for database queries; use scripts/dbquery.py
and scripts/sqlitequery.py exclusively
- Ensure all python commands use uv run prefix (mcp run, pytest, etc.)
- Replace Progressive Consent section with concise Deployment Modes
overview (single-user, multi-user BasicAuth, Login Flow v2)
- Normalize docker-compose to docker compose throughout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pythonvCard4 parses vCard BDAY fields into datetime.date objects, but
the Contact model expects Optional[str]. This caused a validation error
that crashed the entire contact list. Convert at the client layer
(consistent with the calendar client pattern) with a defensive check
at the server mapping layer.
Closes#672
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both /app/provision and /app/provision/status now require a valid
Nextcloud OIDC bearer token via the Authorization header, reusing the
existing validate_token_and_get_user pattern from the management API.
This eliminates the open redirect vulnerability (only authenticated
Astrolabe users can trigger the flow) and prevents unauthenticated
resource exhaustion via Login Flow v2 session creation.
The authenticated user_id from the token replaces the untrusted
user_id query parameter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Log warning if /app mount not found when sharing poll task group
- Add docstring explaining unconditional task group creation
- Check session expires_at in provision_status to catch stale sessions
- Guard _poll_and_store status writes against cleanup-while-polling race
- Use "error" status (not "expired") when app_password is missing
- Remove hardcoded "Astrolabe Background Sync" user_agent string
- Fix async mock pattern (new_callable=AsyncMock) in test
- Add autouse fixture to clear _provision_sessions between tests
- Add _poll_and_store unit tests: completed, expired, error, cleanup
- Document all status values in provision_status docstring
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Escape HTML in _render_error to prevent XSS from exception messages
- Replace asyncio.create_task/sleep with anyio task group and sleep,
tying poll task lifetime to the app lifespan for proper cleanup
- Extract rewrite_url_origin() utility to fix duplicated URL rewriting
logic and replace urlparse._replace with stable urlunparse API
- Add warning log for insecure HTTP redirect URIs
- Add unit tests for validation, XSS escaping, route handlers, and
URL rewriting (16 new tests in test_provision_routes.py)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Login Flow v2 is a deployment-wide mode where all users authenticate
with app passwords (not OAuth refresh tokens). Set use_basic_auth=True
when enable_login_flow is true so the background sync user manager
queries the app_passwords table and scanners use app password
authentication for Nextcloud API calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When enable_login_flow is true, also check the app_passwords table
for provisioned users. Previously, OAuth mode only queried the
refresh_tokens table, missing users who were provisioned via
Login Flow v2 (which stores app passwords, not refresh tokens).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>