-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PUT endpoint for userChangemakerPermission
This endpoint allows the creation of new user changemaker permissions. It is also the first endpoint to actually require a particular changemaker permission in order to access the functionality. Issue #1250 Support associations between users and organizational entities
- Loading branch information
Showing
5 changed files
with
268 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import request from 'supertest'; | ||
import { app } from '../app'; | ||
import { | ||
createChangemaker, | ||
createOrUpdateUserChangemakerPermission, | ||
} from '../database'; | ||
import { expectTimestamp, loadTestUser } from '../test/utils'; | ||
import { | ||
mockJwt as authHeader, | ||
mockJwtWithAdminRole as authHeaderWithAdminRole, | ||
} from '../test/mockJwt'; | ||
import { keycloakUserIdToString, Permission } from '../types'; | ||
|
||
describe('/users/changemakers/:changemakerId/permissions/:permission', () => { | ||
describe('PUT /', () => { | ||
it('returns 401 if the request lacks authentication', async () => { | ||
const user = await loadTestUser(); | ||
const changemaker = await createChangemaker({ | ||
taxId: '11-1111111', | ||
name: 'Example Inc.', | ||
}); | ||
await request(app) | ||
.put( | ||
`/users/${keycloakUserIdToString(user.keycloakUserId)}/changemakers/${changemaker.id}/permissions/${Permission.MANAGE}`, | ||
) | ||
.send({}) | ||
.expect(401); | ||
}); | ||
|
||
it('returns 401 if the authenticated user lacks permission', async () => { | ||
const user = await loadTestUser(); | ||
const changemaker = await createChangemaker({ | ||
taxId: '11-1111111', | ||
name: 'Example Inc.', | ||
}); | ||
await request(app) | ||
.put( | ||
`/users/${keycloakUserIdToString(user.keycloakUserId)}/changemakers/${changemaker.id}/permissions/${Permission.MANAGE}`, | ||
) | ||
.set(authHeader) | ||
.send({}) | ||
.expect(401); | ||
}); | ||
|
||
it('returns 400 if the userId is not a valid keycloak user ID', async () => { | ||
await request(app) | ||
.put(`/users/notaguid/changemakers/1/permissions/${Permission.MANAGE}`) | ||
.set(authHeaderWithAdminRole) | ||
.send({}) | ||
.expect(400); | ||
}); | ||
|
||
it('returns 400 if the changemaker ID is not a valid ID', async () => { | ||
const user = await loadTestUser(); | ||
await request(app) | ||
.put( | ||
`/users/${keycloakUserIdToString(user.keycloakUserId)}/changemakers/notanId/permissions/${Permission.MANAGE}`, | ||
) | ||
.set(authHeaderWithAdminRole) | ||
.send({}) | ||
.expect(400); | ||
}); | ||
|
||
it('returns 400 if the permission is not a valid permission', async () => { | ||
const user = await loadTestUser(); | ||
await request(app) | ||
.put( | ||
`/users/${keycloakUserIdToString(user.keycloakUserId)}/changemakers/1/permissions/notAPermission`, | ||
) | ||
.set(authHeaderWithAdminRole) | ||
.send({}) | ||
.expect(400); | ||
}); | ||
|
||
it('creates and returns the new user changemaker permission when user has administrative credentials', async () => { | ||
const user = await loadTestUser(); | ||
const changemaker = await createChangemaker({ | ||
taxId: '11-1111111', | ||
name: 'Example Inc.', | ||
}); | ||
|
||
const response = await request(app) | ||
.put( | ||
`/users/${keycloakUserIdToString(user.keycloakUserId)}/changemakers/${changemaker.id}/permissions/${Permission.EDIT}`, | ||
) | ||
.set(authHeaderWithAdminRole) | ||
.send({}) | ||
.expect(201); | ||
expect(response.body).toEqual({ | ||
changemakerId: changemaker.id, | ||
createdAt: expectTimestamp, | ||
createdBy: user.keycloakUserId, | ||
permission: Permission.EDIT, | ||
userKeycloakUserId: user.keycloakUserId, | ||
}); | ||
}); | ||
|
||
it('creates and returns the new user changemaker permission when user has permission to manage the changemaker', async () => { | ||
const user = await loadTestUser(); | ||
const changemaker = await createChangemaker({ | ||
taxId: '11-1111111', | ||
name: 'Example Inc.', | ||
}); | ||
await createOrUpdateUserChangemakerPermission({ | ||
userKeycloakUserId: user.keycloakUserId, | ||
changemakerId: changemaker.id, | ||
permission: Permission.MANAGE, | ||
createdBy: user.keycloakUserId, | ||
}); | ||
const response = await request(app) | ||
.put( | ||
`/users/${keycloakUserIdToString(user.keycloakUserId)}/changemakers/${changemaker.id}/permissions/${Permission.EDIT}`, | ||
) | ||
.set(authHeader) | ||
.send({}) | ||
.expect(201); | ||
expect(response.body).toEqual({ | ||
changemakerId: changemaker.id, | ||
createdAt: expectTimestamp, | ||
createdBy: user.keycloakUserId, | ||
permission: Permission.EDIT, | ||
userKeycloakUserId: user.keycloakUserId, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { createOrUpdateUserChangemakerPermission } from '../database'; | ||
import { | ||
isAuthContext, | ||
isId, | ||
isKeycloakUserId, | ||
isPermission, | ||
isTinyPgErrorWithQueryContext, | ||
isWritableUserChangemakerPermission, | ||
} from '../types'; | ||
import { | ||
DatabaseError, | ||
FailedMiddlewareError, | ||
InputValidationError, | ||
} from '../errors'; | ||
import type { Request, Response, NextFunction } from 'express'; | ||
|
||
const putUserChangemakerPermission = ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction, | ||
): void => { | ||
if (!isAuthContext(req)) { | ||
next(new FailedMiddlewareError('Unexpected lack of auth context.')); | ||
return; | ||
} | ||
|
||
const { userKeycloakUserId, changemakerId, permission } = req.params; | ||
const createdBy = req.user.keycloakUserId; | ||
|
||
if (!isKeycloakUserId(userKeycloakUserId)) { | ||
next( | ||
new InputValidationError( | ||
'Invalid userKeycloakUserId parameter.', | ||
isKeycloakUserId.errors ?? [], | ||
), | ||
); | ||
return; | ||
} | ||
if (!isId(changemakerId)) { | ||
next( | ||
new InputValidationError( | ||
'Invalid changemakerId parameter.', | ||
isId.errors ?? [], | ||
), | ||
); | ||
return; | ||
} | ||
if (!isPermission(permission)) { | ||
next( | ||
new InputValidationError( | ||
'Invalid permission parameter.', | ||
isPermission.errors ?? [], | ||
), | ||
); | ||
return; | ||
} | ||
if (!isWritableUserChangemakerPermission(req.body)) { | ||
next( | ||
new InputValidationError( | ||
'Invalid request body.', | ||
isWritableUserChangemakerPermission.errors ?? [], | ||
), | ||
); | ||
return; | ||
} | ||
|
||
(async () => { | ||
const userChangemakerPermission = | ||
await createOrUpdateUserChangemakerPermission({ | ||
userKeycloakUserId, | ||
changemakerId, | ||
permission, | ||
createdBy, | ||
}); | ||
res | ||
.status(201) | ||
.contentType('application/json') | ||
.send(userChangemakerPermission); | ||
})().catch((error: unknown) => { | ||
if (isTinyPgErrorWithQueryContext(error)) { | ||
next(new DatabaseError('Error creating item.', error)); | ||
return; | ||
} | ||
next(error); | ||
}); | ||
}; | ||
|
||
const userChangemakerPermissionsHandlers = { | ||
putUserChangemakerPermission, | ||
}; | ||
|
||
export { userChangemakerPermissionsHandlers }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Response, NextFunction } from 'express'; | ||
import { InputValidationError, UnauthorizedError } from '../errors'; | ||
import { isAuthContext, isId } from '../types'; | ||
import type { Permission } from '../types'; | ||
import type { Request } from 'express'; | ||
|
||
const requireChangemakerPermission = | ||
(permission: Permission) => | ||
(req: Request, res: Response, next: NextFunction) => { | ||
if (!isAuthContext(req)) { | ||
next(new UnauthorizedError('The request lacks an AuthContext.')); | ||
return; | ||
} | ||
if (req.role?.isAdministrator === true) { | ||
next(); | ||
return; | ||
} | ||
const { changemakerId } = req.params; | ||
if (!isId(changemakerId)) { | ||
next( | ||
new InputValidationError('Invalid changemakerId.', isId.errors ?? []), | ||
); | ||
return; | ||
} | ||
const { user } = req; | ||
const permissions = user.permissions.changemaker[changemakerId] ?? []; | ||
if (!permissions.includes(permission)) { | ||
next( | ||
new UnauthorizedError( | ||
'Authenticated user does not have permission to perform this action.', | ||
), | ||
); | ||
return; | ||
} | ||
next(); | ||
}; | ||
|
||
export { requireChangemakerPermission }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,19 @@ | ||
import express from 'express'; | ||
import { userChangemakerPermissionsHandlers } from '../handlers/userChangemakerPermissionsHandlers'; | ||
import { usersHandlers } from '../handlers/usersHandlers'; | ||
import { requireAuthentication } from '../middleware'; | ||
import { | ||
requireAuthentication, | ||
requireChangemakerPermission, | ||
} from '../middleware'; | ||
import { Permission } from '../types'; | ||
|
||
const usersRouter = express.Router(); | ||
|
||
usersRouter.get('/', requireAuthentication, usersHandlers.getUsers); | ||
usersRouter.put( | ||
'/:userKeycloakUserId/changemakers/:changemakerId/permissions/:permission', | ||
requireChangemakerPermission(Permission.MANAGE), | ||
userChangemakerPermissionsHandlers.putUserChangemakerPermission, | ||
); | ||
|
||
export { usersRouter }; |