diff --git a/src/AgController/index.ts b/src/AgController/index.ts index f2f73e3..d68a0f4 100644 --- a/src/AgController/index.ts +++ b/src/AgController/index.ts @@ -192,9 +192,7 @@ class AgController { if (!userRes.ok) throw new Error(`${userRes.status} ${userRes.statusText}`); user = await userRes.json(); goodRoles = JSON.parse(process.env.userRoles || /* istanbul ignore next */'{}').roles; - if (!goodRoles || !user || !user.userType || goodRoles.indexOf(user.userType) === -1) { - throw new Error('Not allowed to create new tour'); - } + utils.assertCanCreateTour(user, goodRoles); if (receiver.value.tour.datetime && receiver.value.tour.city && receiver.value.tour.usState && receiver.value.tour.venue) { await utils.handleTour( diff --git a/src/AgController/utils.ts b/src/AgController/utils.ts index e0f8698..7ca3b4a 100644 --- a/src/AgController/utils.ts +++ b/src/AgController/utils.ts @@ -49,4 +49,20 @@ async function removeTour(receiver:any, client:any, tourController:any, server:a } } -export default { resetData, removeTour, handleTour }; +function assertCanCreateTour( + user: { userType?: string; privileges?: string[] } | null | undefined, + goodRoles: string[] | undefined, +): void { + if (!user) throw new Error('Not allowed to create new tour'); + if (Array.isArray(user.privileges) && user.privileges.length > 0) { + if (!user.privileges.includes('tour:create')) throw new Error('missing capability tour:create'); + return; + } + if (!goodRoles || !user.userType || goodRoles.indexOf(user.userType) === -1) { + throw new Error('Not allowed to create new tour'); + } +} + +export default { + resetData, removeTour, handleTour, assertCanCreateTour, +}; diff --git a/test/AgController/index.spec.ts b/test/AgController/index.spec.ts index ffac4b4..592d181 100644 --- a/test/AgController/index.spec.ts +++ b/test/AgController/index.spec.ts @@ -302,6 +302,78 @@ describe('AgControler', () => { expect(clientStub.socket.transmit).toHaveBeenCalledWith('socketError', { newTour: 'Not allowed to create new tour' }); }); + it('allows newTour when user has tour:create privilege (capability path)', async () => { + const agController = new AgController(aStub); + agController.clients = ['123']; + agController.tourController.createDocs = vi.fn(() => Promise.resolve([])); + const cStub:any = { + socket: { + id: '123', + listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }), + transmit: () => { }, + receiver: () => ({ + createConsumer: () => ({ + next: () => Promise.resolve({ + value: { + token: 'token', + tour: { + venue: 'venue', datetime: new Date(), city: 'city', usState: 'state', + }, + }, + done: true, + }), + }), + }), + }, + }; + const setIntervalMock:any = vi.fn((cb:any) => cb()); + global.setInterval = setIntervalMock; + agController.jwt.verify = vi.fn(() => '123') as any; + vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ userType: 'unrecognized-role', privileges: ['tour:create'] }), + })); + agController.newTour(cStub); + await delay(1000); + expect(agController.tourController.createDocs).toHaveBeenCalled(); + }); + + it('rejects newTour with missing capability error when privileges lack tour:create', async () => { + const agController = new AgController(aStub); + agController.clients = ['123']; + agController.tourController.createDocs = vi.fn(() => Promise.resolve([])); + const cStub:any = { + socket: { + id: '123', + listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }), + transmit: vi.fn(), + receiver: () => ({ + createConsumer: () => ({ + next: () => Promise.resolve({ + value: { + token: 'token', + tour: { + venue: 'venue', datetime: new Date(), city: 'city', usState: 'state', + }, + }, + done: true, + }), + }), + }), + }, + }; + const setIntervalMock:any = vi.fn((cb:any) => cb()); + global.setInterval = setIntervalMock; + agController.jwt.verify = vi.fn(() => '123') as any; + vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ userType: 'unrecognized-role', privileges: ['song:read'] }), + })); + agController.newTour(cStub); + await delay(1000); + expect(cStub.socket.transmit).toHaveBeenCalledWith('socketError', { newTour: 'missing capability tour:create' }); + }); + it('return the invalid request socketError when processes the newTour message from client', async () => { const agController = new AgController(aStub); agController.clients = ['123'];