From dcab1bc97aa889c8671f4d8c9d0ceece5469adff Mon Sep 17 00:00:00 2001 From: Anugerah Erlaut Date: Mon, 5 Jun 2023 16:00:29 +0100 Subject: [PATCH] add encryption to post-register handler --- .../helpers/access/postRegistrationHandler.js | 32 ++++++++++++++- src/specs/api.v2.yaml | 13 +------ .../access/postRegistrationHandler.test.js | 39 +++++++++++++++---- tests/api.v2/routes/access.test.js | 31 +++++++++++---- 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/api.v2/helpers/access/postRegistrationHandler.js b/src/api.v2/helpers/access/postRegistrationHandler.js index 76022b159..25a081a94 100644 --- a/src/api.v2/helpers/access/postRegistrationHandler.js +++ b/src/api.v2/helpers/access/postRegistrationHandler.js @@ -1,12 +1,40 @@ +const crypto = require('crypto'); + +const ENC_METHOD = 'aes-256-cbc'; + const getLogger = require('../../../utils/getLogger'); -const { OK } = require('../../../utils/responses'); +const { OK, BadRequestError } = require('../../../utils/responses'); const UserAccess = require('../../model/UserAccess'); const logger = getLogger('[PostRegistrationHandler] - '); +const config = require('../../../config'); + +const decrypt = (encryptedData, key, iv) => { + const buff = Buffer.from(encryptedData, 'base64'); + const decipher = crypto.createDecipheriv(ENC_METHOD, key, iv); + return ( + decipher.update(buff.toString('utf8'), 'hex', 'utf8') + + decipher.final('utf8') + ); +}; const postRegistrationHandler = async (req) => { - const { userEmail, userId } = req.body; + const key = crypto.createHash('sha512').update(config.domainName) + .digest('hex') + .substring(0, 32); + + const [encryptedData, iv] = req.body.split('.'); + let payload = ''; + + try { + // An invalid request will not be parsed into JSON correctly + payload = decrypt(encryptedData, key, iv); + } catch (e) { + throw new BadRequestError('Invalid request'); + } + + const { userEmail, userId } = JSON.parse(payload); new UserAccess().registerNewUserAccess(userEmail, userId); diff --git a/src/specs/api.v2.yaml b/src/specs/api.v2.yaml index 251306d6f..c6f0e31bc 100644 --- a/src/specs/api.v2.yaml +++ b/src/specs/api.v2.yaml @@ -1659,17 +1659,8 @@ paths: text/plain: schema: type: string - examples: {} - application/json: - schema: - type: object - properties: - userEmail: - type: string - minLength: 1 - userId: - type: string - minLength: 1 + + "/workResults/{experimentId}/{ETag}": get: summary: Get the work results from S3 diff --git a/tests/api.v2/helpers/access/postRegistrationHandler.test.js b/tests/api.v2/helpers/access/postRegistrationHandler.test.js index bff4f7129..9d4325ef6 100644 --- a/tests/api.v2/helpers/access/postRegistrationHandler.test.js +++ b/tests/api.v2/helpers/access/postRegistrationHandler.test.js @@ -1,9 +1,11 @@ // Disabled ts because it doesn't recognize jest mocks // @ts-nocheck +const crypto = require('crypto'); const UserAccess = require('../../../../src/api.v2/model/UserAccess'); const postRegistrationHandler = require('../../../../src/api.v2/helpers/access/postRegistrationHandler'); -const { OK } = require('../../../../src/utils/responses'); +const { OK, BadRequestError } = require('../../../../src/utils/responses'); +const config = require('../../../../src/config'); jest.mock('../../../../src/api.v2/model/UserAccess'); @@ -18,17 +20,32 @@ describe('postRegistrationHandler', () => { jest.clearAllMocks(); }); - it('Registers new user on correct message', async () => { + it('Associates new users with experiemnts correctly', async () => { + const ENC_METHOD = 'aes-256-cbc'; + + const mockKey = crypto.createHash('sha512').update(config.domainName) + .digest('hex') + .substring(0, 32); + + // iv length has to be 16 + const mockIV = '1234567890111213'; + const mockUserEmail = 'mock-user-email'; const mockUserId = 'mock-user-email'; - const mockReq = { - body: { - userEmail: mockUserEmail, - userId: mockUserId, - }, + const payload = { + userEmail: mockUserEmail, + userId: mockUserId, }; + const cipher = crypto.createCipheriv(ENC_METHOD, mockKey, mockIV); + const encryptedBody = Buffer.from( + cipher.update(JSON.stringify(payload), 'utf8', 'hex') + cipher.final('hex'), + ).toString('base64'); + + const mockReq = { + body: `${encryptedBody}.${mockIV}`, + }; const res = await postRegistrationHandler(mockReq); expect(mockUserAccess.registerNewUserAccess).toHaveBeenCalledWith(mockUserEmail, mockUserId); @@ -36,4 +53,12 @@ describe('postRegistrationHandler', () => { expect(res).toEqual(OK()); }); + + it('Throws an error if message is invalid', async () => { + const mockReq = { + body: 'SomeInvalidMessage', + }; + + await expect(postRegistrationHandler(mockReq)).rejects.toThrowError(BadRequestError); + }); }); diff --git a/tests/api.v2/routes/access.test.js b/tests/api.v2/routes/access.test.js index 86b5ae208..7b7244382 100644 --- a/tests/api.v2/routes/access.test.js +++ b/tests/api.v2/routes/access.test.js @@ -5,7 +5,7 @@ const expressLoader = require('../../../src/loaders/express'); const accessController = require('../../../src/api.v2/controllers/accessController'); const { - UnauthenticatedError, UnauthorizedError, NotFoundError, OK, + UnauthenticatedError, UnauthorizedError, NotFoundError, OK, BadRequestError, } = require('../../../src/utils/responses'); const AccessRole = require('../../../src/utils/enums/AccessRole'); @@ -181,15 +181,12 @@ describe('User access endpoint', () => { Promise.resolve(); }); - const mockUserInfo = JSON.stringify({ - userId: 'mockUserId', - userEmail: 'user@example.com', - }); + const mockPayload = 'mock.payload'; request(app) .post('/v2/access/post-registration') - .send(mockUserInfo) - .set('Content-type', 'application/json') + .send(mockPayload) + .set('Content-type', 'text/plain') .expect(200) .end((err) => { if (err) { @@ -198,4 +195,24 @@ describe('User access endpoint', () => { return done(); }); }); + + it('Post-user registration endpoint returns error on invalid body', async (done) => { + accessController.postRegistration.mockImplementationOnce((req, res) => { + throw new BadRequestError('Invalid request'); + }); + + const mockPayload = 'invalid.payload'; + + request(app) + .post('/v2/access/post-registration') + .send(mockPayload) + .set('Content-type', 'text/plain') + .expect(400) + .end((err) => { + if (err) { + return done(err); + } + return done(); + }); + }); });