From 4ca3b38667a546cfcd8c0b00a2054cc9826c9b1c Mon Sep 17 00:00:00 2001 From: Shatti2 Date: Tue, 2 Jun 2026 23:23:28 -0500 Subject: [PATCH] fix(calendar): negotiate Digest auth in CalDAV test endpoint (#1767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /api/calendar/test issues a single PROPFIND with raw httpx Basic auth. CalDAV servers configured for Digest (Baïkal default, SabreDAV-based servers, Radicale with htdigest) reject Basic with 401, so the UI "Test connection" button surfaces "Auth failed — check username/password" even when the URL and credentials are correct. src/caldav_sync.py (the real sync path) uses caldav.DAVClient, which negotiates the scheme via niquests, so production sync already works against these servers. The test endpoint just doesn't match. Bring it to parity: keep the cheap Basic first attempt, and on a 401-with-Digest-challenge retry once with httpx.DigestAuth before deciding it's an auth failure. Repro: configure CalDAV against a stock Baïkal install — test button returns 401, sync succeeds. Co-authored-by: Shatti2 --- routes/calendar_routes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/routes/calendar_routes.py b/routes/calendar_routes.py index 7f6d846..4509c3f 100644 --- a/routes/calendar_routes.py +++ b/routes/calendar_routes.py @@ -629,6 +629,18 @@ def setup_calendar_routes() -> APIRouter: headers={"Depth": "0", "Content-Type": "application/xml"}, content=propfind_body, ) + # If the server demands Digest (Baïkal default, SabreDAV-based + # servers, Radicale with htdigest), the Basic attempt above + # 401s. Retry once with httpx.DigestAuth so this test matches + # what the real sync does via caldav.DAVClient in + # src/caldav_sync.py (which negotiates the scheme). + if r.status_code == 401 and "digest" in r.headers.get("www-authenticate", "").lower(): + r = await cx.request( + "PROPFIND", url, + auth=httpx.DigestAuth(user, pw), + headers={"Depth": "0", "Content-Type": "application/xml"}, + content=propfind_body, + ) # 207 = Multi-Status — standard CalDAV success. 200 also # acceptable. Anything else (401/403/404/5xx) means trouble. if r.status_code in (200, 207):