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>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e2955e8246
commit
ec9b9b2a75
@@ -5,6 +5,7 @@ between server/ and auth/ layers.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
@@ -232,7 +233,13 @@ async def verify_id_token(
|
||||
f"Unexpected error verifying ID token: {e}"
|
||||
) from e
|
||||
|
||||
if expected_nonce is not None and payload.get("nonce") != expected_nonce:
|
||||
# Constant-time comparison mirrors the PKCE verifier check
|
||||
# (oauth_routes.py:1029) — short-circuit `!=` is avoided in
|
||||
# security-sensitive equality even when the secret is server-generated
|
||||
# (round-6 review).
|
||||
if expected_nonce is not None and not secrets.compare_digest(
|
||||
payload.get("nonce", "") or "", expected_nonce
|
||||
):
|
||||
raise IdTokenVerificationError("ID token nonce does not match request nonce")
|
||||
|
||||
return payload
|
||||
|
||||
Reference in New Issue
Block a user