refactor(config)!: derive enable_login_flow from mode, remove ENABLE_LOGIN_FLOW env var

Once OAUTH_SINGLE_AUDIENCE was renamed to LOGIN_FLOW and the validation
gate ensured the only meaningful configuration was
`MCP_DEPLOYMENT_MODE=login_flow + ENABLE_LOGIN_FLOW=true`, the two
controls became redundant. Setting the mode is sufficient; the
ENABLE_LOGIN_FLOW env var doesn't add information.

This commit makes the deployment mode the single source of truth for
the Login Flow v2 toggle:

- `nextcloud_mcp_server/config.py`: drop the `ENABLE_LOGIN_FLOW`
  dynaconf env-var alias. The `enable_login_flow` field stays as an
  internal attribute so the 6 runtime call sites (app.py x4,
  context.py, auth/scope_authorization.py) keep working unchanged.
  Updated field docstring to flag it as derived.
- `nextcloud_mcp_server/config_validators.py`:
  - Drop `enable_login_flow` from `MODE_REQUIREMENTS[LOGIN_FLOW].required`.
  - Drop the validation gate that required ENABLE_LOGIN_FLOW=true for
    LOGIN_FLOW mode (no longer possible to misconfigure — the flag is
    derived, not user input).
  - Add `_sync_derived_flags()` helper called at every return path of
    `detect_auth_mode` to set `settings.enable_login_flow` from the
    resolved mode.
- `tests/unit/test_config_validators.py`: drop `enable_login_flow=True`
  from happy-path fixtures (no longer needed — detection sets it).
  Repurpose `test_login_flow_requires_enable_login_flow_flag` into
  `test_login_flow_mode_auto_derives_enable_login_flow_flag` which
  asserts the new auto-derivation behaviour for both LOGIN_FLOW and a
  non-LOGIN_FLOW mode.
- `docker-compose.yml`: remove `ENABLE_LOGIN_FLOW=true` from the
  `mcp-login-flow` and `mcp-keycloak` profiles.
- `env.sample`: remove the ENABLE_LOGIN_FLOW reference; the comment
  on `MCP_DEPLOYMENT_MODE` now notes the derived flag.
- `docs/configuration.md`, `docs/authentication.md`,
  `docs/login-flow-v2.md`, `docs/auth-flows.md`,
  `docs/troubleshooting.md`, `docs/ADR-025-*.md`: replace
  ENABLE_LOGIN_FLOW=true examples and references with
  MCP_DEPLOYMENT_MODE=login_flow.

BREAKING CHANGE: `ENABLE_LOGIN_FLOW` is no longer read from the
environment. Anyone who relied on `ENABLE_LOGIN_FLOW=true` to activate
Login Flow v2 should set `MCP_DEPLOYMENT_MODE=login_flow` instead (or
rely on it being the default when no other auth env vars are set).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-12 19:45:50 +02:00
co-authored by Claude Opus 4.7
parent c74ef014ee
commit df4994e860
11 changed files with 80 additions and 54 deletions
@@ -38,7 +38,7 @@ The nextcloud-mcp-server configuration system has grown to ~80+ environment vari
|----------|-------------|---------|
| Core Nextcloud | 6 | `NEXTCLOUD_HOST`, `NEXTCLOUD_USERNAME`, `NEXTCLOUD_VERIFY_SSL` |
| OAuth/OIDC | 12 | `OIDC_DISCOVERY_URL`, `NEXTCLOUD_OIDC_CLIENT_ID`, `JWKS_URI` |
| Mode Selection | 4 | `MCP_DEPLOYMENT_MODE`, `ENABLE_LOGIN_FLOW`, `ENABLE_TOKEN_EXCHANGE` |
| Mode Selection | 2 | `MCP_DEPLOYMENT_MODE`, `ENABLE_MULTI_USER_BASIC_AUTH` |
| Token Storage | 3 | `TOKEN_ENCRYPTION_KEY`, `TOKEN_STORAGE_DB` |
| Semantic Search | 6 | `ENABLE_SEMANTIC_SEARCH`, `VECTOR_SYNC_SCAN_INTERVAL` |
| Qdrant | 4 | `QDRANT_URL`, `QDRANT_LOCATION`, `QDRANT_API_KEY` |
@@ -115,7 +115,8 @@ nextcloud_ca_bundle = "@none"
# === Authentication Toggles ===
enable_multi_user_basic_auth = false
enable_login_flow = false
# `enable_login_flow` is derived from MCP_DEPLOYMENT_MODE=login_flow in
# detect_auth_mode (ADR-022 follow-up) — no separate toggle.
enable_token_exchange = false
# === Token Storage ===
@@ -202,7 +203,7 @@ enable_multi_user_basic_auth = true
token_storage_db = "/app/data/tokens.db"
[login_flow]
enable_login_flow = true
# enable_login_flow is now derived from the mode (ADR-022 follow-up).
token_storage_db = "/app/data/tokens.db"
[keycloak]
@@ -266,7 +267,7 @@ Dynaconf merges configuration in this order (last wins):
This means:
- **File-based config is optional** — env vars alone still work (they override everything)
- **Mode-specific defaults reduce boilerplate** — `[login_flow]` sets `enable_login_flow=true` and `token_storage_db=/app/data/tokens.db` so deployers don't need to
- **Mode-specific defaults reduce boilerplate** — `[login_flow]` sets `token_storage_db=/app/data/tokens.db` so deployers don't need to
- **Secrets are separated** — `.secrets.toml` holds `TOKEN_ENCRYPTION_KEY`, passwords, API keys
- **Local dev overrides don't pollute** — `settings.local.toml` is gitignored
+1 -1
View File
@@ -231,7 +231,7 @@ TOKEN_STORAGE_DB=/app/data/tokens.db
### Login Flow v2
```bash
NEXTCLOUD_HOST=https://nextcloud.example.com
ENABLE_LOGIN_FLOW=true
MCP_DEPLOYMENT_MODE=login_flow
# Required for app-password storage
TOKEN_ENCRYPTION_KEY=<fernet-key>
+1 -1
View File
@@ -75,7 +75,7 @@ The server detects the active mode from environment variables at startup:
|------------------|---------------|
| `NEXTCLOUD_USERNAME` + `NEXTCLOUD_PASSWORD` | Single-User (BasicAuth) |
| `ENABLE_MULTI_USER_BASIC_AUTH=true` (no creds) | Multi-User (BasicAuth pass-through) |
| `ENABLE_LOGIN_FLOW=true` (no creds) | Multi-User (Login Flow v2) |
| `MCP_DEPLOYMENT_MODE=login_flow` or no auth env vars set | Multi-User (Login Flow v2) |
You can also force a mode via CLI flag:
+3 -4
View File
@@ -91,7 +91,7 @@ The recommended multi-user mode. MCP clients authenticate to the MCP server via
```dotenv
NEXTCLOUD_HOST=https://your.nextcloud.instance.com
ENABLE_LOGIN_FLOW=true
MCP_DEPLOYMENT_MODE=login_flow
# App-password storage (required)
TOKEN_ENCRYPTION_KEY=<fernet-key>
@@ -105,7 +105,7 @@ NEXTCLOUD_PUBLIC_ISSUER_URL=https://your.nextcloud.instance.com
| Variable | Required | Description |
|----------|----------|-------------|
| `NEXTCLOUD_HOST` | ✅ Yes | Internal URL of your Nextcloud instance (server-to-server) |
| `ENABLE_LOGIN_FLOW` | ✅ Yes | Set to `true` to enable Login Flow v2 |
| `MCP_DEPLOYMENT_MODE` | ✅ Yes | Set to `login_flow` to select this mode. The Login Flow v2 browser-app-password layer is derived from the mode automatically — no separate flag needed. |
| `TOKEN_ENCRYPTION_KEY` | ✅ Yes | Fernet key for app-password encryption — generate with `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |
| `TOKEN_STORAGE_DB` | ✅ Yes | Path to SQLite DB for stored app passwords (use a persistent volume) |
| `NEXTCLOUD_MCP_SERVER_URL` | ✅ Yes | Public URL of the MCP server (used as the audience claim and for browser redirects) |
@@ -197,8 +197,7 @@ OLLAMA_BASE_URL=http://ollama:11434
**Multi-User Login Flow v2 Mode:**
```dotenv
NEXTCLOUD_HOST=https://nextcloud.example.com
MCP_DEPLOYMENT_MODE=login_flow_v2
ENABLE_LOGIN_FLOW=true
MCP_DEPLOYMENT_MODE=login_flow
# Enable semantic search
# In multi-user modes, this AUTOMATICALLY enables background operations!
+4 -4
View File
@@ -58,8 +58,8 @@ NEXTCLOUD_HOST=https://your.nextcloud.example.com
NEXTCLOUD_OIDC_CLIENT_ID=<your-client-id>
NEXTCLOUD_OIDC_CLIENT_SECRET=<your-client-secret>
# Enable Login Flow v2 (per-user Nextcloud app-password provisioning for the data leg)
ENABLE_LOGIN_FLOW=true
# Select Login Flow v2 mode (per-user Nextcloud app-password provisioning for the data leg)
MCP_DEPLOYMENT_MODE=login_flow
# App-password storage (required for persistence across restarts)
TOKEN_STORAGE_DB=/app/data/tokens.db
@@ -172,7 +172,7 @@ mcp-login-flow:
- NEXTCLOUD_HOST=http://app:80
- NEXTCLOUD_MCP_SERVER_URL=http://localhost:8004
- NEXTCLOUD_PUBLIC_ISSUER_URL=http://localhost:8080
- ENABLE_LOGIN_FLOW=true
- MCP_DEPLOYMENT_MODE=login_flow
# Dev-only inline value. In production, mount via Docker secret and read
# from a *_FILE env var or a secrets-management init step.
- TOKEN_ENCRYPTION_KEY=<your-fernet-key>
@@ -351,7 +351,7 @@ The user revoked it from **Settings → Security → Devices & Sessions**. Delet
### Multiple worker processes
The provisioning session store is in-memory; `ENABLE_LOGIN_FLOW=true` assumes a single worker. Running with `uvicorn --workers N` will cause provisioning sessions to randomly fail. For higher concurrency, scale horizontally (multiple containers behind a sticky-session load balancer) rather than within a single process.
The provisioning session store is in-memory; `MCP_DEPLOYMENT_MODE=login_flow` assumes a single worker. Running with `uvicorn --workers N` will cause provisioning sessions to randomly fail. For higher concurrency, scale horizontally (multiple containers behind a sticky-session load balancer) rather than within a single process.
> **Sticky-session keying:** route on the **user identity** (e.g. the `sub` claim from the OAuth Bearer token) — **not** the raw token value, and **not** source IP. Bearer tokens rotate on refresh, which would silently break token-value affinity if a refresh lands between the request that initiates provisioning and the polling request that completes it. MCP clients may also not maintain stable IPs across those requests. A stable per-user identifier extracted from the `Authorization` header (e.g. `sub`) is the right key.
+1 -1
View File
@@ -148,7 +148,7 @@ For multi-user deployment issues — provisioning loops, app-password storage, O
```bash
# To Single-User BasicAuth: set NEXTCLOUD_USERNAME and NEXTCLOUD_PASSWORD
# To Multi-User BasicAuth pass-through: ENABLE_MULTI_USER_BASIC_AUTH=true (no creds)
# To Login Flow v2: ENABLE_LOGIN_FLOW=true (no creds)
# To Login Flow v2: MCP_DEPLOYMENT_MODE=login_flow (no creds; also the default fallback)
```
Restart the server after changing modes. The active mode is logged at startup; you can also set `MCP_DEPLOYMENT_MODE` explicitly to fail fast if the env vars don't match.