From 1cb720daab082e3f5d2434f3ab72302dff1d54a2 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 13:26:51 +1200 Subject: [PATCH 1/8] Add tests for password in user endpoints --- .../src/controllers/UsersController.test.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/application/backend/src/controllers/UsersController.test.ts b/application/backend/src/controllers/UsersController.test.ts index 60822eb5..9ef2ee47 100644 --- a/application/backend/src/controllers/UsersController.test.ts +++ b/application/backend/src/controllers/UsersController.test.ts @@ -76,6 +76,17 @@ describe('UsersController', () => { const body: GetAllUsersResponse = response.body expect(body.data).toBe(undefined) }) + + it('should not return user password', async () => { + const response = await request(app) + .get('/users') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(response.status).toBe(200) + + const body: GetAllUsersResponse = response.body + expect(body).toHaveProperty('data') + expect(body.data[0]).not.toHaveProperty('password') + }) }) describe('GET /users/admin', () => { @@ -91,6 +102,17 @@ describe('UsersController', () => { expect(user.role).not.toEqual('Participant') }) }) + + it('should not return admin password', async () => { + const response = await request(app) + .get('/users') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(response.status).toBe(200) + + const body: GetAllUsersResponse = response.body + expect(body).toHaveProperty('data') + expect(body.data[0]).not.toHaveProperty('password') + }) }) describe('GET /users/:id', () => { @@ -118,6 +140,17 @@ describe('UsersController', () => { expect(response.body.message).toBe(`User with ID: ${userId} not found`) }) + + it('should not return user password', async () => { + const response = await request(app) + .get('/users') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(response.status).toBe(200) + + const body: GetAllUsersResponse = response.body + expect(body).toHaveProperty('data') + expect(body.data[0]).not.toHaveProperty('password') + }) }) describe('POST /users', () => { @@ -583,6 +616,25 @@ describe('UsersController', () => { expect(res2.body.data).toHaveLength(1) expect(res2.body.data[0].id).toBe(OPERATOR_ADMIN_ID) }) + + it('should not return deleted admin password', async () => { + const res1 = await request(app) + .get('/users/admin/deleted') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(res1.ok).toBe(true) + expect(res1.body.data).toHaveLength(0) + await prisma.user.delete({ + where: { + id: OPERATOR_ADMIN_ID, + }, + }) + const res2 = await request(app) + .get('/users/admin/deleted') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(res2.ok).toBe(true) + expect(res2.body.data).toHaveLength(1) + expect(res2.body.data[0]).not.toHaveProperty('password') + }) }) describe('PATCH /users/{userId}/restore', () => { From 8c2a52dd62a9c0a2d8e68a9781a57a59f266dfc6 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 13:43:07 +1200 Subject: [PATCH 2/8] Add tests for emailHash in users endpoints --- .../src/controllers/UsersController.test.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/application/backend/src/controllers/UsersController.test.ts b/application/backend/src/controllers/UsersController.test.ts index 9ef2ee47..6f4fb7fd 100644 --- a/application/backend/src/controllers/UsersController.test.ts +++ b/application/backend/src/controllers/UsersController.test.ts @@ -87,6 +87,17 @@ describe('UsersController', () => { expect(body).toHaveProperty('data') expect(body.data[0]).not.toHaveProperty('password') }) + + it('should not return user emailHash', async () => { + const response = await request(app) + .get('/users') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(response.status).toBe(200) + + const body: GetAllUsersResponse = response.body + expect(body).toHaveProperty('data') + expect(body.data[0]).not.toHaveProperty('emailHash') + }) }) describe('GET /users/admin', () => { @@ -113,6 +124,17 @@ describe('UsersController', () => { expect(body).toHaveProperty('data') expect(body.data[0]).not.toHaveProperty('password') }) + + it('should not return admin emailHash', async () => { + const response = await request(app) + .get('/users') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(response.status).toBe(200) + + const body: GetAllUsersResponse = response.body + expect(body).toHaveProperty('data') + expect(body.data[0]).not.toHaveProperty('emailHash') + }) }) describe('GET /users/:id', () => { @@ -151,6 +173,17 @@ describe('UsersController', () => { expect(body).toHaveProperty('data') expect(body.data[0]).not.toHaveProperty('password') }) + + it('should not return user emailHash', async () => { + const response = await request(app) + .get('/users') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(response.status).toBe(200) + + const body: GetAllUsersResponse = response.body + expect(body).toHaveProperty('data') + expect(body.data[0]).not.toHaveProperty('emailHash') + }) }) describe('POST /users', () => { @@ -635,6 +668,25 @@ describe('UsersController', () => { expect(res2.body.data).toHaveLength(1) expect(res2.body.data[0]).not.toHaveProperty('password') }) + + it('should not return deleted admin emailHash', async () => { + const res1 = await request(app) + .get('/users/admin/deleted') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(res1.ok).toBe(true) + expect(res1.body.data).toHaveLength(0) + await prisma.user.delete({ + where: { + id: OPERATOR_ADMIN_ID, + }, + }) + const res2 = await request(app) + .get('/users/admin/deleted') + .set({ Authorization: `Bearer ${orgAdminToken}` }) + expect(res2.ok).toBe(true) + expect(res2.body.data).toHaveLength(1) + expect(res2.body.data[0]).not.toHaveProperty('emailHash') + }) }) describe('PATCH /users/{userId}/restore', () => { From f78572855bd51b849de5c7da97eb290935930e3c Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 14:49:25 +1200 Subject: [PATCH 3/8] Add omitApi prisma feature --- application/backend/prisma/schema.prisma | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/backend/prisma/schema.prisma b/application/backend/prisma/schema.prisma index f59e6e14..2118e797 100644 --- a/application/backend/prisma/schema.prisma +++ b/application/backend/prisma/schema.prisma @@ -1,6 +1,7 @@ generator client { provider = "prisma-client-js" -} + previewFeatures = ["omitApi"] +} generator json { provider = "prisma-json-types-generator" From 79efc94c5238e7131f56ef109107048c8050bce7 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 14:52:53 +1200 Subject: [PATCH 4/8] update types and docs --- application/backend/src/routes.ts | 27 ++- application/backend/swagger.json | 168 +++++++++++++----- .../common/types/api/users/getAllUsers.ts | 4 +- .../common/types/api/users/getUserById.ts | 4 +- application/common/types/api/users/index.ts | 3 +- 5 files changed, 148 insertions(+), 58 deletions(-) diff --git a/application/backend/src/routes.ts b/application/backend/src/routes.ts index 0105ba23..9ad2428d 100644 --- a/application/backend/src/routes.ts +++ b/application/backend/src/routes.ts @@ -94,20 +94,25 @@ const models: TsoaRoute.Models = { "type": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["OperatorAdmin"]},{"dataType":"enum","enums":["Participant"]},{"dataType":"enum","enums":["OrganisationAdmin"]},{"dataType":"enum","enums":["StudyAdmin"]}],"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "DefaultSelection_Prisma._36_UserPayload_": { + "Pick_User.Exclude_keyofUser.password-or-emailHash__": { "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"deleted":{"dataType":"boolean","required":true},"retriesRemaining":{"dataType":"double","required":true},"lockedUntil":{"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},"agreedTermsAt":{"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},"updatedAt":{"dataType":"datetime","required":true},"createdAt":{"dataType":"datetime","required":true},"role":{"ref":"_36_Enums.Role","required":true},"password":{"dataType":"string","required":true},"emailHash":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},"email":{"dataType":"string","required":true},"lastName":{"dataType":"string","required":true},"middleName":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},"firstName":{"dataType":"string","required":true},"id":{"dataType":"double","required":true}},"validators":{}}, + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"id":{"dataType":"double","required":true},"firstName":{"dataType":"string","required":true},"middleName":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},"lastName":{"dataType":"string","required":true},"email":{"dataType":"string","required":true},"role":{"ref":"_36_Enums.Role","required":true},"createdAt":{"dataType":"datetime","required":true},"updatedAt":{"dataType":"datetime","required":true},"agreedTermsAt":{"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},"lockedUntil":{"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},"retriesRemaining":{"dataType":"double","required":true},"deleted":{"dataType":"boolean","required":true}},"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "User": { + "Omit_User.password-or-emailHash_": { "dataType": "refAlias", - "type": {"ref":"DefaultSelection_Prisma._36_UserPayload_","validators":{}}, + "type": {"ref":"Pick_User.Exclude_keyofUser.password-or-emailHash__","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UserResponse": { + "dataType": "refAlias", + "type": {"ref":"Omit_User.password-or-emailHash_","validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "GetAllUsersResponse": { "dataType": "refObject", "properties": { - "data": {"dataType":"array","array":{"dataType":"refAlias","ref":"User"},"required":true}, + "data": {"dataType":"array","array":{"dataType":"refAlias","ref":"UserResponse"},"required":true}, }, "additionalProperties": false, }, @@ -124,7 +129,7 @@ const models: TsoaRoute.Models = { "GetUserByIdResponse": { "dataType": "refObject", "properties": { - "data": {"ref":"User","required":true}, + "data": {"ref":"UserResponse","required":true}, }, "additionalProperties": false, }, @@ -762,6 +767,16 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DefaultSelection_Prisma._36_UserPayload_": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"deleted":{"dataType":"boolean","required":true},"retriesRemaining":{"dataType":"double","required":true},"lockedUntil":{"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},"agreedTermsAt":{"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},"updatedAt":{"dataType":"datetime","required":true},"createdAt":{"dataType":"datetime","required":true},"role":{"ref":"_36_Enums.Role","required":true},"password":{"dataType":"string","required":true},"emailHash":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},"email":{"dataType":"string","required":true},"lastName":{"dataType":"string","required":true},"middleName":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},"firstName":{"dataType":"string","required":true},"id":{"dataType":"double","required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "User": { + "dataType": "refAlias", + "type": {"ref":"DefaultSelection_Prisma._36_UserPayload_","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "GetOrganisationUsersResponse": { "dataType": "refObject", "properties": { diff --git a/application/backend/swagger.json b/application/backend/swagger.json index 3b1cebc5..efb86777 100644 --- a/application/backend/swagger.json +++ b/application/backend/swagger.json @@ -120,85 +120,83 @@ "type": "object", "additionalProperties": false }, - "User": { - "description": "Model User", + "Pick_User.Exclude_keyofUser.password-or-emailHash__": { "properties": { - "deleted": { - "type": "boolean" - }, - "retriesRemaining": { + "id": { "type": "number", "format": "double" }, - "lockedUntil": { - "type": "string", - "format": "date-time", - "nullable": true + "firstName": { + "type": "string" }, - "agreedTermsAt": { + "middleName": { "type": "string", - "format": "date-time", "nullable": true }, - "updatedAt": { - "type": "string", - "format": "date-time" + "lastName": { + "type": "string" }, - "createdAt": { - "type": "string", - "format": "date-time" + "email": { + "type": "string" }, "role": { "$ref": "#/components/schemas/_36_Enums.Role" }, - "password": { - "type": "string" - }, - "emailHash": { + "createdAt": { "type": "string", - "nullable": true - }, - "email": { - "type": "string" + "format": "date-time" }, - "lastName": { - "type": "string" + "updatedAt": { + "type": "string", + "format": "date-time" }, - "middleName": { + "agreedTermsAt": { "type": "string", + "format": "date-time", "nullable": true }, - "firstName": { - "type": "string" + "lockedUntil": { + "type": "string", + "format": "date-time", + "nullable": true }, - "id": { + "retriesRemaining": { "type": "number", "format": "double" + }, + "deleted": { + "type": "boolean" } }, "required": [ - "deleted", - "retriesRemaining", - "lockedUntil", - "agreedTermsAt", - "updatedAt", - "createdAt", - "role", - "password", - "emailHash", - "email", - "lastName", - "middleName", + "id", "firstName", - "id" + "middleName", + "lastName", + "email", + "role", + "createdAt", + "updatedAt", + "agreedTermsAt", + "lockedUntil", + "retriesRemaining", + "deleted" ], - "type": "object" + "type": "object", + "description": "From T, pick a set of properties whose keys are in the union K" + }, + "Omit_User.password-or-emailHash_": { + "$ref": "#/components/schemas/Pick_User.Exclude_keyofUser.password-or-emailHash__", + "description": "Construct a type with the properties of T except for those in type K." + }, + "UserResponse": { + "$ref": "#/components/schemas/Omit_User.password-or-emailHash_" }, "GetAllUsersResponse": { "properties": { "data": { "items": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/UserResponse" }, "type": "array" } @@ -231,7 +229,7 @@ "GetUserByIdResponse": { "properties": { "data": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/UserResponse" } }, "required": [ @@ -2234,6 +2232,80 @@ "type": "object", "additionalProperties": false }, + "User": { + "description": "Model User", + "properties": { + "deleted": { + "type": "boolean" + }, + "retriesRemaining": { + "type": "number", + "format": "double" + }, + "lockedUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "agreedTermsAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "role": { + "$ref": "#/components/schemas/_36_Enums.Role" + }, + "password": { + "type": "string" + }, + "emailHash": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "middleName": { + "type": "string", + "nullable": true + }, + "firstName": { + "type": "string" + }, + "id": { + "type": "number", + "format": "double" + } + }, + "required": [ + "deleted", + "retriesRemaining", + "lockedUntil", + "agreedTermsAt", + "updatedAt", + "createdAt", + "role", + "password", + "emailHash", + "email", + "lastName", + "middleName", + "firstName", + "id" + ], + "type": "object" + }, "GetOrganisationUsersResponse": { "properties": { "data": { diff --git a/application/common/types/api/users/getAllUsers.ts b/application/common/types/api/users/getAllUsers.ts index f242afa5..8bee5d3a 100644 --- a/application/common/types/api/users/getAllUsers.ts +++ b/application/common/types/api/users/getAllUsers.ts @@ -1,5 +1,7 @@ import { User } from '@prisma/client' +export type UserResponse = Omit + export interface GetAllUsersResponse { - data: User[] + data: UserResponse[] } diff --git a/application/common/types/api/users/getUserById.ts b/application/common/types/api/users/getUserById.ts index 702547a9..4f0a115f 100644 --- a/application/common/types/api/users/getUserById.ts +++ b/application/common/types/api/users/getUserById.ts @@ -1,5 +1,5 @@ -import { User } from '@prisma/client' +import { UserResponse } from './getAllUsers' export interface GetUserByIdResponse { - data: User + data: UserResponse } diff --git a/application/common/types/api/users/index.ts b/application/common/types/api/users/index.ts index 7d6e9c57..42ac32b4 100644 --- a/application/common/types/api/users/index.ts +++ b/application/common/types/api/users/index.ts @@ -1,6 +1,6 @@ import type { GetUserByIdResponse } from './getUserById' import type { GetParticipantProfileResponse, FamilyMember } from './getParticipantProfile' -import type { GetAllUsersResponse } from './getAllUsers' +import type { UserResponse, GetAllUsersResponse } from './getAllUsers' import type { CreateUserRequest, CreateUserResponse } from './createUser' import type { UpdateUserRequest } from './updateUser' import type { UpdateProfileRequest } from './updateProfile' @@ -8,6 +8,7 @@ import type { UpdateUserRoleRequest } from './updateUserRole' import type { GeneratePasswordResetLinkRequest, ResetPasswordRequest } from './passwordReset' export { + UserResponse, GetUserByIdResponse, GetParticipantProfileResponse, FamilyMember, From 49c0783841c97abc3109fe8b9a3690c21823c093 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 14:54:54 +1200 Subject: [PATCH 5/8] Update users endpoints to use new types and omit pw and emailHash --- .../backend/src/controllers/UsersController.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/application/backend/src/controllers/UsersController.ts b/application/backend/src/controllers/UsersController.ts index 4c1af545..fc42fbe6 100644 --- a/application/backend/src/controllers/UsersController.ts +++ b/application/backend/src/controllers/UsersController.ts @@ -19,6 +19,7 @@ import { } from 'tsoa' import logger from 'common/src/logger' import type { + UserResponse, GetUserByIdResponse, GetAllUsersResponse, CreateUserRequest, @@ -28,7 +29,7 @@ import type { GeneratePasswordResetLinkRequest, ResetPasswordRequest, } from 'common/types/api/users' -import { User } from '@prisma/client' +// import { User } from '@prisma/client' import prisma from '../PrismaClient' import { InternalErrorResponse, @@ -72,8 +73,10 @@ export class UsersController extends Controller { @Security('jwt', ['OrganisationAdmin']) @Response('401', 'Unauthorized') public async getAllUsers(): Promise { - const users: User[] = await this.userRepo.findMany({}) - const responseData = { data: users } + const users: UserResponse[] = await this.userRepo.findMany({ + omit: { password: true, emailHash: true }, + }) + const responseData: GetAllUsersResponse = { data: users } logger.info({ ...responseData }) return responseData } @@ -87,10 +90,11 @@ export class UsersController extends Controller { @Security('jwt', ['OrganisationAdmin', 'StudyAdmin']) @Response('401', 'Unauthorized') public async getAllAdminUsers(): Promise { - const users: User[] = await this.userRepo.findMany({ + const users: UserResponse[] = await this.userRepo.findMany({ where: { role: { in: ['OperatorAdmin', 'OrganisationAdmin', 'StudyAdmin'] } }, include: { adminOfStudies: { select: { id: true, name: true } } }, orderBy: { id: 'asc' }, + omit: { password: true, emailHash: true }, }) const responseData = { data: users } logger.info({ ...responseData }) @@ -114,8 +118,9 @@ export class UsersController extends Controller { ) { return { data: [] } } - const users: User[] = await this.userRepo.findMany({ + const users: UserResponse[] = await this.userRepo.findMany({ where: { role: { in: ['OperatorAdmin', 'OrganisationAdmin', 'StudyAdmin'] }, deleted: true }, + omit: { password: true, emailHash: true }, }) const responseData = { data: users } return responseData @@ -144,7 +149,7 @@ export class UsersController extends Controller { @Response('404', 'Not Found') @Response('401', 'Unauthorized') public async getUserById(@Path() userId: number): Promise { - const user: User | null = await this.userRepo.findUnique({ + const user: UserResponse | null = await this.userRepo.findUnique({ where: { id: userId }, include: { adminOfStudies: { select: { name: true, id: true } } }, }) From 445418319214a1569bccb7260e7246519b3b1c29 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 18:08:12 +1200 Subject: [PATCH 6/8] 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 6f4fb7fd..41cd049a 100644 --- a/application/backend/src/controllers/UsersController.test.ts +++ b/application/backend/src/controllers/UsersController.test.ts @@ -268,6 +268,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 758d867ca8e52bf2c1ae615724ac463b064ca2e0 Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 9 Jul 2026 18:12:38 +1200 Subject: [PATCH 7/8] Revert "Adds test for info exposure via patch user endpoint" This reverts commit 445418319214a1569bccb7260e7246519b3b1c29. --- .../src/controllers/UsersController.test.ts | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/application/backend/src/controllers/UsersController.test.ts b/application/backend/src/controllers/UsersController.test.ts index 41cd049a..6f4fb7fd 100644 --- a/application/backend/src/controllers/UsersController.test.ts +++ b/application/backend/src/controllers/UsersController.test.ts @@ -268,34 +268,6 @@ 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 56913384c67e8a64606c0a8e49e5d859b19b257b Mon Sep 17 00:00:00 2001 From: ignatiusm Date: Thu, 16 Jul 2026 09:26:08 +1200 Subject: [PATCH 8/8] Remove commented line --- application/backend/src/controllers/UsersController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/application/backend/src/controllers/UsersController.ts b/application/backend/src/controllers/UsersController.ts index fc42fbe6..733e5598 100644 --- a/application/backend/src/controllers/UsersController.ts +++ b/application/backend/src/controllers/UsersController.ts @@ -29,7 +29,6 @@ import type { GeneratePasswordResetLinkRequest, ResetPasswordRequest, } from 'common/types/api/users' -// import { User } from '@prisma/client' import prisma from '../PrismaClient' import { InternalErrorResponse,