From bfb12c2dde405aef897d6a3e5e3c068d6b16d03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 27 Aug 2026 08:48:53 +0000 Subject: [PATCH] =?UTF-8?q?drop=20the=20jsonBody=20workaround=20=E2=80=94?= =?UTF-8?q?=20the=20platform=20parses=20these=20bodies=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this plugin read ctx.req.json() directly because registerProxiedPrefix claimed the whole of /api/caldav, so bodyParser skipped the plugin's OWN routes and every field read as missing. the platform's proxy takes a proxiedSubPath now, so this claims only /api/caldav/_officer — what the sidecar actually answers — and /passwords and /provision/ios get their bodies the ordinary way. verified end to end: POST /api/caldav/passwords with a label came back with the label, and _officer/collections still reaches the sidecar. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- api/app-passwords.ts | 34 ++-------------------------------- api/sidecar-server.ts | 7 ++++++- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/api/app-passwords.ts b/api/app-passwords.ts index 0e7d82b..c6086c5 100644 --- a/api/app-passwords.ts +++ b/api/app-passwords.ts @@ -1,4 +1,3 @@ -import type { Context } from 'hono'; import { createRouter } from '@@/create-router'; import { listDavAppPasswords, @@ -19,35 +18,6 @@ import { stashIosProfile } from './ios-profile'; export const appPasswordsRouter = createRouter(); -/** - * Read this request's JSON body. - * - * `ctx.get('body')` is the platform's parsed body, and it is EMPTY here — deliberately, and for a reason - * that only became true when caldav became a plugin. - * - * `createSidecarProxy` registers `/api/caldav` with `registerProxiedPrefix`, which tells `bodyParser` to - * leave those request streams untouched. That is right for forwarded traffic and load-bearing for - * multipart uploads: a body parsed here is re-serialised with a new boundary while the old content-type - * header is still forwarded, and every upload fails. But the prefix covers this plugin's OWN routes too, - * so they get `{}` and every field reads as missing. - * - * Before the extraction these routes lived under `/api/dav`, a second top-level prefix that was not - * proxied, so their bodies parsed. One prefix per plugin is the rule that ended that arrangement. - * - * Read here rather than fixed in the platform because the platform's rule is correct — the narrower fix - * is for the proxy to register only the sub-path it actually serves (`_officer/*`), which is a change to - * shared machinery every sidecar depends on and not one to make while a phone is waiting. - */ -async function jsonBody(ctx: Context): Promise { - try { - return (await ctx.req.json()) as T; - } catch { - // No body, or not JSON. Every caller below treats a missing field as a bad request anyway. - return {} as T; - } -} - - appPasswordsRouter.get('/passwords', async (ctx) => { const user = ctx.get('user'); return ctx.json({ passwords: await listDavAppPasswords(user.id) }); @@ -55,7 +25,7 @@ appPasswordsRouter.get('/passwords', async (ctx) => { appPasswordsRouter.post('/passwords', async (ctx) => { const user = ctx.get('user'); - const body = await jsonBody<{ label?: string }>(ctx); + const body = (ctx.get('body') ?? {}) as { label?: string }; const label = body?.label?.trim(); if (!label) throw errors.BAD_REQUEST('label is required'); @@ -72,7 +42,7 @@ appPasswordsRouter.post('/passwords', async (ctx) => { // exists because the alternative is the owner typing a 25-character secret into a phone keyboard twice. appPasswordsRouter.post('/provision/ios', async (ctx) => { const user = ctx.get('user'); - const body = await jsonBody<{ deviceLabel?: string; only?: 'caldav' | 'carddav' }>(ctx); + const body = (ctx.get('body') ?? {}) as { deviceLabel?: string; only?: 'caldav' | 'carddav' }; const deviceLabel = body?.deviceLabel?.trim(); if (!deviceLabel) throw errors.BAD_REQUEST('deviceLabel is required'); diff --git a/api/sidecar-server.ts b/api/sidecar-server.ts index 9aa331d..6cbda7a 100644 --- a/api/sidecar-server.ts +++ b/api/sidecar-server.ts @@ -11,7 +11,12 @@ import { createSidecarProxy } from '@@/sidecar/create-proxy'; // // Hence one registration, two forwarders. The factory's own comment says it must never grow per-app // logic, so the DAV-specific half is a separate file rather than a branch inside it. -const proxy = createSidecarProxy({ name: 'caldav', prefix: '/api/caldav' }); +// `proxiedSubPath`: this sidecar answers `_officer/*` and nothing else, so the proxy claims only that. +// +// Claiming the whole prefix is what stopped this plugin's OWN routes from seeing a request body — +// `registerProxiedPrefix` tells bodyParser to leave the prefix alone, which is right for forwarded +// traffic and wrong for `/passwords` and `/provision/ios`, which are served here. +const proxy = createSidecarProxy({ name: 'caldav', prefix: '/api/caldav', proxiedSubPath: '/_officer' }); export const caldavRouter = proxy.router; export const getCaldavUrl = proxy.getHttpUrl;