From 75b3484636afb0331d0d2edab26fb16db3584864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 4 Aug 2026 11:19:50 +0000 Subject: [PATCH] log every dav request, and every rejected credential MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a DAV client tells you almost nothing when it fails — iOS reports every setup failure as "Cannot connect using SSL" regardless of cause — so the only way to know whether a phone ever asked for an address book is to record that it did. one line per proxied request with the client's own user-agent, and a warning when a credential is present but wrong. a missing credential is the normal opening move and stays unlogged; a wrong one is indistinguishable from it at the client end, where both just say "password incorrect". no body is ever logged. Co-Authored-By: Claude Opus 5 --- src/servers/api/dav/sync-router.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/servers/api/dav/sync-router.ts b/src/servers/api/dav/sync-router.ts index 3f7f6b8f..92c01561 100644 --- a/src/servers/api/dav/sync-router.ts +++ b/src/servers/api/dav/sync-router.ts @@ -79,11 +79,20 @@ davSyncRouter.all('/*', async (ctx) => { const creds = parseBasic(ctx.req.header('authorization')); if (!creds) return unauthorized(); + // A credential-less request is the normal opening move — the client is asking to be challenged — so it + // is not worth a line. A credential that is present and wrong is the interesting case, and it is + // indistinguishable from the normal one at the client end: both just say "password incorrect". const user = await getUserByEmail(creds.username); - if (!user) return unauthorized(); + if (!user) { + console.warn(`[dav] auth failed: no such user ${creds.username}`); + return unauthorized(); + } const userId = await verifyDavAppPassword(user.id, creds.password); - if (!userId) return unauthorized(); + if (!userId) { + console.warn(`[dav] auth failed: bad app password for ${creds.username}`); + return unauthorized(); + } const base = getCaldavUrl(); if (!base) return new Response('caldav sidecar not available', { status: 503 }); @@ -121,5 +130,14 @@ davSyncRouter.all('/*', async (ctx) => { const value = upstream.headers.get(name); if (value) out.set(name, value); } + + // One line per request, with the client's own name for itself. A DAV client tells you almost nothing + // when it fails — iOS in particular reports every setup failure as "Cannot connect using SSL" — so the + // only way to know whether a phone ever asked for an address book is to record that it did. Cheap: + // a sync is a handful of requests, and a body is never logged. + console.log( + `[dav] ${method} ${url.pathname} -> ${upstream.status} (user=${userId}, ua=${(ctx.req.header('user-agent') ?? '-').slice(0, 60)})`, + ); + return new Response(upstream.body, { status: upstream.status, headers: out }); });