Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/cors-allow-if-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@objectstack/plugin-hono-server': patch
'@objectstack/hono': patch
---

CORS default `allowHeaders` now includes `If-Match`. The REST record update
accepts the OCC token as an `If-Match` header (objectui's record-level inline
edit sends it on every save), but the preflight allow-list omitted it — so on
any split-origin deployment (console dev server against a backend on another
origin) the browser failed the preflight and every inline-edit save died with
"Failed to fetch". Found live while dogfooding objectui#2572; same
split-origin failure class as the #2548 Bearer fixes. Explicit user-supplied
`allowHeaders` still win unchanged.
4 changes: 3 additions & 1 deletion packages/adapters/hono/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
app.use('*', cors({
origin: origin as any,
allowMethods: corsOpts.methods || ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
allowHeaders: corsOpts.allowHeaders || ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Tenant-ID', 'X-Environment-Id'],
// Keep in sync with plugin-hono-server's defaultAllowHeaders — `If-Match`
// carries the OCC token on cross-origin record PATCHes (objectui#2572).
allowHeaders: corsOpts.allowHeaders || ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Tenant-ID', 'X-Environment-Id', 'If-Match'],
exposeHeaders,
credentials,
maxAge,
Expand Down
12 changes: 12 additions & 0 deletions packages/plugins/plugin-hono-server/src/hono-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ describe('HonoServerPlugin', () => {
expect(corsConfigCapture.last.allowHeaders).toContain('Authorization');
});

it('should allow If-Match by default (OCC token on cross-origin record PATCHes)', async () => {
corsConfigCapture.last = undefined;

const plugin = new HonoServerPlugin();
await plugin.init(context as PluginContext);

// objectui#2572 dogfood find: the record-level inline edit sends the
// OCC token as an `If-Match` header; a preflight that doesn't allow
// it makes every split-origin save fail with "Failed to fetch".
expect(corsConfigCapture.last.allowHeaders).toContain('If-Match');
});

it('should merge user-supplied exposeHeaders with set-auth-token default', async () => {
corsConfigCapture.last = undefined;

Expand Down
7 changes: 6 additions & 1 deletion packages/plugins/plugin-hono-server/src/hono-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,12 @@ export class HonoServerPlugin implements Plugin {
// the better-auth `bearer()` plugin can deliver rotated
// session tokens to cross-origin clients (see plugin-auth).
// User-supplied exposeHeaders are merged with this default.
const defaultAllowHeaders = ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Tenant-ID', 'X-Environment-Id'];
// `If-Match` carries the OCC token on record PATCHes (objectui's
// record-level inline edit, REST `update` with `ifMatch`) — without
// it in the preflight allow-list, every cross-origin save fails in
// the browser with "Failed to fetch" (objectui#2572 dogfood find;
// same split-origin class as the #2548 Bearer fixes).
const defaultAllowHeaders = ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Tenant-ID', 'X-Environment-Id', 'If-Match'];
const defaultExposeHeaders = ['set-auth-token'];
const allowHeaders = corsOpts.allowHeaders ?? defaultAllowHeaders;
const exposeHeaders = Array.from(new Set([
Expand Down