Skip to content

Commit

Permalink
Merge branch 'main' into feat/forms-1331-event-stream
Browse files Browse the repository at this point in the history
  • Loading branch information
usingtechnology committed Aug 21, 2024
2 parents a61bc72 + b7411d6 commit afaca19
Show file tree
Hide file tree
Showing 43 changed files with 1,199 additions and 184 deletions.
2 changes: 1 addition & 1 deletion app/src/forms/admin/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ module.exports = {
},
updateExternalAPI: async (req, res, next) => {
try {
const response = await service.updateExternalAPI(req.params.id, req.body);
const response = await service.updateExternalAPI(req.params.externalApiId, req.body);
res.status(200).json(response);
} catch (error) {
next(error);
Expand Down
29 changes: 19 additions & 10 deletions app/src/forms/admin/routes.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
const routes = require('express').Router();

const jwtService = require('../../components/jwtService');
const currentUser = require('../auth/middleware/userAccess').currentUser;

const controller = require('./controller');
const validateParameter = require('../common/middleware/validateParameter');
const userController = require('../user/controller');
const jwtService = require('../../components/jwtService');
const controller = require('./controller');

// Always have this applied to all routes here
// Routes under /admin fetch data without doing form permission checks. All
// routes in this file should remain under the "admin" role check, with the
// "admin" role only given to people who have permission to read all data.
routes.use(jwtService.protect('admin'));

routes.use(currentUser);

// Routes under the /admin pathing will fetch data without doing Form permission checks in the database
// As such, this should ALWAYS remain under the :admin role check and that KC role should not be given out
// other than to people who have permission to read all data
routes.param('componentId', validateParameter.validateComponentId);
routes.param('externalApiId', validateParameter.validateExternalAPIId);
routes.param('formId', validateParameter.validateFormId);
routes.param('formVersionId', validateParameter.validateFormVersionId);
routes.param('userId', validateParameter.validateUserId);

//
// Forms
//

routes.get('/forms', async (req, res, next) => {
await controller.listForms(req, res, next);
});
Expand Down Expand Up @@ -52,6 +58,7 @@ routes.put('/forms/:formId/addUser', async (req, res, next) => {
//
// Users
//

routes.get('/users', async (req, res, next) => {
await controller.getUsers(req, res, next);
});
Expand All @@ -61,21 +68,23 @@ routes.get('/users/:userId', async (req, res, next) => {
});

//
// APIs
// External APIs
//

routes.get('/externalAPIs', async (req, res, next) => {
await controller.getExternalAPIs(req, res, next);
});

routes.put('/externalAPIs/:id', async (req, res, next) => {
routes.put('/externalAPIs/:externalApiId', async (req, res, next) => {
await controller.updateExternalAPI(req, res, next);
});

routes.get('/externalAPIs/statusCodes', async (req, res, next) => {
await controller.getExternalAPIStatusCodes(req, res, next);
});

//
//Form componets help info
// Form Components Help
//

routes.post('/formcomponents/proactivehelp/object', async (req, res, next) => {
Expand Down
22 changes: 11 additions & 11 deletions app/src/forms/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,38 @@ module.exports = Object.freeze({
REMINDER_FORM_NOT_FILL: 'formNotFill',
},
Permissions: {
DESIGN_CREATE: 'design_create',
DESIGN_DELETE: 'design_delete',
DESIGN_READ: 'design_read',
DESIGN_UPDATE: 'design_update',
DOCUMENT_TEMPLATE_CREATE: 'document_template_create',
DOCUMENT_TEMPLATE_DELETE: 'document_template_delete',
DOCUMENT_TEMPLATE_READ: 'document_template_read',
EMAIL_TEMPLATE_READ: 'email_template_read',
EMAIL_TEMPLATE_UPDATE: 'email_template_update',
FORM_API_CREATE: 'form_api_create',
FORM_API_DELETE: 'form_api_delete',
FORM_API_READ: 'form_api_read',
FORM_API_UPDATE: 'form_api_update',
FORM_API_DELETE: 'form_api_delete',
FORM_DELETE: 'form_delete',
FORM_READ: 'form_read',
FORM_SUBMITTER: ['form_read', 'submission_create'],
FORM_UPDATE: 'form_update',
FORM_DELETE: 'form_delete',
SUBMISSION_CREATE: 'submission_create',
SUBMISSION_DELETE: 'submission_delete',
SUBMISSION_READ: 'submission_read',
SUBMISSION_REVIEW: 'submission_review',
SUBMISSION_UPDATE: 'submission_update',
SUBMISSION_DELETE: 'submission_delete',
DESIGN_CREATE: 'design_create',
DESIGN_READ: 'design_read',
DESIGN_UPDATE: 'design_update',
DESIGN_DELETE: 'design_delete',
TEAM_READ: 'team_read',
TEAM_UPDATE: 'team_update',
FORM_SUBMITTER: ['form_read', 'submission_create'],
},
Roles: {
OWNER: 'owner',
TEAM_MANAGER: 'team_manager',
FORM_DESIGNER: 'form_designer',
FORM_SUBMITTER: 'form_submitter',
OWNER: 'owner',
SUBMISSION_APPROVER: 'submission_approver',
SUBMISSION_REVIEWER: 'submission_reviewer',
FORM_SUBMITTER: 'form_submitter',
TEAM_MANAGER: 'team_manager',
},
Regex: {
CONFIRMATION_ID: '^[0-9A-Fa-f]{8}$',
Expand Down
68 changes: 67 additions & 1 deletion app/src/forms/common/middleware/validateParameter.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const Problem = require('api-problem');
const uuid = require('uuid');

const encryptionKeyService = require('../../form/encryptionKey/service');
const constants = require('../../common/constants');
const externalApiService = require('../../form/externalApi/service');
const formService = require('../../form/service');
const submissionService = require('../../submission/service');
const encryptionKeyService = require('../../form/encryptionKey/service');

/**
* Throws a 400 problem if the parameter is not a valid UUID.
Expand All @@ -21,6 +22,24 @@ const _validateUuid = (parameter, parameterName) => {
}
};

/**
* Validates that the :componentId route parameter exists and is a UUID.
*
* @param {*} _req the Express object representing the HTTP request - unused.
* @param {*} _res the Express object representing the HTTP response - unused.
* @param {*} next the Express chaining function.
* @param {*} componentId the :componentId value from the route.
*/
const validateComponentId = async (_req, _res, next, componentId) => {
try {
_validateUuid(componentId, 'componentId');

next();
} catch (error) {
next(error);
}
};

/**
* Validates that the :documentTemplateId route parameter exists and is a UUID.
* This validator requires that either the :formId or :formSubmissionId route
Expand Down Expand Up @@ -193,6 +212,50 @@ const validateFormVersionId = async (req, _res, next, formVersionId) => {
}
};

/**
* Validates that the :code route parameter for permissions is valid.
*
* @param {*} _req the Express object representing the HTTP request - unused.
* @param {*} _res the Express object representing the HTTP response - unused.
* @param {*} next the Express chaining function.
* @param {*} code the :code value from the route.
*/
const validatePermissionCode = async (_req, _res, next, code) => {
try {
if (!Object.values(constants.Permissions).includes(code)) {
throw new Problem(400, {
detail: 'Bad permission code',
});
}

next();
} catch (error) {
next(error);
}
};

/**
* Validates that the :code route parameter for roles is valid.
*
* @param {*} _req the Express object representing the HTTP request - unused.
* @param {*} _res the Express object representing the HTTP response - unused.
* @param {*} next the Express chaining function.
* @param {*} code the :code value from the route.
*/
const validateRoleCode = async (_req, _res, next, code) => {
try {
if (!Object.values(constants.Roles).includes(code)) {
throw new Problem(400, {
detail: 'Bad role code',
});
}

next();
} catch (error) {
next(error);
}
};

/**
* Validates that the :userId route parameter exists and is a UUID.
*
Expand Down Expand Up @@ -236,13 +299,16 @@ const validateFormEncryptionKeyId = async (req, _res, next, formEncryptionKeyId)
};

module.exports = {
validateComponentId,
validateDocumentTemplateId,
validateExternalAPIId,
validateFileId,
validateFormId,
validateFormSubmissionId,
validateFormVersionDraftId,
validateFormVersionId,
validatePermissionCode,
validateRoleCode,
validateUserId,
validateFormEncryptionKeyId,
};
10 changes: 5 additions & 5 deletions app/src/forms/file/routes.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const routes = require('express').Router();
const controller = require('./controller');
const apiAccess = require('../auth/middleware/apiAccess');
const validateParameter = require('../common/middleware/validateParameter');

const apiAccess = require('../auth/middleware/apiAccess');
const { currentUser } = require('../auth/middleware/userAccess');
const P = require('../common/constants').Permissions;
const rateLimiter = require('../common/middleware').apiKeyRateLimiter;
const validateParameter = require('../common/middleware/validateParameter');
const controller = require('./controller');
const { currentFileRecord, hasFileCreate, hasFilePermissions } = require('./middleware/filePermissions');
const fileUpload = require('./middleware/upload').fileUpload;
const { currentUser } = require('../auth/middleware/userAccess');
const rateLimiter = require('../common/middleware').apiKeyRateLimiter;

routes.use(currentUser);

Expand Down
6 changes: 3 additions & 3 deletions app/src/forms/form/routes.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const routes = require('express').Router();

const jwtService = require('../../components/jwtService');
const apiAccess = require('../auth/middleware/apiAccess');
const { currentUser, hasFormPermissions } = require('../auth/middleware/userAccess');
const validateParameter = require('../common/middleware/validateParameter');
const P = require('../common/constants').Permissions;
const rateLimiter = require('../common/middleware').apiKeyRateLimiter;

const jwtService = require('../../components/jwtService');
const validateParameter = require('../common/middleware/validateParameter');
const controller = require('./controller');

routes.use(currentUser);
Expand Down
6 changes: 4 additions & 2 deletions app/src/forms/permission/routes.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const routes = require('express').Router();

const jwtService = require('../../components/jwtService');
const currentUser = require('../auth/middleware/userAccess').currentUser;

const validateParameter = require('../common/middleware/validateParameter');
const controller = require('./controller');
const jwtService = require('../../components/jwtService');

routes.use(jwtService.protect('admin'));
routes.use(currentUser);

routes.param('code', validateParameter.validatePermissionCode);

routes.get('/', async (req, res, next) => {
await controller.list(req, res, next);
});
Expand Down
11 changes: 5 additions & 6 deletions app/src/forms/proxy/routes.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
const cors = require('cors');
const routes = require('express').Router();

const { currentUser } = require('../auth/middleware/userAccess');
const controller = require('./controller');

const routes = require('express').Router();

// need to allow cors for OPTIONS call
// formio component will call OPTIONS pre-flight
routes.options('/external', cors());

// called with encrypted headers, no current user!!!
routes.get('/external', cors(), async (_req, res, next) => {
await controller.callExternalApi(_req, res, next);
routes.get('/external', cors(), async (req, res, next) => {
await controller.callExternalApi(req, res, next);
});

routes.post('/headers', currentUser, async (_req, res, next) => {
await controller.generateProxyHeaders(_req, res, next);
routes.post('/headers', currentUser, async (req, res, next) => {
await controller.generateProxyHeaders(req, res, next);
});

module.exports = routes;
31 changes: 27 additions & 4 deletions app/src/forms/proxy/service.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { encryptionService } = require('../../components/encryptionService');
const jwtService = require('../../components/jwtService');
const ProxyServiceError = require('./error');
const { ExternalAPI } = require('../../forms/common/models');
const { ExternalAPI, SubmissionMetadata } = require('../../forms/common/models');

const headerValue = (headers, key) => {
if (headers && key) {
Expand All @@ -19,15 +19,38 @@ const trimLeadingSlashes = (str) => str.replace(/^\/+|\$/g, '');
const trimTrailingSlashes = (str) => str.replace(/\/+$/g, '');

const service = {
_getIds: async (payload) => {
let formId = payload['formId'];
let versionId = payload['versionId'];
let submissionId = payload['submissionId'];
// when we are provided with a submission id (ex. submitting a draft submission)
// we need to fetch the related form and version id (if not provided)
if (submissionId) {
const meta = await SubmissionMetadata.query().where('submissionId', submissionId).first();
if (meta) {
formId = meta.formId;
versionId = meta.formVersionId;
submissionId = meta.submissionId;
}
}
return {
formId: formId,
versionId: versionId,
submissionId: submissionId,
};
},

generateProxyHeaders: async (payload, currentUser, token) => {
if (!payload || !currentUser || !currentUser.idp) {
throw new ProxyServiceError('Cannot generate proxy headers with missing or incomplete parameters');
}

const { formId, versionId, submissionId } = await service._getIds(payload);

const headerData = {
formId: payload['formId'],
versionId: payload['versionId'],
submissionId: payload['submissionId'],
formId: formId,
versionId: versionId,
submissionId: submissionId,
userId: currentUser.idpUserId,
username: currentUser.username,
firstName: currentUser.firstName,
Expand Down
Loading

0 comments on commit afaca19

Please sign in to comment.