Commit Graph
5 Commits
Author SHA1 Message Date
Chris CoutinhoandClaude Opus 4.7 ec9b9b2a75 fix(auth): address PR #758 round-6 medium/low review
Five findings from the latest review on #758 (2 medium, 3 nit):

Medium:
- browser_oauth_routes.oauth_login_callback + oauth_routes.oauth_callback_nextcloud:
  fail closed with 400 when the oauth_session row is unknown/expired. Previously
  both callbacks fell through with code_verifier="" and expected_nonce=None,
  silently bypassing the PKCE + nonce protections introduced in earlier rounds.
  Symmetric unit tests pin both contracts.
- token_utils.verify_id_token: use secrets.compare_digest for the nonce check
  instead of short-circuit !=. Mirrors the sibling PKCE verifier comparison;
  closes the last secret-equality timing-side-channel surface in the auth path.

Nit:
- Tighten the comment at all 4 mcp_authorization_code/code_verifier store +
  retrieve sites so a future refactor sees the field reuse immediately
  (renaming the column requires a schema migration).
- _should_use_secure_cookies: explicit string normalisation instead of
  bool(settings.cookie_secure). Dynaconf normally coerces but tests / direct
  settings.set calls can leave the raw string in place — bool("false") is True.
  New parametrized unit tests cover the coercion matrix + http/https fallback.
- oauth_routes.py:591 f-string log converted to lazy %s formatting (folded into
  the Flow 2 callback rewrite).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:03:49 +02:00
Chris CoutinhoandClaude Opus 4.7 3a4fa8adc8 fix(auth): address PR #758 round-3 final review
Seven findings from the latest review on #758, plus a regression test
catching the substance of the cache-stampede fix:

- verify_id_token: widen id_token annotation to str | None to match
  callers passing nc_token_response.get("id_token")
- extract_user_id_from_token: use JSON-RPC reserved error code -32001
  instead of -1
- _get_cached: per-URL anyio.Lock dict + meta-lock coalesces concurrent
  cache misses into a single IdP fetch (mirrors token_broker.py idiom)
- delete_browser_session: collapse SELECT+DELETE into atomic
  DELETE ... RETURNING user_id (SQLite >= 3.35)
- new test_origin_normalise.py: parametrized port/scheme/host equivalence
  cases for the CSRF Origin guard
- browser_oauth_routes: correct misleading "PR #758 finding 5" cross-
  references (finding 5 was Fernet-key hardening, not CSRF)
- ASProxySession.nonce: make required, drop spurious "legacy session"
  default; reword the in-flight `or None` comment to reflect that
  ASProxySession is purely in-memory
- new test_get_cached_coalesces_concurrent_misses: pins the
  cache-stampede protection — fires 10 concurrent _get_cached calls and
  asserts exactly one HTTP fetch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:14:07 +02:00
Chris CoutinhoandClaude Opus 4.7 c33d52ea91 fix(auth): address PR #758 round-2 review
- oauth_login_callback's integrated-mode token-exchange branch now reuses
  the shared discovery cache via get_oidc_discovery (round-2 finding 1).
- AS proxy flow now generates an OIDC nonce in oauth_authorize, stores it
  on ASProxySession, forwards it to the IdP, and passes it as
  expected_nonce to verify_id_token in _oauth_callback_as_proxy
  (round-2 finding 2).
- Consolidate the two parallel discovery caches: oauth_routes' local
  _discovery_cache and _get_cached_discovery are removed; all callers
  now go through token_utils.get_oidc_discovery, which acquires the
  follow_redirects=True knob it needs for Nextcloud installs without
  pretty URLs (round-2 finding 3).
- Demote per-user INFO logs in oauth_tools.py (check_logged_in,
  get_provisioning_status) to DEBUG; the elicitation auth URL is no
  longer logged because it contains a sensitive state token
  (round-2 finding 4).

Also pin nonce binding behaviour with a new unit test that asserts
_oauth_callback_as_proxy forwards session.nonce to verify_id_token, and
update test mocks to track the cache consolidation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:56:08 +02:00
Chris CoutinhoandClaude Opus 4.7 4c84d82984 fix(auth): address PR #758 auto-review (id-token verify, nonce, CI key)
Blocking:
- AS proxy callback now calls verify_id_token before caching the proxy
  code so a tampered IdP response can't smuggle identity claims.

Important:
- Browser OAuth flow generates and verifies an OIDC nonce; new alembic
  migration 006 adds the nonce column to oauth_sessions.
- _origin_matches_self logs a warning when CSRF check is bypassed.
- oauth_tools.py uses get_shared_storage instead of fresh handles.

Nits:
- New token_utils.get_oidc_discovery shares the 5-minute cache with
  verify_id_token; oauth_login (integrated) and _revoke_refresh_token_at_idp
  now use it instead of issuing fresh discovery fetches.
- Drop typing.Optional from oauth_tools.py in favour of X | None.

CI:
- test.yml generates an ephemeral Fernet TOKEN_ENCRYPTION_KEY per run
  with openssl, removing the dependency on a missing repo secret.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:48:25 +02:00
Chris CoutinhoandClaude Opus 4.7 2ef4bfc4af fix(auth): fail closed on missing sub claim, delete Flow 2 callback session
Addresses the two remaining 🟡 findings from the PR #758 follow-up review:

  1. extract_user_id_from_token previously fell back to "default_user" when
     the verified access token had no sub claim. In a multi-tenant deployment
     a malformed IdP token could have bucketed every request under a single
     sentinel user, risking cross-tenant data exposure. The function now
     raises McpError on that branch; the BasicAuth no-token sentinel path is
     preserved.

  2. oauth_callback_nextcloud (Flow 2) read the PKCE code_verifier from
     oauth_sessions but never deleted the row, leaving the verifier valid for
     the full 10-minute TTL. The row is now deleted eagerly inside the same
     branch, mirroring oauth_login_callback in browser_oauth_routes.

Also wires TOKEN_ENCRYPTION_KEY through the docker-compose step in the CI
test workflow so the integration matrix can boot — every job had been
failing fast on the ${TOKEN_ENCRYPTION_KEY:?...} interpolation guard added
in PR #758 finding 5.

Tests pin both fixes (test_token_utils_user_id.py,
test_oauth_callback_session_cleanup.py).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:46:36 +02:00