make the notify identity rule testable, and fix what the test found

Extracting resolveNotifyUser into its own module immediately caught a hole
in the fix from the previous commit: a header that was present but
unparseable fell through to the body, so a browser could send junk in the
header, name any user in the body and win.

PRESENCE of X-Officer-User is the signal, not its validity — a malformed
header means a proxied request went wrong, and falling through hands the
decision back to the caller we just declined to trust.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 10:00:02 +00:00
co-authored by Claude Opus 5
parent 33d96f19ee
commit b077671f19
4 changed files with 84 additions and 13 deletions
@@ -0,0 +1,40 @@
import { describe, expect, test } from 'bun:test';
import { resolveNotifyUser } from './resolve-user';
describe('resolveNotifyUser', () => {
test('a loopback producer with no header is trusted with the body', () => {
expect(resolveNotifyUser({ header: null, bodyUserId: 13 })).toBe(13);
});
test('a proxied request uses the header the platform injected', () => {
expect(resolveNotifyUser({ header: '1', bodyUserId: undefined })).toBe(1);
});
// The regression this module exists for: `body.userId ?? header` let any account holding the
// `notify` capability push to any other account's devices by naming them in a JSON body.
test('the header beats a conflicting body userId', () => {
expect(resolveNotifyUser({ header: '13', bodyUserId: 1 })).toBe(13);
expect(resolveNotifyUser({ header: '1', bodyUserId: 13 })).toBe(1);
});
test('neither claimant supplying an id is a 400, not a default', () => {
expect(resolveNotifyUser({ header: null, bodyUserId: undefined })).toBeNull();
expect(resolveNotifyUser({ header: '', bodyUserId: null })).toBeNull();
});
test('a malformed header does not silently fall through to the body', () => {
// Otherwise a browser could send a junk header alongside a chosen body id and win.
expect(resolveNotifyUser({ header: 'abc', bodyUserId: 13 })).toBeNull();
expect(resolveNotifyUser({ header: '0', bodyUserId: 13 })).toBeNull();
expect(resolveNotifyUser({ header: '-1', bodyUserId: 13 })).toBeNull();
expect(resolveNotifyUser({ header: '1.5', bodyUserId: 13 })).toBeNull();
});
test('a body userId that is not a positive integer is refused', () => {
expect(resolveNotifyUser({ header: null, bodyUserId: '13' })).toBeNull();
expect(resolveNotifyUser({ header: null, bodyUserId: 0 })).toBeNull();
expect(resolveNotifyUser({ header: null, bodyUserId: -3 })).toBeNull();
expect(resolveNotifyUser({ header: null, bodyUserId: 2.5 })).toBeNull();
expect(resolveNotifyUser({ header: null, bodyUserId: { id: 1 } })).toBeNull();
});
});