caldav/carddav: officer-caldav sidecar and the /dav door

first two steps of docs/nextcloud-replacement.md — the half that has to work on
a phone, because that is the half that cannot be faked.

radicale is supervised by the sidecar rather than reimplemented. nextcloud does
not implement caldav either; it vendors sabre/dav. icalendar and vcard are a
weekend, but sync-collection, rrule expansion, vtimezone and ctag/etag are not,
and when they are subtly wrong a phone does not error — it silently stops
syncing, or silently duplicates every event.

two doors, because a browser should not speak dav:

  /dav/*        top-level, http basic against a scoped app password, every
                verb and every dav header forwarded verbatim. this is what
                davx5 and ios talk to. same reasoning as /api/vault being
                mounted outside protectedRouter.
  /api/caldav/* the ordinary sidecar proxy, for officer's own ui. json.

the shared proxy factory could not carry the dav door: it forwards three
headers and dav dies without Depth, and it derives the user from a jwt a phone
cannot hold. so it is a separate file, per that factory's own instruction never
to grow per-app logic.

new `dav_app_passwords` — a phone cannot do jwt, and the alternative is the
account password living in a phone's account manager. argon2, shown once,
revocable per device, and accepted ONLY by /dav.

.well-known/caldav and carddav redirect to the dav root. they are most of what
makes adding an account feel transparent, and they need naming explicitly in
server.tsx or the SPA `/*` fallback answers the phone with html.

verified end to end against the running stack: 401 + WWW-Authenticate
unauthenticated; 207 with calendar-access and addressbook advertised; MKCALENDAR,
PUT and GET of a real VEVENT; calendar-query and sync-collection REPORTs; MKCOL,
PUT and GET of a real vCard. X-Script-Name is set because radicale otherwise
generates hrefs at / and the client follows them into the SPA.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 03:16:17 +00:00
co-authored by Claude Opus 5
parent 86d979046b
commit ccd104a28b
14 changed files with 955 additions and 0 deletions
+3
View File
@@ -75,6 +75,9 @@ export type SidecarEvent =
| { type: 'invoiceshelf:server'; port: number }
// Photos (Immich) — the sidecar reports where its HTTP server is listening (random port) on connect
| { type: 'photos:server'; port: number }
// CalDAV/CardDAV — the sidecar reports where its HTTP server is listening (random port) on connect.
// One port serves both doors: /dav (forwarded to Radicale) and /_officer (JSON for Officer's UI).
| { type: 'caldav:server'; port: number }
// Wallet — the sidecar reports where its HTTP server is listening (random port) on connect
| { type: 'wallet:server'; port: number }
// PTY — the sidecar reports where its terminal HTTP/WS server is listening (random port) on connect