From d3ad0161bf72a2ba4d05d2da69070afb4af1d8de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 15:54:04 +0800 Subject: [PATCH 01/37] chore(deps-dev): bump @babel/core from 7.12.3 to 7.12.10 (#878) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.12.3 to 7.12.10. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.12.10/packages/babel-core) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 206 ++++++++++++++++------------------------------ package.json | 2 +- 2 files changed, 71 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index 141165d4c8..974fa00a1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,25 +20,24 @@ "dev": true }, "@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", + "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", + "@babel/generator": "^7.12.10", "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", "lodash": "^4.17.19", - "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, @@ -53,12 +52,12 @@ } }, "@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz", + "integrity": "sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww==", "dev": true, "requires": { - "@babel/types": "^7.12.1", + "@babel/types": "^7.12.10", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -75,77 +74,12 @@ } }, "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", - "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-module-imports": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", - "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", - "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.12.10" } }, "@babel/helper-split-export-declaration": { @@ -175,43 +109,43 @@ } }, "@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz", + "integrity": "sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA==", "dev": true }, "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" } }, "@babel/traverse": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", - "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz", + "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", + "@babel/generator": "^7.12.10", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.1", - "@babel/types": "^7.12.1", + "@babel/parser": "^7.12.10", + "@babel/types": "^7.12.10", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", - "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz", + "integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", @@ -220,9 +154,9 @@ } }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -1101,14 +1035,14 @@ } }, "@babel/helpers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", - "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "dev": true, "requires": { "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" }, "dependencies": { "@babel/code-frame": { @@ -1121,12 +1055,12 @@ } }, "@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz", + "integrity": "sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww==", "dev": true, "requires": { - "@babel/types": "^7.12.1", + "@babel/types": "^7.12.10", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -1143,12 +1077,12 @@ } }, "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.10" } }, "@babel/helper-split-export-declaration": { @@ -1178,43 +1112,43 @@ } }, "@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz", + "integrity": "sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA==", "dev": true }, "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" } }, "@babel/traverse": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", - "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz", + "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", + "@babel/generator": "^7.12.10", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.1", - "@babel/types": "^7.12.1", + "@babel/parser": "^7.12.10", + "@babel/types": "^7.12.10", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", - "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz", + "integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", @@ -1223,9 +1157,9 @@ } }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" diff --git a/package.json b/package.json index 92252ab8b9..3f7b1158f9 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "winston-cloudwatch": "^2.4.0" }, "devDependencies": { - "@babel/core": "^7.12.3", + "@babel/core": "^7.12.10", "@babel/plugin-transform-runtime": "^7.12.1", "@babel/preset-env": "^7.12.7", "@opengovsg/mockpass": "^2.6.0", From d9ae40fd172bf35a05746cc2939fefbf3c5ba2d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 18:12:30 +0800 Subject: [PATCH 02/37] chore(deps-dev): bump husky from 4.3.5 to 4.3.6 (#877) Bumps [husky](https://github.com/typicode/husky) from 4.3.5 to 4.3.6. - [Release notes](https://github.com/typicode/husky/releases) - [Commits](https://github.com/typicode/husky/compare/v4.3.5...v4.3.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 974fa00a1c..01ce22120a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13368,9 +13368,9 @@ "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=" }, "husky": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.5.tgz", - "integrity": "sha512-E5S/1HMoDDaqsH8kDF5zeKEQbYqe3wL9zJDyqyYqc8I4vHBtAoxkDBGXox0lZ9RI+k5GyB728vZdmnM4bYap+g==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.6.tgz", + "integrity": "sha512-o6UjVI8xtlWRL5395iWq9LKDyp/9TE7XMOTvIpEVzW638UcGxTmV5cfel6fsk/jbZSTlvfGVJf2svFtybcIZag==", "dev": true, "requires": { "chalk": "^4.0.0", diff --git a/package.json b/package.json index 3f7b1158f9..c712cbdcc8 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,7 @@ "google-fonts-plugin": "4.1.0", "html-loader": "~0.5.5", "htmlhint": "^0.14.2", - "husky": "^4.3.5", + "husky": "^4.3.6", "jasmine": "^3.6.3", "jasmine-core": "^3.6.0", "jasmine-sinon": "^0.4.0", From 55ab297b67207d1a8b22e1978b3e5b7bfae2fa13 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Mon, 14 Dec 2020 12:12:53 +0200 Subject: [PATCH 03/37] fix: upgrade twilio from 3.51.0 to 3.52.0 (#869) Snyk has created this PR to upgrade twilio from 3.51.0 to 3.52.0. See this package in npm: https://www.npmjs.com/package/twilio See this project in Snyk: https://app.snyk.io/org/open-government-products/project/4c4f5244-5ce5-491a-a42d-c295bc7f51fd?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 33 +++++++++++++++++---------------- package.json | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 01ce22120a..8d2f33b2c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4214,6 +4214,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -4262,6 +4263,7 @@ "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "dev": true, "requires": { "@types/node": "*" } @@ -4318,6 +4320,7 @@ "version": "4.17.9", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.9.tgz", "integrity": "sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==", + "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -4347,6 +4350,7 @@ "version": "4.17.15", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.15.tgz", "integrity": "sha512-pb71P0BrBAx7cQE+/7QnA1HTQUkdBKMlkPY7lHUMn0YvPJkL2UA+KW3BdWQ309IT+i9En/qm45ZxpjIcpgEhNQ==", + "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", @@ -4475,7 +4479,8 @@ "@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", - "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "dev": true }, "@types/minimatch": { "version": "3.0.3", @@ -4666,12 +4671,14 @@ "@types/qs": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==" + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", + "dev": true }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true }, "@types/request": { "version": "2.48.5", @@ -4708,6 +4715,7 @@ "version": "1.13.8", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", "integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==", + "dev": true, "requires": { "@types/mime": "*", "@types/node": "*" @@ -9804,9 +9812,9 @@ } }, "dayjs": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.6.tgz", - "integrity": "sha512-HngNLtPEBWRo8EFVmHFmSXAjtCX8rGNqeXQI0Gh7wCTSqwaKgPIDqu9m07wABVopNwzvOeCb+2711vQhDlcIXw==" + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.7.tgz", + "integrity": "sha512-IC877KBdMhBrCfBfJXHQlo0G8keZ0Opy7YIIq5QKtUbCuHMzim8S4PyiVK4YmihI3iOF9lhfUBW4AQWHTR5WHA==" }, "debug": { "version": "3.1.0", @@ -25138,12 +25146,10 @@ "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" }, "twilio": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.51.0.tgz", - "integrity": "sha512-6TjXI7U1FWlKhqqdM2tKSZoq7MlRxv+K5IgKhKSrgcoYTm6/qZ51UwwY2rfVHUMicr6y6j4NgaBDrPiOtiu9Xg==", + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.52.0.tgz", + "integrity": "sha512-G/2J4iva5T8080Mei3e24bCBxAemVe766iYQP+OonAzP7EUx9sv/hnNoNsM5u1vKkqKn7ER2uJ+mRI6bJrdEMA==", "requires": { - "@types/express": "^4.17.7", - "@types/qs": "6.9.4", "axios": "^0.19.2", "dayjs": "^1.8.29", "jsonwebtoken": "^8.5.1", @@ -25156,11 +25162,6 @@ "xmlbuilder": "^13.0.2" }, "dependencies": { - "@types/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==" - }, "axios": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", diff --git a/package.json b/package.json index c712cbdcc8..6a4709340e 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "toastr": "^2.1.4", "triple-beam": "^1.3.0", "tweetnacl": "^1.0.1", - "twilio": "^3.51.0", + "twilio": "^3.52.0", "ui-select": "^0.19.8", "uid-generator": "^2.0.0", "uuid": "^8.3.1", From 6f2ee653f3b6772562bc54e950174f4e9793b622 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Dec 2020 17:14:41 +0800 Subject: [PATCH 04/37] chore(deps-dev): bump jest from 26.6.2 to 26.6.3 (#890) Bumps [jest](https://github.com/facebook/jest) from 26.6.2 to 26.6.3. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/compare/v26.6.2...v26.6.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 2865 ++++++++++++--------------------------------- package.json | 2 +- 2 files changed, 777 insertions(+), 2090 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7363728a8..f53ba146de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1451,9 +1451,9 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1591,9 +1591,9 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -2948,19 +2948,6 @@ "slash": "^3.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3001,20 +2988,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3027,9 +3000,9 @@ } }, "@jest/core": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.2.tgz", - "integrity": "sha512-x0v0LVlEslGYGYk4StT90NUp7vbFBrh0K7KDyAg3hMhG0drrxOIQHsY05uC7XVlKHXFgGI+HdnU35qewMZOLFQ==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", + "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", "dev": true, "requires": { "@jest/console": "^26.6.2", @@ -3043,14 +3016,14 @@ "exit": "^0.1.2", "graceful-fs": "^4.2.4", "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.2", + "jest-config": "^26.6.3", "jest-haste-map": "^26.6.2", "jest-message-util": "^26.6.2", "jest-regex-util": "^26.0.0", "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.2", - "jest-runner": "^26.6.2", - "jest-runtime": "^26.6.2", + "jest-resolve-dependencies": "^26.6.3", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", "jest-snapshot": "^26.6.2", "jest-util": "^26.6.2", "jest-validate": "^26.6.2", @@ -3062,19 +3035,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -3121,20 +3081,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -3165,21 +3111,66 @@ "@jest/types": "^26.6.2", "@types/node": "*", "jest-mock": "^26.6.2" + } + }, + "@jest/fake-timers": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", + "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "@jest/globals": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", + "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/types": "^26.6.2", + "expect": "^26.6.2" + } + }, + "@jest/reporters": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", + "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/transform": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^26.6.2", + "jest-resolve": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "node-notifier": "^8.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^7.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3220,6 +3211,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3231,33 +3228,73 @@ } } }, - "@jest/fake-timers": { + "@jest/source-map": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", + "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", "dev": true, "requires": { + "@jest/console": "^26.6.2", "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", + "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "dev": true, + "requires": { + "@jest/test-result": "^26.6.2", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3" + } + }, + "@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3298,19 +3335,11 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "supports-color": { "version": "7.2.0", @@ -3323,30 +3352,19 @@ } } }, - "@jest/globals": { + "@jest/types": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3398,610 +3416,214 @@ } } }, - "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, - "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "@opengovsg/angular-daterangepicker-webpack": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@opengovsg/angular-daterangepicker-webpack/-/angular-daterangepicker-webpack-1.1.5.tgz", + "integrity": "sha512-GhNia6tPH0PtCuo1g9Re0ZlByP5t+nYB/cz66yGqxLLQuYH7qAQOOESfpJuRjDGlq2nxSzXtBAeERDOClxf9yQ==", + "requires": { + "angular": "^1.2.17", + "bootstrap": "^3.0.0", + "bootstrap-daterangepicker": "^2.0.0", + "jquery": "^3.1.1", + "moment": "^2.17.1" + } + }, + "@opengovsg/angular-legacy-sortablejs-maintained": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@opengovsg/angular-legacy-sortablejs-maintained/-/angular-legacy-sortablejs-maintained-1.0.0.tgz", + "integrity": "sha512-3QFfRM7CtnNGHUIcbMhHqx7K15OyMzFSPzl23cmNCHlk6MC2lI/poJSxmg2+r7uSd79/Ni7zZ/TGapgblXeUpA==" + }, + "@opengovsg/angular-recaptcha-fallback": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@opengovsg/angular-recaptcha-fallback/-/angular-recaptcha-fallback-5.0.0.tgz", + "integrity": "sha512-MKlviL+Fw3cK56PwlrTmrkLMl2zbeDNcKqyTwZzSZbInsue+DYy6KQOt/SAotKjGG2+bt2ttgv+GoUUEQccr8A==" + }, + "@opengovsg/formsg-sdk": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@opengovsg/formsg-sdk/-/formsg-sdk-0.8.2.tgz", + "integrity": "sha512-2ez69ywsxtfU9tEVSFTI0FBmltFKkRVLEIKn1EI7epz10Bshkz5lk7qMoFQVwStArATQ4ssK1jAhyF1tpt6E6Q==", + "requires": { + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, + "@opengovsg/mockpass": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opengovsg/mockpass/-/mockpass-2.6.0.tgz", + "integrity": "sha512-fFS0PgyfS/FvaEN83MaDS1+5EUxyEBnDG4i34mz8wggPjs3lMbB4+zGYBQWj74o+Uobm4AX5n4W2piUjXYeqXA==", "dev": true, "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" + "base-64": "^1.0.0", + "cookie-parser": "^1.4.3", + "dotenv": "^8.1.0", + "expiry-map": "^1.1.0", + "express": "^4.16.3", + "jsonwebtoken": "^8.4.0", + "lodash": "^4.17.11", + "moment": "^2.24.0", + "morgan": "^1.9.1", + "mustache": "^4.0.0", + "node-jose": "^2.0.0", + "uuid": "^8.0.0", + "xml-crypto": "^2.0.0", + "xml-encryption": "^1.2.0", + "xmldom": "^0.4.0", + "xpath": "0.0.32" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", "dev": true } } }, - "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, + "@opengovsg/myinfo-gov-client": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@opengovsg/myinfo-gov-client/-/myinfo-gov-client-2.1.2.tgz", + "integrity": "sha512-YrHswABeH1Gcl3ga6onQOMa09Ohqe57a+lrCOdFcehlvNSc6QOmN8Sd8Z/nkFJr2xOsgPGl+kcRTYfK4vooV9g==", "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "axios": "^0.21.0", + "jose": "^0.3.2", + "node-jose": "^2.0.0", + "path": "^0.12.7", + "qs": "^6.9.4", + "request": "^2.88.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, + "axios": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" + "follow-redirects": "^1.10.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" } } }, - "@jest/test-sequencer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.2.tgz", - "integrity": "sha512-iHiEXLMP69Ohe6kFMOVz6geADRxwK+OkLGg0VIGfZrUdkJGiCpghkMb2946FLh7jvzOwwZGyQoMi+kaHiOdM5g==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.2", - "jest-runtime": "^26.6.2" - } + "@opengovsg/ng-file-upload": { + "version": "12.2.14", + "resolved": "https://registry.npmjs.org/@opengovsg/ng-file-upload/-/ng-file-upload-12.2.14.tgz", + "integrity": "sha512-YZx96jBfRCgIbGErhYLfnsdZ199laYJt6zknI/ea17tPhWw4+kYE09a6G5dckiN/QCBxHeZRsjLU6WRRKHleKw==" }, - "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, + "@opengovsg/spcp-auth-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@opengovsg/spcp-auth-client/-/spcp-auth-client-1.4.0.tgz", + "integrity": "sha512-5dstOdhAPGgFMLaYJgHBOSL/y7GiMM+rj9cFqOiKYuFQl4uMrkgPij1yTlxLe3NVXBjuhVS0ZiEyV/EUtnF13g==", "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "base-64": "^1.0.0", + "jsonwebtoken": "^8.3.0", + "lodash": "^4.17.11", + "request": "^2.87.0", + "xml-crypto": "^2.0.0", + "xml-encryption": "^1.2.1", + "xml2json-light": "^1.0.6", + "xmldom": "^0.4.0", + "xpath": "0.0.32" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } + "base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "xmldom": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz", + "integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==" }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==" } } }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, + "@sentry/browser": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.0.tgz", + "integrity": "sha512-kRlt1mE2wrYjspnIupNnPxqsUrRuy02SuXhbpP7J6uu8QasoEmJ78hk0hHz4jOZRmuWwfs2zIXD4tLGgWOKq8A==", "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@sentry/core": "5.29.0", + "@sentry/types": "5.29.0", + "@sentry/utils": "5.29.0", + "tslib": "^1.9.3" } }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "dev": true, + "@sentry/core": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.0.tgz", + "integrity": "sha512-a1sZBJ2u3NG0YDlGvOTwUCWiNjhfmDtAQiKK1o6RIIbcrWy9TlSps7CYDkBP239Y3A4pnvohjEEKEP3v3L3LZQ==", "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "@opengovsg/angular-daterangepicker-webpack": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@opengovsg/angular-daterangepicker-webpack/-/angular-daterangepicker-webpack-1.1.5.tgz", - "integrity": "sha512-GhNia6tPH0PtCuo1g9Re0ZlByP5t+nYB/cz66yGqxLLQuYH7qAQOOESfpJuRjDGlq2nxSzXtBAeERDOClxf9yQ==", - "requires": { - "angular": "^1.2.17", - "bootstrap": "^3.0.0", - "bootstrap-daterangepicker": "^2.0.0", - "jquery": "^3.1.1", - "moment": "^2.17.1" - } - }, - "@opengovsg/angular-legacy-sortablejs-maintained": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@opengovsg/angular-legacy-sortablejs-maintained/-/angular-legacy-sortablejs-maintained-1.0.0.tgz", - "integrity": "sha512-3QFfRM7CtnNGHUIcbMhHqx7K15OyMzFSPzl23cmNCHlk6MC2lI/poJSxmg2+r7uSd79/Ni7zZ/TGapgblXeUpA==" - }, - "@opengovsg/angular-recaptcha-fallback": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@opengovsg/angular-recaptcha-fallback/-/angular-recaptcha-fallback-5.0.0.tgz", - "integrity": "sha512-MKlviL+Fw3cK56PwlrTmrkLMl2zbeDNcKqyTwZzSZbInsue+DYy6KQOt/SAotKjGG2+bt2ttgv+GoUUEQccr8A==" - }, - "@opengovsg/formsg-sdk": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@opengovsg/formsg-sdk/-/formsg-sdk-0.8.2.tgz", - "integrity": "sha512-2ez69ywsxtfU9tEVSFTI0FBmltFKkRVLEIKn1EI7epz10Bshkz5lk7qMoFQVwStArATQ4ssK1jAhyF1tpt6E6Q==", - "requires": { - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" - } - }, - "@opengovsg/mockpass": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opengovsg/mockpass/-/mockpass-2.6.0.tgz", - "integrity": "sha512-fFS0PgyfS/FvaEN83MaDS1+5EUxyEBnDG4i34mz8wggPjs3lMbB4+zGYBQWj74o+Uobm4AX5n4W2piUjXYeqXA==", - "dev": true, - "requires": { - "base-64": "^1.0.0", - "cookie-parser": "^1.4.3", - "dotenv": "^8.1.0", - "expiry-map": "^1.1.0", - "express": "^4.16.3", - "jsonwebtoken": "^8.4.0", - "lodash": "^4.17.11", - "moment": "^2.24.0", - "morgan": "^1.9.1", - "mustache": "^4.0.0", - "node-jose": "^2.0.0", - "uuid": "^8.0.0", - "xml-crypto": "^2.0.0", - "xml-encryption": "^1.2.0", - "xmldom": "^0.4.0", - "xpath": "0.0.32" - }, - "dependencies": { - "xpath": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", - "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", - "dev": true - } - } - }, - "@opengovsg/myinfo-gov-client": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@opengovsg/myinfo-gov-client/-/myinfo-gov-client-2.1.2.tgz", - "integrity": "sha512-YrHswABeH1Gcl3ga6onQOMa09Ohqe57a+lrCOdFcehlvNSc6QOmN8Sd8Z/nkFJr2xOsgPGl+kcRTYfK4vooV9g==", - "requires": { - "axios": "^0.21.0", - "jose": "^0.3.2", - "node-jose": "^2.0.0", - "path": "^0.12.7", - "qs": "^6.9.4", - "request": "^2.88.0" - }, - "dependencies": { - "axios": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", - "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" - }, - "qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" - } - } - }, - "@opengovsg/ng-file-upload": { - "version": "12.2.14", - "resolved": "https://registry.npmjs.org/@opengovsg/ng-file-upload/-/ng-file-upload-12.2.14.tgz", - "integrity": "sha512-YZx96jBfRCgIbGErhYLfnsdZ199laYJt6zknI/ea17tPhWw4+kYE09a6G5dckiN/QCBxHeZRsjLU6WRRKHleKw==" - }, - "@opengovsg/spcp-auth-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@opengovsg/spcp-auth-client/-/spcp-auth-client-1.4.0.tgz", - "integrity": "sha512-5dstOdhAPGgFMLaYJgHBOSL/y7GiMM+rj9cFqOiKYuFQl4uMrkgPij1yTlxLe3NVXBjuhVS0ZiEyV/EUtnF13g==", - "requires": { - "base-64": "^1.0.0", - "jsonwebtoken": "^8.3.0", - "lodash": "^4.17.11", - "request": "^2.87.0", - "xml-crypto": "^2.0.0", - "xml-encryption": "^1.2.1", - "xml2json-light": "^1.0.6", - "xmldom": "^0.4.0", - "xpath": "0.0.32" - }, - "dependencies": { - "base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" - }, - "xmldom": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz", - "integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==" - }, - "xpath": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", - "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==" - } - } - }, - "@sentry/browser": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.0.tgz", - "integrity": "sha512-kRlt1mE2wrYjspnIupNnPxqsUrRuy02SuXhbpP7J6uu8QasoEmJ78hk0hHz4jOZRmuWwfs2zIXD4tLGgWOKq8A==", - "requires": { - "@sentry/core": "5.29.0", - "@sentry/types": "5.29.0", - "@sentry/utils": "5.29.0", - "tslib": "^1.9.3" - } - }, - "@sentry/core": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.0.tgz", - "integrity": "sha512-a1sZBJ2u3NG0YDlGvOTwUCWiNjhfmDtAQiKK1o6RIIbcrWy9TlSps7CYDkBP239Y3A4pnvohjEEKEP3v3L3LZQ==", - "requires": { - "@sentry/hub": "5.29.0", - "@sentry/minimal": "5.29.0", - "@sentry/types": "5.29.0", - "@sentry/utils": "5.29.0", - "tslib": "^1.9.3" + "@sentry/hub": "5.29.0", + "@sentry/minimal": "5.29.0", + "@sentry/types": "5.29.0", + "@sentry/utils": "5.29.0", + "tslib": "^1.9.3" } }, "@sentry/hub": { @@ -4158,9 +3780,9 @@ "integrity": "sha512-zneUmi5I6oSkGBqkRP9rxbWX1mi6Yj7gNV+WNffmJLf8x4cnV0MGqXFNSP90NZ1kRRLCOdKBf9RIVD1TMg4aog==" }, "@types/babel__core": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.11.tgz", - "integrity": "sha512-E5nSOzrjnvhURYnbOR2dClTqcyhPbPvtEwLHf7JJADKedPbcZsoJVfP+I2vBNfBjz4bnZIuhL/tNmRi5nJ7Jlw==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", + "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -4180,9 +3802,9 @@ } }, "@types/babel__template": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.3.tgz", - "integrity": "sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", + "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -4190,9 +3812,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.15.tgz", - "integrity": "sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", + "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -6200,9 +5822,9 @@ } }, "babel-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.2.tgz", - "integrity": "sha512-pysyz/mZ7T5sozKnvSa1n7QEf22W9yc+dUmn2zNuQTN0saG51q8A/8k9wbED9X4YNxmwjuhIwf4JRXXQGzui3Q==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", + "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", "dev": true, "requires": { "@jest/transform": "^26.6.2", @@ -6215,19 +5837,6 @@ "slash": "^3.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -11499,19 +11108,6 @@ "jest-regex-util": "^26.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -11521,16 +11117,6 @@ "color-convert": "^2.0.1" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -11545,21 +11131,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -13838,9 +13409,9 @@ } }, "is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -14169,696 +13740,37 @@ } } }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", - "requires": { - "async": "0.9.x", - "chalk": "^2.4.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" - } - } - }, - "jasmine": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.3.tgz", - "integrity": "sha512-Th91zHsbsALWjDUIiU5d/W5zaYQsZFMPTdeNmi8GivZPmAaUAK8MblSG3yQI4VMGC/abF2us7ex60NH1AAIMTA==", - "dev": true, - "requires": { - "glob": "^7.1.6", - "jasmine-core": "~3.6.0" - }, - "dependencies": { - "jasmine-core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", - "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", - "dev": true - } - } - }, - "jasmine-core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", - "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", - "dev": true - }, - "jasmine-sinon": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/jasmine-sinon/-/jasmine-sinon-0.4.0.tgz", - "integrity": "sha1-gECheaAa4DSbI0ruQ4wkGRI5rpg=", - "dev": true, - "requires": { - "sinon": ">= 1.7.1" - } - }, - "jasmine-spec-reporter": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz", - "integrity": "sha512-MvTOVoMxDZAftQYBApIlSfKnGMzi9cj351nXeqtnZTuXffPlbONN31+Es7F+Ke4okUeQ2xISukt4U1npfzLVrQ==", - "dev": true, - "requires": { - "colors": "1.4.0" - } - }, - "jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.2.tgz", - "integrity": "sha512-lL0hW7mh/2hhQmpo/1fDWQji/BUB3Xcxxj7r0fAOa3t56OAnwbE0HEl2bZ7XjAwV5TXOt8UpCgaa/WBJBB0CYw==", - "dev": true, - "requires": { - "@jest/core": "^26.6.2", - "import-local": "^3.0.2", - "jest-cli": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.2.tgz", - "integrity": "sha512-5SBxa0bXc43fTHgxMfonDFDWTmQTiC6RSS4GpKhVekWkwpaeMHWt/FvGIy5GlTHMbCpzULWV++N3v93OdlFfQA==", - "dev": true, - "requires": { - "@jest/core": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "jest-config": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.2.tgz", - "integrity": "sha512-0ApZqPd+L/BUWvNj1GHcptb5jwF23lo+BskjgJV/Blht1hgpu6eIwaYRgHPrS6I6HrxwRfJvlGbzoZZVb3VHTA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.2", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.2", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" + "semver": "^6.0.0" } }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "supports-color": { "version": "7.2.0", @@ -14871,126 +13783,115 @@ } } }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "ms": "2.1.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + } + } + }, + "jasmine": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.3.tgz", + "integrity": "sha512-Th91zHsbsALWjDUIiU5d/W5zaYQsZFMPTdeNmi8GivZPmAaUAK8MblSG3yQI4VMGC/abF2us7ex60NH1AAIMTA==", + "dev": true, + "requires": { + "glob": "^7.1.6", + "jasmine-core": "~3.6.0" + }, + "dependencies": { + "jasmine-core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", "dev": true - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "jasmine-core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", + "dev": true + }, + "jasmine-sinon": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jasmine-sinon/-/jasmine-sinon-0.4.0.tgz", + "integrity": "sha1-gECheaAa4DSbI0ruQ4wkGRI5rpg=", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "sinon": ">= 1.7.1" + } + }, + "jasmine-spec-reporter": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-6.0.0.tgz", + "integrity": "sha512-MvTOVoMxDZAftQYBApIlSfKnGMzi9cj351nXeqtnZTuXffPlbONN31+Es7F+Ke4okUeQ2xISukt4U1npfzLVrQ==", + "dev": true, + "requires": { + "colors": "1.4.0" + } + }, + "jest": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", + "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", + "dev": true, + "requires": { + "@jest/core": "^26.6.3", + "import-local": "^3.0.2", + "jest-cli": "^26.6.3" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15031,6 +13932,27 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "jest-cli": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "dev": true, + "requires": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15042,157 +13964,118 @@ } } }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-haste-map": { + "jest-changed-files": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", + "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", "dev": true, "requires": { "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" + "execa": "^4.0.0", + "throat": "^5.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "chalk": { + "execa": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "color-name": "~1.1.4" + "path-key": "^3.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" + "shebang-regex": "^3.0.0" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "isexe": "^2.0.0" } } } }, - "jest-jasmine2": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.2.tgz", - "integrity": "sha512-Om6q632kogggOBGjSr34jErXGOQy0+IkxouGUbyzB0lQmufu8nm1AcxLIKpB/FN36I43f2T3YajeNlxwJZ94PQ==", + "jest-config": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", + "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.6.3", "@jest/types": "^26.6.2", - "@types/node": "*", + "babel-jest": "^26.6.3", "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.2", - "jest-snapshot": "^26.6.2", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.6.2", + "jest-environment-node": "^26.6.2", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.6.3", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.6.2", "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" + "jest-validate": "^26.6.2", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15233,32 +14116,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15267,38 +14124,21 @@ "requires": { "has-flag": "^4.0.0" } - } - } - }, - "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, + } + } + }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15339,18 +14179,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15362,37 +14190,28 @@ } } }, - "jest-matcher-utils": { + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", + "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", "dev": true, "requires": { + "@jest/types": "^26.6.2", "chalk": "^4.0.0", - "jest-diff": "^26.6.2", "jest-get-type": "^26.3.0", + "jest-util": "^26.6.2", "pretty-format": "^26.6.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15427,42 +14246,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15474,42 +14263,89 @@ } } }, - "jest-message-util": { + "jest-environment-jsdom": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", + "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", + "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true + }, + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", + "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/source-map": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "throat": "^5.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15550,18 +14386,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15573,29 +14397,28 @@ } } }, - "jest-mock": { + "jest-leak-detector": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", + "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*" + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + } + }, + "jest-matcher-utils": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15647,47 +14470,23 @@ } } }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true - }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true - }, - "jest-resolve": { + "jest-message-util": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", "dev": true, "requires": { + "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15728,30 +14527,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "resolve": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dev": true, - "requires": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15763,30 +14538,44 @@ } } }, - "jest-resolve-dependencies": { + "jest-mock": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.2.tgz", - "integrity": "sha512-lXXQqBLlKlnOPyCfJZnrYydd7lZzWux9sMwKJxOmjsuVmoSlnmTOJ8kW1FYxotTyMzqoNtBuSF6qE+iXuAr6qQ==", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", + "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", "dev": true, "requires": { "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, + "jest-resolve": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", + "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.6.2", + "read-pkg-up": "^7.0.1", + "resolve": "^1.18.1", + "slash": "^3.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15827,6 +14616,16 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15838,10 +14637,21 @@ } } }, + "jest-resolve-dependencies": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", + "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.6.2" + } + }, "jest-runner": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.2.tgz", - "integrity": "sha512-OsWTIGx/MHSuPqjYwap1LAxT0qvlqmwTYSFOwc+G14AtyZlL7ngrrDes7moLRqFkDVpCHL2RT0i317jogyw81Q==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", + "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", "dev": true, "requires": { "@jest/console": "^26.6.2", @@ -15853,32 +14663,19 @@ "emittery": "^0.7.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-config": "^26.6.2", + "jest-config": "^26.6.3", "jest-docblock": "^26.0.0", "jest-haste-map": "^26.6.2", "jest-leak-detector": "^26.6.2", "jest-message-util": "^26.6.2", "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.2", + "jest-runtime": "^26.6.3", "jest-util": "^26.6.2", "jest-worker": "^26.6.2", "source-map-support": "^0.5.6", "throat": "^5.0.0" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -15925,20 +14722,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15951,9 +14734,9 @@ } }, "jest-runtime": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.2.tgz", - "integrity": "sha512-VEjfoim4tkvq8Gh8z7wMXlKva3DnIlgvmGR1AajiRK1nEHuXtuaR17jnVYOi+wW0i1dS3NH4jVdUQl08GodgZQ==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", + "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", "dev": true, "requires": { "@jest/console": "^26.6.2", @@ -15971,7 +14754,7 @@ "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-config": "^26.6.2", + "jest-config": "^26.6.3", "jest-haste-map": "^26.6.2", "jest-message-util": "^26.6.2", "jest-mock": "^26.6.2", @@ -15985,19 +14768,6 @@ "yargs": "^15.4.1" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16038,20 +14808,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -16103,25 +14859,6 @@ "semver": "^7.3.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16156,48 +14893,30 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "yallist": "^4.0.0" } }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "lru-cache": "^6.0.0" } }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16206,6 +14925,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -16301,25 +15026,6 @@ "pretty-format": "^26.6.2" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16366,18 +15072,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16404,19 +15098,6 @@ "string-length": "^4.0.1" }, "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16457,20 +15138,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -19027,12 +17694,25 @@ "which": "^2.0.2" }, "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "which": { "version": "2.0.2", @@ -19043,6 +17723,13 @@ "requires": { "isexe": "^2.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true } } }, @@ -19491,9 +18178,9 @@ "dev": true }, "p-each-series": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", - "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "dev": true }, "p-finally": { @@ -22671,9 +21358,9 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stack-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", - "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" diff --git a/package.json b/package.json index ba3a8c7a01..2ac7603640 100644 --- a/package.json +++ b/package.json @@ -225,7 +225,7 @@ "jasmine-core": "^3.6.0", "jasmine-sinon": "^0.4.0", "jasmine-spec-reporter": "^6.0.0", - "jest": "^26.6.2", + "jest": "^26.6.3", "lint-staged": "^10.5.2", "maildev": "^1.1.0", "mini-css-extract-plugin": "^0.5.0", From ef5d6c6b64e34ed930384b913b2083bb17c6ae34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Dec 2020 17:15:29 +0800 Subject: [PATCH 05/37] fix(deps): bump @sentry/integrations from 5.27.4 to 5.29.0 (#888) Bumps [@sentry/integrations](https://github.com/getsentry/sentry-javascript) from 5.27.4 to 5.29.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/5.27.4...5.29.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 26 +++++--------------------- package.json | 2 +- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index f53ba146de..940e288e0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3637,30 +3637,14 @@ } }, "@sentry/integrations": { - "version": "5.27.4", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.27.4.tgz", - "integrity": "sha512-/2KRNrpbRDatNfurKzhpeYa5YQCYSXgR2JbPGQzg8d3fKggSTDLiVxrc+LC7oHeHgv6LWOzkVVzfmB01LJRZTA==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.29.0.tgz", + "integrity": "sha512-SGqpi1Qd1a7gGL6aYrJnKqU/DNJcHvnhD3qOgow23ivEpaJv1BtQSKxv17IbO/CIFn3A0o1a18wY6xef9isKEQ==", "requires": { - "@sentry/types": "5.27.4", - "@sentry/utils": "5.27.4", + "@sentry/types": "5.29.0", + "@sentry/utils": "5.29.0", "localforage": "1.8.1", "tslib": "^1.9.3" - }, - "dependencies": { - "@sentry/types": { - "version": "5.27.4", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.27.4.tgz", - "integrity": "sha512-41h3c7tgtSS8UBmfvEckSr+7V7/IVOjt/EiydyOd6s0N18zSFfGY5HdA6g+eFtIJK3DhWkUHCHZNanD5IY5YCQ==" - }, - "@sentry/utils": { - "version": "5.27.4", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.27.4.tgz", - "integrity": "sha512-shV1I/q+Tob3hUxRj11DfMhe9PNDiv85hUUoRloZGGwu275dMwpswb2uwgSmjc2Ao4pnMKVx8TL1hC3kGLVHTQ==", - "requires": { - "@sentry/types": "5.27.4", - "tslib": "^1.9.3" - } - } } }, "@sentry/minimal": { diff --git a/package.json b/package.json index 2ac7603640..36fbc0d078 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@opengovsg/ng-file-upload": "^12.2.14", "@opengovsg/spcp-auth-client": "^1.4.0", "@sentry/browser": "^5.29.0", - "@sentry/integrations": "^5.27.4", + "@sentry/integrations": "^5.29.0", "@stablelib/base64": "^1.0.0", "JSONStream": "^1.3.5", "angular": "~1.8.2", From afc73ab42f1ff5fead6624501a83a60a70725aba Mon Sep 17 00:00:00 2001 From: tshuli <63710093+tshuli@users.noreply.github.com> Date: Tue, 15 Dec 2020 19:27:20 +0800 Subject: [PATCH 06/37] refactor: validateAndProcessEncryptSubmission to typescript (#887) * chore: use correct type for validators * refactor: migrate validateAndProcessEncryptSubmission to ts * refactor: update tests --- .../email-submission.types.ts | 1 + .../encrypt-submission.middleware.ts | 70 +++++++++++++++++++ .../encrypt-submission.utils.ts | 27 ++++++- .../modules/submission/submission.types.ts | 1 + src/app/routes/admin-forms.server.routes.js | 5 +- src/app/utils/encryption.ts | 2 +- .../field-validation/validators/common.ts | 4 +- .../validators/dateValidator.ts | 4 +- .../validators/decimalValidator.ts | 4 +- .../validators/homeNoValidator.ts | 4 +- .../validators/mobileNoValidator.ts | 4 +- .../validators/nricValidator.ts | 4 +- .../validators/radioButtonValidator.ts | 4 +- .../validators/ratingValidator.ts | 4 +- .../validators/textValidator.ts | 4 +- src/types/express.locals.ts | 6 ++ src/types/response/index.ts | 3 +- ...rypt-submissions.server.controller.spec.js | 5 +- 18 files changed, 131 insertions(+), 25 deletions(-) create mode 100644 src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts diff --git a/src/app/modules/submission/email-submission/email-submission.types.ts b/src/app/modules/submission/email-submission/email-submission.types.ts index 90aacf358b..bbc34f768a 100644 --- a/src/app/modules/submission/email-submission/email-submission.types.ts +++ b/src/app/modules/submission/email-submission/email-submission.types.ts @@ -39,6 +39,7 @@ export interface EmailDataForOneField { // When a response has been formatted for email, all answerArray // should have been converted to answer interface IResponseFormattedForEmail extends IBaseResponse { + question: string fieldType: BasicField answer: string } diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts new file mode 100644 index 0000000000..9f9011f39c --- /dev/null +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts @@ -0,0 +1,70 @@ +import { RequestHandler } from 'express' +import { SetOptional } from 'type-fest' + +import { createReqMeta } from '../../../../app/utils/request' +import { createLoggerWithLabel } from '../../../../config/logger' +import { FieldResponse, WithForm, WithParsedResponses } from '../../../../types' +import { checkIsEncryptedEncoding } from '../../../utils/encryption' +import { getProcessedResponses } from '../submission.service' + +import { mapRouteError } from './encrypt-submission.utils' + +const logger = createLoggerWithLabel(module) + +type EncryptSubmissionBody = { + responses: FieldResponse[] + encryptedContent: string + attachments?: { + encryptedFile?: { + binary: string + nonce: string + submissionPublicKey: string + } + } + isPreview: boolean + version: number +} + +export const validateAndProcessEncryptSubmission: RequestHandler< + { formId: string }, + unknown, + EncryptSubmissionBody +> = (req, res, next) => { + const { form } = req as WithForm + const { encryptedContent, responses } = req.body + + // Step 1: Check whether submitted encryption is valid. + return ( + checkIsEncryptedEncoding(encryptedContent) + // Step 2: Encryption is valid, process given responses. + .andThen(() => getProcessedResponses(form, responses)) + // If pass, then set parsedResponses and delete responses. + .map((processedResponses) => { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;(req.body as WithParsedResponses< + typeof req.body + >).parsedResponses = processedResponses + // Prevent downstream functions from using responses by deleting it. + delete (req.body as SetOptional) + .responses + return next() + }) + // If error, log and return res error. + .mapErr((error) => { + logger.error({ + message: 'Error validating encrypt submission responses', + meta: { + action: 'validateEncryptSubmission', + ...createReqMeta(req), + formId: form._id, + }, + error, + }) + + const { statusCode, errorMessage } = mapRouteError(error) + return res.status(statusCode).json({ + message: errorMessage, + }) + }) + ) +} diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts index 4a84921b46..6f99594cb7 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts @@ -4,7 +4,13 @@ import { createLoggerWithLabel } from '../../../../config/logger' import { MapRouteError } from '../../../../types/routing' import { DatabaseError, MalformedParametersError } from '../../core/core.errors' import { CreatePresignedUrlError } from '../../form/admin-form/admin-form.errors' -import { SubmissionNotFoundError } from '../submission.errors' +import { + ConflictError, + InvalidEncodingError, + ProcessingError, + SubmissionNotFoundError, + ValidateFieldError, +} from '../submission.errors' const logger = createLoggerWithLabel(module) @@ -25,6 +31,25 @@ export const mapRouteError: MapRouteError = (error) => { statusCode: StatusCodes.NOT_FOUND, errorMessage: error.message, } + case InvalidEncodingError: + return { + statusCode: StatusCodes.BAD_REQUEST, + errorMessage: + 'Invalid data was found. Please check your responses and submit again.', + } + case ValidateFieldError: + case ProcessingError: + return { + statusCode: StatusCodes.BAD_REQUEST, + errorMessage: + 'There is something wrong with your form submission. Please check your responses and try again. If the problem persists, please refresh the page.', + } + case ConflictError: + return { + statusCode: StatusCodes.CONFLICT, + errorMessage: + 'The form has been updated. Please refresh and submit again.', + } case CreatePresignedUrlError: case DatabaseError: return { diff --git a/src/app/modules/submission/submission.types.ts b/src/app/modules/submission/submission.types.ts index 20666cdf60..ede40f812c 100644 --- a/src/app/modules/submission/submission.types.ts +++ b/src/app/modules/submission/submission.types.ts @@ -6,6 +6,7 @@ import { } from 'src/types/response' export type ProcessedResponse = { + question: string isVisible?: boolean isUserVerified?: boolean } diff --git a/src/app/routes/admin-forms.server.routes.js b/src/app/routes/admin-forms.server.routes.js index 6087329886..7f43732e79 100644 --- a/src/app/routes/admin-forms.server.routes.js +++ b/src/app/routes/admin-forms.server.routes.js @@ -18,6 +18,7 @@ const EncryptSubmissionController = require('../modules/submission/encrypt-submi const { PermissionLevel, } = require('../modules/form/admin-form/admin-form.types') +const EncryptSubmissionMiddleware = require('../modules/submission/encrypt-submission/encrypt-submission.middleware') const SpcpController = require('../modules/spcp/spcp.controller') const { BasicField, ResponseMode } = require('../../types') @@ -476,8 +477,6 @@ module.exports = function (app) { * @security OTP */ app.route('/v2/submissions/encrypt/preview/:formId([a-fA-F0-9]{24})').post( - authActiveForm(PermissionLevel.Read), - encryptSubmissions.validateEncryptSubmission, celebrate({ [Segments.BODY]: Joi.object({ responses: Joi.array() @@ -522,6 +521,8 @@ module.exports = function (app) { version: Joi.number().required(), }), }), + authActiveForm(PermissionLevel.Read), + EncryptSubmissionMiddleware.validateAndProcessEncryptSubmission, AdminFormController.passThroughSpcp, submissions.injectAutoReplyInfo, webhookVerifiedContentFactory.encryptedVerifiedFields, diff --git a/src/app/utils/encryption.ts b/src/app/utils/encryption.ts index 8256a11ce7..080d469fab 100644 --- a/src/app/utils/encryption.ts +++ b/src/app/utils/encryption.ts @@ -5,7 +5,7 @@ import { InvalidEncodingError } from '../modules/submission/submission.errors' export const checkIsEncryptedEncoding = ( encryptedStr: string, -): Result => { +): Result => { // TODO (#42): Remove this type check once whole backend is in TypeScript. if (typeof encryptedStr !== 'string') { return err(new InvalidEncodingError('encryptedStr is not of type `string`')) diff --git a/src/app/utils/field-validation/validators/common.ts b/src/app/utils/field-validation/validators/common.ts index 9fc4da180f..1eddc822b4 100644 --- a/src/app/utils/field-validation/validators/common.ts +++ b/src/app/utils/field-validation/validators/common.ts @@ -1,9 +1,9 @@ import { left, right } from 'fp-ts/lib/Either' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' -export const notEmptySingleAnswerResponse: ResponseValidator = ( +export const notEmptySingleAnswerResponse: ResponseValidator = ( response, ) => { if (response.answer.trim().length === 0) diff --git a/src/app/utils/field-validation/validators/dateValidator.ts b/src/app/utils/field-validation/validators/dateValidator.ts index a9abcd380b..b2c447bb75 100644 --- a/src/app/utils/field-validation/validators/dateValidator.ts +++ b/src/app/utils/field-validation/validators/dateValidator.ts @@ -2,15 +2,15 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' import moment from 'moment-timezone' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { IDateField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { DateSelectedValidation } from '../../../../shared/constants' import { notEmptySingleAnswerResponse } from './common' -type DateValidator = ResponseValidator +type DateValidator = ResponseValidator type DateValidatorConstructor = (dateField: IDateField) => DateValidator /** diff --git a/src/app/utils/field-validation/validators/decimalValidator.ts b/src/app/utils/field-validation/validators/decimalValidator.ts index cb49790dce..294b8fbd62 100644 --- a/src/app/utils/field-validation/validators/decimalValidator.ts +++ b/src/app/utils/field-validation/validators/decimalValidator.ts @@ -3,13 +3,13 @@ import { flow } from 'fp-ts/lib/function' import isFloat from 'validator/lib/isFloat' import isInt from 'validator/lib/isInt' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { IDecimalField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { notEmptySingleAnswerResponse } from './common' -type DecimalValidator = ResponseValidator +type DecimalValidator = ResponseValidator type DecimalValidatorConstructor = ( decimalField: IDecimalField, ) => DecimalValidator diff --git a/src/app/utils/field-validation/validators/homeNoValidator.ts b/src/app/utils/field-validation/validators/homeNoValidator.ts index dd5696d242..fcf53078d1 100644 --- a/src/app/utils/field-validation/validators/homeNoValidator.ts +++ b/src/app/utils/field-validation/validators/homeNoValidator.ts @@ -1,9 +1,9 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { IHomenoField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { isHomePhoneNumber, @@ -12,7 +12,7 @@ import { import { notEmptySingleAnswerResponse } from './common' -type HomeNoValidator = ResponseValidator +type HomeNoValidator = ResponseValidator type HomeNoValidatorConstructor = ( homeNumberField: IHomenoField, ) => HomeNoValidator diff --git a/src/app/utils/field-validation/validators/mobileNoValidator.ts b/src/app/utils/field-validation/validators/mobileNoValidator.ts index 41ade8dc62..1637882ffe 100644 --- a/src/app/utils/field-validation/validators/mobileNoValidator.ts +++ b/src/app/utils/field-validation/validators/mobileNoValidator.ts @@ -1,9 +1,9 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { IMobileField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { isMobilePhoneNumber, @@ -12,7 +12,7 @@ import { import { notEmptySingleAnswerResponse } from './common' -type MobileNoValidator = ResponseValidator +type MobileNoValidator = ResponseValidator type MobileNoValidatorConstructor = ( mobileNumberField: IMobileField, ) => MobileNoValidator diff --git a/src/app/utils/field-validation/validators/nricValidator.ts b/src/app/utils/field-validation/validators/nricValidator.ts index 75e5a8f8ba..7385a5617a 100644 --- a/src/app/utils/field-validation/validators/nricValidator.ts +++ b/src/app/utils/field-validation/validators/nricValidator.ts @@ -1,14 +1,14 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { isNricValid } from '../../../../shared/util/nric-validation' import { notEmptySingleAnswerResponse } from './common' -type NricValidator = ResponseValidator +type NricValidator = ResponseValidator type NricValidatorConstructor = () => NricValidator const nricValidator: NricValidator = (response) => { diff --git a/src/app/utils/field-validation/validators/radioButtonValidator.ts b/src/app/utils/field-validation/validators/radioButtonValidator.ts index 0a6ec426e7..2664b84bf9 100644 --- a/src/app/utils/field-validation/validators/radioButtonValidator.ts +++ b/src/app/utils/field-validation/validators/radioButtonValidator.ts @@ -1,14 +1,14 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { IRadioField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { notEmptySingleAnswerResponse } from './common' import { isOneOfOptions, isOtherOption } from './options' -type RadioButtonValidator = ResponseValidator +type RadioButtonValidator = ResponseValidator type RadioButtonValidatorConstructor = ( radioButtonField: IRadioField, ) => RadioButtonValidator diff --git a/src/app/utils/field-validation/validators/ratingValidator.ts b/src/app/utils/field-validation/validators/ratingValidator.ts index 2aa40d4cad..69b6c6b6fd 100644 --- a/src/app/utils/field-validation/validators/ratingValidator.ts +++ b/src/app/utils/field-validation/validators/ratingValidator.ts @@ -2,13 +2,13 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' import isInt from 'validator/lib/isInt' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { IRatingField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { notEmptySingleAnswerResponse } from './common' -type RatingValidator = ResponseValidator +type RatingValidator = ResponseValidator type RatingValidatorConstructor = (ratingField: IRatingField) => RatingValidator const makeRatingLimitsValidator: RatingValidatorConstructor = (ratingField) => ( diff --git a/src/app/utils/field-validation/validators/textValidator.ts b/src/app/utils/field-validation/validators/textValidator.ts index e723a2aeb8..758e3554e8 100644 --- a/src/app/utils/field-validation/validators/textValidator.ts +++ b/src/app/utils/field-validation/validators/textValidator.ts @@ -1,9 +1,9 @@ import { chain, left, right } from 'fp-ts/lib/Either' import { flow } from 'fp-ts/lib/function' +import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { ILongTextField, IShortTextField } from 'src/types/field' import { ResponseValidator } from 'src/types/field/utils/validation' -import { ISingleAnswerResponse } from 'src/types/response' import { TextSelectedValidation } from '../../../../types/field/baseField' @@ -11,7 +11,7 @@ import { notEmptySingleAnswerResponse } from './common' type TextFieldValidatorConstructor = ( textField: IShortTextField | ILongTextField, -) => ResponseValidator +) => ResponseValidator const minLengthValidator: TextFieldValidatorConstructor = (textField) => ( response, diff --git a/src/types/express.locals.ts b/src/types/express.locals.ts index 0c72a6af2c..5af57c4f31 100644 --- a/src/types/express.locals.ts +++ b/src/types/express.locals.ts @@ -1,5 +1,7 @@ // TODO (#42): remove these types when migrating away from middleware pattern +import { ProcessedFieldResponse } from '../app/modules/submission/submission.types' + import { IPopulatedForm } from './form' import { SpcpSession } from './spcp' @@ -7,6 +9,10 @@ export type WithForm = T & { form: IPopulatedForm } +export type WithParsedResponses = T & { + parsedResponses: ProcessedFieldResponse[] +} + export type ResWithSpcpSession = T & { locals: { spcpSession?: SpcpSession } } diff --git a/src/types/response/index.ts b/src/types/response/index.ts index 558912ab9e..00af308ee9 100644 --- a/src/types/response/index.ts +++ b/src/types/response/index.ts @@ -5,8 +5,9 @@ export type AttachmentsMap = Record export interface IBaseResponse { _id: IFieldSchema['_id'] fieldType: BasicField - question: string myInfo?: IMyInfo + // Signature exists for verifiable fields if the answer is verified. + signature?: string } export interface ISingleAnswerResponse extends IBaseResponse { diff --git a/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js b/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js index 895af7562e..3858c13414 100644 --- a/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js +++ b/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js @@ -4,6 +4,7 @@ const express = require('express') const request = require('supertest') const dbHandler = require('../helpers/db-handler') +const EncryptSubmissionsMiddleware = require('../../../../dist/backend/app/modules/submission/encrypt-submission/encrypt-submission.middleware') const User = dbHandler.makeModel('user.server.model', 'User') const Agency = dbHandler.makeModel('agency.server.model', 'Agency') @@ -141,7 +142,7 @@ describe('Encrypt Submissions Controller', () => { }) } - describe('validateEncryptSubmission', () => { + describe('validateAndProcessEncryptSubmission', () => { const app = express() beforeAll(() => { @@ -149,7 +150,7 @@ describe('Encrypt Submissions Controller', () => { .route(endpointPath) .post( injectFixtures, - Controller.validateEncryptSubmission, + EncryptSubmissionsMiddleware.validateAndProcessEncryptSubmission, sendSubmissionBack, ) }) From d265c6627f0ef539906f786049ae166c4d3979ee Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 16 Dec 2020 04:51:30 +0200 Subject: [PATCH 07/37] fix: upgrade angular-ui-router from 1.0.26 to 1.0.28 (#868) Snyk has created this PR to upgrade angular-ui-router from 1.0.26 to 1.0.28. See this package in npm: https://www.npmjs.com/package/angular-ui-router See this project in Snyk: https://app.snyk.io/org/open-government-products/project/4c4f5244-5ce5-491a-a42d-c295bc7f51fd?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 940e288e0a..73f97ac6d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4639,9 +4639,9 @@ } }, "@uirouter/core": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.5.tgz", - "integrity": "sha512-WN2HyELsxvqLaCMYCWojGW/QLRBgFBfwyERp5jC3Vn1njo/FLI5hplVqWMu6LIGp6Hpz3pbdtSaiFb1iY7iTVw==" + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.6.tgz", + "integrity": "sha512-+i1iNaPQRp1JoEnxi4EsTX30bSvkWtPEenENWdvdIzX615Q3WCWwnbW9DtrwX9gSD5qmWEaQEtGMU0uF3qJ+1g==" }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -5046,11 +5046,11 @@ "integrity": "sha512-yzcHpPMLQl0232nDzm5P4iAFTFQ9dMw0QgFLuKYbDj9M0xJ62z0oudYD/Lvh1pWfRsukiytP4Xj6BHOSrSXP8A==" }, "angular-ui-router": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/angular-ui-router/-/angular-ui-router-1.0.26.tgz", - "integrity": "sha512-jg5nohfbfSujxdQXcAWZCYG3R1xG9OSlUwa/wsEGGZ06met5rkZopcysc4dq0tcCZToL8CxwBgLyqldTvbXfjw==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/angular-ui-router/-/angular-ui-router-1.0.28.tgz", + "integrity": "sha512-BhcaW/ac+6/Ps9to9tHEuCG3LHgatD5mL0a6WRie4mBxdOjaEqNMB6SIB0l7qNpJMhMsi3m2SRMknk52rZCQQg==", "requires": { - "@uirouter/core": "6.0.5" + "@uirouter/core": "6.0.6" } }, "ansi-colors": { diff --git a/package.json b/package.json index 36fbc0d078..dd1ec53abb 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "angular-translate": "^2.18.3", "angular-translate-loader-partial": "^2.18.3", "angular-ui-bootstrap": "~2.5.6", - "angular-ui-router": "~1.0.22", + "angular-ui-router": "~1.0.28", "await-to-js": "^2.1.1", "aws-info": "^1.1.0", "aws-sdk": "^2.805.0", From 811ab92e1f44b5ee433f50857621b8f5c3d62c7b Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Wed, 16 Dec 2020 11:14:28 +0800 Subject: [PATCH 08/37] feat: add FIXED_LINE_OR_MOBILE numbers to pass homeno validation (#886) --- src/shared/util/phone-num-validation.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shared/util/phone-num-validation.ts b/src/shared/util/phone-num-validation.ts index ec80919053..dbf151ea2e 100644 --- a/src/shared/util/phone-num-validation.ts +++ b/src/shared/util/phone-num-validation.ts @@ -59,10 +59,17 @@ export const isMobilePhoneNumber = (mobileNumber: string): boolean => { */ export const isHomePhoneNumber = (phoneNum: string): boolean => { const parsedNumber = parsePhoneNumberFromString(phoneNum) - if (!parsedNumber) return false - return isPhoneNumber(phoneNum) && 'FIXED_LINE' === parsedNumber.getType() + const parsedType = parsedNumber.getType() + if (!parsedType) return false + + return ( + isPhoneNumber(phoneNum) && + // Have to include both FIXED_LINE, FIXED_LINE_OR_MOBILE as some countries lump + // the types together. + ['FIXED_LINE', 'FIXED_LINE_OR_MOBILE'].includes(parsedType) + ) } export const startsWithSgPrefix = (mobileNumber: string): boolean => { From acc349f1f6412b2ed9628cc4cda9e1ccee02884d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Dec 2020 18:16:29 +0800 Subject: [PATCH 09/37] fix(deps): bump uuid from 8.3.1 to 8.3.2 (#892) Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.1 to 8.3.2. - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v8.3.1...v8.3.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73f97ac6d4..8d13002e9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24287,9 +24287,9 @@ } }, "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.1.1", diff --git a/package.json b/package.json index dd1ec53abb..c608b1fe06 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "twilio": "^3.52.0", "ui-select": "^0.19.8", "uid-generator": "^2.0.0", - "uuid": "^8.3.1", + "uuid": "^8.3.2", "validator": "^13.5.2", "web-streams-polyfill": "^2.1.1", "whatwg-fetch": "^3.5.0", From b0e009dda3c6f229a387fff57059925e75988d75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Dec 2020 18:16:55 +0800 Subject: [PATCH 10/37] chore(deps-dev): bump lint-staged from 10.5.2 to 10.5.3 (#893) Bumps [lint-staged](https://github.com/okonet/lint-staged) from 10.5.2 to 10.5.3. - [Release notes](https://github.com/okonet/lint-staged/releases) - [Commits](https://github.com/okonet/lint-staged/compare/v10.5.2...v10.5.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 49 +++++++++-------------------------------------- package.json | 2 +- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d13002e9b..31f4e24855 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15468,9 +15468,9 @@ "dev": true }, "lint-staged": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.2.tgz", - "integrity": "sha512-e8AYR1TDlzwB8VVd38Xu2lXDZf6BcshVqKVuBQThDJRaJLobqKnpbm4dkwJ2puypQNbLr9KF/9mfA649mAGvjA==", + "version": "10.5.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.3.tgz", + "integrity": "sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -15525,24 +15525,11 @@ "dev": true }, "commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -15601,30 +15588,12 @@ "path-key": "^3.0.0" } }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15670,9 +15639,9 @@ } }, "listr2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.2.tgz", - "integrity": "sha512-AajqcZEUikF2ioph6PfH3dIuxJclhr3i3kHgTOP0xeXdWQohrvJAAmqVcV43/GI987HFY/vzT73jYXoa4esDHg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.2.3.tgz", + "integrity": "sha512-vUb80S2dSUi8YxXahO8/I/s29GqnOL8ozgHVLjfWQXa03BNEeS1TpBLjh2ruaqq5ufx46BRGvfymdBSuoXET5w==", "dev": true, "requires": { "chalk": "^4.1.0", diff --git a/package.json b/package.json index c608b1fe06..77849dacff 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,7 @@ "jasmine-sinon": "^0.4.0", "jasmine-spec-reporter": "^6.0.0", "jest": "^26.6.3", - "lint-staged": "^10.5.2", + "lint-staged": "^10.5.3", "maildev": "^1.1.0", "mini-css-extract-plugin": "^0.5.0", "mockdate": "^3.0.2", From 12ff86c3bc775243ef279829aa2882cc67722e01 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Thu, 17 Dec 2020 13:09:39 +0800 Subject: [PATCH 11/37] refactor: inline form permissions check for presigned POST URL endpoints (#863) --- .../__tests__/admin-form.controller.spec.ts | 354 ++++++++++++++++-- .../__tests__/admin-form.service.spec.ts | 32 +- .../form/admin-form/admin-form.controller.ts | 116 ++++-- .../form/admin-form/admin-form.service.ts | 70 ++-- src/app/routes/admin-forms.server.routes.js | 8 +- 5 files changed, 466 insertions(+), 114 deletions(-) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts index 0c3943a00f..23dab6e2b2 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts @@ -550,8 +550,27 @@ describe('admin-form.controller', () => { }) }) - describe('handleCreatePresignedPostForImages', () => { + describe('handleCreatePresignedPostUrlForImages', () => { + const MOCK_USER_ID = new ObjectId().toHexString() + const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_USER = { + _id: MOCK_USER_ID, + email: 'somerandom@example.com', + } as IPopulatedUser + const MOCK_FORM = { + admin: MOCK_USER, + _id: MOCK_FORM_ID, + title: 'mock title', + } as IPopulatedForm const MOCK_REQ = expressHandler.mockRequest({ + params: { + formId: MOCK_FORM_ID, + }, + session: { + user: { + _id: MOCK_USER_ID, + }, + }, body: { fileId: 'any file id', fileMd5Hash: 'any hash', @@ -559,9 +578,16 @@ describe('admin-form.controller', () => { }, }) - it('should return 200 with presigned POST object when successful', async () => { + it('should return 200 with presigned POST URL object when successful', async () => { // Arrange const mockRes = expressHandler.mockResponse() + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + okAsync(MOCK_FORM), + ) const expectedPresignedPost: PresignedPost = { fields: { 'X-Amz-Signature': 'some-amz-signature', @@ -569,12 +595,12 @@ describe('admin-form.controller', () => { }, url: 'some url', } - MockAdminFormService.createPresignedPostForImages.mockReturnValueOnce( + MockAdminFormService.createPresignedPostUrlForImages.mockReturnValueOnce( okAsync(expectedPresignedPost), ) // Act - await AdminFormController.handleCreatePresignedPostForImages( + await AdminFormController.handleCreatePresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -584,17 +610,24 @@ describe('admin-form.controller', () => { expect(mockRes.json).toHaveBeenCalledWith(expectedPresignedPost) }) - it('should return 400 when InvalidFileTypeError is returned when creating presigned POST', async () => { + it('should return 400 when InvalidFileTypeError is returned when creating presigned POST URL', async () => { // Arrange + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + okAsync(MOCK_FORM), + ) // Mock error const mockErrorString = 'bad file type, bad!' const mockRes = expressHandler.mockResponse() - MockAdminFormService.createPresignedPostForImages.mockReturnValueOnce( + MockAdminFormService.createPresignedPostUrlForImages.mockReturnValueOnce( errAsync(new InvalidFileTypeError(mockErrorString)), ) // Act - await AdminFormController.handleCreatePresignedPostForImages( + await AdminFormController.handleCreatePresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -607,17 +640,24 @@ describe('admin-form.controller', () => { }) }) - it('should return 400 when CreatePresignedUrlError is returned when creating presigned POST', async () => { + it('should return 400 when CreatePresignedUrlError is returned when creating presigned POST URL', async () => { // Arrange + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + okAsync(MOCK_FORM), + ) // Mock error - const mockErrorString = 'creating presigned post failed, oh no' + const mockErrorString = 'creating presigned post url failed, oh no' const mockRes = expressHandler.mockResponse() - MockAdminFormService.createPresignedPostForImages.mockReturnValueOnce( + MockAdminFormService.createPresignedPostUrlForImages.mockReturnValueOnce( errAsync(new CreatePresignedUrlError(mockErrorString)), ) // Act - await AdminFormController.handleCreatePresignedPostForImages( + await AdminFormController.handleCreatePresignedPostUrlForImages( MOCK_REQ, mockRes, jest.fn(), @@ -629,10 +669,143 @@ describe('admin-form.controller', () => { message: mockErrorString, }) }) + + it('should return 403 when user does not have write permissions to form', async () => { + // Arrange + const expectedErrorString = 'no write permissions' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + errAsync(new ForbiddenFormError(expectedErrorString)), + ) + const mockRes = expressHandler.mockResponse() + + // Act + await AdminFormController.handleCreatePresignedPostUrlForImages( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(403) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect( + MockAdminFormService.createPresignedPostUrlForImages, + ).not.toHaveBeenCalled() + }) + + it('should return 404 when form cannot be found', async () => { + // Arrange + const expectedErrorString = 'no form found' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + errAsync(new FormNotFoundError(expectedErrorString)), + ) + const mockRes = expressHandler.mockResponse() + + // Act + await AdminFormController.handleCreatePresignedPostUrlForImages( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(404) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect( + MockAdminFormService.createPresignedPostUrlForImages, + ).not.toHaveBeenCalled() + }) + + it('should return 410 when form is archived', async () => { + // Arrange + const expectedErrorString = 'form deleted' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + errAsync(new FormDeletedError(expectedErrorString)), + ) + const mockRes = expressHandler.mockResponse() + + // Act + await AdminFormController.handleCreatePresignedPostUrlForImages( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(410) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect( + MockAdminFormService.createPresignedPostUrlForImages, + ).not.toHaveBeenCalled() + }) + + it('should return 422 when MissingUserError is returned when retrieving logged in user', async () => { + // Arrange + const mockRes = expressHandler.mockResponse() + // Mock various services to return expected results. + const expectedErrorString = 'user is not found' + MockUserService.getPopulatedUserById.mockReturnValueOnce( + errAsync(new MissingUserError(expectedErrorString)), + ) + + // Act + await AdminFormController.handleCreatePresignedPostUrlForImages( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(422) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect(AuthService.getFormAfterPermissionChecks).not.toHaveBeenCalled() + expect( + MockAdminFormService.createPresignedPostUrlForImages, + ).not.toHaveBeenCalled() + }) }) - describe('handleCreatePresignedPostForLogos', () => { + describe('handleCreatePresignedPostUrlForLogos', () => { + const MOCK_USER_ID = new ObjectId().toHexString() + const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_USER = { + _id: MOCK_USER_ID, + email: 'somerandom@example.com', + } as IPopulatedUser + const MOCK_FORM = { + admin: MOCK_USER, + _id: MOCK_FORM_ID, + title: 'mock title', + } as IPopulatedForm const MOCK_REQ = expressHandler.mockRequest({ + params: { + formId: MOCK_FORM_ID, + }, + session: { + user: { + _id: MOCK_USER_ID, + }, + }, body: { fileId: 'any file id', fileMd5Hash: 'any hash', @@ -640,9 +813,16 @@ describe('admin-form.controller', () => { }, }) - it('should return 200 with presigned POST object when successful', async () => { + it('should return 200 with presigned POST URL object when successful', async () => { // Arrange const mockRes = expressHandler.mockResponse() + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + okAsync(MOCK_FORM), + ) const expectedPresignedPost: PresignedPost = { fields: { 'X-Amz-Signature': 'some-amz-signature', @@ -650,12 +830,12 @@ describe('admin-form.controller', () => { }, url: 'some url', } - MockAdminFormService.createPresignedPostForLogos.mockReturnValueOnce( + MockAdminFormService.createPresignedPostUrlForLogos.mockReturnValueOnce( okAsync(expectedPresignedPost), ) // Act - await AdminFormController.handleCreatePresignedPostForLogos( + await AdminFormController.handleCreatePresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -665,17 +845,24 @@ describe('admin-form.controller', () => { expect(mockRes.json).toHaveBeenCalledWith(expectedPresignedPost) }) - it('should return 400 when InvalidFileTypeError is returned when creating presigned POST', async () => { + it('should return 400 when InvalidFileTypeError is returned when creating presigned POST URL', async () => { // Arrange // Mock error const mockErrorString = 'bad file type, bad!' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + okAsync(MOCK_FORM), + ) const mockRes = expressHandler.mockResponse() - MockAdminFormService.createPresignedPostForLogos.mockReturnValueOnce( + MockAdminFormService.createPresignedPostUrlForLogos.mockReturnValueOnce( errAsync(new InvalidFileTypeError(mockErrorString)), ) // Act - await AdminFormController.handleCreatePresignedPostForLogos( + await AdminFormController.handleCreatePresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -688,17 +875,24 @@ describe('admin-form.controller', () => { }) }) - it('should return 400 when CreatePresignedUrlError is returned when creating presigned POST', async () => { + it('should return 400 when CreatePresignedUrlError is returned when creating presigned POST URL', async () => { // Arrange - // Mock error - const mockErrorString = 'creating presigned post failed, oh no' const mockRes = expressHandler.mockResponse() - MockAdminFormService.createPresignedPostForLogos.mockReturnValueOnce( + // Mock error + const mockErrorString = 'creating presigned post url failed, oh no' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + okAsync(MOCK_FORM), + ) + MockAdminFormService.createPresignedPostUrlForLogos.mockReturnValueOnce( errAsync(new CreatePresignedUrlError(mockErrorString)), ) // Act - await AdminFormController.handleCreatePresignedPostForLogos( + await AdminFormController.handleCreatePresignedPostUrlForLogos( MOCK_REQ, mockRes, jest.fn(), @@ -710,6 +904,120 @@ describe('admin-form.controller', () => { message: mockErrorString, }) }) + + it('should return 403 when user does not have write permissions to form', async () => { + // Arrange + const expectedErrorString = 'no write permissions' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + errAsync(new ForbiddenFormError(expectedErrorString)), + ) + const mockRes = expressHandler.mockResponse() + + // Act + await AdminFormController.handleCreatePresignedPostUrlForLogos( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(403) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect( + MockAdminFormService.createPresignedPostUrlForLogos, + ).not.toHaveBeenCalled() + }) + + it('should return 404 when form cannot be found', async () => { + // Arrange + const expectedErrorString = 'no form found' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + errAsync(new FormNotFoundError(expectedErrorString)), + ) + const mockRes = expressHandler.mockResponse() + + // Act + await AdminFormController.handleCreatePresignedPostUrlForLogos( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(404) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect( + MockAdminFormService.createPresignedPostUrlForLogos, + ).not.toHaveBeenCalled() + }) + + it('should return 410 when form is archived', async () => { + // Arrange + const expectedErrorString = 'form deleted' + // Mock various services to return expected results. + MockUserService.getPopulatedUserById.mockReturnValueOnce( + okAsync(MOCK_USER), + ) + MockAuthService.getFormAfterPermissionChecks.mockReturnValueOnce( + errAsync(new FormDeletedError(expectedErrorString)), + ) + const mockRes = expressHandler.mockResponse() + + // Act + await AdminFormController.handleCreatePresignedPostUrlForLogos( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(410) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect( + MockAdminFormService.createPresignedPostUrlForLogos, + ).not.toHaveBeenCalled() + }) + + it('should return 422 when MissingUserError is returned when retrieving logged in user', async () => { + // Arrange + const mockRes = expressHandler.mockResponse() + // Mock various services to return expected results. + const expectedErrorString = 'user is not found' + MockUserService.getPopulatedUserById.mockReturnValueOnce( + errAsync(new MissingUserError(expectedErrorString)), + ) + + // Act + await AdminFormController.handleCreatePresignedPostUrlForLogos( + MOCK_REQ, + mockRes, + jest.fn(), + ) + + // Assert + expect(mockRes.status).toHaveBeenCalledWith(422) + expect(mockRes.json).toHaveBeenCalledWith({ + message: expectedErrorString, + }) + expect(AuthService.getFormAfterPermissionChecks).not.toHaveBeenCalled() + expect( + MockAdminFormService.createPresignedPostUrlForLogos, + ).not.toHaveBeenCalled() + }) }) describe('handleCountFormSubmissions', () => { diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts index 69d03c2545..7533c327c8 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts @@ -39,8 +39,8 @@ import { import { archiveForm, createForm, - createPresignedPostForImages, - createPresignedPostForLogos, + createPresignedPostUrlForImages, + createPresignedPostUrlForLogos, duplicateForm, getDashboardForms, transferFormOwnership, @@ -134,10 +134,10 @@ describe('admin-form.service', () => { }) }) - describe('createPresignedPostForImages', () => { + describe('createPresignedPostUrlForImages', () => { it('should successfully create presigned POST URL', async () => { // Arrange - const expectedPresignedPost: PresignedPost = { + const expectedPresignedPostUrl: PresignedPost = { fields: { 'X-Amz-Signature': 'some-amz-signature', Policy: 'some policy', @@ -150,11 +150,11 @@ describe('admin-form.service', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce((_obj, cb) => { - cb(null, expectedPresignedPost) + cb(null, expectedPresignedPostUrl) }) // Act - const actualResult = await createPresignedPostForImages({ + const actualResult = await createPresignedPostUrlForImages({ fileId: 'any id', fileMd5Hash: 'any hash', fileType: VALID_UPLOAD_FILE_TYPES[0], @@ -169,7 +169,7 @@ describe('admin-form.service', () => { expect.any(Function), ) expect(actualResult.isOk()).toEqual(true) - expect(actualResult._unsafeUnwrap()).toEqual(expectedPresignedPost) + expect(actualResult._unsafeUnwrap()).toEqual(expectedPresignedPostUrl) }) it('should return InvalidFileTypeError when given file type is not supported', async () => { @@ -178,7 +178,7 @@ describe('admin-form.service', () => { expect(VALID_UPLOAD_FILE_TYPES.includes(invalidFileType)).toEqual(false) // Act - const actualResult = await createPresignedPostForImages({ + const actualResult = await createPresignedPostUrlForImages({ fileId: 'any id', fileMd5Hash: 'any hash', fileType: invalidFileType, @@ -203,7 +203,7 @@ describe('admin-form.service', () => { }) // Act - const actualResult = await createPresignedPostForImages({ + const actualResult = await createPresignedPostUrlForImages({ fileId: 'any id', fileMd5Hash: 'any hash', fileType: VALID_UPLOAD_FILE_TYPES[0], @@ -224,10 +224,10 @@ describe('admin-form.service', () => { }) }) - describe('createPresignedPostForLogos', () => { + describe('createPresignedPostUrlForLogos', () => { it('should successfully create presigned POST URL', async () => { // Arrange - const expectedPresignedPost: PresignedPost = { + const expectedPresignedPostUrl: PresignedPost = { fields: { 'X-Amz-Signature': 'some-amz-signature', Policy: 'some policy', @@ -240,11 +240,11 @@ describe('admin-form.service', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce((_obj, cb) => { - cb(null, expectedPresignedPost) + cb(null, expectedPresignedPostUrl) }) // Act - const actualResult = await createPresignedPostForLogos({ + const actualResult = await createPresignedPostUrlForLogos({ fileId: 'any id', fileMd5Hash: 'any hash', fileType: VALID_UPLOAD_FILE_TYPES[0], @@ -259,7 +259,7 @@ describe('admin-form.service', () => { expect.any(Function), ) expect(actualResult.isOk()).toEqual(true) - expect(actualResult._unsafeUnwrap()).toEqual(expectedPresignedPost) + expect(actualResult._unsafeUnwrap()).toEqual(expectedPresignedPostUrl) }) it('should return InvalidFileTypeError when given file type is not supported', async () => { @@ -268,7 +268,7 @@ describe('admin-form.service', () => { expect(VALID_UPLOAD_FILE_TYPES.includes(invalidFileType)).toEqual(false) // Act - const actualResult = await createPresignedPostForLogos({ + const actualResult = await createPresignedPostUrlForLogos({ fileId: 'any id', fileMd5Hash: 'any hash', fileType: invalidFileType, @@ -293,7 +293,7 @@ describe('admin-form.service', () => { }) // Act - const actualResult = await createPresignedPostForLogos({ + const actualResult = await createPresignedPostUrlForLogos({ fileId: 'any id', fileMd5Hash: 'any hash', fileType: VALID_UPLOAD_FILE_TYPES[0], diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts index 27c7fd9e2c..d28008f2ba 100644 --- a/src/app/modules/form/admin-form/admin-form.controller.ts +++ b/src/app/modules/form/admin-form/admin-form.controller.ts @@ -16,8 +16,8 @@ import { removePrivateDetailsFromForm } from '../form.utils' import { archiveForm, createForm, - createPresignedPostForImages, - createPresignedPostForLogos, + createPresignedPostUrlForImages, + createPresignedPostUrlForLogos, duplicateForm, getDashboardForms, getMockSpcpLocals, @@ -108,11 +108,15 @@ export const handleGetAdminForm: RequestHandler<{ formId: string }> = ( * Handler for POST /:formId([a-fA-F0-9]{24})/adminform/images. * @security session * - * @returns 200 with presigned POST object - * @returns 400 when error occurs whilst creating presigned POST object + * @returns 200 with presigned POST URL object + * @returns 400 when error occurs whilst creating presigned POST URL object + * @returns 403 when user does not have write permissions for form + * @returns 404 when form cannot be found + * @returns 410 when form is archived + * @returns 422 when user in session cannot be retrieved from the database */ -export const handleCreatePresignedPostForImages: RequestHandler< - ParamsDictionary, +export const handleCreatePresignedPostUrlForImages: RequestHandler< + { formId: string }, unknown, { fileId: string @@ -120,33 +124,54 @@ export const handleCreatePresignedPostForImages: RequestHandler< fileType: string } > = async (req, res) => { + const { formId } = req.params const { fileId, fileMd5Hash, fileType } = req.body + const sessionUserId = (req.session as Express.AuthedSession).user._id - return createPresignedPostForImages({ fileId, fileMd5Hash, fileType }) - .map((presignedPost) => res.json(presignedPost)) - .mapErr((error) => { - logger.error({ - message: 'Presigning post data encountered an error', - meta: { - action: 'handleCreatePresignedPostForImages', - ...createReqMeta(req), - }, - error, - }) + return ( + // Step 1: Retrieve currently logged in user. + UserService.getPopulatedUserById(sessionUserId) + .andThen((user) => + // Step 2: Check whether user has write permissions to form + AuthService.getFormAfterPermissionChecks({ + user, + formId, + level: PermissionLevel.Write, + }), + ) + // Step 3: Has write permissions, generate presigned POST URL. + .andThen(() => + createPresignedPostUrlForImages({ fileId, fileMd5Hash, fileType }), + ) + .map((presignedPostUrl) => res.json(presignedPostUrl)) + .mapErr((error) => { + logger.error({ + message: 'Presigning post data encountered an error', + meta: { + action: 'handleCreatePresignedPostUrlForImages', + ...createReqMeta(req), + }, + error, + }) - const { statusCode, errorMessage } = mapRouteError(error) - return res.status(statusCode).json({ message: errorMessage }) - }) + const { statusCode, errorMessage } = mapRouteError(error) + return res.status(statusCode).json({ message: errorMessage }) + }) + ) } /** * Handler for POST /:formId([a-fA-F0-9]{24})/adminform/logos. * @security session * - * @returns 200 with presigned POST object - * @returns 400 when error occurs whilst creating presigned POST object + * @returns 200 with presigned POST URL object + * @returns 400 when error occurs whilst creating presigned POST URL object + * @returns 403 when user does not have write permissions for form + * @returns 404 when form cannot be found + * @returns 410 when form is archived + * @returns 422 when user in session cannot be retrieved from the database */ -export const handleCreatePresignedPostForLogos: RequestHandler< +export const handleCreatePresignedPostUrlForLogos: RequestHandler< ParamsDictionary, unknown, { @@ -155,23 +180,40 @@ export const handleCreatePresignedPostForLogos: RequestHandler< fileType: string } > = async (req, res) => { + const { formId } = req.params const { fileId, fileMd5Hash, fileType } = req.body + const sessionUserId = (req.session as Express.AuthedSession).user._id - return createPresignedPostForLogos({ fileId, fileMd5Hash, fileType }) - .map((presignedPost) => res.json(presignedPost)) - .mapErr((error) => { - logger.error({ - message: 'Presigning post data encountered an error', - meta: { - action: 'handleCreatePresignedPostForLogos', - ...createReqMeta(req), - }, - error, - }) + return ( + // Step 1: Retrieve currently logged in user. + UserService.getPopulatedUserById(sessionUserId) + .andThen((user) => + // Step 2: Check whether user has write permissions to form + AuthService.getFormAfterPermissionChecks({ + user, + formId, + level: PermissionLevel.Write, + }), + ) + // Step 3: Has write permissions, generate presigned POST URL. + .andThen(() => + createPresignedPostUrlForLogos({ fileId, fileMd5Hash, fileType }), + ) + .map((presignedPostUrl) => res.json(presignedPostUrl)) + .mapErr((error) => { + logger.error({ + message: 'Presigning post data encountered an error', + meta: { + action: 'handleCreatePresignedPostUrlForLogos', + ...createReqMeta(req), + }, + error, + }) - const { statusCode, errorMessage } = mapRouteError(error) - return res.status(statusCode).json({ message: errorMessage }) - }) + const { statusCode, errorMessage } = mapRouteError(error) + return res.status(statusCode).json({ message: errorMessage }) + }) + ) } /** diff --git a/src/app/modules/form/admin-form/admin-form.service.ts b/src/app/modules/form/admin-form/admin-form.service.ts index b6c6344dc4..9a448991cb 100644 --- a/src/app/modules/form/admin-form/admin-form.service.ts +++ b/src/app/modules/form/admin-form/admin-form.service.ts @@ -47,7 +47,7 @@ import { processDuplicateOverrideProps } from './admin-form.utils' const logger = createLoggerWithLabel(module) const FormModel = getFormModel(mongoose) -type PresignedPostParams = { +type PresignedPostUrlParams = { fileId: string fileMd5Hash: string fileType: string @@ -97,9 +97,9 @@ export const getDashboardForms = ( * @param param.fileMd5Hash the MD5 hash of the file * @param param.fileType the file type of the file */ -const createPresignedPost = ( +const createPresignedPostUrl = ( bucketName: string, - { fileId, fileMd5Hash, fileType }: PresignedPostParams, + { fileId, fileMd5Hash, fileType }: PresignedPostUrlParams, ): ResultAsync< PresignedPost, InvalidFileTypeError | CreatePresignedUrlError @@ -110,36 +110,38 @@ const createPresignedPost = ( ) } - const presignedPostPromise = new Promise((resolve, reject) => { - AwsConfig.s3.createPresignedPost( - { - Bucket: bucketName, - Expires: PRESIGNED_POST_EXPIRY_SECS, - Conditions: [ - // Content length restrictions: 0 to MAX_UPLOAD_FILE_SIZE. - ['content-length-range', 0, MAX_UPLOAD_FILE_SIZE], - ], - Fields: { - acl: 'public-read', - key: fileId, - 'Content-MD5': fileMd5Hash, - 'Content-Type': fileType, + const presignedPostUrlPromise = new Promise( + (resolve, reject) => { + AwsConfig.s3.createPresignedPost( + { + Bucket: bucketName, + Expires: PRESIGNED_POST_EXPIRY_SECS, + Conditions: [ + // Content length restrictions: 0 to MAX_UPLOAD_FILE_SIZE. + ['content-length-range', 0, MAX_UPLOAD_FILE_SIZE], + ], + Fields: { + acl: 'public-read', + key: fileId, + 'Content-MD5': fileMd5Hash, + 'Content-Type': fileType, + }, }, - }, - (err, data) => { - if (err) { - return reject(err) - } - return resolve(data) - }, - ) - }) + (err, data) => { + if (err) { + return reject(err) + } + return resolve(data) + }, + ) + }, + ) - return ResultAsync.fromPromise(presignedPostPromise, (error) => { + return ResultAsync.fromPromise(presignedPostUrlPromise, (error) => { logger.error({ message: 'Error encountered when creating presigned POST URL', meta: { - action: 'createPresignedPost', + action: 'createPresignedPostUrl', fileId, fileMd5Hash, fileType, @@ -162,13 +164,13 @@ const createPresignedPost = ( * @returns err(InvalidFileTypeError) when given file type is not supported * @returns err(CreatePresignedUrlError) when errors occurs on S3 side whilst creating presigned post url. */ -export const createPresignedPostForImages = ( - uploadParams: PresignedPostParams, +export const createPresignedPostUrlForImages = ( + uploadParams: PresignedPostUrlParams, ): ResultAsync< PresignedPost, InvalidFileTypeError | CreatePresignedUrlError > => { - return createPresignedPost(AwsConfig.imageS3Bucket, uploadParams) + return createPresignedPostUrl(AwsConfig.imageS3Bucket, uploadParams) } /** @@ -182,13 +184,13 @@ export const createPresignedPostForImages = ( * @returns err(InvalidFileTypeError) when given file type is not supported * @returns err(CreatePresignedUrlError) when errors occurs on S3 side whilst creating presigned post url. */ -export const createPresignedPostForLogos = ( - uploadParams: PresignedPostParams, +export const createPresignedPostUrlForLogos = ( + uploadParams: PresignedPostUrlParams, ): ResultAsync< PresignedPost, InvalidFileTypeError | CreatePresignedUrlError > => { - return createPresignedPost(AwsConfig.logoS3Bucket, uploadParams) + return createPresignedPostUrl(AwsConfig.logoS3Bucket, uploadParams) } export const getMockSpcpLocals = ( diff --git a/src/app/routes/admin-forms.server.routes.js b/src/app/routes/admin-forms.server.routes.js index 7f43732e79..2ed841c76f 100644 --- a/src/app/routes/admin-forms.server.routes.js +++ b/src/app/routes/admin-forms.server.routes.js @@ -645,6 +645,7 @@ module.exports = function (app) { * @security OTP */ app.route('/:formId([a-fA-F0-9]{24})/adminform/images').post( + withUserAuthentication, celebrate({ [Segments.BODY]: { fileId: Joi.string() @@ -659,8 +660,7 @@ module.exports = function (app) { .error(() => 'Error - your file could not be verified'), }, }), - authActiveForm(PermissionLevel.Write), - AdminFormController.handleCreatePresignedPostForImages, + AdminFormController.handleCreatePresignedPostUrlForImages, ) /** @@ -673,6 +673,7 @@ module.exports = function (app) { * @security OTP */ app.route('/:formId([a-fA-F0-9]{24})/adminform/logos').post( + withUserAuthentication, celebrate({ [Segments.BODY]: { fileId: Joi.string() @@ -687,7 +688,6 @@ module.exports = function (app) { .error(() => 'Error - your file could not be verified'), }, }), - authActiveForm(PermissionLevel.Write), - AdminFormController.handleCreatePresignedPostForLogos, + AdminFormController.handleCreatePresignedPostUrlForLogos, ) } From bea640c6a19c261897cfc8488231307c7e952164 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 17 Dec 2020 11:47:59 +0200 Subject: [PATCH 12/37] fix: upgrade fp-ts from 2.8.6 to 2.9.0 (#896) Snyk has created this PR to upgrade fp-ts from 2.8.6 to 2.9.0. See this package in npm: https://www.npmjs.com/package/fp-ts See this project in Snyk: https://app.snyk.io/org/open-government-products/project/4c4f5244-5ce5-491a-a42d-c295bc7f51fd?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31f4e24855..6e89d6c2d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11824,9 +11824,9 @@ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fp-ts": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.8.6.tgz", - "integrity": "sha512-fGGpKf/Jy3UT4s16oM+hr/8F5QXFcZ+20NAvaZXH5Y5jsiLPMDCaNqffXq0z1Kr6ZUJj0346cH9tq+cI2SoJ4w==" + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.9.0.tgz", + "integrity": "sha512-zYPaS+3BWy+hRhkCMJXLTHxdy7oshyKOfHge/S/n+LcLL5VIhctR9Xucxi3GwJbjwDZaz9IIbU1o+YjK0LsgYg==" }, "fragment-cache": { "version": "0.2.1", diff --git a/package.json b/package.json index 77849dacff..c3d872e290 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "file-loader": "^4.0.0", "file-saver": "^2.0.5", "font-awesome": "4.7.0", - "fp-ts": "^2.8.6", + "fp-ts": "^2.9.0", "has-ansi": "^4.0.0", "helmet": "^4.2.0", "http-status-codes": "^2.1.4", From ed7adc5edc17f307b2d9bfc21acaf5f7aba67b62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Dec 2020 17:48:26 +0800 Subject: [PATCH 13/37] fix(deps): bump @sentry/browser from 5.29.0 to 5.29.1 (#899) Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.29.0 to 5.29.1. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/5.29.0...5.29.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 103 +++++++++++++++++++++++++++++++++++----------- package.json | 2 +- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e89d6c2d1..2e9433edc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3604,36 +3604,84 @@ } }, "@sentry/browser": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.0.tgz", - "integrity": "sha512-kRlt1mE2wrYjspnIupNnPxqsUrRuy02SuXhbpP7J6uu8QasoEmJ78hk0hHz4jOZRmuWwfs2zIXD4tLGgWOKq8A==", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.1.tgz", + "integrity": "sha512-cVlXoQBJ64eNNkQuOB+bS6sK5KWV+Fw+ZYxT+XqjpeXkOPaxh8aeoi9CHz2DsFfbLV91P4AnXZEUdDl+7ktQNg==", "requires": { - "@sentry/core": "5.29.0", - "@sentry/types": "5.29.0", - "@sentry/utils": "5.29.0", + "@sentry/core": "5.29.1", + "@sentry/types": "5.29.1", + "@sentry/utils": "5.29.1", "tslib": "^1.9.3" + }, + "dependencies": { + "@sentry/types": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.1.tgz", + "integrity": "sha512-QXZBA1gJheMYTGFV+UUhr3+jKpGZqPx8kEJABs8htlKabCDJlEeoFNmeqPuVxCxukoy5ZaaHACoE+2Z87T0g2A==" + }, + "@sentry/utils": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.1.tgz", + "integrity": "sha512-FOhWxASvIQREAlSuWf3Vmb4uIkG0fmRdHkULpuv5dFmrMX2PpudYAppQtS8K9V4BYxFy6KFdUht1Qz5zYTecMw==", + "requires": { + "@sentry/types": "5.29.1", + "tslib": "^1.9.3" + } + } } }, "@sentry/core": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.0.tgz", - "integrity": "sha512-a1sZBJ2u3NG0YDlGvOTwUCWiNjhfmDtAQiKK1o6RIIbcrWy9TlSps7CYDkBP239Y3A4pnvohjEEKEP3v3L3LZQ==", - "requires": { - "@sentry/hub": "5.29.0", - "@sentry/minimal": "5.29.0", - "@sentry/types": "5.29.0", - "@sentry/utils": "5.29.0", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.1.tgz", + "integrity": "sha512-SMybIx9IlswkJ7a61ez/zjdiMdAo51Adpo4nVrzke2k84U/t726/EbJj0FJ4vVgsGdLCvSSZ6v7BQlINcwWupg==", + "requires": { + "@sentry/hub": "5.29.1", + "@sentry/minimal": "5.29.1", + "@sentry/types": "5.29.1", + "@sentry/utils": "5.29.1", "tslib": "^1.9.3" + }, + "dependencies": { + "@sentry/types": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.1.tgz", + "integrity": "sha512-QXZBA1gJheMYTGFV+UUhr3+jKpGZqPx8kEJABs8htlKabCDJlEeoFNmeqPuVxCxukoy5ZaaHACoE+2Z87T0g2A==" + }, + "@sentry/utils": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.1.tgz", + "integrity": "sha512-FOhWxASvIQREAlSuWf3Vmb4uIkG0fmRdHkULpuv5dFmrMX2PpudYAppQtS8K9V4BYxFy6KFdUht1Qz5zYTecMw==", + "requires": { + "@sentry/types": "5.29.1", + "tslib": "^1.9.3" + } + } } }, "@sentry/hub": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.0.tgz", - "integrity": "sha512-kcDPQsRG4cFdmqDh+TzjeO7lWYxU8s1dZYAbbl1J4uGKmhNB0J7I4ak4SGwTsXLY6fhbierxr6PRaoNojCxjPw==", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.1.tgz", + "integrity": "sha512-Ig/vqCiJcsnGaWajkWRFH+5IKeo50ZtsjM0zJb8IfTadLjQuF/gTQst0aXO3l6q4HzveeGsELY8jlm6WVcq9Aw==", "requires": { - "@sentry/types": "5.29.0", - "@sentry/utils": "5.29.0", + "@sentry/types": "5.29.1", + "@sentry/utils": "5.29.1", "tslib": "^1.9.3" + }, + "dependencies": { + "@sentry/types": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.1.tgz", + "integrity": "sha512-QXZBA1gJheMYTGFV+UUhr3+jKpGZqPx8kEJABs8htlKabCDJlEeoFNmeqPuVxCxukoy5ZaaHACoE+2Z87T0g2A==" + }, + "@sentry/utils": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.1.tgz", + "integrity": "sha512-FOhWxASvIQREAlSuWf3Vmb4uIkG0fmRdHkULpuv5dFmrMX2PpudYAppQtS8K9V4BYxFy6KFdUht1Qz5zYTecMw==", + "requires": { + "@sentry/types": "5.29.1", + "tslib": "^1.9.3" + } + } } }, "@sentry/integrations": { @@ -3648,13 +3696,20 @@ } }, "@sentry/minimal": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.0.tgz", - "integrity": "sha512-nhXofdjtO41/caiF1wk1oT3p/QuhOZDYdF/b29DoD2MiAMK9IjhhOXI/gqaRpDKkXlDvd95fDTcx4t/MqqcKXA==", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.1.tgz", + "integrity": "sha512-lAa3+Duxum1qQvR0tKiBUsH6Ehit3g/vO53SqBib7YK3qdvIUWHacmkJvfz/AeSvVnpJ9bsBMCVRJNSVe8BPVA==", "requires": { - "@sentry/hub": "5.29.0", - "@sentry/types": "5.29.0", + "@sentry/hub": "5.29.1", + "@sentry/types": "5.29.1", "tslib": "^1.9.3" + }, + "dependencies": { + "@sentry/types": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.1.tgz", + "integrity": "sha512-QXZBA1gJheMYTGFV+UUhr3+jKpGZqPx8kEJABs8htlKabCDJlEeoFNmeqPuVxCxukoy5ZaaHACoE+2Z87T0g2A==" + } } }, "@sentry/types": { diff --git a/package.json b/package.json index c3d872e290..a9f39d9cf4 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@opengovsg/myinfo-gov-client": "^2.1.2", "@opengovsg/ng-file-upload": "^12.2.14", "@opengovsg/spcp-auth-client": "^1.4.0", - "@sentry/browser": "^5.29.0", + "@sentry/browser": "^5.29.1", "@sentry/integrations": "^5.29.0", "@stablelib/base64": "^1.0.0", "JSONStream": "^1.3.5", From daae34a712a371aff5a199643481add1ebd4b4b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Dec 2020 17:48:57 +0800 Subject: [PATCH 14/37] fix(deps): bump opossum from 5.0.2 to 5.1.1 (#898) Bumps [opossum](https://github.com/nodeshift/opossum) from 5.0.2 to 5.1.1. - [Release notes](https://github.com/nodeshift/opossum/releases) - [Changelog](https://github.com/nodeshift/opossum/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeshift/opossum/compare/v5.0.2...v5.1.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e9433edc5..f692cc21e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18105,9 +18105,9 @@ } }, "opossum": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/opossum/-/opossum-5.0.2.tgz", - "integrity": "sha512-WLroHTijCRgt539CF563QnkEUuoR8j1O1fzFmnXCbUM0Pc6qfb2LOjW7ekVJKoZ4djPaqpXCzAvhJb8T+pQEUg==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/opossum/-/opossum-5.1.1.tgz", + "integrity": "sha512-LcGPYktVC9cJz9VZq34aCv8h+jLH2HPqo+M04wsU+CkgqrZ/fo26bZC+fRLfZxTZGMApVffrDXmn82KHTeqtyw==" }, "optimist": { "version": "0.6.1", diff --git a/package.json b/package.json index a9f39d9cf4..c09ae01e9b 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "node-cache": "^5.1.2", "nodemailer": "^6.4.16", "nodemailer-direct-transport": "~3.3.2", - "opossum": "^5.0.2", + "opossum": "^5.1.1", "promise-retry": "^2.0.1", "puppeteer-core": "^5.3.1", "selectize": "0.12.6", From df0449c83478f4631c7c88d3d5ca6be3ec941e2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Dec 2020 20:28:05 +0800 Subject: [PATCH 15/37] fix(deps): bump web-streams-polyfill from 2.1.1 to 3.0.1 (#838) Bumps [web-streams-polyfill](https://github.com/MattiasBuelens/web-streams-polyfill) from 2.1.1 to 3.0.1. - [Release notes](https://github.com/MattiasBuelens/web-streams-polyfill/releases) - [Changelog](https://github.com/MattiasBuelens/web-streams-polyfill/blob/master/CHANGELOG.md) - [Commits](https://github.com/MattiasBuelens/web-streams-polyfill/compare/v2.1.1...v3.0.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b8beadc63..3c11cfff83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24687,9 +24687,9 @@ "integrity": "sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes=" }, "web-streams-polyfill": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-2.1.1.tgz", - "integrity": "sha512-dlNpL2aab3g8CKfGz6rl8FNmGaRWLLn2g/DtSc9IjB30mEdE6XxzPfPSig5BwGSzI+oLxHyETrQGKjrVVhbLCg==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.0.1.tgz", + "integrity": "sha512-M+EmTdszMWINywOZaqpZ6VIEDUmNpRaTOuizF0ZKPjSDC8paMRe/jBBwFv0Yeyn5WYnM5pMqMQa82vpaE+IJRw==" }, "webauth": { "version": "1.1.0", diff --git a/package.json b/package.json index ad0c12b5b3..31c19d43dd 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "uid-generator": "^2.0.0", "uuid": "^8.3.2", "validator": "^13.5.2", - "web-streams-polyfill": "^2.1.1", + "web-streams-polyfill": "^3.0.1", "whatwg-fetch": "^3.5.0", "winston": "^3.3.3", "winston-cloudwatch": "^2.4.0" From acc1707f84b4c7c22010ba460e32d5a95e0f84fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 10:32:16 +0800 Subject: [PATCH 16/37] fix(deps): bump mongoose from 5.10.18 to 5.11.8 (#889) Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.10.18 to 5.11.8. - [Release notes](https://github.com/Automattic/mongoose/releases) - [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md) - [Commits](https://github.com/Automattic/mongoose/compare/5.10.18...5.11.8) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kar Rui Lau --- package-lock.json | 43 +++++++------------ package.json | 3 +- .../controllers/forms.server.controller.js | 1 + .../controllers/myinfo.server.controller.ts | 3 +- src/app/models/form.server.model.ts | 32 ++++++++------ src/app/models/login.server.model.ts | 2 +- src/app/models/submission.server.model.ts | 30 +++++++------ src/app/models/user.server.model.ts | 17 +++++--- src/app/models/verification.server.model.ts | 6 +-- .../__tests__/analytics.service.spec.ts | 8 ++-- .../__tests__/helpers/prepareTestData.ts | 5 ++- src/app/modules/examples/examples.service.ts | 4 ++ .../form/admin-form/admin-form.service.ts | 34 ++++++++------- .../encrypt-submission.service.spec.ts | 8 +--- .../encrypt-submission.service.ts | 1 + .../modules/submission/submission.service.ts | 6 +-- src/app/modules/user/user.service.ts | 4 +- src/app/services/mail/mail.service.ts | 7 +-- src/app/services/mail/mail.types.ts | 9 ++-- src/app/services/myinfo/myinfo.factory.ts | 3 +- src/app/services/myinfo/myinfo.service.ts | 6 +-- src/app/services/myinfo/myinfo.types.ts | 4 +- src/app/services/sms/sms.types.ts | 1 - src/app/utils/field-validation/index.ts | 12 +++--- src/shared/util/logic.ts | 10 ++--- src/types/agency.ts | 1 - src/types/bounce.ts | 1 - src/types/express.locals.ts | 5 +++ src/types/field/baseField.ts | 1 - src/types/form.ts | 37 +++++++++++++--- src/types/form_feedback.ts | 2 - src/types/form_logic.ts | 9 ++-- src/types/form_statistics_total.ts | 1 - src/types/login.ts | 5 +-- src/types/myinfo_hash.ts | 7 +-- src/types/submission.ts | 2 - src/types/token.ts | 5 +-- src/types/user.ts | 2 - src/types/verification.ts | 9 ++-- ...form_statistics_total.server.model.spec.ts | 15 ++++--- 40 files changed, 191 insertions(+), 170 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c11cfff83..c8e94e2af6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3885,7 +3885,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", - "dev": true, "requires": { "@types/node": "*" } @@ -4159,7 +4158,6 @@ "version": "3.6.3", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz", "integrity": "sha512-6YNqGP1hk5bjUFaim+QoFFuI61WjHiHE1BNeB41TA00Xd2K7zG4lcWyLLq/XtIp36uMavvS5hoAUJ+1u/GcX2Q==", - "dev": true, "requires": { "@types/bson": "*", "@types/node": "*" @@ -4171,16 +4169,6 @@ "integrity": "sha512-N52kUCiYyH4H2xOMHV7lIDjv4ZLRcRgEiN0xut/BNKHD/dLox10Q6WVl1vjnfA+rvdL9rFZGUxs9EQunQosAlA==", "dev": true }, - "@types/mongoose": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.10.1.tgz", - "integrity": "sha512-5yqbLHOyCQhUb7GPGW0A2dauUbhwgBvUWMzYcaUQiHdLZ8slgRp2R6i8FETZ+t5xeXpfhylYp9U7dAng7WamqQ==", - "dev": true, - "requires": { - "@types/mongodb": "*", - "@types/node": "*" - } - }, "@types/node": { "version": "14.14.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.11.tgz", @@ -15445,9 +15433,9 @@ "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" }, "kareem": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", - "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", + "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" }, "kind-of": { "version": "6.0.3", @@ -17167,16 +17155,17 @@ "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" }, "mongoose": { - "version": "5.10.18", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.18.tgz", - "integrity": "sha512-vaLUzBpUxqacoCqP/xXWMg/uVwCDrlc8LvYjDXCf8hdApvX/CXa0HLa7v2ieFaVd5Fgv3W2QXODLoC4Z/abbNw==", + "version": "5.11.8", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.8.tgz", + "integrity": "sha512-RRfrYLg7pyuyx7xu5hwadjIZZJB9W2jqIMkL1CkTmk/uOCX3MX2tl4BVIi2rJUtgMNwn6dy3wBD3soB8I9Nlog==", "requires": { + "@types/mongodb": "^3.5.27", "bson": "^1.1.4", - "kareem": "2.3.1", + "kareem": "2.3.2", "mongodb": "3.6.3", "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.7.0", - "mquery": "3.2.2", + "mpath": "0.8.1", + "mquery": "3.2.3", "ms": "2.1.2", "regexp-clone": "1.0.0", "safe-buffer": "5.2.1", @@ -17271,14 +17260,14 @@ } }, "mpath": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", - "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.1.tgz", + "integrity": "sha512-norEinle9aFc05McBawVPwqgFZ7npkts9yu17ztIVLwPwO9rq0OTp89kGVTqvv5rNLMz96E5iWHpVORjI411vA==" }, "mquery": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", - "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz", + "integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==", "requires": { "bluebird": "3.5.1", "debug": "3.1.0", diff --git a/package.json b/package.json index 31c19d43dd..5d9aee568f 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "lodash": "^4.17.20", "moment-timezone": "0.5.32", "mongodb-uri": "^0.9.7", - "mongoose": "^5.10.18", + "mongoose": "^5.11.8", "multiparty": ">=4.2.2", "neverthrow": "^3.0.0", "ng-infinite-scroll": "^1.3.0", @@ -185,7 +185,6 @@ "@types/json-stringify-safe": "^5.0.0", "@types/mongodb": "^3.6.3", "@types/mongodb-uri": "^0.9.0", - "@types/mongoose": "^5.10.1", "@types/node": "^14.14.11", "@types/nodemailer": "^6.4.0", "@types/nodemailer-direct-transport": "^1.0.31", diff --git a/src/app/controllers/forms.server.controller.js b/src/app/controllers/forms.server.controller.js index dab7a4cb13..55ec9296a1 100644 --- a/src/app/controllers/forms.server.controller.js +++ b/src/app/controllers/forms.server.controller.js @@ -53,6 +53,7 @@ const formPublicFields = [ */ exports.read = (requestType) => /** + * ! Note that this function should not call any mongoose functions on req.form as it is possibly already a plain JSON object. * Takes the form and replaces admin details with agency details, as well as scrubbing the form if the * request is not for admin purposes. * @param {Object} req - Express request object diff --git a/src/app/controllers/myinfo.server.controller.ts b/src/app/controllers/myinfo.server.controller.ts index 2de8de6ca8..79eab57630 100644 --- a/src/app/controllers/myinfo.server.controller.ts +++ b/src/app/controllers/myinfo.server.controller.ts @@ -14,6 +14,7 @@ import { ResWithSpcpSession, ResWithUinFin, WithForm, + WithJsonForm, } from '../../types' import { MyInfoFactory } from '../services/myinfo/myinfo.factory' import { mapVerifyMyInfoError } from '../services/myinfo/myinfo.util' @@ -58,7 +59,7 @@ export const addMyInfo: RequestHandler = async ( // Step 3: Hash the values and save them .andThen((prefilledFields) => { form.form_fields = prefilledFields - ;(req as WithForm).form = form + ;(req as WithJsonForm).form = form return MyInfoFactory.saveMyInfoHashes(uinFin, formId, prefilledFields) }) .map(() => next()) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index a57e461d4d..a8d52721b0 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -1,6 +1,6 @@ import BSON from 'bson-ext' import { compact, pick, uniq } from 'lodash' -import mongoose, { Mongoose, Schema, SchemaOptions } from 'mongoose' +import mongoose, { Mongoose, Query, Schema, SchemaOptions } from 'mongoose' import validator from 'validator' import { @@ -14,6 +14,7 @@ import { IEmailFormSchema, IEncryptedFormModel, IEncryptedFormSchema, + IFormDocument, IFormModel, IFormSchema, IPopulatedForm, @@ -130,14 +131,16 @@ const compileFormModel = (db: Mongoose): IFormModel => { { title: { type: String, - trim: true, - required: 'Form name cannot be blank', - minlength: [4, 'Form name must be at least 4 characters'], - maxlength: [200, 'Form name can have a maximum of 200 characters'], - match: [ + validate: [ /^[a-zA-Z0-9_\-./() &`;'"]*$/, 'Form name cannot contain special characters', ], + required: 'Form name cannot be blank', + minlength: [4, 'Form name must be at least 4 characters'], + maxlength: [200, 'Form name can have a maximum of 200 characters'], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + trim: true, }, form_fields: [BaseFieldSchema], @@ -273,7 +276,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { esrvcId: { type: String, required: false, - match: [ + validate: [ /^([a-zA-Z0-9-]){1,25}$/i, 'e-service ID must be alphanumeric, dashes are allowed', ], @@ -296,7 +299,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Name of credentials for messaging service, stored in secrets manager type: String, required: false, - match: [ + validate: [ /^([a-zA-Z0-9-])+$/i, 'msgSrvcName must be alphanumeric, dashes are allowed', ], @@ -420,13 +423,18 @@ const compileFormModel = (db: Mongoose): IFormModel => { } // Transfer ownership of the form to another user - FormSchema.methods.transferOwner = async function (currentOwner, newOwner) { + FormSchema.methods.transferOwner = async function ( + this: IFormDocument, + currentOwner: IUserSchema, + newOwner: IUserSchema, + ) { // Update form's admin to new owner's id. this.admin = newOwner._id // Remove new owner from perm list and include previous owner as an editor. - this.permissionList = - this.permissionList?.filter((item) => item.email !== newOwner.email) ?? [] + this.permissionList = this.permissionList.filter( + (item) => item.email !== newOwner.email, + ) this.permissionList.push({ email: currentOwner.email, write: true }) return this.save() @@ -471,7 +479,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { path: 'agency', select: '-__v -created -lastModified -updatedAt', }, - }) + }) as Query } // Deactivate form by ID diff --git a/src/app/models/login.server.model.ts b/src/app/models/login.server.model.ts index d8c6ade6cb..c0a2b1f8b5 100644 --- a/src/app/models/login.server.model.ts +++ b/src/app/models/login.server.model.ts @@ -39,7 +39,7 @@ const LoginSchema = new Schema( esrvcId: { type: String, required: true, - match: [ + validate: [ /^([a-zA-Z0-9-]){1,25}$/i, 'e-service ID must be alphanumeric, dashes are allowed', ], diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index 047d4d790e..f27c02e9fb 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -1,5 +1,5 @@ import moment from 'moment-timezone' -import mongoose, { Mongoose, Schema } from 'mongoose' +import mongoose, { Mongoose, QueryCursor, Schema } from 'mongoose' import { FixedLengthArray } from 'type-fest' import { @@ -13,6 +13,7 @@ import { ISubmissionSchema, IWebhookResponseSchema, MyInfoAttribute, + SubmissionCursorData, SubmissionMetadata, SubmissionType, WebhookData, @@ -304,18 +305,21 @@ const getSubmissionCursorByFormId: IEncryptSubmissionModel['getSubmissionCursorB form: formId, ...createQueryWithDateParam(dateRange?.startDate, dateRange?.endDate), } - return this.find(streamQuery) - .select({ - encryptedContent: 1, - verifiedContent: 1, - attachmentMetadata: 1, - created: 1, - id: 1, - }) - .batchSize(2000) - .read('secondary') - .lean() - .cursor() + return ( + this.find(streamQuery) + .select({ + encryptedContent: 1, + verifiedContent: 1, + attachmentMetadata: 1, + created: 1, + id: 1, + }) + .batchSize(2000) + .read('secondary') + .lean() + // Override typing as Map is converted to Object once passed through `lean()`. + .cursor() as QueryCursor + ) } EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = getSubmissionCursorByFormId diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts index 5b58163506..ca229ad17b 100644 --- a/src/app/models/user.server.model.ts +++ b/src/app/models/user.server.model.ts @@ -1,5 +1,6 @@ import { parsePhoneNumberFromString } from 'libphonenumber-js/mobile' -import { Mongoose, Schema } from 'mongoose' +import { MongoError } from 'mongodb' +import { CallbackError, Mongoose, Schema } from 'mongoose' import validator from 'validator' import { IUser, IUserModel, IUserSchema } from '../../types' @@ -11,10 +12,12 @@ export const USER_SCHEMA_ID = 'User' const compileUserModel = (db: Mongoose) => { const Agency = getAgencyModel(db) - const UserSchema = new Schema( + const UserSchema: Schema = new Schema( { email: { type: String, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore trim: true, unique: true, required: 'Please enter your email', @@ -80,9 +83,13 @@ const compileUserModel = (db: Mongoose) => { */ UserSchema.post( 'save', - function (err: any, _doc: IUserSchema, next: any) { - if (err.name === 'MongoError' && err.code === 11000) { - next(new Error('Account already exists with this email')) + function (err: Error, _doc: unknown, next: (err?: CallbackError) => void) { + if (err) { + if (err.name === 'MongoError' && (err as MongoError)?.code === 11000) { + next(new Error('Account already exists with this email')) + } else { + next(err) + } } else { next() } diff --git a/src/app/models/verification.server.model.ts b/src/app/models/verification.server.model.ts index 063cb81165..ae0d2c5836 100644 --- a/src/app/models/verification.server.model.ts +++ b/src/app/models/verification.server.model.ts @@ -1,4 +1,4 @@ -import { DocumentQuery, Model, Mongoose, Schema } from 'mongoose' +import { Model, Mongoose, Query, Schema } from 'mongoose' import * as vfnConstants from '../../shared/util/verification' import { IVerificationFieldSchema, IVerificationSchema } from '../../types' @@ -11,7 +11,7 @@ const VERIFICATION_SCHEMA_ID = 'Verification' interface IVerificationModel extends Model { findTransactionMetadata( id: IVerificationSchema['_id'], - ): DocumentQuery, IVerificationSchema> + ): Query, IVerificationSchema> } const VerificationFieldSchema = new Schema({ @@ -85,7 +85,7 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { * @param db The mongoose instance to retrieve the Verification model from * @returns The Verification model */ -const getVerificationModel = (db: Mongoose) => { +const getVerificationModel = (db: Mongoose): IVerificationModel => { try { return db.model(VERIFICATION_SCHEMA_ID) as IVerificationModel } catch { diff --git a/src/app/modules/analytics/__tests__/analytics.service.spec.ts b/src/app/modules/analytics/__tests__/analytics.service.spec.ts index 76d09778a1..3d6977e016 100644 --- a/src/app/modules/analytics/__tests__/analytics.service.spec.ts +++ b/src/app/modules/analytics/__tests__/analytics.service.spec.ts @@ -42,11 +42,11 @@ describe('analytics.service', () => { const submissionPromises: Promise[] = [] formCounts.forEach((count) => { submissionPromises.push( - FormStatsModel.create({ + (FormStatsModel.collection.insertOne({ formId: mongoose.Types.ObjectId(), totalCount: count, lastSubmission: new Date(), - }), + }) as unknown) as Promise, ) }) await Promise.all(submissionPromises) @@ -68,11 +68,11 @@ describe('analytics.service', () => { const submissionPromises: Promise[] = [] formCounts.forEach((count) => { submissionPromises.push( - FormStatsModel.create({ + (FormStatsModel.collection.insertOne({ formId: mongoose.Types.ObjectId(), totalCount: count, lastSubmission: new Date(), - }), + }) as unknown) as Promise, ) }) await Promise.all(submissionPromises) diff --git a/src/app/modules/examples/__tests__/helpers/prepareTestData.ts b/src/app/modules/examples/__tests__/helpers/prepareTestData.ts index f3706dcd3a..70259ac154 100644 --- a/src/app/modules/examples/__tests__/helpers/prepareTestData.ts +++ b/src/app/modules/examples/__tests__/helpers/prepareTestData.ts @@ -141,7 +141,8 @@ const prepareTestData = async ( // Add form statistics for "submissions" for both form prefixes. const formStatsPromises = testData.first.forms .map((form) => - FormStatsModel.create({ + // Using mongodb native function to bypass collection presave hook. + FormStatsModel.collection.insertOne({ lastSubmission: new Date(), totalCount: testData.first.submissionCount, formId: form._id, @@ -149,7 +150,7 @@ const prepareTestData = async ( ) .concat( testData.second.forms.map((form) => - FormStatsModel.create({ + FormStatsModel.collection.insertOne({ lastSubmission: new Date(), totalCount: testData.second.submissionCount, formId: form._id, diff --git a/src/app/modules/examples/examples.service.ts b/src/app/modules/examples/examples.service.ts index 3ca8f7ba31..b4d4ef62c3 100644 --- a/src/app/modules/examples/examples.service.ts +++ b/src/app/modules/examples/examples.service.ts @@ -157,6 +157,10 @@ const execExamplesQuery = ( ): ResultAsync => { return ResultAsync.fromPromise( queryBuilder + // TODO(#42): Missing type in native typescript, waiting on upstream fixes. + // Tracking at https://github.com/Automattic/mongoose/issues/9714. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore .append( selectAndProjectCardInfo(/* limit= */ PAGE_SIZE, /* offset= */ offset), ) diff --git a/src/app/modules/form/admin-form/admin-form.service.ts b/src/app/modules/form/admin-form/admin-form.service.ts index 9a448991cb..751a1f38a1 100644 --- a/src/app/modules/form/admin-form/admin-form.service.ts +++ b/src/app/modules/form/admin-form/admin-form.service.ts @@ -16,6 +16,7 @@ import { FormMetaView, IFieldSchema, IForm, + IFormDocument, IFormSchema, IPopulatedForm, IUserSchema, @@ -299,7 +300,7 @@ export const transferFormOwnership = ( // Step 3: Perform form ownership transfer. .andThen((newOwner) => ResultAsync.fromPromise( - currentForm.transferOwner(currentOwner, newOwner), + currentForm.transferOwner(currentOwner, newOwner), (error) => { logger.error({ message: 'Error occurred whilst transferring form ownership', @@ -370,10 +371,10 @@ export const createForm = ( * @returns the newly created duplicated form */ export const duplicateForm = ( - originalForm: IFormSchema, + originalForm: IFormDocument, newAdminId: string, overrideParams: DuplicateFormBody, -): ResultAsync => { +): ResultAsync => { const overrideProps = processDuplicateOverrideProps( overrideParams, newAdminId, @@ -392,17 +393,20 @@ export const duplicateForm = ( const duplicateParams = originalForm.getDuplicateParams(overrideProps) - return ResultAsync.fromPromise(FormModel.create(duplicateParams), (error) => { - logger.error({ - message: 'Error encountered while duplicating form', - meta: { - action: 'duplicateForm', - duplicateParams, - newAdminId, - }, - error, - }) + return ResultAsync.fromPromise( + FormModel.create(duplicateParams) as Promise, + (error) => { + logger.error({ + message: 'Error encountered while duplicating form', + meta: { + action: 'duplicateForm', + duplicateParams, + newAdminId, + }, + error, + }) - return new DatabaseError(getMongoErrorMessage(error)) - }) + return new DatabaseError(getMongoErrorMessage(error)) + }, + ) } diff --git a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts index 4493097d78..54658f618b 100644 --- a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts +++ b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts @@ -124,14 +124,10 @@ describe('encrypt-submission.service', () => { describe('transformAttachmentMetaStream', () => { const MOCK_SUB_CURSOR_DATA_1: Partial = { - attachmentMetadata: { - mockId1: 'mock metadata 1', - }, + attachmentMetadata: { mockId1: 'mock metadata 1' }, } const MOCK_SUB_CURSOR_DATA_2: Partial = { - attachmentMetadata: { - mockId2: 'mock metadata 2', - }, + attachmentMetadata: { mockId2: 'mock metadata 2' }, } const EMPTY_METADATA = { diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts index af0731f95d..daa5214739 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.service.ts @@ -70,6 +70,7 @@ export const transformAttachmentMetaStream = ({ objectMode: true, transform: (data: SubmissionCursorData, _encoding, callback) => { const unprocessedMetadata = data.attachmentMetadata ?? {} + const totalCount = Object.keys(unprocessedMetadata).length // Early return if pipe is disabled or nothing to transform. if (!enabled || totalCount === 0) { diff --git a/src/app/modules/submission/submission.service.ts b/src/app/modules/submission/submission.service.ts index 259dcb25d0..f8186f2559 100644 --- a/src/app/modules/submission/submission.service.ts +++ b/src/app/modules/submission/submission.service.ts @@ -7,7 +7,7 @@ import { getLogicUnitPreventingSubmit, getVisibleFieldIds, } from '../../../shared/util/logic' -import { FieldResponse, IFieldSchema, IFormSchema } from '../../../types' +import { FieldResponse, IFieldSchema, IFormDocument } from '../../../types' import getSubmissionModel from '../../models/submission.server.model' import { createQueryWithDateParam, isMalformedDate } from '../../utils/date' import { validateField } from '../../utils/field-validation' @@ -34,7 +34,7 @@ const SubmissionModel = getSubmissionModel(mongoose) * @returns neverthrow err(ConflictError) if the given form's form field ids count do not match given responses' */ const getFilteredResponses = ( - form: IFormSchema, + form: IFormDocument, responses: FieldResponse[], ): Result => { const modeFilter = getModeFilter(form.responseMode) @@ -73,7 +73,7 @@ const getFilteredResponses = ( * @returns neverthrow err() if response validation fails */ export const getProcessedResponses = ( - form: IFormSchema, + form: IFormDocument, originalResponses: FieldResponse[], ): Result< ProcessedFieldResponse[], diff --git a/src/app/modules/user/user.service.ts b/src/app/modules/user/user.service.ts index 800350f3c8..9b8b91a5cc 100644 --- a/src/app/modules/user/user.service.ts +++ b/src/app/modules/user/user.service.ts @@ -6,7 +6,7 @@ import config from '../../../config/config' import { createLoggerWithLabel } from '../../../config/logger' import { IAdminVerificationDoc, - IAgency, + IAgencySchema, IPopulatedUser, IUserSchema, UpsertOtpParams, @@ -212,7 +212,7 @@ export const getPopulatedUserById = ( */ export const retrieveUser = ( email: string, - agencyId: IAgency['_id'], + agencyId: IAgencySchema['_id'], ): ResultAsync => { if (!validator.isEmail(email)) { return errAsync(new InvalidDomainError()) diff --git a/src/app/services/mail/mail.service.ts b/src/app/services/mail/mail.service.ts index 7d45c8ca75..6bed20093e 100644 --- a/src/app/services/mail/mail.service.ts +++ b/src/app/services/mail/mail.service.ts @@ -9,6 +9,7 @@ import config from '../../../config/config' import { createLoggerWithLabel } from '../../../config/logger' import { HASH_EXPIRE_AFTER_SECONDS } from '../../../shared/util/verification' import { BounceType, IEmailFormSchema, ISubmissionSchema } from '../../../types' +import { EmailFormField } from '../../modules/submission/email-submission/email-submission.types' import { EMAIL_HEADERS, EmailType } from './mail.constants' import { MailSendError } from './mail.errors' @@ -214,7 +215,7 @@ export class MailService { html: mailHtml, headers: { [EMAIL_HEADERS.formId]: String(form._id), - [EMAIL_HEADERS.submissionId]: submission.id, + [EMAIL_HEADERS.submissionId]: String(submission.id), [EMAIL_HEADERS.emailType]: EmailType.EmailConfirmation, }, } @@ -374,13 +375,13 @@ export class MailService { form: Pick submission: Pick attachments?: Mail.Attachment[] - formData: any[] + formData: EmailFormField[] jsonData: { question: string answer: string | number }[] }): Promise => { - const refNo = submission.id + const refNo = String(submission.id) const formTitle = form.title const submissionTime = moment(submission.created) .tz('Asia/Singapore') diff --git a/src/app/services/mail/mail.types.ts b/src/app/services/mail/mail.types.ts index f50cd48b30..61a0592ab6 100644 --- a/src/app/services/mail/mail.types.ts +++ b/src/app/services/mail/mail.types.ts @@ -7,6 +7,7 @@ import { IPopulatedForm, ISubmissionSchema, } from '../../../types' +import { EmailFormField } from '../../modules/submission/email-submission/email-submission.types' export type SendMailOptions = { mailId?: string @@ -26,7 +27,7 @@ export type SendAutoReplyEmailsArgs = { form: Pick submission: Pick attachments?: Mail.Attachment[] - responsesData: { question: string; answerTemplate: string[] }[] + responsesData: Pick[] autoReplyMailDatas: AutoReplyMailData[] } @@ -50,8 +51,7 @@ export type AutoreplySummaryRenderData = { refNo: ISubmissionSchema['_id'] formTitle: IFormSchema['title'] submissionTime: string - // TODO (#42): Add proper types once the type is determined. - formData: any + formData: Pick[] formUrl: string } @@ -63,8 +63,7 @@ export type SubmissionToAdminHtmlData = { refNo: string formTitle: string submissionTime: string - // TODO (#42): Add proper types once the type is determined. - formData: any[] + formData: EmailFormField[] jsonData: { question: string answer: string | number diff --git a/src/app/services/myinfo/myinfo.factory.ts b/src/app/services/myinfo/myinfo.factory.ts index d47e63574d..a6d8b5e26c 100644 --- a/src/app/services/myinfo/myinfo.factory.ts +++ b/src/app/services/myinfo/myinfo.factory.ts @@ -1,5 +1,6 @@ import { IPersonBasic, IPersonBasicRequest } from '@opengovsg/myinfo-gov-client' import { pick } from 'lodash' +import { LeanDocument } from 'mongoose' import { err, errAsync, Result, ResultAsync } from 'neverthrow' import config from '../../../config/config' @@ -33,7 +34,7 @@ interface IMyInfoFactory { > prefillMyInfoFields: ( myInfoData: IPersonBasic, - currFormFields: IFieldSchema[], + currFormFields: LeanDocument, ) => Result saveMyInfoHashes: ( uinFin: string, diff --git a/src/app/services/myinfo/myinfo.service.ts b/src/app/services/myinfo/myinfo.service.ts index 19f03edb33..40ec1d3f98 100644 --- a/src/app/services/myinfo/myinfo.service.ts +++ b/src/app/services/myinfo/myinfo.service.ts @@ -7,7 +7,7 @@ import { import Bluebird from 'bluebird' import fs from 'fs' import { cloneDeep } from 'lodash' -import mongoose from 'mongoose' +import mongoose, { LeanDocument } from 'mongoose' import { errAsync, ok, okAsync, Result, ResultAsync } from 'neverthrow' import CircuitBreaker from 'opossum' @@ -164,10 +164,10 @@ export class MyInfoService { */ prefillMyInfoFields( myInfoData: IPersonBasic, - currFormFields: IFieldSchema[], + currFormFields: LeanDocument, ): Result { const prefilledFields = currFormFields.map((field) => { - if (!field?.myInfo?.attr) return field + if (!field.myInfo?.attr) return field const myInfoAttr = field.myInfo.attr const myInfoValue = getMyInfoValue(myInfoAttr, myInfoData) diff --git a/src/app/services/myinfo/myinfo.types.ts b/src/app/services/myinfo/myinfo.types.ts index ec7c5026ef..282da1d265 100644 --- a/src/app/services/myinfo/myinfo.types.ts +++ b/src/app/services/myinfo/myinfo.types.ts @@ -1,7 +1,9 @@ +import { LeanDocument } from 'mongoose' + import { IFieldSchema, IMyInfo, MyInfoAttribute } from '../../../types' import { ProcessedFieldResponse } from '../../modules/submission/submission.types' -export interface IPossiblyPrefilledField extends IFieldSchema { +export interface IPossiblyPrefilledField extends LeanDocument { fieldValue?: string } diff --git a/src/app/services/sms/sms.types.ts b/src/app/services/sms/sms.types.ts index a5d52222f3..15552fb4e1 100644 --- a/src/app/services/sms/sms.types.ts +++ b/src/app/services/sms/sms.types.ts @@ -31,7 +31,6 @@ export interface ISmsCount { logType: LogType smsType: SmsType createdAt?: Date - _id: Document['_id'] } export interface ISmsCountSchema extends ISmsCount, Document {} diff --git a/src/app/utils/field-validation/index.ts b/src/app/utils/field-validation/index.ts index 5ac17d9d9e..41f80c7fa4 100644 --- a/src/app/utils/field-validation/index.ts +++ b/src/app/utils/field-validation/index.ts @@ -6,7 +6,7 @@ import { ProcessedSingleAnswerResponse, } from '../../../app/modules/submission/submission.types' import { createLoggerWithLabel } from '../../../config/logger' -import { IField } from '../../../types/field/baseField' +import { IFieldSchema } from '../../../types/field/baseField' import { BasicField } from '../../../types/field/fieldTypes' import { FieldResponse } from '../../../types/response' import { ValidateFieldError } from '../../modules/submission/submission.errors' @@ -35,7 +35,7 @@ const isValidResponseFieldType = (response: ProcessedFieldResponse): boolean => * @param response The submitted response */ const doFieldTypesMatch = ( - formField: IField, + formField: IFieldSchema, response: ProcessedFieldResponse, ): Either => { return response.fieldType !== formField.fieldType @@ -53,7 +53,7 @@ const doFieldTypesMatch = ( * @param response The submitted response */ const singleAnswerRequiresValidation = ( - formField: IField, + formField: IFieldSchema, response: ProcessedSingleAnswerResponse, ) => (formField.required && response.isVisible) || response.answer.trim() !== '' @@ -67,7 +67,7 @@ const singleAnswerRequiresValidation = ( */ const logInvalidAnswer = ( formId: string, - formField: IField, + formField: IFieldSchema, message: string, ) => { logger.error({ @@ -91,7 +91,7 @@ const logInvalidAnswer = ( */ export const validateField = ( formId: string, - formField: IField, + formField: IFieldSchema, response: ProcessedFieldResponse, ): Result => { if (!isValidResponseFieldType(response)) { @@ -154,7 +154,7 @@ export const validateField = ( */ const classBasedValidation = ( formId: string, - formField: IField, + formField: IFieldSchema, response: FieldResponse, ): Result => { const fieldValidator = fieldValidatorFactory.createFieldValidator( diff --git a/src/shared/util/logic.ts b/src/shared/util/logic.ts index 1c166528d0..4aac238e41 100644 --- a/src/shared/util/logic.ts +++ b/src/shared/util/logic.ts @@ -2,7 +2,7 @@ import { FieldResponse, IClientFieldSchema, IConditionSchema, - IFormSchema, + IFormDocument, ILogicSchema, IPreventSubmitLogicSchema, IShowFieldsLogicSchema, @@ -62,7 +62,7 @@ const isPreventSubmitLogic = ( * @param form the form object to group its logic by field for * @returns an object containing fields to be displayed and their corresponding conditions, keyed by id of the displayable field */ -export const groupLogicUnitsByField = (form: IFormSchema): GroupedLogic => { +export const groupLogicUnitsByField = (form: IFormDocument): GroupedLogic => { const formId = form._id const formLogics = form.form_logics?.filter(isShowFieldsLogic) ?? [] const formFieldIds = new Set( @@ -101,7 +101,7 @@ export const groupLogicUnitsByField = (form: IFormSchema): GroupedLogic => { * @returns array of conditions that prevent submission, can be empty */ const getPreventSubmitConditions = ( - form: IFormSchema, + form: IFormDocument, ): IPreventSubmitLogicSchema[] => { const formFieldIds = new Set( form.form_fields?.map((field) => String(field._id)), @@ -126,7 +126,7 @@ const getPreventSubmitConditions = ( */ export const getLogicUnitPreventingSubmit = ( submission: LogicFieldArray, - form: IFormSchema, + form: IFormDocument, visibleFieldIds?: FieldIdSet, ): IPreventSubmitLogicSchema | undefined => { if (!visibleFieldIds) { @@ -165,7 +165,7 @@ const allConditionsExist = ( */ export const getVisibleFieldIds = ( submission: LogicFieldArray, - form: IFormSchema, + form: IFormDocument, ): FieldIdSet => { const logicUnitsGroupedByField = groupLogicUnitsByField(form) const visibleFieldIds: FieldIdSet = new Set() diff --git a/src/types/agency.ts b/src/types/agency.ts index ea1340eb11..70701e82d8 100644 --- a/src/types/agency.ts +++ b/src/types/agency.ts @@ -7,7 +7,6 @@ export interface IAgency { logo: string created?: Date lastModified?: Date - _id: Document['_id'] } export interface IAgencySchema extends IAgency, Document {} diff --git a/src/types/bounce.ts b/src/types/bounce.ts index 0a180595dd..5f792d3208 100644 --- a/src/types/bounce.ts +++ b/src/types/bounce.ts @@ -20,7 +20,6 @@ export interface IBounce { bounces: ISingleBounce[] hasAutoEmailed: boolean expireAt: Date - _id: Document['_id'] } export interface IBounceSchema extends IBounce, Document { diff --git a/src/types/express.locals.ts b/src/types/express.locals.ts index 5af57c4f31..b8bb55a852 100644 --- a/src/types/express.locals.ts +++ b/src/types/express.locals.ts @@ -1,4 +1,5 @@ // TODO (#42): remove these types when migrating away from middleware pattern +import { LeanDocument } from 'mongoose' import { ProcessedFieldResponse } from '../app/modules/submission/submission.types' @@ -9,6 +10,10 @@ export type WithForm = T & { form: IPopulatedForm } +export type WithJsonForm = T & { + form: LeanDocument +} + export type WithParsedResponses = T & { parsedResponses: ProcessedFieldResponse[] } diff --git a/src/types/field/baseField.ts b/src/types/field/baseField.ts index 7105015c09..2632cb9801 100644 --- a/src/types/field/baseField.ts +++ b/src/types/field/baseField.ts @@ -23,7 +23,6 @@ export interface IField { disabled: boolean fieldType: BasicField myInfo?: IMyInfo - _id: Document['_id'] } // Manual override since mongoose types don't have generics yet. diff --git a/src/types/form.ts b/src/types/form.ts index 075af6481c..f8d5e1a9a6 100644 --- a/src/types/form.ts +++ b/src/types/form.ts @@ -1,5 +1,5 @@ -import { Document, Model } from 'mongoose' -import { Merge } from 'type-fest' +import { Document, LeanDocument, Model, ToObjectOptions } from 'mongoose' +import { Merge, SetRequired } from 'type-fest' import { OverrideProps } from '../app/modules/form/admin-form/admin-form.types' @@ -139,10 +139,10 @@ export interface IFormSchema extends IForm, Document { * @param newOwner the new owner of the form. Similarly retrieved outside of method to force correct validation. * @returns updated form */ - transferOwner( + transferOwner( currentOwner: IUserSchema, newOwner: IUserSchema, - ): Promise + ): Promise /** * Return essential form creation parameters with the given properties. * @param overrideProps the props to override on the duplicated form @@ -153,7 +153,26 @@ export interface IFormSchema extends IForm, Document { ): PickDuplicateForm & OverrideProps } -export interface IPopulatedForm extends IFormSchema { +/** + * Schema type with defaults populated and thus set to be defined. + */ +export interface IFormDocument extends IFormSchema { + form_logics: NonNullable + permissionList: NonNullable + hasCaptcha: NonNullable + authType: NonNullable + status: NonNullable + inactiveMessage: NonNullable + isListed: NonNullable + form_fields: NonNullable + startPage: SetRequired, 'colorTheme'> + endPage: SetRequired< + NonNullable, + 'title' | 'buttonText' + > + webhook: SetRequired, 'url'> +} +export interface IPopulatedForm extends Omit { // Remove extraneous keys that the populated form should not require. admin: Merge< Omit< @@ -167,6 +186,9 @@ export interface IPopulatedForm extends IFormSchema { > } > + + // Override types. + toJSON(options?: ToObjectOptions): LeanDocument } export interface IEncryptedForm extends IForm { @@ -195,8 +217,9 @@ export interface IFormModel extends Model { ): Promise } -export type IEncryptedFormModel = Model & IFormModel -export type IEmailFormModel = Model & IFormModel +export type IEncryptedFormModel = Merge> +export type IEmailFormModel = Merge> + /** Typing for the shape of the important meta subset for form document. */ export type FormMetaView = Pick< IFormSchema, diff --git a/src/types/form_feedback.ts b/src/types/form_feedback.ts index 31a8d00d3f..243fa64654 100644 --- a/src/types/form_feedback.ts +++ b/src/types/form_feedback.ts @@ -6,11 +6,9 @@ export interface IFormFeedback { formId: IFormSchema['_id'] rating: number comment?: string - _id?: Document['_id'] } export interface IFormFeedbackSchema extends IFormFeedback, Document { - _id: Document['_id'] created?: Date lastModified?: Date } diff --git a/src/types/form_logic.ts b/src/types/form_logic.ts index 56946dc0bd..4f58b95e46 100644 --- a/src/types/form_logic.ts +++ b/src/types/form_logic.ts @@ -27,16 +27,13 @@ export interface ICondition { ifValueType?: LogicIfValue } -export interface IConditionSchema extends ICondition, Document { - // Override ObjectId with String type since the field id passed in is in - // String form. - _id: string -} +// Override ObjectId with String type since the field id passed in is in +// String form. +export interface IConditionSchema extends ICondition, Document {} export interface ILogic { conditions: IConditionSchema[] logicType: LogicType - _id: Document['_id'] } export interface ILogicSchema extends ILogic, Document {} diff --git a/src/types/form_statistics_total.ts b/src/types/form_statistics_total.ts index d0d66dde8a..a3ff50e67a 100644 --- a/src/types/form_statistics_total.ts +++ b/src/types/form_statistics_total.ts @@ -6,7 +6,6 @@ export interface IFormStatisticsTotal { formId: IFormSchema['_id'] totalCount: number lastSubmission: Date - _id: Document['_id'] } export interface IFormStatisticsTotalSchema diff --git a/src/types/login.ts b/src/types/login.ts index 43784350ea..a9764557b6 100644 --- a/src/types/login.ts +++ b/src/types/login.ts @@ -11,12 +11,9 @@ export interface ILogin { authType: AuthType esrvcId: string created?: Date - _id?: Document['_id'] } -export interface ILoginSchema extends ILogin, Document { - _id: Document['_id'] -} +export interface ILoginSchema extends ILogin, Document {} export type LoginStatistic = { adminEmail: IUserSchema['email'] diff --git a/src/types/myinfo_hash.ts b/src/types/myinfo_hash.ts index 81ce6391bf..d2458a0198 100644 --- a/src/types/myinfo_hash.ts +++ b/src/types/myinfo_hash.ts @@ -3,11 +3,7 @@ import { Document, Model } from 'mongoose' import { MyInfoAttribute } from './field' import { IFormSchema } from './form' -export type IHashes = Partial< - { - [key in MyInfoAttribute]: string - } -> +export type IHashes = Partial<{ [key in MyInfoAttribute]: string }> interface IMyInfoHash { uinFin: string @@ -15,7 +11,6 @@ interface IMyInfoHash { fields: IHashes expireAt: Date created: Date - _id: Document['_id'] } export interface IMyInfoHashSchema extends IMyInfoHash, Document {} diff --git a/src/types/submission.ts b/src/types/submission.ts index c7847bf6b8..999c61b6d6 100644 --- a/src/types/submission.ts +++ b/src/types/submission.ts @@ -22,7 +22,6 @@ export interface ISubmission { submissionType: SubmissionType created?: Date lastModified?: Date - _id?: Document['_id'] recipientEmails?: string[] responseHash?: string responseSalt?: string @@ -48,7 +47,6 @@ export interface WebhookView { } export interface ISubmissionSchema extends ISubmission, Document { - _id: Document['_id'] getWebhookView(): WebhookView | null } diff --git a/src/types/token.ts b/src/types/token.ts index 55d4332d71..4c064695a2 100644 --- a/src/types/token.ts +++ b/src/types/token.ts @@ -6,12 +6,9 @@ export interface IToken { expireAt: Date numOtpAttempts?: number numOtpSent?: number - _id?: Document['_id'] } -export interface ITokenSchema extends IToken, Document { - _id: Document['_id'] -} +export interface ITokenSchema extends IToken, Document {} export interface ITokenModel extends Model { upsertOtp: ( diff --git a/src/types/user.ts b/src/types/user.ts index ad1f4af133..7d54af298e 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -11,13 +11,11 @@ export interface IUser { agency: IAgencySchema['_id'] contact?: string betaFlags?: Record - _id?: Document['_id'] lastAccessed?: Date updatedAt?: Date } export interface IUserSchema extends IUser, Document { - _id: Document['_id'] created?: Date } diff --git a/src/types/verification.ts b/src/types/verification.ts index 570706987e..9f4ef3527d 100644 --- a/src/types/verification.ts +++ b/src/types/verification.ts @@ -3,10 +3,6 @@ import { Document } from 'mongoose' import { IFormSchema } from './form' export interface IVerificationField { - // _id is basically a generated transactionId, so it has to be a string, - // instead of being converted to a string from ObjectId. - // This must be a string, or transaction fetching will fail. - _id: string fieldType: string signedData?: string | null hashedOtp?: string | null @@ -14,11 +10,12 @@ export interface IVerificationField { hashRetries?: number } -export interface IVerificationFieldSchema extends IVerificationField, Document { +export interface IVerificationFieldSchema + extends IVerificationField, + Document { // _id is basically a generated transactionId, so it has to be a string, // instead of being converted to a string from ObjectId. // This must be a string, or transaction fetching will fail. - _id: string } export interface IVerification { diff --git a/tests/unit/backend/models/form_statistics_total.server.model.spec.ts b/tests/unit/backend/models/form_statistics_total.server.model.spec.ts index b3270c14aa..5cb9b8d46b 100644 --- a/tests/unit/backend/models/form_statistics_total.server.model.spec.ts +++ b/tests/unit/backend/models/form_statistics_total.server.model.spec.ts @@ -1,3 +1,4 @@ +import { ObjectId } from 'bson-ext' import mongoose from 'mongoose' import getFormStatisticsTotalModel from 'src/app/models/form_statistics_total.server.model' @@ -20,11 +21,12 @@ describe('FormStatisticsTotal Model', () => { const submissionPromises: Promise[] = [] formCounts.forEach((count) => { submissionPromises.push( - FormStatsModel.create({ - formId: mongoose.Types.ObjectId(), + // Using mongodb native function to bypass collection presave hook. + (FormStatsModel.collection.insertOne({ + formId: new ObjectId(), totalCount: count, lastSubmission: new Date(), - }), + }) as unknown) as Promise, ) }) await Promise.all(submissionPromises) @@ -50,11 +52,12 @@ describe('FormStatisticsTotal Model', () => { const submissionPromises: Promise[] = [] formCounts.forEach((count) => { submissionPromises.push( - FormStatsModel.create({ - formId: mongoose.Types.ObjectId(), + // Using mongodb native function to bypass collection presave hook. + (FormStatsModel.collection.insertOne({ + formId: new ObjectId(), totalCount: count, lastSubmission: new Date(), - }), + }) as unknown) as Promise, ) }) await Promise.all(submissionPromises) From 195b0be6b14f600b5170da4234aecaeb7d0bc2e1 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Mon, 21 Dec 2020 04:58:54 +0200 Subject: [PATCH 17/37] fix: upgrade sortablejs from 1.10.2 to 1.12.0 (#865) Snyk has created this PR to upgrade sortablejs from 1.10.2 to 1.12.0. See this package in npm: https://www.npmjs.com/package/sortablejs See this project in Snyk: https://app.snyk.io/org/open-government-products/project/4c4f5244-5ce5-491a-a42d-c295bc7f51fd?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8e94e2af6..d8e220bb6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21173,9 +21173,9 @@ } }, "sortablejs": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz", - "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.12.0.tgz", + "integrity": "sha512-bPn57rCjBRlt2sC24RBsu40wZsmLkSo2XeqG8k6DC1zru5eObQUIPPZAQG7W2SJ8FZQYq+BEJmvuw1Zxb3chqg==" }, "source-list-map": { "version": "2.0.1", diff --git a/package.json b/package.json index 5d9aee568f..c236df37f3 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "puppeteer-core": "^5.3.1", "selectize": "0.12.6", "slick-carousel": "1.8.1", - "sortablejs": "~1.10.2", + "sortablejs": "~1.12.0", "text-encoding": "^0.7.0", "toastr": "^2.1.4", "triple-beam": "^1.3.0", From 53248f476164a6b94452e49cca9eb56a410fac91 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Mon, 21 Dec 2020 04:59:24 +0200 Subject: [PATCH 18/37] fix: upgrade intl-tel-input from 12.1.16 to 12.4.0 (#866) Snyk has created this PR to upgrade intl-tel-input from 12.1.16 to 12.4.0. See this package in npm: https://www.npmjs.com/package/intl-tel-input See this project in Snyk: https://app.snyk.io/org/open-government-products/project/4c4f5244-5ce5-491a-a42d-c295bc7f51fd?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8e220bb6f..63e5d045b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13303,9 +13303,9 @@ "dev": true }, "intl-tel-input": { - "version": "12.1.16", - "resolved": "https://registry.npmjs.org/intl-tel-input/-/intl-tel-input-12.1.16.tgz", - "integrity": "sha512-rIDwyDP3eQzKw79YA5OmSmGfBPtAyt0pz3typSb3Q8rnDCrff2Kd7YaPpvEvxiV7FfK2iq0B4gqVPE8ZBAidPg==" + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/intl-tel-input/-/intl-tel-input-12.4.0.tgz", + "integrity": "sha512-tVXt9QcczIp9Y6yWxDf6p8+4YfmU1NGSbNU3kxY4EPY+QV++j4DlC1gZ/wkNIi8oTpyJUWZKqzynCF9Y135weQ==" }, "invariant": { "version": "2.2.4", diff --git a/package.json b/package.json index c236df37f3..c6da174587 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "has-ansi": "^4.0.0", "helmet": "^4.2.0", "http-status-codes": "^2.1.4", - "intl-tel-input": "~12.1.6", + "intl-tel-input": "~12.4.0", "json-stringify-safe": "^5.0.1", "jszip": "^3.2.2", "jwt-decode": "^3.1.2", From ce90ab37c83ae903478c98abf4d44ffba09d9209 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 11:23:55 +0800 Subject: [PATCH 19/37] chore(deps-dev): bump core-js from 3.6.5 to 3.8.1 (#907) Bumps [core-js](https://github.com/zloirock/core-js) from 3.6.5 to 3.8.1. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/compare/v3.6.5...v3.8.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63e5d045b1..642e378f49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8680,9 +8680,9 @@ } }, "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", + "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==", "dev": true }, "core-js-compat": { diff --git a/package.json b/package.json index c6da174587..9977fe708c 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "babel-loader": "^8.2.2", "concurrently": "^5.3.0", "copy-webpack-plugin": "^6.0.2", - "core-js": "^3.6.4", + "core-js": "^3.8.1", "coveralls": "^3.1.0", "css-loader": "^2.1.1", "csv-parse": "^4.14.1", From e22448e9192aaa5bacbe4060d38b72d2cc0d81ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 11:24:28 +0800 Subject: [PATCH 20/37] chore(deps-dev): bump @types/jest from 26.0.16 to 26.0.19 (#906) Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.16 to 26.0.19. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 642e378f49..7f281ffea0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4095,9 +4095,9 @@ } }, "@types/jest": { - "version": "26.0.16", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.16.tgz", - "integrity": "sha512-Gp12+7tmKCgv9JjtltxUXokohCAEZfpJaEW5tn871SGRp8I+bRWBonQO7vW5NHwnAHe5dd50+Q4zyKuN35i09g==", + "version": "26.0.19", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.19.tgz", + "integrity": "sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==", "dev": true, "requires": { "jest-diff": "^26.0.0", diff --git a/package.json b/package.json index 9977fe708c..16e7e28874 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "@types/has-ansi": "^3.0.0", "@types/helmet": "4.0.0", "@types/ip": "^1.1.0", - "@types/jest": "^26.0.16", + "@types/jest": "^26.0.19", "@types/json-stringify-safe": "^5.0.0", "@types/mongodb": "^3.6.3", "@types/mongodb-uri": "^0.9.0", From 1b2cf199596749fdccf13eb9c2e5799677174158 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 11:24:45 +0800 Subject: [PATCH 21/37] chore(deps-dev): bump @types/express-serve-static-core (#905) Bumps [@types/express-serve-static-core](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express-serve-static-core) from 4.17.15 to 4.17.17. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express-serve-static-core) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f281ffea0..efe443e631 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4007,9 +4007,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.15.tgz", - "integrity": "sha512-pb71P0BrBAx7cQE+/7QnA1HTQUkdBKMlkPY7lHUMn0YvPJkL2UA+KW3BdWQ309IT+i9En/qm45ZxpjIcpgEhNQ==", + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.17.tgz", + "integrity": "sha512-YYlVaCni5dnHc+bLZfY908IG1+x5xuibKZMGv8srKkvtul3wUuanYvpIj9GXXoWkQbaAdR+kgX46IETKUALWNQ==", "dev": true, "requires": { "@types/node": "*", diff --git a/package.json b/package.json index 16e7e28874..6d76ce2af5 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@types/express": "^4.17.9", "@types/express-rate-limit": "^5.1.0", "@types/express-request-id": "^1.4.1", - "@types/express-serve-static-core": "^4.17.15", + "@types/express-serve-static-core": "^4.17.17", "@types/express-session": "^1.17.0", "@types/has-ansi": "^3.0.0", "@types/helmet": "4.0.0", From d18e5ce985ef77b6e121ff905ccb712fd858333d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 11:25:25 +0800 Subject: [PATCH 22/37] chore(deps-dev): bump @types/node from 14.14.11 to 14.14.14 (#900) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.11 to 14.14.14. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index efe443e631..722625fc83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4170,9 +4170,9 @@ "dev": true }, "@types/node": { - "version": "14.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.11.tgz", - "integrity": "sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ==" + "version": "14.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", + "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==" }, "@types/nodemailer": { "version": "6.4.0", diff --git a/package.json b/package.json index 6d76ce2af5..c96161fb76 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,7 @@ "@types/json-stringify-safe": "^5.0.0", "@types/mongodb": "^3.6.3", "@types/mongodb-uri": "^0.9.0", - "@types/node": "^14.14.11", + "@types/node": "^14.14.14", "@types/nodemailer": "^6.4.0", "@types/nodemailer-direct-transport": "^1.0.31", "@types/opossum": "^4.1.1", From 4322c1adb8ef470eb13ea77c79de733806483f15 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Mon, 21 Dec 2020 05:26:11 +0200 Subject: [PATCH 23/37] fix: upgrade angular-moment from 1.2.0 to 1.3.0 (#870) Snyk has created this PR to upgrade angular-moment from 1.2.0 to 1.3.0. See this package in npm: https://www.npmjs.com/package/angular-moment See this project in Snyk: https://app.snyk.io/org/open-government-products/project/4c4f5244-5ce5-491a-a42d-c295bc7f51fd?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 722625fc83..452b0c3d21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5035,9 +5035,9 @@ "integrity": "sha512-M1qNh/30cLJi4yJJ+3YB8saPonRcavz5Dquqz0T/aUySKJhIkUoeCkmF+BcLH4SJ5PBp04yy4CZUUeNRVi7jZA==" }, "angular-moment": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.2.0.tgz", - "integrity": "sha512-0jm7AfCTk/zbBuVuoz9MecMplGfTQ9Wk9JB8J4qYX9IddcETRukpbKXMy27YTL1wKwYJkRTbGMu/Pb3IXbMfyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz", + "integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==", "requires": { "moment": ">=2.8.0 <3.0.0" } diff --git a/package.json b/package.json index c96161fb76..1d529f55ff 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "angular-cookies": "~1.8.2", "angular-drag-scroll": "^0.2.1", "angular-messages": "^1.8.2", - "angular-moment": "~1.2.0", + "angular-moment": "~1.3.0", "angular-permission": "~1.1.1", "angular-resource": "^1.8.2", "angular-sanitize": "^1.8.2", From 06f463ec491936d5b93762e6fc453dcf09d4d780 Mon Sep 17 00:00:00 2001 From: Antariksh Mahajan Date: Mon, 21 Dec 2020 12:10:35 +0800 Subject: [PATCH 24/37] feat: harden rate limits (#909) * feat: return 429 when rate limit is exceeded * feat: format auth OTP err message correctly * feat: return consistent error shape for send OTP * feat: improve error message * test: update tests * feat: update env var default rate limit --- .../auth/__tests__/auth.controller.spec.ts | 16 ++++++----- .../auth/__tests__/auth.routes.spec.ts | 28 +++++++++++-------- src/app/modules/auth/auth.controller.ts | 4 +-- src/app/utils/__tests__/limit-rate.spec.ts | 18 ++++++------ src/app/utils/limit-rate.ts | 9 ++++-- src/config/schema.ts | 5 ++-- .../authentication.client.controller.js | 7 +++-- 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/app/modules/auth/__tests__/auth.controller.spec.ts b/src/app/modules/auth/__tests__/auth.controller.spec.ts index c622892805..844538b2a9 100644 --- a/src/app/modules/auth/__tests__/auth.controller.spec.ts +++ b/src/app/modules/auth/__tests__/auth.controller.spec.ts @@ -104,7 +104,7 @@ describe('auth.controller', () => { // Assert expect(mockRes.status).toBeCalledWith(expectedError.status) - expect(mockRes.json).toBeCalledWith(expectedError.message) + expect(mockRes.json).toBeCalledWith({ message: expectedError.message }) }) it('should return 500 when there is an error generating login OTP', async () => { @@ -123,9 +123,10 @@ describe('auth.controller', () => { // Assert expect(mockRes.status).toBeCalledWith(500) - expect(mockRes.json).toBeCalledWith( - 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', - ) + expect(mockRes.json).toBeCalledWith({ + message: + 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', + }) // Sending login OTP should not have been called. expect(MockAuthService.createLoginOtp).toHaveBeenCalledTimes(1) expect(MockMailService.sendLoginOtp).not.toHaveBeenCalled() @@ -148,9 +149,10 @@ describe('auth.controller', () => { // Assert expect(mockRes.status).toBeCalledWith(500) - expect(mockRes.json).toBeCalledWith( - 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', - ) + expect(mockRes.json).toBeCalledWith({ + message: + 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', + }) // Services should have been invoked. expect(MockAuthService.createLoginOtp).toHaveBeenCalledTimes(1) expect(MockMailService.sendLoginOtp).toHaveBeenCalledTimes(1) diff --git a/src/app/modules/auth/__tests__/auth.routes.spec.ts b/src/app/modules/auth/__tests__/auth.routes.spec.ts index 1d58bdf74f..e3ceaa14a3 100644 --- a/src/app/modules/auth/__tests__/auth.routes.spec.ts +++ b/src/app/modules/auth/__tests__/auth.routes.spec.ts @@ -167,9 +167,10 @@ describe('auth.routes', () => { // Assert expect(response.status).toEqual(401) - expect(response.body).toEqual( - 'This is not a whitelisted public service email domain. Please log in with your official government or government-linked email address.', - ) + expect(response.body).toEqual({ + message: + 'This is not a whitelisted public service email domain. Please log in with your official government or government-linked email address.', + }) }) it('should return 500 when error occurs whilst creating OTP', async () => { @@ -186,9 +187,10 @@ describe('auth.routes', () => { // Assert expect(createLoginOtpSpy).toHaveBeenCalled() expect(response.status).toEqual(500) - expect(response.body).toEqual( - 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', - ) + expect(response.body).toEqual({ + message: + 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', + }) }) it('should return 500 when error occurs whilst sending login OTP', async () => { @@ -205,9 +207,10 @@ describe('auth.routes', () => { // Assert expect(sendLoginOtpSpy).toHaveBeenCalled() expect(response.status).toEqual(500) - expect(response.body).toEqual( - 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', - ) + expect(response.body).toEqual({ + message: + 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', + }) }) it('should return 500 when validating domain returns a database error', async () => { @@ -224,9 +227,10 @@ describe('auth.routes', () => { // Assert expect(getAgencySpy).toBeCalled() expect(response.status).toEqual(500) - expect(response.body).toEqual( - 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', - ) + expect(response.body).toEqual({ + message: + 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', + }) }) it('should return 200 when otp is sent successfully', async () => { diff --git a/src/app/modules/auth/auth.controller.ts b/src/app/modules/auth/auth.controller.ts index cc1ba0da1a..19b4dc231e 100644 --- a/src/app/modules/auth/auth.controller.ts +++ b/src/app/modules/auth/auth.controller.ts @@ -54,7 +54,7 @@ export const handleCheckUser: RequestHandler< */ export const handleLoginSendOtp: RequestHandler< ParamsDictionary, - string, + { message: string } | string, { email: string } > = async (req, res) => { // Joi validation ensures existence. @@ -99,7 +99,7 @@ export const handleLoginSendOtp: RequestHandler< error, /* coreErrorMessage=*/ 'Failed to send login OTP. Please try again later and if the problem persists, contact us.', ) - return res.status(statusCode).json(errorMessage) + return res.status(statusCode).json({ message: errorMessage }) }) ) } diff --git a/src/app/utils/__tests__/limit-rate.spec.ts b/src/app/utils/__tests__/limit-rate.spec.ts index 77e04a8a7f..97b53f38a4 100644 --- a/src/app/utils/__tests__/limit-rate.spec.ts +++ b/src/app/utils/__tests__/limit-rate.spec.ts @@ -30,15 +30,17 @@ describe('limitRate', () => { ) }) - it('should call next() in the handler', () => { - limitRate() + it('should return 429 when the rate limit is exceeded', () => { + limitRate({ max: 0 }) const handler = MockRateLimit.mock.calls[0][0]!.handler! const mockNext = jest.fn() - handler( - expressHandler.mockRequest(), - expressHandler.mockResponse(), - mockNext, - ) - expect(mockNext).toHaveBeenCalled() + const mockRes = expressHandler.mockResponse() + handler(expressHandler.mockRequest(), mockRes, mockNext) + expect(mockNext).not.toHaveBeenCalled() + expect(mockRes.status).toHaveBeenCalledWith(429) + expect(mockRes.json).toHaveBeenCalledWith({ + message: + 'We are experiencing a temporary issue. Please try again in one minute.', + }) }) }) diff --git a/src/app/utils/limit-rate.ts b/src/app/utils/limit-rate.ts index 7d584797df..073b45a863 100644 --- a/src/app/utils/limit-rate.ts +++ b/src/app/utils/limit-rate.ts @@ -2,6 +2,7 @@ import RateLimit, { Options as RateLimitOptions, RateLimit as RateLimitFn, } from 'express-rate-limit' +import { StatusCodes } from 'http-status-codes' import { merge } from 'lodash' import { createLoggerWithLabel } from '../../config/logger' @@ -21,7 +22,7 @@ export const limitRate = (options: RateLimitOptions = {}): RateLimitFn => { const defaultOptions: RateLimitOptions = { windowMs: 60 * 1000, // Apply rate per-minute max: 1200, - handler: (req, _res, next) => { + handler: (req, res) => { logger.warn({ message: 'Rate limit exceeded', meta: { @@ -31,8 +32,10 @@ export const limitRate = (options: RateLimitOptions = {}): RateLimitFn => { rateLimitInfo: req.rateLimit, }, }) - // TODO (private #49): terminate the request with HTTP 429 - return next() + return res.status(StatusCodes.TOO_MANY_REQUESTS).json({ + message: + 'We are experiencing a temporary issue. Please try again in one minute.', + }) }, } return RateLimit(merge(defaultOptions, options)) diff --git a/src/config/schema.ts b/src/config/schema.ts index f7953860aa..c1c7cba7f2 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -268,9 +268,10 @@ export const optionalVarsSchema: Schema = { }, rateLimit: { submissions: { - doc: 'Per-minute, per-IP request limit for submissions endpoints', + doc: + 'Per-minute, per-IP, per-instance request limit for submissions endpoints', format: 'int', - default: 200, + default: 80, env: 'SUBMISSIONS_RATE_LIMIT', }, sendAuthOtp: { diff --git a/src/public/modules/users/controllers/authentication.client.controller.js b/src/public/modules/users/controllers/authentication.client.controller.js index 5fde795d4d..c35a825202 100755 --- a/src/public/modules/users/controllers/authentication.client.controller.js +++ b/src/public/modules/users/controllers/authentication.client.controller.js @@ -180,11 +180,14 @@ function AuthenticationController($state, $timeout, $window, Auth, GTag) { function (error) { vm.isOtpSending = false vm.buttonClicked = false - // Configure message to be show + // Configure message to be shown + const msg = + (error && error.data && error.data.message) || + 'Failed to send login OTP. Please try again later and if the problem persists, contact us.' vm.signInMsg = { isMsg: true, isError: true, - msg: error.data, + msg, } $timeout(function () { angular.element('#otp-input').focus() From 4725ee731d0dfdf25e114d53f209b3e31e7d0b3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 15:41:13 +0800 Subject: [PATCH 25/37] chore(deps-dev): bump @typescript-eslint/parser from 4.9.0 to 4.10.0 (#910) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.9.0 to 4.10.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.10.0/packages/parser) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 46 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 452b0c3d21..69874c2ca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4520,41 +4520,41 @@ } }, "@typescript-eslint/parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.9.0.tgz", - "integrity": "sha512-QRSDAV8tGZoQye/ogp28ypb8qpsZPV6FOLD+tbN4ohKUWHD2n/u0Q2tIBnCsGwQCiD94RdtLkcqpdK4vKcLCCw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.10.0.tgz", + "integrity": "sha512-amBvUUGBMadzCW6c/qaZmfr3t9PyevcSWw7hY2FuevdZVp5QPw/K76VSQ5Sw3BxlgYCHZcK6DjIhSZK0PQNsQg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.9.0", - "@typescript-eslint/types": "4.9.0", - "@typescript-eslint/typescript-estree": "4.9.0", + "@typescript-eslint/scope-manager": "4.10.0", + "@typescript-eslint/types": "4.10.0", + "@typescript-eslint/typescript-estree": "4.10.0", "debug": "^4.1.1" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.0.tgz", - "integrity": "sha512-q/81jtmcDtMRE+nfFt5pWqO0R41k46gpVLnuefqVOXl4QV1GdQoBWfk5REcipoJNQH9+F5l+dwa9Li5fbALjzg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.10.0.tgz", + "integrity": "sha512-WAPVw35P+fcnOa8DEic0tQUhoJJsgt+g6DEcz257G7vHFMwmag58EfowdVbiNcdfcV27EFR0tUBVXkDoIvfisQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.9.0", - "@typescript-eslint/visitor-keys": "4.9.0" + "@typescript-eslint/types": "4.10.0", + "@typescript-eslint/visitor-keys": "4.10.0" } }, "@typescript-eslint/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.0.tgz", - "integrity": "sha512-luzLKmowfiM/IoJL/rus1K9iZpSJK6GlOS/1ezKplb7MkORt2dDcfi8g9B0bsF6JoRGhqn0D3Va55b+vredFHA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.10.0.tgz", + "integrity": "sha512-+dt5w1+Lqyd7wIPMa4XhJxUuE8+YF+vxQ6zxHyhLGHJjHiunPf0wSV8LtQwkpmAsRi1lEOoOIR30FG5S2HS33g==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.0.tgz", - "integrity": "sha512-rmDR++PGrIyQzAtt3pPcmKWLr7MA+u/Cmq9b/rON3//t5WofNR4m/Ybft2vOLj0WtUzjn018ekHjTsnIyBsQug==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.10.0.tgz", + "integrity": "sha512-mGK0YRp9TOk6ZqZ98F++bW6X5kMTzCRROJkGXH62d2azhghmq+1LNLylkGe6uGUOQzD452NOAEth5VAF6PDo5g==", "dev": true, "requires": { - "@typescript-eslint/types": "4.9.0", - "@typescript-eslint/visitor-keys": "4.9.0", + "@typescript-eslint/types": "4.10.0", + "@typescript-eslint/visitor-keys": "4.10.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -4564,12 +4564,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.0.tgz", - "integrity": "sha512-sV45zfdRqQo1A97pOSx3fsjR+3blmwtdCt8LDrXgCX36v4Vmz4KHrhpV6Fo2cRdXmyumxx11AHw0pNJqCNpDyg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.10.0.tgz", + "integrity": "sha512-hPyz5qmDMuZWFtHZkjcCpkAKHX8vdu1G3YsCLEd25ryZgnJfj6FQuJ5/O7R+dB1ueszilJmAFMtlU4CA6se3Jg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.9.0", + "@typescript-eslint/types": "4.10.0", "eslint-visitor-keys": "^2.0.0" } }, diff --git a/package.json b/package.json index 1d529f55ff..6959bcbf59 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,7 @@ "@types/uuid": "^8.3.0", "@types/validator": "^13.1.0", "@typescript-eslint/eslint-plugin": "^4.0.1", - "@typescript-eslint/parser": "^4.9.0", + "@typescript-eslint/parser": "^4.10.0", "auto-changelog": "^2.2.1", "axios-mock-adapter": "^1.19.0", "babel-loader": "^8.2.2", From 973e573173670bc221ab61fb67a47c5870c4f581 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 15:41:39 +0800 Subject: [PATCH 26/37] chore(deps-dev): bump csv-parse from 4.14.1 to 4.14.2 (#911) Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.14.1 to 4.14.2. - [Release notes](https://github.com/wdavidw/node-csv-parse/releases) - [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md) - [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.14.1...v4.14.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69874c2ca6..2df8556299 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9201,9 +9201,9 @@ } }, "csv-parse": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.14.1.tgz", - "integrity": "sha512-4wmcO7QbWtDAncGFaBwlWFPhEN4Akr64IbM4zvDwEOFekI8blLc04Nw7XjQjtSNy+3AUAgBgtUa9nWo5Cq89Xg==" + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.14.2.tgz", + "integrity": "sha512-YE2xlTKtM035/94llhgsp9qFQxGi47EkQJ1pZ+mLT/98GpIsbjkMGAb7Rmu9hNxVfYFOLf10hP+rPVqnoccLgw==" }, "csv-string": { "version": "4.0.1", diff --git a/package.json b/package.json index 6959bcbf59..dced19587c 100644 --- a/package.json +++ b/package.json @@ -206,7 +206,7 @@ "core-js": "^3.8.1", "coveralls": "^3.1.0", "css-loader": "^2.1.1", - "csv-parse": "^4.14.1", + "csv-parse": "^4.14.2", "env-cmd": "^10.1.0", "eslint": "^7.14.0", "eslint-config-prettier": "^7.0.0", From 9775a29e2800568466b6969e431c281708291815 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 15:42:06 +0800 Subject: [PATCH 27/37] fix(deps): bump twilio from 3.52.0 to 3.54.1 (#913) Bumps [twilio](https://github.com/twilio/twilio-node) from 3.52.0 to 3.54.1. - [Release notes](https://github.com/twilio/twilio-node/releases) - [Changelog](https://github.com/twilio/twilio-node/blob/main/CHANGES.md) - [Commits](https://github.com/twilio/twilio-node/compare/3.52.0...3.54.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2df8556299..01ee00ba34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23830,9 +23830,9 @@ "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" }, "twilio": { - "version": "3.52.0", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.52.0.tgz", - "integrity": "sha512-G/2J4iva5T8080Mei3e24bCBxAemVe766iYQP+OonAzP7EUx9sv/hnNoNsM5u1vKkqKn7ER2uJ+mRI6bJrdEMA==", + "version": "3.54.1", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.54.1.tgz", + "integrity": "sha512-rUlEGFNAmmnaxYsWvoTib77eycOCIfncveh6SvP3+RbbKpos2wuzhLy0p1gm8FQSAZvjAm2xNVWi5oJRRF8Ueg==", "requires": { "axios": "^0.19.2", "dayjs": "^1.8.29", diff --git a/package.json b/package.json index dced19587c..e465260755 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "toastr": "^2.1.4", "triple-beam": "^1.3.0", "tweetnacl": "^1.0.1", - "twilio": "^3.52.0", + "twilio": "^3.54.1", "ui-select": "^0.19.8", "uid-generator": "^2.0.0", "uuid": "^8.3.2", From 48967b58c8574d25b1207f0fb225abbabca238eb Mon Sep 17 00:00:00 2001 From: Yuan Ruo Date: Mon, 21 Dec 2020 16:01:29 +0800 Subject: [PATCH 28/37] docs(public-form): add warning comment to GET endpoint in case of API refactor (#897) Co-authored-by: Yuanruo Liang --- src/app/routes/public-forms.server.routes.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/routes/public-forms.server.routes.js b/src/app/routes/public-forms.server.routes.js index fa78ce01d3..7b3e13a34d 100644 --- a/src/app/routes/public-forms.server.routes.js +++ b/src/app/routes/public-forms.server.routes.js @@ -116,6 +116,11 @@ module.exports = function (app) { * Returns the specified form to the user, along with any * identity information obtained from SingPass/CorpPass, * and MyInfo details, if any. + * + * WARNING: TemperatureSG batch jobs rely on this endpoint to + * retrieve the master list of personnel for daily reporting. + * Please strictly ensure backwards compatibility. + * * @route GET /{formId}/publicform * @group forms - endpoints to serve forms * @param {string} formId.path.required - the form id From bee431a62616d25adfd6be4a6592f2b4b3a537ce Mon Sep 17 00:00:00 2001 From: Antariksh Mahajan Date: Mon, 21 Dec 2020 16:01:44 +0800 Subject: [PATCH 29/37] fix: add request metadata to email data error log (#915) --- .../submission/email-submission/email-submission.middleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/modules/submission/email-submission/email-submission.middleware.ts b/src/app/modules/submission/email-submission/email-submission.middleware.ts index 8d74305084..52829379b6 100644 --- a/src/app/modules/submission/email-submission/email-submission.middleware.ts +++ b/src/app/modules/submission/email-submission/email-submission.middleware.ts @@ -56,6 +56,7 @@ export const prepareEmailSubmission: RequestHandler< message: 'Failed to create email data', meta: { action: 'prepareEmailSubmission', + ...createReqMeta(req), responseMetaData: req.body.parsedResponses.map((response) => ({ question: response?.question, // Cast just for logging purposes From ce79eb7970e76be398b52167a7de49ea4ce34821 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 16:03:23 +0800 Subject: [PATCH 30/37] fix(deps): bump fp-ts from 2.9.0 to 2.9.1 (#914) Bumps [fp-ts](https://github.com/gcanti/fp-ts) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/gcanti/fp-ts/releases) - [Changelog](https://github.com/gcanti/fp-ts/blob/master/CHANGELOG.md) - [Commits](https://github.com/gcanti/fp-ts/compare/2.9.0...2.9.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 01ee00ba34..3b145cb29a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11867,9 +11867,9 @@ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fp-ts": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.9.0.tgz", - "integrity": "sha512-zYPaS+3BWy+hRhkCMJXLTHxdy7oshyKOfHge/S/n+LcLL5VIhctR9Xucxi3GwJbjwDZaz9IIbU1o+YjK0LsgYg==" + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.9.1.tgz", + "integrity": "sha512-9++IpEtF2blK7tfSV+iHxO3KXdAGO/bPPQtUYqzC6XKzGOWNctqvlf13SpXxcu2mYaibOvneh/m9vAPLAHdoRQ==" }, "fragment-cache": { "version": "0.2.1", diff --git a/package.json b/package.json index e465260755..0772d23b40 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "file-loader": "^4.0.0", "file-saver": "^2.0.5", "font-awesome": "4.7.0", - "fp-ts": "^2.9.0", + "fp-ts": "^2.9.1", "has-ansi": "^4.0.0", "helmet": "^4.2.0", "http-status-codes": "^2.1.4", From 859017e4e7829f7e46129d5dc2e429ecc88ef3c8 Mon Sep 17 00:00:00 2001 From: Antariksh Mahajan Date: Mon, 21 Dec 2020 16:04:52 +0800 Subject: [PATCH 31/37] fix(deps): run snyk wizard (#876) --- package-lock.json | 29 ++--------------------------- package.json | 4 ++-- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b145cb29a..d085e53fa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3545,19 +3545,6 @@ "request": "^2.88.0" }, "dependencies": { - "axios": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", - "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" - }, "qs": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", @@ -3586,16 +3573,6 @@ "xpath": "0.0.32" }, "dependencies": { - "base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" - }, - "xmldom": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz", - "integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==" - }, "xpath": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", @@ -6896,8 +6873,7 @@ "base-64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "dev": true + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" }, "base32.js": { "version": "0.1.0", @@ -25536,8 +25512,7 @@ "xmldom": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz", - "integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==", - "dev": true + "integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==" }, "xmlhttprequest-ssl": { "version": "1.5.5", diff --git a/package.json b/package.json index 0772d23b40..62b821fc17 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "express-session": "^1.15.6", "express-winston": "^4.0.5", "fetch-readablestream": "^0.2.0", - "file-loader": "^4.0.0", + "file-loader": "^4.3.0", "file-saver": "^2.0.5", "font-awesome": "4.7.0", "fp-ts": "^2.9.1", @@ -259,4 +259,4 @@ "webpack-merge": "^4.1.3", "worker-loader": "^2.0.0" } -} +} \ No newline at end of file From c90318ab9e4be31e4f8176f27ecb420a46261550 Mon Sep 17 00:00:00 2001 From: Antariksh Mahajan Date: Mon, 21 Dec 2020 16:05:59 +0800 Subject: [PATCH 32/37] fix: upgrade to use latest Node v12 (#879) --- Dockerfile.development | 2 +- Dockerfile.production | 4 ++-- docs/TROUBLESHOOTING.md | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile.development b/Dockerfile.development index 5e12e9ff59..59e631e474 100644 --- a/Dockerfile.development +++ b/Dockerfile.development @@ -1,4 +1,4 @@ -FROM node:12.18.4-alpine3.12 +FROM node:12-alpine3.12 LABEL maintainer=FormSG WORKDIR /opt/formsg diff --git a/Dockerfile.production b/Dockerfile.production index 044c005f68..3ecc305b49 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -1,4 +1,4 @@ -FROM node:12.18.4-alpine3.12 AS node-modules-builder +FROM node:12-alpine3.12 AS node-modules-builder # node-modules-builder stage installs/compiles the node_modules folder # Python version must be specified starting in alpine3.12 RUN apk update && apk upgrade && \ @@ -11,7 +11,7 @@ RUN npm ci COPY . /opt/formsg # This stage builds the final container -FROM node:12.18.4-alpine3.12 +FROM node:12-alpine3.12 LABEL maintainer=FormSG WORKDIR /opt/formsg diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 3498b03969..5d9e31292c 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -5,14 +5,14 @@ A list of common issues that developers face and how to resolve them. ## `Error: Module did not self-register.` This could happen if node modules were compiled with a different version of node, or if node modules fail to compile due to other configuration errors. -Running tests locally requires `node` to specifically be of version `12.18.4`. You can use `nvm` to manually set the node version. +Running tests locally requires `node` to specifically be the latest version of NodeJS 12. You can use `nvm` to manually set the node version. ### [Node Versioning Error](https://stackoverflow.com/questions/28486891/uncaught-error-module-did-not-self-register) Run the following commands to set the node version and then re-install the node modules: ``` -nvm use 12.18.4 +nvm use 12 rm -r node_modules npm install ``` diff --git a/package.json b/package.json index 62b821fc17..bee07db7ce 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/datagovsg/formsg.git" }, "engines": { - "node": "~12.18.4", + "node": "~12", "npm": "~6.4.0" }, "scripts": { From d18d2d4e43d62d0fd9f14c29d29ac83631917ba1 Mon Sep 17 00:00:00 2001 From: Yuan Ruo Date: Mon, 21 Dec 2020 18:06:55 +0800 Subject: [PATCH 33/37] fix: include noopener, noreferrer to tags that open in a new page (#916) Co-authored-by: Yuanruo Liang --- .../forms/admin/componentViews/share-form.client.view.html | 6 +++++- .../forms/admin/views/edit-start-page.client.modal.html | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/public/modules/forms/admin/componentViews/share-form.client.view.html b/src/public/modules/forms/admin/componentViews/share-form.client.view.html index b9c47a1385..3baa3c894b 100644 --- a/src/public/modules/forms/admin/componentViews/share-form.client.view.html +++ b/src/public/modules/forms/admin/componentViews/share-form.client.view.html @@ -50,7 +50,11 @@ diff --git a/src/public/modules/forms/admin/views/edit-start-page.client.modal.html b/src/public/modules/forms/admin/views/edit-start-page.client.modal.html index 5c07a0de37..a142822161 100644 --- a/src/public/modules/forms/admin/views/edit-start-page.client.modal.html +++ b/src/public/modules/forms/admin/views/edit-start-page.client.modal.html @@ -122,7 +122,11 @@ Your link is not hosted with us. - Visit it Visit it , save it and upload here! From ae8f5df31d42d0b73321c5122e92ef49937bbc93 Mon Sep 17 00:00:00 2001 From: tshuli <63710093+tshuli@users.noreply.github.com> Date: Mon, 21 Dec 2020 19:53:12 +0800 Subject: [PATCH 34/37] fix: backend validation does not prevent responses on hidden fields (#809) * fix: add check for responses on hidden fields * fix: correct isVisible values for tests * chore: update status code on ValidateFieldError * chore: add tests for hidden fields in field validator specs * refactor: type FieldResponse as union of Response interfaces * refactor: use else if * fix: check for table response on hidden field * fix: concatenate flattened array for table response before evaluating length * refactor: beforeAll and afterAll for jasmine clock * refactor: type ITableRow is string array * refactor: check if array contains any value by traversing each element instead of using string concatenation * chore: fix tests * fix: set isVisible as true for encrypt mode if responses * fix: response interface * refactor: add return false if isVisible upfront * refactor: string[] instead of Array * fix: tests * chore: pass a FieldResponse instead of ProcessedResponse to getProcessedResponse * chore: fix tests * refactor: remove unnecessary !response.isVisible --- .../submission/submission.service.spec.ts | 74 ++++++++++++++++++- .../modules/submission/submission.errors.ts | 4 +- .../modules/submission/submission.service.ts | 20 ++++- src/app/utils/field-validation/index.ts | 38 ++++++++++ src/types/response/index.ts | 8 +- ...mail-submissions.server.controller.spec.js | 34 ++++----- .../backend/helpers/generate-form-data.ts | 28 ++++++- .../attachment-validation.spec.js | 11 +++ .../checkbox-validation.spec.js | 11 +++ .../field-validation/date-validation.spec.js | 65 ++++++++-------- .../decimal-validation.spec.js | 23 ++++++ .../dropdown-validation.spec.js | 18 +++++ .../field-validation/email-validation.spec.ts | 34 ++++++++- .../home-num-validation.spec.js | 31 ++++++-- .../mobile-num-validation.spec.js | 31 ++++++-- .../field-validation/nric-validation.spec.js | 19 +++++ .../number-validation.spec.js | 54 ++++++++++---- .../radio-button-validation.spec.js | 20 +++++ .../rating-validation.spec.js | 21 ++++++ .../field-validation/table-validation.spec.js | 11 +++ .../field-validation/text-validation.spec.js | 30 ++++++++ .../field-validation/yesno-validation.spec.js | 19 +++++ 22 files changed, 512 insertions(+), 92 deletions(-) diff --git a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts index 72fae99eef..b13aa6cdf4 100644 --- a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts +++ b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts @@ -26,6 +26,7 @@ import { import { generateDefaultField, + generateProcessedSingleAnswerResponse, generateSingleAnswerResponse, } from 'tests/unit/backend/helpers/generate-form-data' import dbHandler from 'tests/unit/backend/helpers/jest-db' @@ -59,6 +60,15 @@ describe('submission.service', () => { 'test@example.com', ) + const mobileProcessedResponse = generateProcessedSingleAnswerResponse( + mobileField, + '+6587654321', + ) + const emailProcessedResponse = generateProcessedSingleAnswerResponse( + emailField, + 'test@example.com', + ) + // Act const actualResult = SubmissionService.getProcessedResponses( ({ @@ -70,8 +80,8 @@ describe('submission.service', () => { // Assert const expectedParsed: ProcessedFieldResponse[] = [ - { ...mobileResponse, isVisible: true }, - { ...emailResponse, isVisible: true }, + { ...mobileProcessedResponse, isVisible: true }, + { ...emailProcessedResponse, isVisible: true }, ] // Should only have email and mobile fields for encrypted forms. expect(actualResult.isOk()).toEqual(true) @@ -94,6 +104,15 @@ describe('submission.service', () => { '3.142', ) + const shortTextProcessedResponse = generateProcessedSingleAnswerResponse( + shortTextField, + 'the quick brown fox jumps over the lazy dog', + ) + const decimalProcessedResponse = generateProcessedSingleAnswerResponse( + decimalField, + '3.142', + ) + // Act const actualResult = SubmissionService.getProcessedResponses( ({ @@ -105,8 +124,8 @@ describe('submission.service', () => { // Assert const expectedParsed: ProcessedFieldResponse[] = [ - { ...shortTextResponse, isVisible: true }, - { ...decimalResponse, isVisible: true }, + { ...shortTextProcessedResponse, isVisible: true }, + { ...decimalProcessedResponse, isVisible: true }, ] expect(actualResult.isOk()).toEqual(true) @@ -152,6 +171,53 @@ describe('submission.service', () => { new ConflictError('Some form fields are missing'), ) }) + it('should allow responses for encrypt mode hidden fields', async () => { + // Arrange + // Only check for mobile and email fields, since the other fields are + // e2e encrypted from the browser. + const mobileField = generateDefaultField(BasicField.Mobile) + const emailField = generateDefaultField(BasicField.Email) + // Add answers to both mobile and email fields + const mobileResponse = generateSingleAnswerResponse( + mobileField, + '+6587654321', + ) + + const emailResponse = generateSingleAnswerResponse( + emailField, + 'test@example.com', + ) + + const mobileProcessedResponse = generateProcessedSingleAnswerResponse( + mobileField, + '+6587654321', + ) + mobileProcessedResponse.isVisible = false + + const emailProcessedResponse = generateProcessedSingleAnswerResponse( + emailField, + 'test@example.com', + ) + emailProcessedResponse.isVisible = false + + // Act + const actualResult = SubmissionService.getProcessedResponses( + ({ + responseMode: ResponseMode.Encrypt, + form_fields: [mobileField, emailField], + } as unknown) as IFormSchema, + [mobileResponse, emailResponse], + ) + + // Assert + const expectedParsed: ProcessedFieldResponse[] = [ + { ...mobileProcessedResponse, isVisible: true }, //getProcessedResponses sets isVisible to be true for encrypt mode + { ...emailProcessedResponse, isVisible: true }, + ] + // Should only have email and mobile fields for encrypted forms. + expect(actualResult.isOk()).toEqual(true) + expect(actualResult._unsafeUnwrap()).toEqual(expectedParsed) + }) it('should return error when any responses are not valid for encrypted form submission', async () => { // Arrange diff --git a/src/app/modules/submission/submission.errors.ts b/src/app/modules/submission/submission.errors.ts index de56468cb9..17f7920df9 100644 --- a/src/app/modules/submission/submission.errors.ts +++ b/src/app/modules/submission/submission.errors.ts @@ -40,8 +40,8 @@ export class ProcessingError extends ApplicationError { * A custom error class returned when given submission has field validation failure */ export class ValidateFieldError extends ApplicationError { - constructor(message = 'Error validating field.') { - super(message) + constructor(message = 'Error validating field.', status = 400) { + super(message, status) } } diff --git a/src/app/modules/submission/submission.service.ts b/src/app/modules/submission/submission.service.ts index f8186f2559..c516094768 100644 --- a/src/app/modules/submission/submission.service.ts +++ b/src/app/modules/submission/submission.service.ts @@ -7,7 +7,12 @@ import { getLogicUnitPreventingSubmit, getVisibleFieldIds, } from '../../../shared/util/logic' -import { FieldResponse, IFieldSchema, IFormDocument } from '../../../types' +import { + FieldResponse, + IFieldSchema, + IFormDocument, + ResponseMode, +} from '../../../types' import getSubmissionModel from '../../models/submission.server.model' import { createQueryWithDateParam, isMalformedDate } from '../../utils/date' import { validateField } from '../../utils/field-validation' @@ -121,7 +126,18 @@ export const getProcessedResponses = ( const processingResponse: ProcessedFieldResponse = { ...response, - isVisible: visibleFieldIds.has(responseId), + isVisible: + // Set isVisible as true for Encrypt mode if there is a response for mobile and email field + // Because we cannot tell if the field is unhidden by logic + // This prevents downstream validateField from incorrectly preventing + // encrypt mode submissions with responses on unhidden fields + // TODO(#780): Remove this once submission service is separated into + // Email and Encrypted services + form.responseMode === ResponseMode.Encrypt + ? 'answer' in response && + typeof response.answer === 'string' && + response.answer.trim() !== '' + : visibleFieldIds.has(responseId), question: formField.getQuestion(), } diff --git a/src/app/utils/field-validation/index.ts b/src/app/utils/field-validation/index.ts index 41f80c7fa4..7107fb4856 100644 --- a/src/app/utils/field-validation/index.ts +++ b/src/app/utils/field-validation/index.ts @@ -13,6 +13,7 @@ import { ValidateFieldError } from '../../modules/submission/submission.errors' import { ALLOWED_VALIDATORS, FIELDS_TO_REJECT } from './config' import { + isProcessedAttachmentResponse, isProcessedCheckboxResponse, isProcessedSingleAnswerResponse, isProcessedTableResponse, @@ -45,6 +46,37 @@ const doFieldTypesMatch = ( : right(undefined) } +/** + * Returns true if response appears on a hidden field. + * This may happen if a submission is made programatically to try and bypass form logic. + * @param response The submitted response + */ +const isResponsePresentOnHiddenField = ( + response: ProcessedFieldResponse, +): boolean => { + if (response.isVisible) return false + if (isProcessedSingleAnswerResponse(response)) { + if (response.answer.trim() !== '') { + return true + } + } else if (isProcessedCheckboxResponse(response)) { + if (response.answerArray.length > 0) { + return true + } + } else if (isProcessedTableResponse(response)) { + if ( + !response.answerArray.every((row) => row.every((elem) => elem === '')) + ) { + return true + } + } else if (isProcessedAttachmentResponse(response)) { + if (response.filename.trim() !== '') { + return true + } + } + return false +} + /** * Determines whether a response requires validation. A required field * may not require an answer if it is not visible due to logic. However, @@ -106,6 +138,12 @@ export const validateField = ( return err(new ValidateFieldError(fieldTypeEither.left)) } + if (isResponsePresentOnHiddenField(response)) { + return err( + new ValidateFieldError(`Attempted to submit response on a hidden field`), + ) + } + if (isProcessedSingleAnswerResponse(response)) { if (singleAnswerRequiresValidation(formField, response)) { switch (formField.fieldType) { diff --git a/src/types/response/index.ts b/src/types/response/index.ts index 00af308ee9..06c9e6ceb6 100644 --- a/src/types/response/index.ts +++ b/src/types/response/index.ts @@ -11,14 +11,18 @@ export interface IBaseResponse { } export interface ISingleAnswerResponse extends IBaseResponse { - fieldType: Exclude + fieldType: Exclude< + BasicField, + BasicField.Table | BasicField.Checkbox | BasicField.Attachment + > answer: string } -export interface IAttachmentResponse extends ISingleAnswerResponse { +export interface IAttachmentResponse extends IBaseResponse { fieldType: BasicField.Attachment filename: string content: Buffer + answer: string } export interface ICheckboxResponse extends IBaseResponse { diff --git a/tests/unit/backend/controllers/email-submissions.server.controller.spec.js b/tests/unit/backend/controllers/email-submissions.server.controller.spec.js index c1dbf79d19..c03b280079 100644 --- a/tests/unit/backend/controllers/email-submissions.server.controller.spec.js +++ b/tests/unit/backend/controllers/email-submissions.server.controller.spec.js @@ -1070,7 +1070,7 @@ describe('Email Submissions Controller', () => { question: nonVisibleField.title, fieldType: nonVisibleField.fieldType, isHeader: false, - answer: 'abc', + answer: '', } const yesNoResponse = { _id: String(yesNoField._id), @@ -1604,7 +1604,7 @@ describe('Email Submissions Controller', () => { logicField._id, logicField.fieldType, logicField.title, - 'lorem', + expectLogicFieldVisible ? 'lorem' : '', null, expectLogicFieldVisible, ), @@ -1711,7 +1711,7 @@ describe('Email Submissions Controller', () => { logicField._id, logicField.fieldType, logicField.title, - 'lorem', + expectLogicFieldVisible ? 'lorem' : '', null, expectLogicFieldVisible, ), @@ -1818,7 +1818,7 @@ describe('Email Submissions Controller', () => { logicField._id, logicField.fieldType, logicField.title, - 'lorem', + expectLogicFieldVisible ? 'lorem' : '', null, expectLogicFieldVisible, ), @@ -1941,7 +1941,7 @@ describe('Email Submissions Controller', () => { logicField._id, logicField.fieldType, logicField.title, - 'lorem', + expectLogicFieldVisible ? 'lorem' : '', null, expectLogicFieldVisible, ), @@ -2104,7 +2104,7 @@ describe('Email Submissions Controller', () => { logicField._id, logicField.fieldType, logicField.title, - 'lorem', + expectLogicFieldVisible ? 'lorem' : '', null, expectLogicFieldVisible, ), @@ -2191,7 +2191,7 @@ describe('Email Submissions Controller', () => { logicField1._id, logicField1.fieldType, logicField1.title, - 'lorem', + '', null, false, ), // This field should be hidden @@ -2199,7 +2199,7 @@ describe('Email Submissions Controller', () => { logicField2._id, logicField2.fieldType, logicField2.title, - 'ipsum', + '', null, false, ), // This field should be hidden @@ -2259,7 +2259,7 @@ describe('Email Submissions Controller', () => { logicField1._id, logicField1.fieldType, logicField1.title, - 'lorem', + '', null, false, ), // This field should be hidden @@ -2401,7 +2401,7 @@ describe('Email Submissions Controller', () => { conditionField2._id, conditionField2.fieldType, conditionField2.title, - conditionField2Val, + expectedField2Visible ? conditionField2Val : '', null, expectedField2Visible, ), @@ -2409,7 +2409,7 @@ describe('Email Submissions Controller', () => { logicField._id, logicField.fieldType, logicField.title, - '12 Dec 2019', + expectLogicFieldVisible ? '12 Dec 2019' : '', null, expectLogicFieldVisible, ), @@ -2643,7 +2643,7 @@ describe('Email Submissions Controller', () => { conditionField1._id, conditionField1.fieldType, conditionField1.title, - 'Yes', + '', null, false, ), // Circular, never shown @@ -2651,7 +2651,7 @@ describe('Email Submissions Controller', () => { conditionField2._id, conditionField2.fieldType, conditionField2.title, - 'Yes', + '', null, false, ), // Circular, never shown @@ -2659,7 +2659,7 @@ describe('Email Submissions Controller', () => { conditionField3._id, conditionField3.fieldType, conditionField3.title, - 'Yes', + '', null, false, ), // Circular, never shown @@ -2683,7 +2683,7 @@ describe('Email Submissions Controller', () => { conditionField1._id, conditionField1.fieldType, conditionField1.title, - 'Yes', + '', null, false, ), // Circular, never shown @@ -2691,7 +2691,7 @@ describe('Email Submissions Controller', () => { conditionField2._id, conditionField2.fieldType, conditionField2.title, - 'Yes', + '', null, false, ), // Circular, never shown @@ -2699,7 +2699,7 @@ describe('Email Submissions Controller', () => { conditionField3._id, conditionField3.fieldType, conditionField3.title, - 'Yes', + '', null, false, ), // Circular, never shown diff --git a/tests/unit/backend/helpers/generate-form-data.ts b/tests/unit/backend/helpers/generate-form-data.ts index cb36645606..811d31f6f6 100644 --- a/tests/unit/backend/helpers/generate-form-data.ts +++ b/tests/unit/backend/helpers/generate-form-data.ts @@ -17,6 +17,7 @@ import { IFieldSchema, IImageFieldSchema, IShortTextFieldSchema, + ISingleAnswerResponse, ITableFieldSchema, } from 'src/types' @@ -98,7 +99,7 @@ export const generateDefaultField = ( } } -export const generateSingleAnswerResponse = ( +export const generateProcessedSingleAnswerResponse = ( field: IFieldSchema, answer = 'answer', ): ProcessedSingleAnswerResponse => { @@ -117,12 +118,35 @@ export const generateSingleAnswerResponse = ( answer, fieldType: field.fieldType as Exclude< BasicField, - BasicField.Table | BasicField.Checkbox + BasicField.Table | BasicField.Checkbox | BasicField.Attachment >, isVisible: true, } } +export const generateSingleAnswerResponse = ( + field: IFieldSchema, + answer = 'answer', +): ISingleAnswerResponse => { + if ( + [BasicField.Attachment, BasicField.Table, BasicField.Checkbox].includes( + field.fieldType, + ) + ) { + throw new Error( + 'Call the custom response generator functions for attachment, table and checkbox.', + ) + } + return { + _id: field._id, + answer, + fieldType: field.fieldType as Exclude< + BasicField, + BasicField.Table | BasicField.Checkbox | BasicField.Attachment + >, + } +} + export const generateNewSingleAnswerResponse = ( fieldType: BasicField, customParams?: Partial, diff --git a/tests/unit/backend/utils/field-validation/attachment-validation.spec.js b/tests/unit/backend/utils/field-validation/attachment-validation.spec.js index 0bbc601943..6a3b871f22 100644 --- a/tests/unit/backend/utils/field-validation/attachment-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/attachment-validation.spec.js @@ -108,4 +108,15 @@ describe('Attachment validation', () => { expect(validateResult._unsafeUnwrap()).toEqual(true) }) }) + + it('should disallow responses submitted for hidden fields', () => { + const formField = makeField(fieldId, '3') + const response = makeResponse(fieldId, Buffer.alloc(2000000)) + response.isVisible = false + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js b/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js index 8cbe784641..41864d7560 100644 --- a/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/checkbox-validation.spec.js @@ -238,4 +238,15 @@ describe('Checkbox validation', () => { ) }) }) + it('should disallow responses submitted for hidden fields', () => { + const fieldOptions = ['a', 'b', 'c'] + const formField = makeCheckboxField(fieldId, fieldOptions) + const response = makeCheckboxResponse(fieldId, ['a']) + response.isVisible = false + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/date-validation.spec.js b/tests/unit/backend/utils/field-validation/date-validation.spec.js index eec0ac0ee6..0a8cf83e68 100644 --- a/tests/unit/backend/utils/field-validation/date-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/date-validation.spec.js @@ -7,6 +7,13 @@ const { } = require('../../../../../dist/backend/app/modules/submission/submission.errors') describe('Date field validation', () => { + beforeAll(() => { + jasmine.clock().install() + jasmine.clock().mockDate(new Date('2020-01-01')) + }) + afterAll(() => { + jasmine.clock().uninstall() + }) it('should allow valid date
', () => { const formField = { _id: 'abc123', @@ -287,9 +294,6 @@ describe('Date field validation', () => { }) it('should allow past dates for normal date fields', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -305,14 +309,9 @@ describe('Date field validation', () => { const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) - - jasmine.clock().uninstall() }) it('should allow past dates if disallow past dates is not set', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -328,14 +327,9 @@ describe('Date field validation', () => { const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) - - jasmine.clock().uninstall() }) it('should disallow past dates if disallow past dates is set', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -354,14 +348,9 @@ describe('Date field validation', () => { expect(validateResult._unsafeUnwrapErr()).toEqual( new ValidateFieldError('Invalid answer submitted'), ) - - jasmine.clock().uninstall() }) it('should allow future dates if disallow future dates is not set', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -377,14 +366,9 @@ describe('Date field validation', () => { const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) - - jasmine.clock().uninstall() }) it('should disallow future dates if disallow future dates is set', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -403,14 +387,9 @@ describe('Date field validation', () => { expect(validateResult._unsafeUnwrapErr()).toEqual( new ValidateFieldError('Invalid answer submitted'), ) - - jasmine.clock().uninstall() }) it('should allow dates inside of Custom Date Range if set', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -431,14 +410,9 @@ describe('Date field validation', () => { const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) - - jasmine.clock().uninstall() }) it('should disallow dates outside of Custom Date Range if set', () => { - jasmine.clock().install() - jasmine.clock().mockDate(new Date('2020-01-01')) - const formField = { _id: 'abc123', fieldType: 'date', @@ -461,7 +435,30 @@ describe('Date field validation', () => { expect(validateResult._unsafeUnwrapErr()).toEqual( new ValidateFieldError('Invalid answer submitted'), ) + }) - jasmine.clock().uninstall() + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'date', + dateValidation: { + customMinDate: '2020-06-25', + customMaxDate: '2020-06-28', + }, + required: true, + } + + const response = { + _id: 'abc123', + fieldType: 'date', + isVisible: false, + answer: '22 Jun 2020', + } + + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) }) }) diff --git a/tests/unit/backend/utils/field-validation/decimal-validation.spec.js b/tests/unit/backend/utils/field-validation/decimal-validation.spec.js index 9f710e0fc4..97ccf06097 100644 --- a/tests/unit/backend/utils/field-validation/decimal-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/decimal-validation.spec.js @@ -385,4 +385,27 @@ describe('Decimal Validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'decimal', + required: true, + ValidationOptions: { + customMin: 0, + customMax: null, + }, + } + const response = { + _id: 'abc123', + fieldType: 'decimal', + isVisible: false, + answer: '-0.2', + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js b/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js index 48818661fb..bc4d2d0971 100644 --- a/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/dropdown-validation.spec.js @@ -135,4 +135,22 @@ describe('Dropdown validation', () => { new ValidateFieldError('Response has invalid shape'), ) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'ddID', + fieldType: 'dropdown', + fieldOptions: ['KISS', 'DRY', 'YAGNI'], + } + const response = { + _id: 'ddID', + fieldType: 'dropdown', + answer: 'KISS', + isVisible: false, + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/email-validation.spec.ts b/tests/unit/backend/utils/field-validation/email-validation.spec.ts index a36a7c33f4..6cfd43e183 100644 --- a/tests/unit/backend/utils/field-validation/email-validation.spec.ts +++ b/tests/unit/backend/utils/field-validation/email-validation.spec.ts @@ -21,12 +21,13 @@ describe('Email field validation', () => { required: true, disabled: false, } - const response: ISingleAnswerResponse = { + const response = { _id: 'abc123', fieldType: BasicField.Email, question: 'random', answer: 'valid@email.com', - } + isVisible: true, + } as ISingleAnswerResponse const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) @@ -47,6 +48,7 @@ describe('Email field validation', () => { fieldType: BasicField.Email, question: 'random', answer: 'abc@163.com', + isVisible: true, } const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) @@ -68,6 +70,7 @@ describe('Email field validation', () => { fieldType: BasicField.Email, question: 'random', answer: 'abc@126.com', + isVisible: true, } const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) @@ -89,6 +92,7 @@ describe('Email field validation', () => { fieldType: BasicField.Email, question: 'random', answer: 'invalidemail.com', + isVisible: true, } as ISingleAnswerResponse const validateResult = validateField('formId', formField, response) expect(validateResult.isErr()).toBe(true) @@ -245,4 +249,30 @@ describe('Email field validation', () => { expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: BasicField.Email, + globalId: 'random', + title: 'random', + description: 'random', + required: true, + disabled: false, + isVerifiable: false, + hasAllowedEmailDomains: true, + allowedEmailDomains: ['@example.com'], + } + const response = { + _id: 'abc123', + fieldType: BasicField.Email, + question: 'random', + isVisible: false, + answer: 'volunteer-testing@test.gov.sg', + } as ISingleAnswerResponse + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/home-num-validation.spec.js b/tests/unit/backend/utils/field-validation/home-num-validation.spec.js index 1e2e20318d..3780e31351 100644 --- a/tests/unit/backend/utils/field-validation/home-num-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/home-num-validation.spec.js @@ -34,7 +34,7 @@ describe('Home phone number validation tests', () => { const response = { _id: 'abc123', fieldType: 'homeno', - isVisible: false, + isVisible: true, answer: '', } const validateResult = validateField('formId', formField, response) @@ -71,7 +71,7 @@ describe('Home phone number validation tests', () => { const response = { _id: 'abc123', fieldType: 'homeno', - isVisible: false, + isVisible: true, answer: '+6565656565', } const validateResult = validateField('formId', formField, response) @@ -89,7 +89,7 @@ describe('Home phone number validation tests', () => { const response = { _id: 'abc123', fieldType: 'homeno', - isVisible: false, + isVisible: true, answer: '6567772918', } const validateResult = validateField('formId', formField, response) @@ -109,7 +109,7 @@ describe('Home phone number validation tests', () => { const response = { _id: 'abc123', fieldType: 'homeno', - isVisible: false, + isVisible: true, answer: '+6598765432', } const validateResult = validateField('formId', formField, response) @@ -129,7 +129,7 @@ describe('Home phone number validation tests', () => { const response = { _id: 'abc123', fieldType: 'homeno', - isVisible: false, + isVisible: true, answer: '+441285291028', } const validateResult = validateField('formId', formField, response) @@ -149,11 +149,30 @@ describe('Home phone number validation tests', () => { const response = { _id: 'abc123', fieldType: 'homeno', - isVisible: false, + isVisible: true, answer: '+441285291028', } const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'homeno', + required: true, + allowIntlNumbers: true, + } + const response = { + _id: 'abc123', + fieldType: 'homeno', + isVisible: false, + answer: '+441285291028', + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/mobile-num-validation.spec.js b/tests/unit/backend/utils/field-validation/mobile-num-validation.spec.js index ad6c9f4424..30b817b590 100644 --- a/tests/unit/backend/utils/field-validation/mobile-num-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/mobile-num-validation.spec.js @@ -34,7 +34,7 @@ describe('Mobile number validation tests', () => { const response = { _id: 'abc123', fieldType: 'mobile', - isVisible: false, + isVisible: true, answer: '', } const validateResult = validateField('formId', formField, response) @@ -72,7 +72,7 @@ describe('Mobile number validation tests', () => { const response = { _id: 'abc123', fieldType: 'mobile', - isVisible: false, + isVisible: true, answer: '+6598765432', } const validateResult = validateField('formId', formField, response) @@ -90,7 +90,7 @@ describe('Mobile number validation tests', () => { const response = { _id: 'abc123', fieldType: 'mobile', - isVisible: false, + isVisible: true, answer: '6598765432', } const validateResult = validateField('formId', formField, response) @@ -110,7 +110,7 @@ describe('Mobile number validation tests', () => { const response = { _id: 'abc123', fieldType: 'mobile', - isVisible: false, + isVisible: true, answer: '+6565656565', } const validateResult = validateField('formId', formField, response) @@ -130,7 +130,7 @@ describe('Mobile number validation tests', () => { const response = { _id: 'abc123', fieldType: 'mobile', - isVisible: false, + isVisible: true, answer: '+447851315617', } const validateResult = validateField('formId', formField, response) @@ -150,11 +150,30 @@ describe('Mobile number validation tests', () => { const response = { _id: 'abc123', fieldType: 'mobile', - isVisible: false, + isVisible: true, answer: '+447851315617', } const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'mobile', + required: true, + allowIntlNumbers: true, + } + const response = { + _id: 'abc123', + fieldType: 'mobile', + isVisible: false, + answer: '+447851315617', + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/nric-validation.spec.js b/tests/unit/backend/utils/field-validation/nric-validation.spec.js index 62591cc85f..ae60f87811 100644 --- a/tests/unit/backend/utils/field-validation/nric-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/nric-validation.spec.js @@ -186,4 +186,23 @@ describe('NRIC field validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'nric', + required: true, + } + const response = { + _id: 'abc123', + fieldType: 'nric', + answer: 'S0000000X', + isVisible: false, + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/number-validation.spec.js b/tests/unit/backend/utils/field-validation/number-validation.spec.js index 5d42e5f611..f825fcf768 100644 --- a/tests/unit/backend/utils/field-validation/number-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/number-validation.spec.js @@ -20,7 +20,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '5', } const validateResult = validateField('formId', formField, response) @@ -43,7 +43,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -66,7 +66,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '555', } const validateResult = validateField('formId', formField, response) @@ -91,7 +91,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '555', } const validateResult = validateField('formId', formField, response) @@ -114,7 +114,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -137,7 +137,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -160,7 +160,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '5', } const validateResult = validateField('formId', formField, response) @@ -185,7 +185,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -208,7 +208,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -231,7 +231,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -254,7 +254,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '55', } const validateResult = validateField('formId', formField, response) @@ -277,7 +277,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '', } const validateResult = validateField('formId', formField, response) @@ -300,7 +300,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '0', } const validateResult = validateField('formId', formField, response) @@ -323,7 +323,7 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '-5', } const validateResult = validateField('formId', formField, response) @@ -348,11 +348,35 @@ describe('Number field validation', () => { const response = { _id: 'abc123', fieldType: 'number', - isVisible: false, + isVisible: true, answer: '05', } const validateResult = validateField('formId', formField, response) expect(validateResult.isOk()).toBe(true) expect(validateResult._unsafeUnwrap()).toEqual(true) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'number', + required: false, + ValidationOptions: { + selectedValidation: null, + customMin: null, + customMax: null, + customVal: null, + }, + } + const response = { + _id: 'abc123', + fieldType: 'number', + isVisible: false, + answer: '05', + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js b/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js index 863e220c4a..5696705d53 100644 --- a/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/radio-button-validation.spec.js @@ -192,4 +192,24 @@ describe('Radio button validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'radioID', + fieldType: 'radiobutton', + fieldOptions: ['a', 'b', 'c'], + othersRadioButton: true, + required: true, + } + const response = { + _id: 'radioID', + fieldType: 'radiobutton', + answer: 'Others: ', + isVisible: false, + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/rating-validation.spec.js b/tests/unit/backend/utils/field-validation/rating-validation.spec.js index 2021b6b4e3..ff3a6fa1d0 100644 --- a/tests/unit/backend/utils/field-validation/rating-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/rating-validation.spec.js @@ -192,4 +192,25 @@ describe('Rating field validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = { + _id: 'abc123', + fieldType: 'rating', + required: true, + ratingOptions: { + steps: 5, + }, + } + const response = { + _id: 'abc123', + fieldType: 'rating', + isVisible: false, + answer: '5', + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/table-validation.spec.js b/tests/unit/backend/utils/field-validation/table-validation.spec.js index 2fa18a97ac..9d6fa6a19f 100644 --- a/tests/unit/backend/utils/field-validation/table-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/table-validation.spec.js @@ -230,4 +230,15 @@ describe('Table validation', () => { ) }) }) + it('should disallow responses submitted for hidden fields', () => { + const columns = [makeTextFieldColumn()] + const formField = makeTableField(fieldId, columns) + const response = makeTableResponse(fieldId, Array(4).fill(['hello'])) + response.isVisible = false + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) diff --git a/tests/unit/backend/utils/field-validation/text-validation.spec.js b/tests/unit/backend/utils/field-validation/text-validation.spec.js index 72fb5d7920..fcba1baba4 100644 --- a/tests/unit/backend/utils/field-validation/text-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/text-validation.spec.js @@ -155,6 +155,21 @@ describe('Text validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = makeShortTextField(fieldId, { + customMax: 10, + selectedValidation: 'Maximum', + }) + const response = makeShortTextResponse(fieldId, 'many more characters') + response.isVisible = false + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError( + 'Attempted to submit response on a hidden field', + ), + ) + }) }) describe('Long text', () => { @@ -277,5 +292,20 @@ describe('Text validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + it('should disallow responses submitted for hidden fields', () => { + const formField = makeLongTextField(fieldId, { + customMax: 10, + selectedValidation: 'Maximum', + }) + const response = makeLongTextResponse(fieldId, 'many more characters') + response.isVisible = false + const validateResult = validateField(formId, formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError( + 'Attempted to submit response on a hidden field', + ), + ) + }) }) }) diff --git a/tests/unit/backend/utils/field-validation/yesno-validation.spec.js b/tests/unit/backend/utils/field-validation/yesno-validation.spec.js index 0dcb593bf8..97df34fa04 100644 --- a/tests/unit/backend/utils/field-validation/yesno-validation.spec.js +++ b/tests/unit/backend/utils/field-validation/yesno-validation.spec.js @@ -92,4 +92,23 @@ describe('Yes/No field validation', () => { new ValidateFieldError('Invalid answer submitted'), ) }) + + it('should disallow responses submitted for hidden fields', () => { + const response = { + _id: 'abc123', + fieldType: 'yes_no', + answer: 'somethingRandom', + isVisible: false, + } + const formField = { + _id: 'abc123', + fieldType: 'yes_no', + required: true, + } + const validateResult = validateField('formId', formField, response) + expect(validateResult.isErr()).toBe(true) + expect(validateResult._unsafeUnwrapErr()).toEqual( + new ValidateFieldError('Attempted to submit response on a hidden field'), + ) + }) }) From a05ef47abdf4514a8e9322682363fb215ee27bc1 Mon Sep 17 00:00:00 2001 From: tshuli <63710093+tshuli@users.noreply.github.com> Date: Tue, 22 Dec 2020 10:13:17 +0800 Subject: [PATCH 35/37] refactor: prepareEncryptSubmission to typescript (#891) * refactor: migrate prepareEncryptSubmission to ts * refactor: delete old code * refactor: shift types to separate file * chore: remove responses from EncryptSubmissionBodyAfterProcess * chore: remove types in jsdocs --- .../encrypt-submissions.server.controller.js | 79 ------------------- .../encrypt-submission.middleware.ts | 48 +++++++---- .../encrypt-submission.types.ts | 36 +++++++++ src/app/routes/admin-forms.server.routes.js | 3 +- src/app/routes/public-forms.server.routes.js | 6 +- ...rypt-submissions.server.controller.spec.js | 2 +- 6 files changed, 73 insertions(+), 101 deletions(-) create mode 100644 src/app/modules/submission/encrypt-submission/encrypt-submission.types.ts diff --git a/src/app/controllers/encrypt-submissions.server.controller.js b/src/app/controllers/encrypt-submissions.server.controller.js index 919a764c37..639213f127 100644 --- a/src/app/controllers/encrypt-submissions.server.controller.js +++ b/src/app/controllers/encrypt-submissions.server.controller.js @@ -8,90 +8,11 @@ const { } = require('../models/submission.server.model') const EncryptSubmission = getEncryptSubmissionModel(mongoose) -const { checkIsEncryptedEncoding } = require('../utils/encryption') -const { ConflictError } = require('../modules/submission/submission.errors') const { createReqMeta } = require('../utils/request') const logger = require('../../config/logger').createLoggerWithLabel(module) const { aws: { attachmentS3Bucket, s3 }, } = require('../../config/config') -const { - getProcessedResponses, -} = require('../modules/submission/submission.service') - -/** - * Extracts relevant fields, injects questions, verifies visibility of field and validates answers - * to produce req.body.parsedResponses - * - * @param {Express.Request} req - Express request object - * @param {Express.Response} res - Express response object - * @param {Function} next - Express next middleware function - */ -exports.validateEncryptSubmission = function (req, res, next) { - const { form } = req - - const isEncryptedResult = checkIsEncryptedEncoding(req.body.encryptedContent) - if (isEncryptedResult.isErr()) { - logger.error({ - message: 'Invalid encryption', - meta: { - action: 'validateEncryptSubmission', - ...createReqMeta(req), - formId: form._id, - }, - error: isEncryptedResult.error, - }) - return res - .status(StatusCodes.BAD_REQUEST) - .json({ message: 'Invalid data was found. Please submit again.' }) - } - - if (!req.body.responses) { - return res.sendStatus(StatusCodes.BAD_REQUEST) - } - - const getProcessedResponsesResult = getProcessedResponses( - form, - req.body.responses, - ) - if (getProcessedResponsesResult.isErr()) { - const err = getProcessedResponsesResult.error - logger.error({ - message: 'Error processing responses', - meta: { - action: 'validateEncryptSubmission', - ...createReqMeta(req), - formId: form._id, - }, - error: err, - }) - if (err instanceof ConflictError) { - return res.status(err.status).json({ - message: 'The form has been updated. Please refresh and submit again.', - }) - } - return res.status(StatusCodes.BAD_REQUEST).json({ - message: - 'There is something wrong with your form submission. Please check your responses and try again. If the problem persists, please refresh the page.', - }) - } - req.body.parsedResponses = getProcessedResponsesResult.value - delete req.body.responses // Prevent downstream functions from using responses by deleting it - return next() -} - -/** - * Verify structure of encrypted response - * - * @param {Express.Request} req - Express request object - * @param {Express.Response} res - Express response object - * @param {Function} next - Express next middleware function - */ -exports.prepareEncryptSubmission = (req, res, next) => { - req.formData = req.body.encryptedContent - req.attachmentData = req.body.attachments || {} - return next() -} /** * @param {Error} err - the Error to report diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts index 9f9011f39c..a7a6483f30 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.middleware.ts @@ -3,27 +3,24 @@ import { SetOptional } from 'type-fest' import { createReqMeta } from '../../../../app/utils/request' import { createLoggerWithLabel } from '../../../../config/logger' -import { FieldResponse, WithForm, WithParsedResponses } from '../../../../types' +import { WithForm, WithParsedResponses } from '../../../../types' import { checkIsEncryptedEncoding } from '../../../utils/encryption' import { getProcessedResponses } from '../submission.service' +import { + EncryptSubmissionBody, + EncryptSubmissionBodyAfterProcess, + WithAttachmentsData, + WithFormData, +} from './encrypt-submission.types' import { mapRouteError } from './encrypt-submission.utils' const logger = createLoggerWithLabel(module) -type EncryptSubmissionBody = { - responses: FieldResponse[] - encryptedContent: string - attachments?: { - encryptedFile?: { - binary: string - nonce: string - submissionPublicKey: string - } - } - isPreview: boolean - version: number -} +/** + * Extracts relevant fields, injects questions, verifies visibility of field and validates answers + * to produce req.body.parsedResponses + */ export const validateAndProcessEncryptSubmission: RequestHandler< { formId: string }, @@ -52,9 +49,10 @@ export const validateAndProcessEncryptSubmission: RequestHandler< // If error, log and return res error. .mapErr((error) => { logger.error({ - message: 'Error validating encrypt submission responses', + message: + 'Error validating and processing encrypt submission responses', meta: { - action: 'validateEncryptSubmission', + action: 'validateAndProcessEncryptSubmission', ...createReqMeta(req), formId: form._id, }, @@ -68,3 +66,21 @@ export const validateAndProcessEncryptSubmission: RequestHandler< }) ) } + +/** + * Verify structure of encrypted response + */ + +export const prepareEncryptSubmission: RequestHandler< + { formId: string }, + unknown, + EncryptSubmissionBodyAfterProcess +> = (req, res, next) => { + // Step 1: Add req.body.encryptedContent to req.formData + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;(req as WithFormData).formData = req.body.encryptedContent + // Step 2: Add req.body.attachments to req.attachmentData + ;(req as WithAttachmentsData).attachmentData = + req.body.attachments || {} + return next() +} diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.types.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.types.ts new file mode 100644 index 0000000000..5a9211de86 --- /dev/null +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.types.ts @@ -0,0 +1,36 @@ +import { FieldResponse } from 'src/types' + +import { ProcessedFieldResponse } from '../submission.types' + +export type EncryptSubmissionBody = { + responses: FieldResponse[] + encryptedContent: string + attachments?: { + encryptedFile?: { + binary: string + nonce: string + submissionPublicKey: string + } + } + isPreview: boolean + version: number +} + +type Attachments = { + encryptedFile?: { + binary: string + nonce: string + submissionPublicKey: string + } +} +export type EncryptSubmissionBodyAfterProcess = { + encryptedContent: string + attachments?: Attachments + isPreview: boolean + version: number + parsedResponses: ProcessedFieldResponse[] +} + +export type WithAttachmentsData = T & { attachmentData: Attachments } + +export type WithFormData = T & { formData: string } diff --git a/src/app/routes/admin-forms.server.routes.js b/src/app/routes/admin-forms.server.routes.js index 2ed841c76f..789d83db3f 100644 --- a/src/app/routes/admin-forms.server.routes.js +++ b/src/app/routes/admin-forms.server.routes.js @@ -10,7 +10,6 @@ let adminForms = require('../../app/controllers/admin-forms.server.controller') let auth = require('../../app/controllers/authentication.server.controller') let submissions = require('../../app/controllers/submissions.server.controller') const EmailSubmissionsMiddleware = require('../../app/modules/submission/email-submission/email-submission.middleware') -let encryptSubmissions = require('../../app/controllers/encrypt-submissions.server.controller') const webhookVerifiedContentFactory = require('../factories/webhook-verified-content.factory') const AdminFormController = require('../modules/form/admin-form/admin-form.controller') const { withUserAuthentication } = require('../modules/auth/auth.middlewares') @@ -526,7 +525,7 @@ module.exports = function (app) { AdminFormController.passThroughSpcp, submissions.injectAutoReplyInfo, webhookVerifiedContentFactory.encryptedVerifiedFields, - encryptSubmissions.prepareEncryptSubmission, + EncryptSubmissionMiddleware.prepareEncryptSubmission, adminForms.passThroughSaveMetadataToDb, submissions.sendAutoReply, ) diff --git a/src/app/routes/public-forms.server.routes.js b/src/app/routes/public-forms.server.routes.js index 7b3e13a34d..2b92e352d9 100644 --- a/src/app/routes/public-forms.server.routes.js +++ b/src/app/routes/public-forms.server.routes.js @@ -18,7 +18,7 @@ const PublicFormController = require('../modules/form/public-form/public-form.co const SpcpController = require('../modules/spcp/spcp.controller') const { BasicField } = require('../../types') const EmailSubmissionsMiddleware = require('../../app/modules/submission/email-submission/email-submission.middleware') - +const EncryptSubmissionMiddleware = require('../modules/submission/encrypt-submission/encrypt-submission.middleware') module.exports = function (app) { /** * Redirect a form to the main index, with the specified path @@ -276,12 +276,12 @@ module.exports = function (app) { forms.formById, publicForms.isFormPublicCheck, CaptchaMiddleware.checkCaptchaResponse, - encryptSubmissions.validateEncryptSubmission, + EncryptSubmissionMiddleware.validateAndProcessEncryptSubmission, SpcpController.isSpcpAuthenticated, myInfoController.verifyMyInfoVals, submissions.injectAutoReplyInfo, webhookVerifiedContentFactory.encryptedVerifiedFields, - encryptSubmissions.prepareEncryptSubmission, + EncryptSubmissionMiddleware.prepareEncryptSubmission, encryptSubmissions.saveResponseToDb, webhookVerifiedContentFactory.post, submissions.sendAutoReply, diff --git a/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js b/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js index 3858c13414..bac35620b8 100644 --- a/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js +++ b/tests/unit/backend/controllers/encrypt-submissions.server.controller.spec.js @@ -236,7 +236,7 @@ describe('Encrypt Submissions Controller', () => { .post( injectFixtures, SpcpController.encryptedVerifiedFields(secretSigningKey), - Controller.prepareEncryptSubmission, + EncryptSubmissionsMiddleware.prepareEncryptSubmission, sendSubmissionBack, ) }) From f2facdb7bf18472896495fec2090a0e368ff7d56 Mon Sep 17 00:00:00 2001 From: shuli-ogp Date: Tue, 22 Dec 2020 10:16:14 +0800 Subject: [PATCH 36/37] chore: bump version to 4.51.0 --- CHANGELOG.md | 150 ++++++++++++++++++++++++++++++++++++++++------ package-lock.json | 2 +- package.json | 4 +- 3 files changed, 136 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a54e0533cc..2d13353bd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,95 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [v4.51.0](https://github.com/opengovsg/FormSG/compare/v4.50.3...v4.51.0) + +- refactor: prepareEncryptSubmission to typescript [`#891`](https://github.com/opengovsg/FormSG/pull/891) +- build: merge 4.50.3 into develop [`#919`](https://github.com/opengovsg/FormSG/pull/919) +- fix: backend validation does not prevent responses on hidden fields [`#809`](https://github.com/opengovsg/FormSG/pull/809) +- fix: include noopener, noreferrer to <a> tags that open in a new page [`#916`](https://github.com/opengovsg/FormSG/pull/916) +- fix: upgrade to use latest Node v12 [`#879`](https://github.com/opengovsg/FormSG/pull/879) +- fix(deps): run snyk wizard [`#876`](https://github.com/opengovsg/FormSG/pull/876) +- fix(deps): bump fp-ts from 2.9.0 to 2.9.1 [`#914`](https://github.com/opengovsg/FormSG/pull/914) +- fix: add request metadata to email data error log [`#915`](https://github.com/opengovsg/FormSG/pull/915) +- docs(public-form): add warning comment to GET endpoint in case of API refactor [`#897`](https://github.com/opengovsg/FormSG/pull/897) +- fix(deps): bump twilio from 3.52.0 to 3.54.1 [`#913`](https://github.com/opengovsg/FormSG/pull/913) +- chore(deps-dev): bump csv-parse from 4.14.1 to 4.14.2 [`#911`](https://github.com/opengovsg/FormSG/pull/911) +- chore(deps-dev): bump @typescript-eslint/parser from 4.9.0 to 4.10.0 [`#910`](https://github.com/opengovsg/FormSG/pull/910) +- feat: harden rate limits [`#909`](https://github.com/opengovsg/FormSG/pull/909) +- fix: upgrade angular-moment from 1.2.0 to 1.3.0 [`#870`](https://github.com/opengovsg/FormSG/pull/870) +- chore(deps-dev): bump @types/node from 14.14.11 to 14.14.14 [`#900`](https://github.com/opengovsg/FormSG/pull/900) +- chore(deps-dev): bump @types/express-serve-static-core [`#905`](https://github.com/opengovsg/FormSG/pull/905) +- chore(deps-dev): bump @types/jest from 26.0.16 to 26.0.19 [`#906`](https://github.com/opengovsg/FormSG/pull/906) +- chore(deps-dev): bump core-js from 3.6.5 to 3.8.1 [`#907`](https://github.com/opengovsg/FormSG/pull/907) +- fix: upgrade intl-tel-input from 12.1.16 to 12.4.0 [`#866`](https://github.com/opengovsg/FormSG/pull/866) +- fix: upgrade sortablejs from 1.10.2 to 1.12.0 [`#865`](https://github.com/opengovsg/FormSG/pull/865) +- fix(deps): bump mongoose from 5.10.18 to 5.11.8 [`#889`](https://github.com/opengovsg/FormSG/pull/889) +- fix(deps): bump web-streams-polyfill from 2.1.1 to 3.0.1 [`#838`](https://github.com/opengovsg/FormSG/pull/838) +- chore: merge release v4.50.2 back into develop [`#895`](https://github.com/opengovsg/FormSG/pull/895) +- fix(deps): bump opossum from 5.0.2 to 5.1.1 [`#898`](https://github.com/opengovsg/FormSG/pull/898) +- fix(deps): bump @sentry/browser from 5.29.0 to 5.29.1 [`#899`](https://github.com/opengovsg/FormSG/pull/899) +- fix: upgrade fp-ts from 2.8.6 to 2.9.0 [`#896`](https://github.com/opengovsg/FormSG/pull/896) +- refactor: inline form permissions check for presigned POST URL endpoints [`#863`](https://github.com/opengovsg/FormSG/pull/863) +- chore(deps-dev): bump lint-staged from 10.5.2 to 10.5.3 [`#893`](https://github.com/opengovsg/FormSG/pull/893) +- fix(deps): bump uuid from 8.3.1 to 8.3.2 [`#892`](https://github.com/opengovsg/FormSG/pull/892) +- feat: add FIXED_LINE_OR_MOBILE numbers to pass homeno validation [`#886`](https://github.com/opengovsg/FormSG/pull/886) +- fix: upgrade angular-ui-router from 1.0.26 to 1.0.28 [`#868`](https://github.com/opengovsg/FormSG/pull/868) +- refactor: validateAndProcessEncryptSubmission to typescript [`#887`](https://github.com/opengovsg/FormSG/pull/887) +- fix(deps): bump @sentry/integrations from 5.27.4 to 5.29.0 [`#888`](https://github.com/opengovsg/FormSG/pull/888) +- chore(deps-dev): bump jest from 26.6.2 to 26.6.3 [`#890`](https://github.com/opengovsg/FormSG/pull/890) +- chore: merge release v4.50.1 into develop [`#884`](https://github.com/opengovsg/FormSG/pull/884) +- fix: upgrade twilio from 3.51.0 to 3.52.0 [`#869`](https://github.com/opengovsg/FormSG/pull/869) +- chore(deps-dev): bump husky from 4.3.5 to 4.3.6 [`#877`](https://github.com/opengovsg/FormSG/pull/877) +- chore(deps-dev): bump @babel/core from 7.12.3 to 7.12.10 [`#878`](https://github.com/opengovsg/FormSG/pull/878) +- chore: bump version to 4.50.3 [`4b0ecec`](https://github.com/opengovsg/FormSG/commit/4b0ecec413c2176adaf5e82065486444bddc1c2f) + #### [v4.50.3](https://github.com/opengovsg/FormSG/compare/v4.50.2...v4.50.3) +> 21 December 2020 + +- build: release 4.50.3 - hotfix for undefined SPCP info [`#918`](https://github.com/opengovsg/FormSG/pull/918) +- build: release v4.50.2 hotfix [`#894`](https://github.com/opengovsg/FormSG/pull/894) +- build: release v4.50.1 - hotfix for email format validation [`#880`](https://github.com/opengovsg/FormSG/pull/880) +- build: release v4.50.0 [`#862`](https://github.com/opengovsg/FormSG/pull/862) +- build: release 4.49.1 [`#848`](https://github.com/opengovsg/FormSG/pull/848) +- build: release 4.49.0 [`#842`](https://github.com/opengovsg/FormSG/pull/842) +- build: release 4.48.2 - log errors in concatResponse [`#835`](https://github.com/opengovsg/FormSG/pull/835) +- build: Release v4.48.1 hotfix [`#766`](https://github.com/opengovsg/FormSG/pull/766) +- build: release v4.48.0 [`#760`](https://github.com/opengovsg/FormSG/pull/760) +- build: release 4.47.0 [`#715`](https://github.com/opengovsg/FormSG/pull/715) +- build: Release v4.46.1 hotfix [`#681`](https://github.com/opengovsg/FormSG/pull/681) +- build: Release v4.46.0 [`#653`](https://github.com/opengovsg/FormSG/pull/653) +- build: Release 4.45.1: hotfix recipient email input [`#647`](https://github.com/opengovsg/FormSG/pull/647) +- build: Release 4.45.0 [`#606`](https://github.com/opengovsg/FormSG/pull/606) +- build: release 4.44.0 [`#576`](https://github.com/opengovsg/FormSG/pull/576) +- build: release 4.43.2 [`#551`](https://github.com/opengovsg/FormSG/pull/551) +- build: Release 4.43.0 [`#529`](https://github.com/opengovsg/FormSG/pull/529) +- build: Release 4.42.0 [`#518`](https://github.com/opengovsg/FormSG/pull/518) +- build: Release 4.41.0 [`#493`](https://github.com/opengovsg/FormSG/pull/493) +- build: release v4.40.0 [`#460`](https://github.com/opengovsg/FormSG/pull/460) +- build: release v4.39.0 [`#434`](https://github.com/opengovsg/FormSG/pull/434) +- build: release v4.38.1 [`#423`](https://github.com/opengovsg/FormSG/pull/423) +- build: release v4.38.0 [`#414`](https://github.com/opengovsg/FormSG/pull/414) +- build: Release v4.37.1 [`#388`](https://github.com/opengovsg/FormSG/pull/388) +- build: Release v4.37.0 [`#381`](https://github.com/opengovsg/FormSG/pull/381) +- fix: release 4.35.1 hotfix - Return generic error instead of Joi error [`#333`](https://github.com/opengovsg/FormSG/pull/333) +- build: release 4.35.0 [`#320`](https://github.com/opengovsg/FormSG/pull/320) +- build: Release 4.34.1 - log all critical bounces [`#310`](https://github.com/opengovsg/FormSG/pull/310) +- feature: release 4.34.0 [`#297`](https://github.com/opengovsg/FormSG/pull/297) +- feat: Release 4.33.0 [`#239`](https://github.com/opengovsg/FormSG/pull/239) +- fix: Hotfix v4.32.1 -- split mail by semicolon in addition to comma when validating [`#222`](https://github.com/opengovsg/FormSG/pull/222) +- feat: Release v4.32.0 [`#201`](https://github.com/opengovsg/FormSG/pull/201) +- Release v4.31.0 - improve docs, log IP, fix tests [`#152`](https://github.com/opengovsg/FormSG/pull/152) +- Release 4.30.4 - revert filtering by submission ID [`#137`](https://github.com/opengovsg/FormSG/pull/137) +- build: Release 4.30.3 - Typescript migrations, filter storage mode responses by submission id [`#123`](https://github.com/opengovsg/FormSG/pull/123) +- Release 4.30.2 - fix AWS endpoint and /emailnotifications log group [`#78`](https://github.com/opengovsg/FormSG/pull/78) +- build: Release 4.30.1 - Fix field creation on old clients [`#74`](https://github.com/opengovsg/FormSG/pull/74) +- Release 4.30.0 - acknowledgement for secret key backup, TypeScript migrations [`#67`](https://github.com/opengovsg/FormSG/pull/67) - build: empty commit to trigger PR build [`d0c6583`](https://github.com/opengovsg/FormSG/commit/d0c65838efa6731a0f10e89ff954c132b9f8b854) - feat: update table field styling to not rely on multiple divs [`db03da3`](https://github.com/opengovsg/FormSG/commit/db03da33eca2fc0a6174ca58d7dd8b3cfadadcea) - fix: return 401 for missing JWT [`e6c1947`](https://github.com/opengovsg/FormSG/commit/e6c19477b05fc4aed90b2db42916220aea2a263c) -#### [v4.50.2](https://github.com/opengovsg/FormSG/compare/v4.50.0...v4.50.2) +#### [v4.50.2](https://github.com/opengovsg/FormSG/compare/v4.50.1...v4.50.2) > 16 December 2020 @@ -18,6 +100,13 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - fix: email format validation should allow 126/163.com, align frontend and backend validation [`be35522`](https://github.com/opengovsg/FormSG/commit/be35522e15d20521f58fada7ed3164e6c0c0894d) - chore: bump version to v4.50.2 [`fad62ec`](https://github.com/opengovsg/FormSG/commit/fad62ec5730412201e208332a91286fd53e2b36a) +#### [v4.50.1](https://github.com/opengovsg/FormSG/compare/v4.50.0...v4.50.1) + +> 14 December 2020 + +- fix: email format validation should allow 126/163.com, align frontend and backend validation [`a577a5d`](https://github.com/opengovsg/FormSG/commit/a577a5d913f24094517c3d84c5902069fa2f623b) +- chore: bump version to 4.50.1 [`3fceed3`](https://github.com/opengovsg/FormSG/commit/3fceed38c939608588599b0f02a3182cd992869c) + #### [v4.50.0](https://github.com/opengovsg/FormSG/compare/v4.49.1...v4.50.0) > 14 December 2020 @@ -105,8 +194,42 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). > 9 December 2020 +- build: release 4.48.2 - log errors in concatResponse [`#835`](https://github.com/opengovsg/FormSG/pull/835) - chore: log errors from concatResponse [`#817`](https://github.com/opengovsg/FormSG/pull/817) +- build: Release v4.48.1 hotfix [`#766`](https://github.com/opengovsg/FormSG/pull/766) +- build: release v4.48.0 [`#760`](https://github.com/opengovsg/FormSG/pull/760) +- build: release 4.47.0 [`#715`](https://github.com/opengovsg/FormSG/pull/715) +- build: Release v4.46.1 hotfix [`#681`](https://github.com/opengovsg/FormSG/pull/681) +- build: Release v4.46.0 [`#653`](https://github.com/opengovsg/FormSG/pull/653) +- build: Release 4.45.1: hotfix recipient email input [`#647`](https://github.com/opengovsg/FormSG/pull/647) +- build: Release 4.45.0 [`#606`](https://github.com/opengovsg/FormSG/pull/606) +- build: release 4.44.0 [`#576`](https://github.com/opengovsg/FormSG/pull/576) +- build: release 4.43.2 [`#551`](https://github.com/opengovsg/FormSG/pull/551) +- build: Release 4.43.0 [`#529`](https://github.com/opengovsg/FormSG/pull/529) +- build: Release 4.42.0 [`#518`](https://github.com/opengovsg/FormSG/pull/518) +- build: Release 4.41.0 [`#493`](https://github.com/opengovsg/FormSG/pull/493) +- build: release v4.40.0 [`#460`](https://github.com/opengovsg/FormSG/pull/460) +- build: release v4.39.0 [`#434`](https://github.com/opengovsg/FormSG/pull/434) +- build: release v4.38.1 [`#423`](https://github.com/opengovsg/FormSG/pull/423) +- build: release v4.38.0 [`#414`](https://github.com/opengovsg/FormSG/pull/414) +- build: Release v4.37.1 [`#388`](https://github.com/opengovsg/FormSG/pull/388) +- build: Release v4.37.0 [`#381`](https://github.com/opengovsg/FormSG/pull/381) +- fix: release 4.35.1 hotfix - Return generic error instead of Joi error [`#333`](https://github.com/opengovsg/FormSG/pull/333) +- build: release 4.35.0 [`#320`](https://github.com/opengovsg/FormSG/pull/320) +- build: Release 4.34.1 - log all critical bounces [`#310`](https://github.com/opengovsg/FormSG/pull/310) +- feature: release 4.34.0 [`#297`](https://github.com/opengovsg/FormSG/pull/297) +- feat: Release 4.33.0 [`#239`](https://github.com/opengovsg/FormSG/pull/239) +- fix: Hotfix v4.32.1 -- split mail by semicolon in addition to comma when validating [`#222`](https://github.com/opengovsg/FormSG/pull/222) +- feat: Release v4.32.0 [`#201`](https://github.com/opengovsg/FormSG/pull/201) +- Release v4.31.0 - improve docs, log IP, fix tests [`#152`](https://github.com/opengovsg/FormSG/pull/152) +- Release 4.30.4 - revert filtering by submission ID [`#137`](https://github.com/opengovsg/FormSG/pull/137) +- build: Release 4.30.3 - Typescript migrations, filter storage mode responses by submission id [`#123`](https://github.com/opengovsg/FormSG/pull/123) +- Release 4.30.2 - fix AWS endpoint and /emailnotifications log group [`#78`](https://github.com/opengovsg/FormSG/pull/78) +- build: Release 4.30.1 - Fix field creation on old clients [`#74`](https://github.com/opengovsg/FormSG/pull/74) +- Release 4.30.0 - acknowledgement for secret key backup, TypeScript migrations [`#67`](https://github.com/opengovsg/FormSG/pull/67) - chore: bump version to 4.48.2 [`5cd7078`](https://github.com/opengovsg/FormSG/commit/5cd70788e6250239a838ba64b7168ff347e26aae) +- build: Release 4.43.1 - Allow edits to prefilled textfields, tighten checks for protected routes [`b55e5d5`](https://github.com/opengovsg/FormSG/commit/b55e5d5be4412a620feefc52bf360914c33a8bc7) +- build: Merge pull request #348 from opengovsg/release-4.36.0 [`211efe7`](https://github.com/opengovsg/FormSG/commit/211efe7aac19d9b5b7b18af67f3707f47d138f96) #### [v4.48.1](https://github.com/opengovsg/FormSG/compare/v4.48.0...v4.48.1) @@ -114,7 +237,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Revert "fix: backend validation does not prevent responses on hidden fields (#736)" [`fead8ce`](https://github.com/opengovsg/FormSG/commit/fead8ce6011003660d0269ceeffba8aaae09ab0a) - chore: bump version to v4.48.1 [`7e6267d`](https://github.com/opengovsg/FormSG/commit/7e6267d6b291b2f43901ebf9266276823bfda580) -- fix: skip travis artifact cleanup [`3bb2d2b`](https://github.com/opengovsg/FormSG/commit/3bb2d2b2875d44dbbef93ecde30597584969ef3e) #### [v4.48.0](https://github.com/opengovsg/FormSG/compare/v4.47.0...v4.48.0) @@ -149,13 +271,14 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - chore(deps-dev): bump lint-staged from 10.5.1 to 10.5.2 [`#722`](https://github.com/opengovsg/FormSG/pull/722) - docs: add script for unlisting array of forms [`#714`](https://github.com/opengovsg/FormSG/pull/714) - build: merge release 4.47.0 into develop [`#719`](https://github.com/opengovsg/FormSG/pull/719) -- fix: convert field ID to string for admin preview MyInfo [`#717`](https://github.com/opengovsg/FormSG/pull/717) - chore: bump version to v4.48.0 [`24866f6`](https://github.com/opengovsg/FormSG/commit/24866f66ae6c95f56ab59a7f5e0cc7db6ba1121a) +- fix: skip travis artifact cleanup [`3bb2d2b`](https://github.com/opengovsg/FormSG/commit/3bb2d2b2875d44dbbef93ecde30597584969ef3e) #### [v4.47.0](https://github.com/opengovsg/FormSG/compare/v4.46.1...v4.47.0) -> 24 November 2020 +> 1 December 2020 +- fix: convert field ID to string for admin preview MyInfo [`#717`](https://github.com/opengovsg/FormSG/pull/717) - feat: Increase attachment size options [`#692`](https://github.com/opengovsg/FormSG/pull/692) - ref: migrate feedback/count endpoint handler flow to TypeScript [`#706`](https://github.com/opengovsg/FormSG/pull/706) - chore(deps-dev): bump sinon from 9.2.0 to 9.2.1 [`#710`](https://github.com/opengovsg/FormSG/pull/710) @@ -194,20 +317,18 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - chore(deps-dev): bump lint-staged from 10.4.0 to 10.5.1 [`#657`](https://github.com/opengovsg/FormSG/pull/657) - build: add lockfile-lint to CI [`#651`](https://github.com/opengovsg/FormSG/pull/651) - chore: bump version to 4.47.0 [`f334474`](https://github.com/opengovsg/FormSG/commit/f334474fd5c251b683f0c78fcb5af643d7cc30cd) -- chore: bump version to v4.46.1 [`375df4f`](https://github.com/opengovsg/FormSG/commit/375df4f23a4a9253a50df6bdb1905cfcb40d83f2) -- fix: early return on undefined verification signature [`e67bb8b`](https://github.com/opengovsg/FormSG/commit/e67bb8b1fc12e41c7c9cf1347a52ed7e30f2fbf5) #### [v4.46.1](https://github.com/opengovsg/FormSG/compare/v4.46.0...v4.46.1) > 19 November 2020 -- chore: bump version to v4.46.0 [`bcdcf03`](https://github.com/opengovsg/FormSG/commit/bcdcf03afdbffb3562386129c793dee5b459b321) - fix: check for undefined-ness on attachmentMetadata [`46d3357`](https://github.com/opengovsg/FormSG/commit/46d335785f51f40722ec0ba87510a17e7cf82edd) -- chore: bump version to v4.46.1 [`37db034`](https://github.com/opengovsg/FormSG/commit/37db034ebafd7388a36762c3ba4fde405bca27de) +- chore: bump version to v4.46.1 [`375df4f`](https://github.com/opengovsg/FormSG/commit/375df4f23a4a9253a50df6bdb1905cfcb40d83f2) +- fix: early return on undefined verification signature [`e67bb8b`](https://github.com/opengovsg/FormSG/commit/e67bb8b1fc12e41c7c9cf1347a52ed7e30f2fbf5) -#### [v4.46.0](https://github.com/opengovsg/FormSG/compare/v4.45.1...v4.46.0) +#### [v4.46.0](https://github.com/opengovsg/FormSG/compare/v4.45.0...v4.46.0) -> 17 November 2020 +> 18 November 2020 - chore: use travis_retry to retry flaky tests automatically [`#641`](https://github.com/opengovsg/FormSG/pull/641) - refactor: migrate MyInfo functionality to TypeScript [`#560`](https://github.com/opengovsg/FormSG/pull/560) @@ -247,14 +368,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - chore(deps-dev): bump @types/node from 14.14.6 to 14.14.7 [`#611`](https://github.com/opengovsg/FormSG/pull/611) - build: merge release 4.45.0 into develop [`#608`](https://github.com/opengovsg/FormSG/pull/608) - ref: migrate inline queries for retrieval of submissions metadata to model static methods [`#601`](https://github.com/opengovsg/FormSG/pull/601) -- chore: bump version to v4.46.9 [`31d6b9b`](https://github.com/opengovsg/FormSG/commit/31d6b9b00ab1cbc5e8a59373fb7a65e52b795f69) - -#### [v4.45.1](https://github.com/opengovsg/FormSG/compare/v4.45.0...v4.45.1) - -> 16 November 2020 - +- chore: bump version to v4.46.0 [`bcdcf03`](https://github.com/opengovsg/FormSG/commit/bcdcf03afdbffb3562386129c793dee5b459b321) - chore: bump version to 4.45.1 [`d2ec536`](https://github.com/opengovsg/FormSG/commit/d2ec536e5154c9459b97d6977724928dc68e2a19) -- fix: update email list correctly when deleting emails [`5776c2d`](https://github.com/opengovsg/FormSG/commit/5776c2d3d079e2876aacb17c41c398031a8ff939) +- fix: add minimal polyfill for ie11 in decryption worker [`a9d2068`](https://github.com/opengovsg/FormSG/commit/a9d2068d70c61c09910599a37bb8d5e14ead2ffc) #### [v4.45.0](https://github.com/opengovsg/FormSG/compare/v4.44.0...v4.45.0) diff --git a/package-lock.json b/package-lock.json index 0f7d4d9b43..2d055e728e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "FormSG", - "version": "4.50.3", + "version": "4.51.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eb32820125..6825005ca8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "FormSG", "description": "Form Manager for Government", - "version": "4.50.3", + "version": "4.51.0", "homepage": "https://form.gov.sg", "authors": [ "FormSG " @@ -259,4 +259,4 @@ "webpack-merge": "^4.1.3", "worker-loader": "^2.0.0" } -} \ No newline at end of file +} From 37561c92e6c9f322a637e2952ee5301c204ba5d7 Mon Sep 17 00:00:00 2001 From: Antariksh Mahajan Date: Tue, 22 Dec 2020 16:24:04 +0800 Subject: [PATCH 37/37] test: add SPCP authentication integration tests (#921) --- .../__tests__/email-submission.routes.spec.ts | 376 +++++++++++++++++ .../encrypt-submission.routes.spec.ts | 395 ++++++++++++++++++ tests/unit/backend/helpers/jest-db.ts | 8 +- 3 files changed, 778 insertions(+), 1 deletion(-) create mode 100644 src/app/modules/submission/email-submission/__tests__/email-submission.routes.spec.ts create mode 100644 src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.routes.spec.ts diff --git a/src/app/modules/submission/email-submission/__tests__/email-submission.routes.spec.ts b/src/app/modules/submission/email-submission/__tests__/email-submission.routes.spec.ts new file mode 100644 index 0000000000..bfa249996f --- /dev/null +++ b/src/app/modules/submission/email-submission/__tests__/email-submission.routes.spec.ts @@ -0,0 +1,376 @@ +import SPCPAuthClient from '@opengovsg/spcp-auth-client' +import { celebrate, Joi } from 'celebrate' +import { RequestHandler, Router } from 'express' +import session, { Session } from 'supertest-session' +import { mocked } from 'ts-jest/utils' + +import * as FormController from 'src/app/controllers/forms.server.controller' +import * as MyInfoController from 'src/app/controllers/myinfo.server.controller' +import * as SubmissionsController from 'src/app/controllers/submissions.server.controller' +import * as PublicFormMiddleware from 'src/app/modules/form/public-form/public-form.middlewares' +import * as SpcpController from 'src/app/modules/spcp/spcp.controller' +import * as EmailSubmissionsMiddleware from 'src/app/modules/submission/email-submission/email-submission.middleware' +import { CaptchaFactory } from 'src/app/services/captcha/captcha.factory' +import * as CaptchaMiddleware from 'src/app/services/captcha/captcha.middleware' +import { AuthType, BasicField, Status } from 'src/types' + +import { setupApp } from 'tests/integration/helpers/express-setup' +import dbHandler from 'tests/unit/backend/helpers/jest-db' + +jest.mock('@opengovsg/spcp-auth-client') +const MockAuthClient = mocked(SPCPAuthClient, true) +const mockSpClient = mocked(MockAuthClient.mock.instances[0], true) +const mockCpClient = mocked(MockAuthClient.mock.instances[1], true) + +jest.mock('nodemailer', () => ({ + createTransport: jest.fn().mockReturnValue({ + sendMail: jest.fn(), + }), +})) + +// TODO (#149): Import router instead of creating it here +const SUBMISSIONS_ENDPT_BASE = '/v2/submissions/email' +const SUBMISSIONS_ENDPT = `${SUBMISSIONS_ENDPT_BASE}/:formId([a-fA-F0-9]{24})` + +const MOCK_SUBMISSION_BODY = { + responses: [], + isPreview: false, +} + +const EmailSubmissionsRouter = Router() +EmailSubmissionsRouter.post( + SUBMISSIONS_ENDPT, + CaptchaFactory.validateCaptchaParams, + FormController.formById, + PublicFormMiddleware.isFormPublicCheck, + CaptchaMiddleware.checkCaptchaResponse as RequestHandler, + SpcpController.isSpcpAuthenticated, + EmailSubmissionsMiddleware.receiveEmailSubmission, + celebrate({ + body: Joi.object({ + responses: Joi.array() + .items( + Joi.object() + .keys({ + _id: Joi.string().required(), + question: Joi.string().required(), + fieldType: Joi.string() + .required() + .valid(...Object.values(BasicField)), + answer: Joi.string().allow(''), + answerArray: Joi.array(), + filename: Joi.string(), + content: Joi.binary(), + isHeader: Joi.boolean(), + myInfo: Joi.object(), + signature: Joi.string().allow(''), + }) + .xor('answer', 'answerArray') // only answer or answerArray can be present at once + .with('filename', 'content'), // if filename is present, content must be present + ) + .required(), + isPreview: Joi.boolean().required(), + }), + }), + EmailSubmissionsMiddleware.validateEmailSubmission, + MyInfoController.verifyMyInfoVals as RequestHandler, + (SubmissionsController.injectAutoReplyInfo as unknown) as RequestHandler, + SpcpController.appendVerifiedSPCPResponses as RequestHandler, + EmailSubmissionsMiddleware.prepareEmailSubmission as RequestHandler, + EmailSubmissionsMiddleware.saveMetadataToDb, + EmailSubmissionsMiddleware.sendAdminEmail, + SubmissionsController.sendAutoReply, +) + +const EmailSubmissionsApp = setupApp('/', EmailSubmissionsRouter) + +describe('email-submission.routes', () => { + let request: Session + + beforeAll(async () => await dbHandler.connect()) + beforeEach(() => { + request = session(EmailSubmissionsApp) + }) + afterEach(async () => { + await dbHandler.clearDatabase() + jest.restoreAllMocks() + }) + afterAll(async () => await dbHandler.closeDatabase()) + + describe('SPCP authentication', () => { + describe('SingPass', () => { + it('should return 200 when submission is valid', async () => { + mockSpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + userName: 'S1234567A', + }), + ) + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(200) + expect(response.body).toEqual({ + message: 'Form submission successful.', + submissionId: expect.any(String), + }) + }) + + it('should return 401 when submission does not have JWT', async () => { + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + // Note cookie is not set + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has the wrong JWT type', async () => { + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + // Note cookie is for CorpPass, not SingPass + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has invalid JWT', async () => { + // Mock auth client to return error when decoding JWT + mockSpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(new Error()), + ) + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has JWT with the wrong shape', async () => { + // Mock auth client to return wrong decoded shape + mockSpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + wrongKey: 'S1234567A', + }), + ) + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + }) + + describe('CorpPass', () => { + it('should return 200 when submission is valid', async () => { + mockCpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + userName: 'S1234567A', + userInfo: 'MyCorpPassUEN', + }), + ) + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(200) + expect(response.body).toEqual({ + message: 'Form submission successful.', + submissionId: expect.any(String), + }) + }) + + it('should return 401 when submission does not have JWT', async () => { + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + // Note cookie is not set + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has the wrong JWT type', async () => { + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + // Note cookie is for SingPass, not CorpPass + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has invalid JWT', async () => { + // Mock auth client to return error when decoding JWT + mockCpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(new Error()), + ) + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has JWT with the wrong shape', async () => { + // Mock auth client to return wrong decoded JWT shape + mockCpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + wrongKey: 'S1234567A', + }), + ) + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .field('body', JSON.stringify(MOCK_SUBMISSION_BODY)) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + }) + }) +}) diff --git a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.routes.spec.ts b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.routes.spec.ts new file mode 100644 index 0000000000..8bfa54df4e --- /dev/null +++ b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.routes.spec.ts @@ -0,0 +1,395 @@ +import SPCPAuthClient from '@opengovsg/spcp-auth-client' +import { celebrate, Joi } from 'celebrate' +import { RequestHandler, Router } from 'express' +import session, { Session } from 'supertest-session' +import { mocked } from 'ts-jest/utils' + +import * as encryptSubmissions from 'src/app/controllers/encrypt-submissions.server.controller' +import * as FormController from 'src/app/controllers/forms.server.controller' +import * as MyInfoController from 'src/app/controllers/myinfo.server.controller' +import * as SubmissionsController from 'src/app/controllers/submissions.server.controller' +import * as webhookVerifiedContentFactory from 'src/app/factories/webhook-verified-content.factory' +import * as PublicFormMiddleware from 'src/app/modules/form/public-form/public-form.middlewares' +import * as SpcpController from 'src/app/modules/spcp/spcp.controller' +import * as EncryptSubmissionsMiddleware from 'src/app/modules/submission/encrypt-submission/encrypt-submission.middleware' +import { CaptchaFactory } from 'src/app/services/captcha/captcha.factory' +import * as CaptchaMiddleware from 'src/app/services/captcha/captcha.middleware' +import { AuthType, BasicField, Status } from 'src/types' + +import { setupApp } from 'tests/integration/helpers/express-setup' +import dbHandler from 'tests/unit/backend/helpers/jest-db' + +jest.mock('@opengovsg/spcp-auth-client') +const MockAuthClient = mocked(SPCPAuthClient, true) +const mockSpClient = mocked(MockAuthClient.mock.instances[0], true) +const mockCpClient = mocked(MockAuthClient.mock.instances[1], true) + +// TODO (#149): Import router instead of creating it here +const SUBMISSIONS_ENDPT_BASE = '/v2/submissions/encrypt' +const SUBMISSIONS_ENDPT = `${SUBMISSIONS_ENDPT_BASE}/:formId([a-fA-F0-9]{24})` + +const MOCK_ENCRYPTED_CONTENT = `${'a'.repeat(44)};${'a'.repeat( + 32, +)}:${'a'.repeat(4)}` +const MOCK_SUBMISSION_BODY = { + responses: [], + encryptedContent: MOCK_ENCRYPTED_CONTENT, + isPreview: false, + version: 1, +} + +// TODO (#149): Import router instead of creating it here +const EncryptSubmissionsRouter = Router() +EncryptSubmissionsRouter.post( + SUBMISSIONS_ENDPT, + CaptchaFactory.validateCaptchaParams, + celebrate({ + body: Joi.object({ + responses: Joi.array() + .items( + Joi.object().keys({ + _id: Joi.string().required(), + answer: Joi.string().allow('').required(), + fieldType: Joi.string() + .required() + .valid(...Object.values(BasicField)), + signature: Joi.string().allow(''), + }), + ) + .required(), + encryptedContent: Joi.string() + .custom((value, helpers) => { + const parts = String(value).split(/;|:/) + if ( + parts.length !== 3 || + parts[0].length !== 44 || // public key + parts[1].length !== 32 || // nonce + !parts.every((part) => Joi.string().base64().validate(part)) + ) { + return helpers.error('Invalid encryptedContent.') + } + return value + }, 'encryptedContent') + .required(), + attachments: Joi.object() + .pattern( + /^[a-fA-F0-9]{24}$/, + Joi.object().keys({ + encryptedFile: Joi.object().keys({ + binary: Joi.string().required(), + nonce: Joi.string().required(), + submissionPublicKey: Joi.string().required(), + }), + }), + ) + .optional(), + isPreview: Joi.boolean().required(), + version: Joi.number().required(), + }), + }), + FormController.formById, + PublicFormMiddleware.isFormPublicCheck, + CaptchaMiddleware.checkCaptchaResponse as RequestHandler, + EncryptSubmissionsMiddleware.validateAndProcessEncryptSubmission, + SpcpController.isSpcpAuthenticated, + MyInfoController.verifyMyInfoVals as RequestHandler, + (SubmissionsController.injectAutoReplyInfo as unknown) as RequestHandler, + webhookVerifiedContentFactory.encryptedVerifiedFields as RequestHandler, + EncryptSubmissionsMiddleware.prepareEncryptSubmission as RequestHandler, + (encryptSubmissions.saveResponseToDb as unknown) as RequestHandler, + webhookVerifiedContentFactory.post as RequestHandler, + SubmissionsController.sendAutoReply, +) + +const EncryptSubmissionsApp = setupApp('/', EncryptSubmissionsRouter) + +describe('encrypt-submission.routes', () => { + let request: Session + + beforeAll(async () => await dbHandler.connect()) + beforeEach(() => { + request = session(EncryptSubmissionsApp) + }) + afterEach(async () => { + await dbHandler.clearDatabase() + jest.restoreAllMocks() + }) + afterAll(async () => await dbHandler.closeDatabase()) + + describe('SPCP authentication', () => { + describe('SingPass', () => { + it('should return 200 when submission is valid', async () => { + mockSpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + userName: 'S1234567A', + }), + ) + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(200) + expect(response.body).toEqual({ + message: 'Form submission successful.', + submissionId: expect.any(String), + }) + }) + + it('should return 401 when submission does not have JWT', async () => { + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + // Note cookie is not set + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has the wrong JWT type', async () => { + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + // Note cookie is for CorpPass, not SingPass + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has invalid JWT', async () => { + // Mock auth client to return error when decoding JWT + mockSpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(new Error()), + ) + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has JWT with the wrong shape', async () => { + // Mock auth client to return wrong decoded JWT shape + mockSpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + wrongKey: 'S1234567A', + }), + ) + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.SP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + }) + + describe('CorpPass', () => { + it('should return 200 when submission is valid', async () => { + mockCpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + userName: 'S1234567A', + userInfo: 'MyCorpPassUEN', + }), + ) + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(200) + expect(response.body).toEqual({ + message: 'Form submission successful.', + submissionId: expect.any(String), + }) + }) + + it('should return 401 when submission does not have JWT', async () => { + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + // Note cookie is not set + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has the wrong JWT type', async () => { + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + // Note cookie is for SingPass, not CorpPass + .set('Cookie', ['jwtSp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has invalid JWT', async () => { + // Mock auth client to return error when decoding JWT + mockCpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(new Error()), + ) + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + + it('should return 401 when submission has JWT with the wrong shape', async () => { + // Mock auth client to return wrong decoded JWT shape + mockCpClient.verifyJWT.mockImplementationOnce((_jwt, cb) => + cb(null, { + wrongKey: 'S1234567A', + }), + ) + const { form } = await dbHandler.insertEncryptForm({ + formOptions: { + esrvcId: 'mockEsrvcId', + authType: AuthType.CP, + hasCaptcha: false, + status: Status.Public, + }, + }) + + const response = await request + .post(`${SUBMISSIONS_ENDPT_BASE}/${form._id}`) + .send(MOCK_SUBMISSION_BODY) + .query({ captchaResponse: 'null' }) + .set('Cookie', ['jwtCp=mockJwt']) + + expect(response.status).toBe(401) + expect(response.body).toEqual({ + message: + 'Something went wrong with your login. Please try logging in and submitting again.', + spcpSubmissionFailure: true, + }) + }) + }) + }) +}) diff --git a/tests/unit/backend/helpers/jest-db.ts b/tests/unit/backend/helpers/jest-db.ts index 89138da9bb..88681103a5 100644 --- a/tests/unit/backend/helpers/jest-db.ts +++ b/tests/unit/backend/helpers/jest-db.ts @@ -132,12 +132,14 @@ const insertEmailForm = async ({ mailDomain = 'test.gov.sg', mailName = 'test', shortName = 'govtest', + formOptions = {}, }: { formId?: ObjectID userId?: ObjectID mailName?: string mailDomain?: string shortName?: string + formOptions?: Partial } = {}): Promise<{ form: IEmailFormSchema user: IUserSchema @@ -158,6 +160,7 @@ const insertEmailForm = async ({ responseMode: ResponseMode.Email, emails: [user.email], _id: formId, + ...formOptions, }) return { @@ -173,12 +176,14 @@ const insertEncryptForm = async ({ mailDomain = 'test.gov.sg', mailName = 'test', shortName = 'govtest', + formOptions = {}, }: { formId?: ObjectID userId?: ObjectID mailName?: string mailDomain?: string shortName?: string + formOptions?: Partial } = {}): Promise<{ form: IEncryptedFormSchema user: IUserSchema @@ -198,7 +203,8 @@ const insertEncryptForm = async ({ admin: user._id, responseMode: ResponseMode.Encrypt, _id: formId, - publicKey: 'publicKey', + publicKey: 'vuUYOfkrC7eiyqZ1OCZhMcjAvMQ7R4Z4zzDWB+og4G4=', + ...formOptions, }) return {