From 623b45d2c9fc6ec3e9d8290f18c715f1fd34e8c4 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 18:08:12 +1200 Subject: [PATCH 1/4] Adds test for info exposure via patch user endpoint --- .../src/controllers/UsersController.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/application/backend/src/controllers/UsersController.test.ts b/application/backend/src/controllers/UsersController.test.ts index 60822eb5..d50adfa1 100644 --- a/application/backend/src/controllers/UsersController.test.ts +++ b/application/backend/src/controllers/UsersController.test.ts @@ -202,6 +202,34 @@ describe('UsersController', () => { expect(updatedUser?.firstName).toBe(newFirstName) }) + it('should not expose information when updating a user', async () => { + const userId: number = PARTICIPANT_UNANSWERED_ID + + // Get existing user details + const existingUser = await prisma.user.findFirst({ where: { id: userId } }) + + const testUser: RegisterRequest = { + firstName: 'Test', + lastName: 'User', + email: 'test2@example.com', + password: 'Password123', + role: Role.Participant, + } + + expect(existingUser?.email).toBe(testUser.email) + expect(existingUser?.firstName).toBe(testUser.firstName) + expect(existingUser?.lastName).toBe(testUser.lastName) + expect(existingUser?.role).toBe(testUser.role) + + const response = await request(app) + .patch(`/users/${userId}`) + .set({ Authorization: `Bearer ${orgAdminToken}` }) + .send({ role: 'ThisIsDefinitelyNotARole' }) + + expect(response.status).toBe(422) + expect(response.body.details).not.toMatch(/should be one of the following; ['OperatorAdmin']/) + }) + it('should return an error for a non-existent user', async () => { const userId: number = 9999 const newFirstName: string = 'Updated' From e21b175fe0632142461448aabe03d8b444793563 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 18:08:12 +1200 Subject: [PATCH 2/4] Adds test for info exposure via patch user endpoint --- .../src/controllers/UsersController.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/application/backend/src/controllers/UsersController.test.ts b/application/backend/src/controllers/UsersController.test.ts index 60822eb5..f69c368b 100644 --- a/application/backend/src/controllers/UsersController.test.ts +++ b/application/backend/src/controllers/UsersController.test.ts @@ -202,6 +202,36 @@ describe('UsersController', () => { expect(updatedUser?.firstName).toBe(newFirstName) }) + it('should not expose information when updating a user', async () => { + const userId: number = PARTICIPANT_UNANSWERED_ID + + // Get existing user details + const existingUser = await prisma.user.findFirst({ where: { id: userId } }) + + const testUser: RegisterRequest = { + firstName: 'Test', + lastName: 'User', + email: 'test2@example.com', + password: 'Password123', + role: Role.Participant, + } + + expect(existingUser?.email).toBe(testUser.email) + expect(existingUser?.firstName).toBe(testUser.firstName) + expect(existingUser?.lastName).toBe(testUser.lastName) + expect(existingUser?.role).toBe(testUser.role) + + const response = await request(app) + .patch(`/users/${userId}`) + .set({ Authorization: `Bearer ${orgAdminToken}` }) + .send({ role: 'ThisIsDefinitelyNotARole' }) + + expect(response.status).toBe(422) + expect(response.body.details['bodyRequest.role'].message).not.toMatch( + /should be one of the following; \['OperatorAdmin'\]/, + ) + }) + it('should return an error for a non-existent user', async () => { const userId: number = 9999 const newFirstName: string = 'Updated' From 391964fd45e186c36d18e39c7c8bcd7faf72f74c Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 18:36:07 +1200 Subject: [PATCH 3/4] Edit ValidationError middlewares to exclude verbose messages --- application/backend/src/middlewares/ErrorHandler.ts | 8 +++++++- application/backend/swagger.json | 9 +-------- application/common/types/api/errors.ts | 1 - 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/application/backend/src/middlewares/ErrorHandler.ts b/application/backend/src/middlewares/ErrorHandler.ts index 56499061..637fb07e 100644 --- a/application/backend/src/middlewares/ErrorHandler.ts +++ b/application/backend/src/middlewares/ErrorHandler.ts @@ -110,9 +110,15 @@ export function ErrorHandler( // Validation Errors if (err instanceof ValidateError) { + const sanitisedDetails: Record = {} + for (const [field] of Object.entries(err.fields)) { + sanitisedDetails[field] = { + message: 'Invalid value provided', + } + } const errorResponse: ValidateErrorResponse = { message: 'Validation Failed', - details: err?.fields, + details: sanitisedDetails, } logger.error({ ...errorResponse }) return res.status(422).json(errorResponse) diff --git a/application/backend/swagger.json b/application/backend/swagger.json index 3b1cebc5..d66dd9c3 100644 --- a/application/backend/swagger.json +++ b/application/backend/swagger.json @@ -92,14 +92,7 @@ "details" ], "type": "object", - "additionalProperties": false, - "example": { - "message": "Validation Failed", - "bodyRequest.firstName": { - "message": "minLength 1", - "value": "" - } - } + "additionalProperties": false }, "UnprocessableError": { "properties": { diff --git a/application/common/types/api/errors.ts b/application/common/types/api/errors.ts index 6ae9098f..471579a6 100644 --- a/application/common/types/api/errors.ts +++ b/application/common/types/api/errors.ts @@ -36,7 +36,6 @@ export interface UnauthorizedErrorResponse { * "message": "Validation Failed", * "bodyRequest.firstName": { * "message": "minLength 1", - * "value": "" * } * } */ From 8c508547ed63a8aa0eea5e92aea9870ea67732c0 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 16 Jul 2026 09:18:50 +1200 Subject: [PATCH 4/4] Fix backend Auth tests to use new terse validation error message --- .../src/controllers/AuthController.test.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/application/backend/src/controllers/AuthController.test.ts b/application/backend/src/controllers/AuthController.test.ts index 71758ee8..8f976790 100644 --- a/application/backend/src/controllers/AuthController.test.ts +++ b/application/backend/src/controllers/AuthController.test.ts @@ -178,8 +178,7 @@ describe('AuthController', () => { expect(body.message).toBe('Validation Failed') expect(body.details).toEqual({ 'bodyRequest.email': { - message: 'Please provide valid email', - value: registerRequest.email, + message: 'Invalid value provided', }, }) }) @@ -203,8 +202,7 @@ describe('AuthController', () => { expect(body.message).toBe('Validation Failed') expect(body.details).toEqual({ 'bodyRequest.password': { - message: 'Password must be at least 8 characters', - value: 'pass', + message: 'Invalid value provided', }, }) }) @@ -227,10 +225,10 @@ describe('AuthController', () => { const body = response.body expect(body.message).toBe('Validation Failed') expect(body.details).toEqual({ - 'bodyRequest.firstName': { message: 'minLength 1', value: '' }, - 'bodyRequest.lastName': { message: 'minLength 1', value: '' }, - 'bodyRequest.email': { message: 'Please provide valid email', value: '' }, - 'bodyRequest.password': { message: 'Password must be at least 8 characters', value: '' }, + 'bodyRequest.firstName': { message: 'Invalid value provided' }, + 'bodyRequest.lastName': { message: 'Invalid value provided' }, + 'bodyRequest.email': { message: 'Invalid value provided' }, + 'bodyRequest.password': { message: 'Invalid value provided' }, }) }) @@ -252,8 +250,8 @@ describe('AuthController', () => { const body = response.body expect(body.message).toBe('Validation Failed') expect(body.details).toEqual({ - Uppercase: { message: 'Password must contain at least one uppercase letter' }, - Number: { message: 'Password must contain at least one number' }, + Uppercase: { message: 'Invalid value provided' }, + Number: { message: 'Invalid value provided' }, }) }) }) @@ -334,8 +332,8 @@ describe('AuthController', () => { expect(registerParticipantBody.message).toEqual('Validation Failed') expect(registerParticipantBody.token).toBe(undefined) expect(registerParticipantBody.details).toEqual({ - Uppercase: { message: 'Password must contain at least one uppercase letter' }, - Number: { message: 'Password must contain at least one number' }, + Uppercase: { message: 'Invalid value provided' }, + Number: { message: 'Invalid value provided' }, }) }) @@ -363,12 +361,10 @@ describe('AuthController', () => { expect(registerParticipantBody.token).toBe(undefined) expect(registerParticipantBody.details).toEqual({ 'bodyRequest.lastName': { - message: 'minLength 1', - value: '', + message: 'Invalid value provided', }, 'bodyRequest.mobile': { - message: 'please provide valid phone number', - value: '12341234', + message: 'Invalid value provided', }, }) }) @@ -565,7 +561,7 @@ describe('AuthController', () => { const body = response.body expect(body.message).toBe('Validation Failed') expect(body.details).toEqual({ - 'bodyRequest.email': { message: 'Please provide valid email', value: '' }, + 'bodyRequest.email': { message: 'Invalid value provided' }, }) }) @@ -581,7 +577,7 @@ describe('AuthController', () => { const body = response.body expect(body.message).toBe('Validation Failed') expect(body.details).toEqual({ - 'bodyRequest.email': { message: 'Please provide valid email', value: loginRequest.email }, + 'bodyRequest.email': { message: 'Invalid value provided' }, }) }) it('Should return an otp token if otp is enabled', async () => {