Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion application/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
generator client {
provider = "prisma-client-js"
}
previewFeatures = ["omitApi"]
}

generator json {
provider = "prisma-json-types-generator"
Expand Down
104 changes: 104 additions & 0 deletions application/backend/src/controllers/UsersController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ 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')
})

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', () => {
Expand All @@ -91,6 +113,28 @@ 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')
})

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', () => {
Expand Down Expand Up @@ -118,6 +162,28 @@ 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')
})

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', () => {
Expand Down Expand Up @@ -583,6 +649,44 @@ 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')
})

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', () => {
Expand Down
16 changes: 10 additions & 6 deletions application/backend/src/controllers/UsersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'tsoa'
import logger from 'common/src/logger'
import type {
UserResponse,
GetUserByIdResponse,
GetAllUsersResponse,
CreateUserRequest,
Expand All @@ -28,7 +29,6 @@ import type {
GeneratePasswordResetLinkRequest,
ResetPasswordRequest,
} from 'common/types/api/users'
import { User } from '@prisma/client'
import prisma from '../PrismaClient'
import {
InternalErrorResponse,
Expand Down Expand Up @@ -72,8 +72,10 @@ export class UsersController extends Controller {
@Security('jwt', ['OrganisationAdmin'])
@Response<UnauthorizedErrorResponse>('401', 'Unauthorized')
public async getAllUsers(): Promise<GetAllUsersResponse> {
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
}
Expand All @@ -87,10 +89,11 @@ export class UsersController extends Controller {
@Security('jwt', ['OrganisationAdmin', 'StudyAdmin'])
@Response<UnauthorizedErrorResponse>('401', 'Unauthorized')
public async getAllAdminUsers(): Promise<GetAllUsersResponse> {
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 })
Expand All @@ -114,8 +117,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
Expand Down Expand Up @@ -144,7 +148,7 @@ export class UsersController extends Controller {
@Response<NotFoundErrorResponse>('404', 'Not Found')
@Response<UnauthorizedErrorResponse>('401', 'Unauthorized')
public async getUserById(@Path() userId: number): Promise<GetUserByIdResponse> {
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 } } },
})
Expand Down
27 changes: 21 additions & 6 deletions application/backend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -124,7 +129,7 @@ const models: TsoaRoute.Models = {
"GetUserByIdResponse": {
"dataType": "refObject",
"properties": {
"data": {"ref":"User","required":true},
"data": {"ref":"UserResponse","required":true},
},
"additionalProperties": false,
},
Expand Down Expand Up @@ -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": {
Expand Down
Loading
Loading