diff --git a/package.json b/package.json index ed7ce82..3ff2137 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Backend system for HackRU.", "main": "serverless.ts", "scripts": { - "dev": "dotenvx run -f .env -- serverless offline", + "dev": "dotenvx run -f .env.local -- serverless offline", "deploy-dev": "dotenvx run -f .env -- serverless deploy", "deploy-prod": "dotenvx run -f .env.production -- serverless deploy", "test": "jest --watch", diff --git a/src/functions/update/handler.ts b/src/functions/update/handler.ts index c0ef4de..0d2d2d6 100644 --- a/src/functions/update/handler.ts +++ b/src/functions/update/handler.ts @@ -45,6 +45,24 @@ const update: ValidatedEventAPIGatewayProxyEvent = async (event) if (!updatedUser) return { statusCode: 404, body: JSON.stringify({ statusCode: 404, message: 'User to be updated not found.' }) }; + //only director or organizer should be able to update registration status for specified status descriptions + const registrationStatus = event.body.updates?.$set?.registration_status as string | undefined; + + if ( + registrationStatus !== undefined && + ['rejected', 'confirmation', 'waitlist', 'confirmed', 'checked_in'].includes(registrationStatus) + ) { + if (!ensureRoles(authUser.role, ['director', 'organizer'])) { + return { + statusCode: 403, + body: JSON.stringify({ + statusCode: 403, + message: `Forbidden. Auth user must be organizer/director to update registration status to ${registrationStatus}.`, + }), + }; + } + } + // validate updates const validationResult = validateUpdates(event.body.updates, updatedUser.registration_status, updatedUser); if (typeof validationResult === 'string') diff --git a/tests/update.test.ts b/tests/update.test.ts index 3beeac0..9acbc7d 100644 --- a/tests/update.test.ts +++ b/tests/update.test.ts @@ -322,7 +322,7 @@ describe('/update endpoint', () => { }); //case 11 - it('Cannot smuggle a locked field alongside a valid registration_status change', async () => { + it('Cannot make invalid registration_status change as hacker', async () => { findOneMock.mockReturnValue({ email: 'test@test.org', password: 'test', @@ -335,6 +335,43 @@ describe('/update endpoint', () => { organizer: false, director: false, }, + registration_status: 'confirmed', + }); + // registered -> confirmation is a valid transition, so the registration_status branch used to + // return early and the locked-field check never ran. + const hackerSetCheckIn = { + user_email: 'test@test.org', + auth_email: 'testAuth@test.org', + auth_token: 'sampleAuthToken', + updates: { + $set: { + registration_status: 'checked_in', + // eslint-disable-next-line @typescript-eslint/naming-convention + }, + }, + }; + const mockEvent = createEvent(hackerSetCheckIn, '/update', 'POST'); + const res = await main(mockEvent, mockContext, mockCallback); + expect(res.statusCode).toBe(403); + expect(JSON.parse(res.body).message).toBe( + 'Forbidden. Auth user must be organizer/director to update registration status to checked_in.' + ); + }); + + //case 12 + it('Cannot smuggle a locked field alongside a valid registration_status change', async () => { + findOneMock.mockReturnValue({ + email: 'test@test.org', + password: 'test', + role: { + hacker: false, + volunteer: false, + judge: false, + sponsor: false, + mentor: false, + organizer: true, + director: false, + }, registration_status: 'registered', }); // registered -> confirmation is a valid transition, so the registration_status branch used to