From 247de9ccca38bb41375306120af2f1f65813388b Mon Sep 17 00:00:00 2001 From: usingtechnology <39388115+usingtechnology@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:50:57 -0700 Subject: [PATCH 1/4] FORMS-1325: Update Ext API Tech docs link (#1513) Signed-off-by: Jason Sherman --- app/frontend/src/components/forms/manage/ExternalAPIs.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/components/forms/manage/ExternalAPIs.vue b/app/frontend/src/components/forms/manage/ExternalAPIs.vue index 93d4da611..b63997fca 100644 --- a/app/frontend/src/components/forms/manage/ExternalAPIs.vue +++ b/app/frontend/src/components/forms/manage/ExternalAPIs.vue @@ -25,7 +25,7 @@ export default { externalAPIStatusCodes: [], items: [], techdocsLink: - 'https://developer.gov.bc.ca/docs/default/component/chefs-techdocs/Capabilities/Functionalities', + 'https://developer.gov.bc.ca/docs/default/component/chefs-techdocs/Capabilities/Integrations/Calling-External-API/', editDialog: { title: '', item: { From edb95e5f985406cc83489ec1975c2d520c2fa940 Mon Sep 17 00:00:00 2001 From: Walter Moar Date: Wed, 9 Oct 2024 08:12:36 -0700 Subject: [PATCH 2/4] fix: FORMS-1303 rate limit file, form, submission (#1511) Applying API rate limiting to all the routes under /file, /form, and /submission. Previously the rate limiting was only applied to routes that allowed API Key access. --- app/src/forms/file/routes.js | 3 +- app/src/forms/form/routes.js | 53 ++++++++++--------- app/src/forms/submission/routes.js | 11 ++-- app/tests/unit/forms/file/routes.spec.js | 4 +- app/tests/unit/forms/form/routes.spec.js | 26 ++++----- .../unit/forms/submission/routes.spec.js | 20 +++---- 6 files changed, 60 insertions(+), 57 deletions(-) diff --git a/app/src/forms/file/routes.js b/app/src/forms/file/routes.js index 0f302a56c..ae65e0b19 100644 --- a/app/src/forms/file/routes.js +++ b/app/src/forms/file/routes.js @@ -9,6 +9,7 @@ const controller = require('./controller'); const { currentFileRecord, hasFileCreate, hasFilePermissions } = require('./middleware/filePermissions'); const fileUpload = require('./middleware/upload').fileUpload; +routes.use(rateLimiter); routes.use(currentUser); routes.param('fileId', validateParameter.validateFileId); @@ -17,7 +18,7 @@ routes.post('/', hasFileCreate, fileUpload.upload, async (req, res, next) => { await controller.create(req, res, next); }); -routes.get('/:fileId', rateLimiter, apiAccess, currentFileRecord, hasFilePermissions([P.SUBMISSION_READ]), async (req, res, next) => { +routes.get('/:fileId', apiAccess, currentFileRecord, hasFilePermissions([P.SUBMISSION_READ]), async (req, res, next) => { await controller.read(req, res, next); }); diff --git a/app/src/forms/form/routes.js b/app/src/forms/form/routes.js index 7c2a8deaa..d462e338b 100644 --- a/app/src/forms/form/routes.js +++ b/app/src/forms/form/routes.js @@ -8,6 +8,7 @@ const rateLimiter = require('../common/middleware').apiKeyRateLimiter; const validateParameter = require('../common/middleware/validateParameter'); const controller = require('./controller'); +routes.use(rateLimiter); routes.use(currentUser); routes.param('documentTemplateId', validateParameter.validateDocumentTemplateId); @@ -23,31 +24,31 @@ routes.post('/', async (req, res, next) => { await controller.createForm(req, res, next); }); -routes.get('/:formId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { +routes.get('/:formId', apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { await controller.readForm(req, res, next); }); -routes.get('/:formId/documentTemplates', rateLimiter, apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_READ]), async (req, res, next) => { +routes.get('/:formId/documentTemplates', apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_READ]), async (req, res, next) => { await controller.documentTemplateList(req, res, next); }); -routes.post('/:formId/documentTemplates', rateLimiter, apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_CREATE]), async (req, res, next) => { +routes.post('/:formId/documentTemplates', apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_CREATE]), async (req, res, next) => { await controller.documentTemplateCreate(req, res, next); }); -routes.delete('/:formId/documentTemplates/:documentTemplateId', rateLimiter, apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_DELETE]), async (req, res, next) => { +routes.delete('/:formId/documentTemplates/:documentTemplateId', apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_DELETE]), async (req, res, next) => { await controller.documentTemplateDelete(req, res, next); }); -routes.get('/:formId/documentTemplates/:documentTemplateId', rateLimiter, apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_READ]), async (req, res, next) => { +routes.get('/:formId/documentTemplates/:documentTemplateId', apiAccess, hasFormPermissions([P.DOCUMENT_TEMPLATE_READ]), async (req, res, next) => { await controller.documentTemplateRead(req, res, next); }); -routes.get('/:formId/export', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { +routes.get('/:formId/export', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { await controller.export(req, res, next); }); -routes.post('/:formId/export/fields', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { +routes.post('/:formId/export/fields', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { await controller.exportWithFields(req, res, next); }); @@ -63,75 +64,75 @@ routes.get('/:formId/options', async (req, res, next) => { await controller.readFormOptions(req, res, next); }); -routes.get('/:formId/version', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { +routes.get('/:formId/version', apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { await controller.readPublishedForm(req, res, next); }); -routes.put('/:formId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.FORM_UPDATE]), async (req, res, next) => { +routes.put('/:formId', apiAccess, hasFormPermissions([P.FORM_READ, P.FORM_UPDATE]), async (req, res, next) => { await controller.updateForm(req, res, next); }); -routes.delete('/:formId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.FORM_DELETE]), async (req, res, next) => { +routes.delete('/:formId', apiAccess, hasFormPermissions([P.FORM_READ, P.FORM_DELETE]), async (req, res, next) => { await controller.deleteForm(req, res, next); }); -routes.get('/:formId/submissions', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { +routes.get('/:formId/submissions', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { await controller.listFormSubmissions(req, res, next); }); -routes.get('/:formId/versions/:formVersionId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { +routes.get('/:formId/versions/:formVersionId', apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { await controller.readVersion(req, res, next); }); -routes.get('/:formId/versions/:formVersionId/fields', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { +routes.get('/:formId/versions/:formVersionId/fields', apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { await controller.readVersionFields(req, res, next); }); -routes.post('/:formId/versions/:formVersionId/publish', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_CREATE]), async (req, res, next) => { +routes.post('/:formId/versions/:formVersionId/publish', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_CREATE]), async (req, res, next) => { await controller.publishVersion(req, res, next); }); -routes.get('/:formId/versions/:formVersionId/submissions', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { +routes.get('/:formId/versions/:formVersionId/submissions', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), async (req, res, next) => { await controller.listSubmissions(req, res, next); }); -routes.post('/:formId/versions/:formVersionId/submissions', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_CREATE]), async (req, res, next) => { +routes.post('/:formId/versions/:formVersionId/submissions', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_CREATE]), async (req, res, next) => { await controller.createSubmission(req, res, next); }); -routes.post('/:formId/versions/:formVersionId/multiSubmission', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_CREATE]), async (req, res, next) => { +routes.post('/:formId/versions/:formVersionId/multiSubmission', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_CREATE]), async (req, res, next) => { await controller.createMultiSubmission(req, res, next); }); -routes.get('/:formId/versions/:formVersionId/submissions/discover', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), (req, res, next) => { +routes.get('/:formId/versions/:formVersionId/submissions/discover', apiAccess, hasFormPermissions([P.FORM_READ, P.SUBMISSION_READ]), (req, res, next) => { controller.listSubmissionFields(req, res, next); }); -routes.get('/:formId/drafts', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_READ]), async (req, res, next) => { +routes.get('/:formId/drafts', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_READ]), async (req, res, next) => { await controller.listDrafts(req, res, next); }); -routes.post('/:formId/drafts', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_CREATE]), async (req, res, next) => { +routes.post('/:formId/drafts', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_CREATE]), async (req, res, next) => { await controller.createDraft(req, res, next); }); -routes.get('/:formId/drafts/:formVersionDraftId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_READ]), async (req, res, next) => { +routes.get('/:formId/drafts/:formVersionDraftId', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_READ]), async (req, res, next) => { await controller.readDraft(req, res, next); }); -routes.put('/:formId/drafts/:formVersionDraftId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_UPDATE]), async (req, res, next) => { +routes.put('/:formId/drafts/:formVersionDraftId', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_UPDATE]), async (req, res, next) => { await controller.updateDraft(req, res, next); }); -routes.delete('/:formId/drafts/:formVersionDraftId', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_DELETE]), async (req, res, next) => { +routes.delete('/:formId/drafts/:formVersionDraftId', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_DELETE]), async (req, res, next) => { await controller.deleteDraft(req, res, next); }); -routes.post('/:formId/drafts/:formVersionDraftId/publish', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_CREATE]), async (req, res, next) => { +routes.post('/:formId/drafts/:formVersionDraftId/publish', apiAccess, hasFormPermissions([P.FORM_READ, P.DESIGN_CREATE]), async (req, res, next) => { await controller.publishDraft(req, res, next); }); -routes.get('/:formId/statusCodes', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { +routes.get('/:formId/statusCodes', apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { await controller.getStatusCodes(req, res, next); }); @@ -155,7 +156,7 @@ routes.get('/formcomponents/proactivehelp/list', async (req, res, next) => { await controller.listFormComponentsProactiveHelp(req, res, next); }); -routes.get('/:formId/csvexport/fields', rateLimiter, apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { +routes.get('/:formId/csvexport/fields', apiAccess, hasFormPermissions([P.FORM_READ]), async (req, res, next) => { await controller.readFieldsForCSVExport(req, res, next); }); diff --git a/app/src/forms/submission/routes.js b/app/src/forms/submission/routes.js index 821c4bacc..17d973105 100644 --- a/app/src/forms/submission/routes.js +++ b/app/src/forms/submission/routes.js @@ -7,13 +7,14 @@ const rateLimiter = require('../common/middleware').apiKeyRateLimiter; const validateParameter = require('../common/middleware/validateParameter'); const controller = require('./controller'); +routes.use(rateLimiter); routes.use(currentUser); routes.param('documentTemplateId', validateParameter.validateDocumentTemplateId); routes.param('formId', validateParameter.validateFormId); routes.param('formSubmissionId', validateParameter.validateFormSubmissionId); -routes.get('/:formSubmissionId', rateLimiter, apiAccess, hasSubmissionPermissions([P.SUBMISSION_READ]), async (req, res, next) => { +routes.get('/:formSubmissionId', apiAccess, hasSubmissionPermissions([P.SUBMISSION_READ]), async (req, res, next) => { await controller.read(req, res, next); }); @@ -21,7 +22,7 @@ routes.put('/:formSubmissionId', hasSubmissionPermissions([P.SUBMISSION_UPDATE]) await controller.update(req, res, next); }); -routes.delete('/:formSubmissionId', rateLimiter, apiAccess, hasSubmissionPermissions([P.SUBMISSION_DELETE]), async (req, res, next) => { +routes.delete('/:formSubmissionId', apiAccess, hasSubmissionPermissions([P.SUBMISSION_DELETE]), async (req, res, next) => { await controller.delete(req, res, next); }); @@ -45,7 +46,7 @@ routes.post('/:formSubmissionId/notes', hasSubmissionPermissions([P.SUBMISSION_R await controller.addNote(req, res, next); }); -routes.get('/:formSubmissionId/status', rateLimiter, apiAccess, hasSubmissionPermissions([P.SUBMISSION_REVIEW]), async (req, res, next) => { +routes.get('/:formSubmissionId/status', apiAccess, hasSubmissionPermissions([P.SUBMISSION_REVIEW]), async (req, res, next) => { await controller.getStatus(req, res, next); }); @@ -69,11 +70,11 @@ routes.get('/:formSubmissionId/edits', hasSubmissionPermissions([P.SUBMISSION_RE await controller.listEdits(req, res, next); }); -routes.get('/:formSubmissionId/template/:documentTemplateId/render', rateLimiter, apiAccess, hasSubmissionPermissions([P.SUBMISSION_READ]), async (req, res, next) => { +routes.get('/:formSubmissionId/template/:documentTemplateId/render', apiAccess, hasSubmissionPermissions([P.SUBMISSION_READ]), async (req, res, next) => { await controller.templateRender(req, res, next); }); -routes.post('/:formSubmissionId/template/render', rateLimiter, apiAccess, hasSubmissionPermissions([P.SUBMISSION_READ]), async (req, res, next) => { +routes.post('/:formSubmissionId/template/render', apiAccess, hasSubmissionPermissions([P.SUBMISSION_READ]), async (req, res, next) => { await controller.templateUploadAndRender(req, res, next); }); diff --git a/app/tests/unit/forms/file/routes.spec.js b/app/tests/unit/forms/file/routes.spec.js index d474948ca..19a00ed35 100644 --- a/app/tests/unit/forms/file/routes.spec.js +++ b/app/tests/unit/forms/file/routes.spec.js @@ -80,7 +80,7 @@ describe(`${basePath}`, () => { expect(filePermissions.currentFileRecord).toBeCalledTimes(0); expect(filePermissions.hasFileCreate).toBeCalledTimes(1); expect(hasFilePermissionsMock).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(upload.fileUpload.upload).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateFileId).toBeCalledTimes(0); @@ -103,7 +103,7 @@ describe(`${basePath}/:id`, () => { expect(filePermissions.currentFileRecord).toBeCalledTimes(1); expect(filePermissions.hasFileCreate).toBeCalledTimes(0); expect(hasFilePermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(upload.fileUpload.upload).toBeCalledTimes(0); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateFileId).toBeCalledTimes(1); diff --git a/app/tests/unit/forms/form/routes.spec.js b/app/tests/unit/forms/form/routes.spec.js index def2835b8..74eb678d3 100644 --- a/app/tests/unit/forms/form/routes.spec.js +++ b/app/tests/unit/forms/form/routes.spec.js @@ -83,7 +83,7 @@ describe(`${basePath}`, () => { expect(controller.listForms).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(0); expect(mockJwtServiceProtect).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(0); @@ -102,7 +102,7 @@ describe(`${basePath}`, () => { expect(controller.createForm).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(0); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(0); @@ -188,7 +188,7 @@ describe(`${basePath}/:formId/apiKey`, () => { expect(controller.deleteApiKey).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -207,7 +207,7 @@ describe(`${basePath}/:formId/apiKey`, () => { expect(controller.readApiKey).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -226,7 +226,7 @@ describe(`${basePath}/:formId/apiKey`, () => { expect(controller.createOrReplaceApiKey).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -250,7 +250,7 @@ describe(`${basePath}/:formId/apiKey/filesApiAccess`, () => { expect(controller.filesApiKeyAccess).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -516,7 +516,7 @@ describe(`${basePath}/:formId/emailTemplate`, () => { expect(controller.createOrUpdateEmailTemplate).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -540,7 +540,7 @@ describe(`${basePath}/:formId/emailTemplates`, () => { expect(controller.readEmailTemplates).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -612,7 +612,7 @@ describe(`${basePath}/:formId/options`, () => { expect(controller.readFormOptions).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(0); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -684,7 +684,7 @@ describe(`${basePath}/:formId/subscriptions`, () => { expect(controller.readFormSubscriptionDetails).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -703,7 +703,7 @@ describe(`${basePath}/:formId/subscriptions`, () => { expect(controller.createOrUpdateSubscriptionDetails).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(1); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(1); @@ -920,7 +920,7 @@ describe(`${basePath}/formcomponents/proactivehelp/imageUrl/:componentId`, () => expect(controller.getFCProactiveHelpImageUrl).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(0); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(0); @@ -943,7 +943,7 @@ describe(`${basePath}/formcomponents/proactivehelp/list`, () => { expect(controller.listFormComponentsProactiveHelp).toBeCalledTimes(1); expect(hasFormPermissionsMock).toBeCalledTimes(0); expect(mockJwtServiceProtect).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); expect(validateParameter.validateFormId).toBeCalledTimes(0); diff --git a/app/tests/unit/forms/submission/routes.spec.js b/app/tests/unit/forms/submission/routes.spec.js index 5e80ff0a9..3e3daf62c 100644 --- a/app/tests/unit/forms/submission/routes.spec.js +++ b/app/tests/unit/forms/submission/routes.spec.js @@ -118,7 +118,7 @@ describe(`${basePath}/:formSubmissionId`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.update).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -142,7 +142,7 @@ describe(`${basePath}/:formSubmissionId/:formId/submissions`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.deleteMultipleSubmissions).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -166,7 +166,7 @@ describe(`${basePath}/:formSubmissionId/:formId/submissions/restore`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.restoreMultipleSubmissions).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(1); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -189,7 +189,7 @@ describe(`${basePath}/:formSubmissionId/edits`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.listEdits).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -212,7 +212,7 @@ describe(`${basePath}/:formSubmissionId/email`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.email).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -235,7 +235,7 @@ describe(`${basePath}/:formSubmissionId/notes`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.getNotes).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -253,7 +253,7 @@ describe(`${basePath}/:formSubmissionId/notes`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.addNote).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -276,7 +276,7 @@ describe(`${basePath}/:formSubmissionId/options`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.readOptions).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(0); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -299,7 +299,7 @@ describe(`${basePath}/:formSubmissionId/restore`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.restore).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); @@ -340,7 +340,7 @@ describe(`${basePath}/:formSubmissionId/status`, () => { expect(apiAccess).toBeCalledTimes(0); expect(controller.addStatus).toBeCalledTimes(1); expect(hasSubmissionPermissionsMock).toBeCalledTimes(1); - expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(0); + expect(rateLimiter.apiKeyRateLimiter).toBeCalledTimes(1); expect(userAccess.currentUser).toBeCalledTimes(1); expect(userAccess.filterMultipleSubmissions).toBeCalledTimes(0); expect(validateParameter.validateDocumentTemplateId).toBeCalledTimes(0); From 92ebb7c005d09fe53b6d885ba4a50dd034cce275 Mon Sep 17 00:00:00 2001 From: jasonchung1871 <101672465+jasonchung1871@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:14:09 -0700 Subject: [PATCH 3/4] refactor: Forms 1120 composition api designer (#1502) * Updated some files FormDisclaimer, FormSettings, FormsTable have been updated to the composition API and tests have been updated with improved coverage * Update FloatButton.vue Initial upgrade to composition API. This is not working yet, there appear to be recursive issues. This entire template may need to be refactored.. * FloatButton refactored FloatButton is completely refactored. Need to fix the tests. * Update FloatButton.vue Added in the cypress test attributes * Update FloatButton Fixed existing FloatButton tests * Update FloatButton Full coverage of the view. * Update FormDesigner FormDesigner is updated to the composition API but the CSS completely gets changed when this view is rendered.. Perhaps this entire view needs to be refactored. * Update FormDesigner Cleaned up FormDesigner, fixed the issue with the styling being broken. The styling in FloatButton was overriding the parent view. * Update FloatButton.vue Fixes the colours for FloatButton buttons that are disabled * Update FormDesigner Added some test coverage for the FormDesigner * Update FormDesigner many tests cases written, mostly with regards to patch history and formio event handling * Update FormDesigner FormDesigner tests are almost at 100% coverage. just missing a test for when the language is changed. * Update FormViewerActions FormViewerActions was updated to the composition API and full test coverage added * Update FormViewer.vue FormViewer is updated to the composition API. This may need to be refactored as there's so much going on in this file. * Update FormViewer test coverage Added many lines of test coverage for FormViewer * Update FormViewer.spec.js Added many more lines of code coverage * Update FormViewer tests FormViewer tests have more coverage, fixed some tests. --- .../src/components/designer/FloatButton.vue | 807 ++---- .../src/components/designer/FormDesigner.vue | 1306 ++++----- .../components/designer/FormDisclaimer.vue | 15 +- .../src/components/designer/FormSettings.vue | 34 +- .../src/components/designer/FormViewer.vue | 2158 +++++++-------- .../components/designer/FormViewerActions.vue | 151 +- .../src/components/designer/FormsTable.vue | 125 +- app/frontend/src/composables/form.js | 11 + app/frontend/src/utils/constants.js | 88 + app/frontend/src/views/form/Create.vue | 6 +- app/frontend/src/views/form/Design.vue | 12 +- .../components/designer/FloatButton.spec.js | 300 ++- .../components/designer/FormDesigner.spec.js | 1340 ++++++++++ .../designer/FormDisclaimer.spec.js | 36 +- .../components/designer/FormViewer.spec.js | 2346 ++++++++++++++++- .../designer/FormViewerActions.spec.js | 193 ++ .../components/designer/FormsTable.spec.js | 63 + .../tests/unit/views/form/Create.spec.js | 13 - .../tests/unit/views/form/Design.spec.js | 8 - 19 files changed, 6294 insertions(+), 2718 deletions(-) create mode 100644 app/frontend/tests/unit/components/designer/FormDesigner.spec.js create mode 100644 app/frontend/tests/unit/components/designer/FormViewerActions.spec.js create mode 100644 app/frontend/tests/unit/components/designer/FormsTable.spec.js diff --git a/app/frontend/src/components/designer/FloatButton.vue b/app/frontend/src/components/designer/FloatButton.vue index e59d81cd5..72b8ab737 100644 --- a/app/frontend/src/components/designer/FloatButton.vue +++ b/app/frontend/src/components/designer/FloatButton.vue @@ -1,624 +1,337 @@ - - diff --git a/app/frontend/src/components/designer/FormDesigner.vue b/app/frontend/src/components/designer/FormDesigner.vue index 7abbab56c..4f459b20b 100644 --- a/app/frontend/src/components/designer/FormDesigner.vue +++ b/app/frontend/src/components/designer/FormDesigner.vue @@ -1,771 +1,596 @@ - +async function schemaCreateNew() { + const response = await formService.createForm({ + name: form.value.name, + description: form.value.description, + schema: formSchema.value, + identityProviders: generateIdps({ + idps: form.value.idps, + userType: form.value.userType, + }), + sendSubmissionReceivedEmail: form.value.sendSubmissionReceivedEmail, + enableSubmitterDraft: form.value.enableSubmitterDraft, + enableCopyExistingSubmission: form.value.enableCopyExistingSubmission, + wideFormLayout: form.value.wideFormLayout, + enableStatusUpdates: form.value.enableStatusUpdates, + showSubmissionConfirmation: form.value.showSubmissionConfirmation, + submissionReceivedEmails: form.value.submissionReceivedEmails, + reminder_enabled: false, + deploymentLevel: form.value.deploymentLevel, + ministry: form.value.ministry, + apiIntegration: form.value.apiIntegration, + useCase: form.value.useCase, + labels: form.value.labels, + }); + // update user labels with any new added labels + if ( + form.value.labels.some((label) => userLabels.value.indexOf(label) === -1) + ) { + const userLabelResponse = await userService.updateUserLabels( + form.value.labels + ); + userLabels.value = userLabelResponse.data; + } + + // Navigate back to this page with ID updated + router.push({ + name: 'FormDesigner', + query: { + f: response.data.id, + d: response.data.draft.id, + sv: true, + svs: 'Saved', + }, + }); +} + +async function schemaCreateDraftFromVersion() { + const { data } = await formService.createDraft(properties.formId, { + schema: formSchema.value, + formVersionId: properties.versionId, + }); + + // Navigate back to this page with ID updated + router.push({ + name: 'FormDesigner', + query: { + f: properties.formId, + d: data.id, + sv: true, + svs: 'Saved', + }, + }); +} +async function schemaUpdateExistingDraft() { + await formService.updateDraft(properties.formId, properties.draftId, { + schema: formSchema.value, + }); + + // Update this route with saved flag + router.replace({ + name: 'FormDesigner', + query: { ...router.currentRoute.value.query, sv: true, svs: 'Saved' }, + }); +} +// ----------------------------------------------------------------------------------/ Saving the Schema +async function setProxyHeaders() { + try { + let response = await formService.getProxyHeaders({ + formId: properties.formId, + versionId: properties.versionId, + }); + // error checking for response + sessionStorage.setItem( + 'X-CHEFS-PROXY-DATA', + response.data['X-CHEFS-PROXY-DATA'] + ); + } catch (error) { + notificationStore.addNotification({ + text: 'Failed to set proxy headers', + consoleError: error, + }); + } +} + +async function getFormSchema() { + try { + let res; + if (properties.versionId) { + // Making a new draft from a previous version + res = await formService.readVersion( + properties.formId, + properties.versionId + ); + } else if (properties.draftId) { + // Editing an existing draft + res = await formService.readDraft(properties.formId, properties.draftId); + } + formSchema.value = { + ...formSchema.value, + ...res.data.schema, + }; + if (patch.value.history.length === 0) { + // We are fetching an existing form, so we get the original schema here because + // using the original schema in the mount will give you the default schema + patch.value.originalSchema = deepClone(formSchema.value); + } + reRenderFormIo.value += 1; + } catch (error) { + notificationStore.addNotification({ + text: t('trans.formDesigner.formLoadErrMsg'), + consoleError: t('trans.formDesigner.formLoadConsoleErrMsg', { + formId: properties.formId, + versionId: properties.versionId, + draftId: properties.draftId, + error: error, + }), + }); + } +} + +async function loadFile(event) { + try { + const result = await importFormSchemaFromFile(event.target.files[0]); + + formSchema.value = JSON.parse(result); + addPatchToHistory(); + patch.value.undoClicked = false; + patch.value.redoClicked = false; + resetHistoryFlags(); + // Key-changing to force a re-render of the formio component when we want to load a new schema after the page is already in + reRenderFormIo.value += 1; + } catch (error) { + notificationStore.addNotification({ + text: t('trans.formDesigner.formSchemaImportErrMsg'), + consoleError: t('trans.formDesigner.formSchemaImportConsoleErrMsg', { + error: error, + }), + }); + } +} + +defineExpose({ designerOptions, reRenderFormIo }); + - - diff --git a/app/frontend/src/components/designer/FormDisclaimer.vue b/app/frontend/src/components/designer/FormDisclaimer.vue index 7863a8c3f..6a96c71ee 100644 --- a/app/frontend/src/components/designer/FormDisclaimer.vue +++ b/app/frontend/src/components/designer/FormDisclaimer.vue @@ -1,19 +1,12 @@ -