diff --git a/functions/communityAdministration.js b/functions/communityAdministration.js index 671e785..9d5e8fe 100644 --- a/functions/communityAdministration.js +++ b/functions/communityAdministration.js @@ -30,7 +30,7 @@ function parseAdminRequest(input) { const extra = ['admin_event_create', 'admin_event_edit'].includes(value.action) ? ['event'] : value.action === 'admin_event_capacity' ? ['capacity'] : []; parseStrictObject(value, { requiredKeys: ['action', 'accountUid', 'requestId', 'expectedRevision', 'reason', target, ...extra], optionalKeys: [] }); check(typeof value.requestId === 'string' && UUID.test(value.requestId) && integer(value.expectedRevision) && identifier(value[target]) && value.reason === 'synthetic_staging_review', 'invalid-argument'); - if (value.event) { + if (extra.includes('event')) { parseStrictObject(value.event, { requiredKeys: ['title', 'location', 'startAtMs', 'opensAtMs', 'closesAtMs', 'capacity'], optionalKeys: [] }); check(text(value.event.title) && text(value.event.location) && [value.event.startAtMs, value.event.opensAtMs, value.event.closesAtMs].every((time) => integer(time, 8_640_000_000_000_000)) diff --git a/functions/communityAdministration.test.js b/functions/communityAdministration.test.js index 10adb45..155d94c 100644 --- a/functions/communityAdministration.test.js +++ b/functions/communityAdministration.test.js @@ -35,6 +35,11 @@ test.each([ { ...request('admin_event_create', { eventId: 'event', event: draft }), event: { ...draft, title: '\u202eInjected' } }, { ...request('admin_event_capacity', { eventId: 'event', capacity: 1 }), capacity: 1.5 }, ])('strict request rejects forged/financial/coerced/unsafe fields %#', (value) => { expect(() => parseAdminRequest(value)).toThrow(); }); +test.each([null, false, 0, 'event', []])('create/edit requires a typed event object %#', (event) => { + for (const action of ['admin_event_create', 'admin_event_edit']) { + expect(() => parseAdminRequest({ action, accountUid: 'officer', requestId: randomUUID(), expectedRevision: 0, reason: 'synthetic_staging_review', eventId: 'event', event })).toThrow(); + } +}); test('synthetic approval is atomic/repeat-safe, supplies no payment or Auth claims', async () => { const command = approve(); await execute(command); await execute(command); expect((await members.status('runner')).membership).toMatchObject({ revision: 2, applicationStatus: 'approved', entitlement: 'current_member', accessThroughMs: term.endsAtMs });