From 65d55e47588c996843080697d00633cdbc73cf5f Mon Sep 17 00:00:00 2001 From: ashutosh-bansal-2136 <82804130+ashutosh-bansal-2136@users.noreply.github.com> Date: Tue, 21 Sep 2021 12:47:28 +0530 Subject: [PATCH] fix(authentication-service): added test cases (#316) * fix(authentication-service): added test cases gh-181 * fix(authentication-service): removed code smells gh-181 * fix(authentication-service): code smells removed gh-181 * fix(authentication-service): updated tests gh-181 Co-authored-by: akshatdubeysf <77672713+akshatdubeysf@users.noreply.github.com> --- .../authentication-service/package-lock.json | 733 +++++++++--------- services/authentication-service/package.json | 6 +- .../auth-client.controller.acceptance.ts | 146 ++++ ...ance.ts => login.controller.acceptance.ts} | 297 +++++-- .../acceptance/otp.controller.acceptance.ts | 50 ++ .../signup-request.controller.acceptance.ts | 61 ++ .../src/__tests__/acceptance/test-helper.ts | 22 +- .../src/__tests__/fixtures/application.ts | 31 +- .../src/__tests__/fixtures/globals.ts | 1 + .../src/__tests__/fixtures/keys.ts | 41 + .../bearer-token-verifier.provider.ts | 18 +- .../providers/local-password.provider.ts | 6 +- .../oauth-password-verifier.provider.ts | 19 + .../providers/resource-owner.provider.ts | 30 + .../unit/apple-oauth2-signup.povider.unit.ts | 29 + .../unit/apple-post-verify.provider.unit.ts | 30 + .../unit/apple-pre-verify.provider.unit.ts | 32 + .../unit/bearer-verify.provider.unit.ts | 57 ++ .../unit/code.reader.provider.unit.ts | 29 + .../unit/code.writer.provider.unit.ts | 29 + .../facebook-oauth2-signup.provider.unit.ts | 36 + .../facebook-post-verify.provider.unit.ts | 37 + .../unit/facebook-pre-verify.provider.unit.ts | 39 + .../google-oauth2-signup.provider.unit.ts | 36 + .../unit/google-post-verify.provider.unit.ts | 37 + .../unit/google-pre-verify.provider.unit.ts | 39 + .../instagram-oauth2-signup.provider.unit.ts | 40 + .../instagram-post-verify.provider.unit.ts | 41 + .../instagram-pre-verify.provider.unit.ts | 43 + .../unit/jwt-payload.provider.unit.ts | 99 +++ .../keycloak-post-verify.provider.unit.ts | 42 + .../unit/keycloak-pre-verify.provider.unit.ts | 44 ++ .../unit/keycloak-signup.provider.unit.ts | 41 + .../unit/local-presignup.provider.unit.ts | 21 + .../unit/local-signup.provider.unit.ts | 21 + .../apple-oauth2-verify.provider.unit.ts | 118 +++ .../bearer-token-verify.provider.unit.ts | 67 ++ .../client-password-verify.provider.unit.ts | 46 ++ .../facebook-oauth-verify.provider.unit.ts | 121 +++ .../google-oauth2-verify.provider.unit.ts | 120 +++ .../instagram-oauth2-verify.provider.unit.ts | 121 +++ .../keycloak-verify.provider.unit.ts | 117 +++ .../local-password-verify.provider.unit.ts | 119 +++ .../resource-owner-verify.provider.unit.ts | 147 ++++ 44 files changed, 2803 insertions(+), 456 deletions(-) create mode 100644 services/authentication-service/src/__tests__/acceptance/auth-client.controller.acceptance.ts rename services/authentication-service/src/__tests__/acceptance/{auth.acceptance.ts => login.controller.acceptance.ts} (71%) create mode 100644 services/authentication-service/src/__tests__/acceptance/otp.controller.acceptance.ts create mode 100644 services/authentication-service/src/__tests__/acceptance/signup-request.controller.acceptance.ts create mode 100644 services/authentication-service/src/__tests__/fixtures/globals.ts create mode 100644 services/authentication-service/src/__tests__/fixtures/keys.ts create mode 100644 services/authentication-service/src/__tests__/fixtures/providers/oauth-password-verifier.provider.ts create mode 100644 services/authentication-service/src/__tests__/fixtures/providers/resource-owner.provider.ts create mode 100644 services/authentication-service/src/__tests__/unit/apple-oauth2-signup.povider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/apple-post-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/apple-pre-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/bearer-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/code.reader.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/code.writer.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/facebook-oauth2-signup.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/facebook-post-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/facebook-pre-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/google-oauth2-signup.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/google-post-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/google-pre-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/instagram-oauth2-signup.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/instagram-post-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/instagram-pre-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/jwt-payload.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/keycloak-post-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/keycloak-pre-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/keycloak-signup.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/local-presignup.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/local-signup.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/apple-oauth2-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/bearer-token-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/client-password-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/facebook-oauth-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/google-oauth2-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/instagram-oauth2-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/keycloak-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/local-password-verify.provider.unit.ts create mode 100644 services/authentication-service/src/__tests__/unit/modules-provider/resource-owner-verify.provider.unit.ts diff --git a/services/authentication-service/package-lock.json b/services/authentication-service/package-lock.json index e10afdfe8d..085ebdf47f 100644 --- a/services/authentication-service/package-lock.json +++ b/services/authentication-service/package-lock.json @@ -14,26 +14,24 @@ } }, "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", - "dev": true + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==" }, "@babel/core": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz", - "integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz", + "integrity": "sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==", + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.0", + "@babel/helper-compilation-targets": "^7.15.0", + "@babel/helper-module-transforms": "^7.15.0", + "@babel/helpers": "^7.14.8", + "@babel/parser": "^7.15.0", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -43,144 +41,160 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "requires": { - "@babel/highlight": "^7.12.13" + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.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 + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, "@babel/generator": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz", - "integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==", - "dev": true, + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", + "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", "requires": { - "@babel/types": "^7.14.1", + "@babel/types": "^7.15.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", - "dev": true, + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz", + "integrity": "sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==", "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", "semver": "^6.3.0" }, "dependencies": { "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "requires": { + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dev": true, + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", + "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.15.0" } }, "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" } }, "@babel/helper-module-transforms": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz", - "integrity": "sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz", + "integrity": "sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==", + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.15.0", + "@babel/helper-simple-access": "^7.14.8", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.9", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" + } } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", - "dev": true, + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", + "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" + "@babel/helper-member-expression-to-functions": "^7.15.0", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" } }, "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dev": true, + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", + "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.8" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { @@ -190,20 +204,18 @@ "dev": true }, "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", - "dev": true + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==" }, "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", - "dev": true, + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.3.tgz", + "integrity": "sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g==", "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" } }, "@babel/highlight": { @@ -218,10 +230,9 @@ } }, "@babel/parser": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz", - "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==", - "dev": true + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", + "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==" }, "@babel/runtime": { "version": "7.14.0", @@ -233,68 +244,100 @@ } }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "requires": { - "@babel/highlight": "^7.12.13" + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" } } } }, "@babel/traverse": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz", - "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.14.0", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", + "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.0", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.15.0", + "@babel/types": "^7.15.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "requires": { - "@babel/highlight": "^7.12.13" + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" } }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" } } }, "@babel/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz", - "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==", - "dev": true, + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==" + } } }, "@dabh/diagnostics": { @@ -404,7 +447,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, "requires": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -417,7 +459,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -427,7 +468,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -437,7 +477,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -446,7 +485,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -455,7 +493,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -463,16 +500,14 @@ "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" } } }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" }, "@loopback/boot": { "version": "3.4.0", @@ -735,6 +770,91 @@ "sinon": "^10.0.0", "supertest": "^6.1.3", "tslib": "^2.2.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "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 + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "nise": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "sinon": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", + "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.1.0", + "supports-color": "^7.1.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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@mapbox/node-pre-gyp": { @@ -822,7 +942,6 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, "requires": { "type-detect": "4.0.8" } @@ -831,16 +950,14 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz", "integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw==", - "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" } }, "@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", - "dev": true, + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", "requires": { "@sinonjs/commons": "^1.6.0", "lodash.get": "^4.4.2", @@ -850,8 +967,7 @@ "@sinonjs/text-encoding": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==" }, "@sourceloop/core": { "version": "2.0.1", @@ -1218,12 +1334,23 @@ } }, "@types/sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-jDZ55oCKxqlDmoTBBbBBEx+N8ZraUVhggMZ9T5t+6/Dh8/4NiOjSUfpLrPiEwxQDlAe3wpAkoXhWvE6LibtsMQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.2.tgz", + "integrity": "sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw==", "dev": true, "requires": { - "@sinonjs/fake-timers": "^7.0.4" + "@sinonjs/fake-timers": "^7.1.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } } }, "@types/superagent": { @@ -1448,7 +1575,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1508,7 +1634,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, "requires": { "default-require-extensions": "^3.0.0" } @@ -1521,8 +1646,7 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, "are-we-there-yet": { "version": "1.1.5", @@ -1825,16 +1949,15 @@ "dev": true }, "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, + "version": "4.16.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.8.tgz", + "integrity": "sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ==", "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", + "caniuse-lite": "^1.0.30001251", + "colorette": "^1.3.0", + "electron-to-chromium": "^1.3.811", "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "node-releases": "^1.1.75" } }, "buffer-equal-constant-time": { @@ -1890,7 +2013,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, "requires": { "hasha": "^5.0.0", "make-dir": "^3.0.0", @@ -1939,10 +2061,9 @@ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, "caniuse-lite": { - "version": "1.0.30001228", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", - "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", - "dev": true + "version": "1.0.30001252", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz", + "integrity": "sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==" }, "capital-case": { "version": "1.0.4", @@ -2043,8 +2164,7 @@ "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, "cliui": { "version": "7.0.4", @@ -2115,10 +2235,9 @@ } }, "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", + "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==" }, "colors": { "version": "1.4.0", @@ -2151,8 +2270,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, "component-emitter": { "version": "1.3.0", @@ -2206,10 +2324,9 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "requires": { "safe-buffer": "~5.1.1" }, @@ -2217,8 +2334,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -2555,7 +2671,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, "requires": { "strip-bom": "^4.0.0" } @@ -2609,8 +2724,7 @@ "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" }, "dir-glob": { "version": "3.0.1", @@ -2712,10 +2826,9 @@ } }, "electron-to-chromium": { - "version": "1.3.727", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz", - "integrity": "sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg==", - "dev": true + "version": "1.3.820", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.820.tgz", + "integrity": "sha512-5cFwDmo2yzEA9hn55KZ9+cX/b6DSFvpKz8Hb2fiDmriXWB+DBoXKXmncQwNRFBBTlUdsvPHCoy594OoMLAO0Tg==" }, "elliptic": { "version": "6.5.4", @@ -2827,8 +2940,7 @@ "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" }, "es6-promise": { "version": "4.2.8", @@ -3074,8 +3186,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", @@ -3383,7 +3494,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -3447,7 +3557,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" @@ -3493,8 +3602,7 @@ "fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==" }, "fs-extra": { "version": "9.1.0", @@ -3628,8 +3736,7 @@ "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-caller-file": { "version": "2.0.5", @@ -3655,8 +3762,7 @@ "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" }, "get-stream": { "version": "5.2.0", @@ -3738,8 +3844,7 @@ "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "grapheme-splitter": { "version": "1.0.4", @@ -3825,7 +3930,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, "requires": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" @@ -3917,8 +4021,7 @@ "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "http-errors": { "version": "1.8.0", @@ -4134,14 +4237,12 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" }, "inflection": { "version": "1.13.1", @@ -4376,8 +4477,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "isarray": { "version": "1.0.0", @@ -4397,14 +4497,12 @@ "istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==" }, "istanbul-lib-hook": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, "requires": { "append-transform": "^2.0.0" } @@ -4413,7 +4511,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, "requires": { "@babel/core": "^7.7.5", "@istanbuljs/schema": "^0.1.2", @@ -4424,8 +4521,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -4433,7 +4529,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", - "dev": true, "requires": { "archy": "^1.0.0", "cross-spawn": "^7.0.0", @@ -4448,7 +4543,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, "requires": { "aggregate-error": "^3.0.0" } @@ -4457,7 +4551,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -4465,8 +4558,7 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" } } }, @@ -4474,7 +4566,6 @@ "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", @@ -4484,14 +4575,12 @@ "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 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "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" } @@ -4502,7 +4591,6 @@ "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", @@ -4512,8 +4600,7 @@ "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 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -4521,7 +4608,6 @@ "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" @@ -4554,8 +4640,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "4.1.0", @@ -4593,8 +4678,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-merge-patch": { "version": "1.0.1", @@ -4656,7 +4740,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -4708,8 +4791,7 @@ "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" }, "jwa": { "version": "1.4.1", @@ -4818,14 +4900,12 @@ "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, "lodash.includes": { "version": "4.3.0", @@ -5579,38 +5659,26 @@ "dev": true }, "nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", - "dev": true, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", "requires": { "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/fake-timers": "^7.0.4", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" }, "dependencies": { - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, "requires": { "isarray": "0.0.1" } @@ -5672,7 +5740,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, "requires": { "process-on-spawn": "^1.0.0" } @@ -5695,10 +5762,9 @@ } }, "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", - "dev": true + "version": "1.1.75", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", + "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==" }, "noop6": { "version": "1.0.9", @@ -5747,7 +5813,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, "requires": { "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", @@ -5782,7 +5847,6 @@ "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" } @@ -5791,7 +5855,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -5802,7 +5865,6 @@ "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" } @@ -5810,20 +5872,17 @@ "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 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -5833,7 +5892,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -5842,7 +5900,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -5851,7 +5908,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -5860,7 +5916,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, "requires": { "aggregate-error": "^3.0.0" } @@ -5868,14 +5923,12 @@ "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -5884,7 +5937,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5894,14 +5946,12 @@ "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -5920,7 +5970,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -6159,14 +6208,12 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-hash": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, "requires": { "graceful-fs": "^4.1.15", "hasha": "^5.0.0", @@ -6375,8 +6422,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -6510,7 +6556,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, "requires": { "find-up": "^4.0.0" }, @@ -6519,7 +6564,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -6529,7 +6573,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -6538,7 +6581,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -6547,7 +6589,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -6598,7 +6639,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, "requires": { "fromentries": "^1.2.0" } @@ -6900,7 +6940,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, "requires": { "es6-error": "^4.0.1" } @@ -6958,8 +6997,7 @@ "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve": { "version": "1.20.0", @@ -7239,45 +7277,35 @@ } }, "sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.1.0", - "supports-color": "^7.1.0" + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" }, "dependencies": { "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", "requires": { "@sinonjs/commons": "^1.7.0" } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "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 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "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" } @@ -7339,8 +7367,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-support": { "version": "0.5.19", @@ -7364,7 +7391,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, "requires": { "foreground-child": "^2.0.0", "is-windows": "^1.0.2", @@ -7378,7 +7404,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -7560,8 +7585,7 @@ "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" }, "strip-eof": { "version": "1.0.0", @@ -7982,7 +8006,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -8009,8 +8032,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-regex-range": { "version": "5.0.1", @@ -8130,14 +8152,12 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { "version": "1.6.18", @@ -8152,7 +8172,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -8368,8 +8387,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "which-typed-array": { "version": "1.1.4", @@ -8844,7 +8862,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", diff --git a/services/authentication-service/package.json b/services/authentication-service/package.json index d5afa2367a..4115d09b00 100644 --- a/services/authentication-service/package.json +++ b/services/authentication-service/package.json @@ -24,7 +24,8 @@ "openapi-spec": "node ./dist/openapi-spec", "apidocs": "./node_modules/.bin/widdershins --search false --language_tabs 'javascript:JavaScript:request' 'javascript--nodejs:Node.JS' --summary openapi.json -o openapi.md", "pretest": "npm run clean && npm run build", - "test": "echo \"No tests !\"", + "test": "lb-mocha --allow-console-logs \"dist/__tests__\"", + "coverage": "nyc npm run test", "posttest": "npm run lint", "prepublishOnly": "npm run test", "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest", @@ -74,11 +75,13 @@ "moment": "^2.27.0", "moment-timezone": "^0.5.31", "node-fetch": "^2.6.1", + "nyc": "^15.1.0", "passport-apple": "^2.0.1", "passport-facebook": "^3.0.0", "passport-google-oauth20": "^2.0.0", "passport-instagram": "^1.0.0", "prom-client": "^13.1.0", + "sinon": "^11.1.2", "tslib": "^2.0.0" }, "devDependencies": { @@ -96,6 +99,7 @@ "@types/passport-facebook": "^2.1.10", "@types/passport-google-oauth20": "^2.0.3", "@types/passport-instagram": "^1.0.0", + "@types/sinon": "^10.0.2", "db-migrate": "^0.11.12", "db-migrate-pg": "^1.2.2", "eslint": "^7.12.1", diff --git a/services/authentication-service/src/__tests__/acceptance/auth-client.controller.acceptance.ts b/services/authentication-service/src/__tests__/acceptance/auth-client.controller.acceptance.ts new file mode 100644 index 0000000000..6bf2d8336a --- /dev/null +++ b/services/authentication-service/src/__tests__/acceptance/auth-client.controller.acceptance.ts @@ -0,0 +1,146 @@ +import {Client, expect} from '@loopback/testlab'; +import * as jwt from 'jsonwebtoken'; +import {AuthClientRepository} from '../../repositories'; +import {TestingApplication} from '../fixtures/application'; +import {setupApplication} from './test-helper'; + +describe('Auth Client Controller', () => { + let app: TestingApplication; + let client: Client; + let authClientRepo: AuthClientRepository; + const basePath = '/auth-clients'; + const pass = 'test_password'; + const testUser = { + id: 1, + username: 'test_user', + password: pass, + permissions: ['NotAllowed'], + }; + + const token = jwt.sign(testUser, 'test', { + expiresIn: 180000, + issuer: 'test', + }); + + before('setupApplication', async () => { + ({app, client} = await setupApplication()); + }); + after(async () => app.stop()); + + before(givenRepositories); + afterEach(deleteMockData); + + it('gives status 401 when no token is passed', async () => { + const response = await client.get(basePath).expect(401); + + expect(response).to.have.property('error'); + }); + + it('gives status 200 when token is passed', async () => { + await client + .get(basePath) + .set('authorization', `Bearer ${token}`) + .expect(200); + }); + + it('gives status 200 and client detail when client is added', async () => { + const reqToAddEntity = await addEntity(); + expect(reqToAddEntity.status).to.be.equal(200); + + const response = await client + .get(`${basePath}/${reqToAddEntity.body.id}`) + .set('authorization', `Bearer ${token}`) + .expect(200); + expect(response.body).to.have.properties(['clientId', 'clientSecret']); + expect(response.body.clientId).to.be.equal('test_client_id'); + }); + + it('updates client successfully using PATCH request', async () => { + const reqToAddEntity = await addEntity(); + + const entityToUpdate = { + clientId: 'test_client_id_updated', + clientSecret: 'test_client_secret', + }; + + await client + .patch(`${basePath}/${reqToAddEntity.body.id}`) + .set('authorization', `Bearer ${token}`) + .send(entityToUpdate) + .expect(204); + + const response = await client + .get(`${basePath}/${reqToAddEntity.body.id}`) + .set('authorization', `Bearer ${token}`) + .expect(200); + + expect(response.body).to.have.properties(['clientId', 'clientSecret']); + expect(response.body.clientId).to.be.equal('test_client_id_updated'); + }); + + it('updates client using PUT request', async () => { + const reqToAddEntity = await addEntity(); + + const entityToUpdate = { + clientId: 'test_client_id_updated', + clientSecret: 'test_client_secret', + secret: 'test_secret', + accessTokenExpiration: 1800, + refreshTokenExpiration: 1800, + authCodeExpiration: 1800, + }; + + await client + .put(`${basePath}/${reqToAddEntity.body.id}`) + .set('authorization', `Bearer ${token}`) + .send(entityToUpdate) + .expect(204); + + const response = await client + .get(`${basePath}/${reqToAddEntity.body.id}`) + .set('authorization', `Bearer ${token}`) + .expect(200); + + expect(response.body).to.have.properties(['clientId', 'clientSecret']); + expect(response.body.clientId).to.be.equal('test_client_id_updated'); + }); + + it('deletes a client successfully', async () => { + const reqToAddEntity = await addEntity(); + await client + .del(`${basePath}/${reqToAddEntity.body.id}`) + .set('authorization', `Bearer ${token}`) + .expect(204); + }); + + it('should return count', async () => { + await client + .get(`${basePath}/count`) + .set('authorization', `Bearer ${token}`) + .expect(200); + }); + + async function addEntity() { + const enitityToAdd = { + clientId: 'test_client_id', + clientSecret: 'test_client_secret', + secret: 'test_secret', + accessTokenExpiration: 1800, + refreshTokenExpiration: 1800, + authCodeExpiration: 1800, + }; + + return client + .post(basePath) + .set('authorization', `Bearer ${token}`) + .send(enitityToAdd); + } + + async function deleteMockData() { + await authClientRepo.deleteAllHard(); + } + + async function givenRepositories() { + authClientRepo = await app.getRepository(AuthClientRepository); + } +}); diff --git a/services/authentication-service/src/__tests__/acceptance/auth.acceptance.ts b/services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts similarity index 71% rename from services/authentication-service/src/__tests__/acceptance/auth.acceptance.ts rename to services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts index 3ade13713a..9b23cb4d93 100644 --- a/services/authentication-service/src/__tests__/acceptance/auth.acceptance.ts +++ b/services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts @@ -1,3 +1,4 @@ +'use strict'; import {Client, createRestAppClient, expect} from '@loopback/testlab'; import {RoleTypes} from '@sourceloop/core'; import { @@ -22,6 +23,10 @@ describe('Authentication microservice', () => { let refreshTokenRepository: RefreshTokenRepository; let userCredentialsRepository: UserCredentialsRepository; let roleRepository: RoleRepository; + const useragent = 'test'; + const deviceId = 'test'; + const useragentName = 'user-agent'; + const deviceIdName = 'device_id'; before('setupApplication', async () => { ({app, client} = await setupApplication()); }); @@ -38,6 +43,10 @@ describe('Authentication microservice', () => { }); before(setMockData); after(deleteMockData); + afterEach(() => { + delete process.env.JWT_ISSUER; + delete process.env.JWT_SECRET; + }); it('gives status 422 for login request with no client credentials', async () => { const reqData = {}; const response = await client.post(`/auth/login`).send(reqData).expect(422); @@ -81,6 +90,7 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; + process.env.JWT_ISSUER = 'test'; await client.post(`/auth/login`).send(reqData).expect(200); }); it('should return code in response', async () => { @@ -91,6 +101,7 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; + process.env.JWT_ISSUER = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) @@ -105,39 +116,27 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) .expect(200); - const response = await client.post(`/auth/token`).send({ - clientId: 'web', - code: reqForCode.body.code, - }); + const response = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }); expect(response.body).to.have.properties([ 'accessToken', 'refreshToken', 'expires', ]); }); - it('should return user details', async () => { - const reqData = { - // eslint-disable-next-line - client_id: 'web', // eslint-disable-next-line - client_secret: 'test', - username: 'test_user', - password: 'temp123!@', - }; - const reqForCode = await client - .post(`/auth/login-token`) - .send(reqData) - .expect(200); - const response = await client - .get(`/auth/me`) - .set('Authorization', `Bearer ${reqForCode.body.accessToken}`) - .expect(200); - expect(response.body).to.have.properties(['id', 'permissions']); - }); - it('should return age in user details', async () => { + it('should return 401 for incorrect moment creation', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -145,15 +144,14 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; - const reqForCode = await client + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + await client .post(`/auth/login-token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) .send(reqData) - .expect(200); - const response = await client - .get(`/auth/me`) - .set('Authorization', `Bearer ${reqForCode.body.accessToken}`) - .expect(200); - expect(response.body).to.have.properties(['age']); + .expect(401); }); it('should change password successfully', async () => { const reqData = { @@ -163,6 +161,8 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) @@ -177,7 +177,6 @@ describe('Authentication microservice', () => { .send({ username: 'test_user', password: 'new_test_password', - oldPassword: 'temp123!@', refreshToken: reqForToken.body.refreshToken, }) .expect(200); @@ -190,14 +189,20 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'new_test_password', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) .expect(200); - const reqForToken = await client.post(`/auth/token`).send({ - clientId: 'web', - code: reqForCode.body.code, - }); + const reqForToken = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }); const response = await client .post(`/auth/token-refresh`) .send({refreshToken: reqForToken.body.refreshToken}) @@ -212,14 +217,20 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) .expect(200); - const reqForToken = await client.post(`/auth/token`).send({ - clientId: 'web', - code: reqForCode.body.code, - }); + const reqForToken = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }); await client .post(`/auth/token-refresh`) .send({refreshToken: reqForToken.body.refreshToken}) @@ -234,14 +245,20 @@ describe('Authentication microservice', () => { username: 'test_user', password: 'temp123!@', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) .expect(200); - const reqForToken = await client.post(`/auth/token`).send({ - clientId: 'web', - code: reqForCode.body.code, - }); + const reqForToken = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }); await client .post(`/auth/token-refresh`) .send({refreshToken: reqForToken.body.refreshToken}) @@ -254,6 +271,8 @@ describe('Authentication microservice', () => { client_secret: 'test', username: 'test_user', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const response = await client .post(`/auth/forget-password`) .send(reqData) @@ -273,32 +292,6 @@ describe('Authentication microservice', () => { .expect(404); expect(response.body).to.have.properties('error'); }); - it('should return error Email for user does not exist', async () => { - const reqData = { - // eslint-disable-next-line - client_id: 'web', // eslint-disable-next-line - client_secret: 'test', - username: 'test_teacher', - }; - const response = await client - .post(`/auth/forget-password`) - .send(reqData) - .expect(404); - expect(response.body).to.have.properties('error'); - }); - it('should give UnAuthoraized Error', async () => { - const reqData = { - // eslint-disable-next-line - client_id: 'web', // eslint-disable-next-line - client_secret: 'testt', - username: 'test_user', - }; - const response = await client - .post(`/auth/forget-password`) - .send(reqData) - .expect(401); - expect(response.body).to.have.properties('error'); - }); it('should verify token successfully', async () => { const reqData = { // eslint-disable-next-line @@ -306,6 +299,8 @@ describe('Authentication microservice', () => { client_secret: 'test', username: 'test_user', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const response = await client .post(`/auth/forget-password`) .send(reqData) @@ -323,6 +318,8 @@ describe('Authentication microservice', () => { client_secret: 'test', username: 'test_user', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; await client.post(`/auth/forget-password`).send(reqData).expect(200); const responseToken = await client .get(`/auth/verify-reset-password-link`) @@ -330,13 +327,33 @@ describe('Authentication microservice', () => { .expect(400); expect(responseToken.body).to.have.properties('error'); }); - it('should reset password successfully', async () => { + it('return error for token missing', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line client_secret: 'test', username: 'test_user', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + await client.post(`/auth/forget-password`).send(reqData).expect(200); + const request = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + password: 'test123', + }; + await client.patch(`/auth/reset-password`).send(request).expect(422); + }); + it('return error for password missing', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + username: 'test_user', + }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const response = await client .post(`/auth/forget-password`) .send(reqData) @@ -346,7 +363,28 @@ describe('Authentication microservice', () => { client_id: 'web', // eslint-disable-next-line client_secret: 'test', token: response.body.code, - password: 'test123@#', + }; + await client.patch(`/auth/reset-password`).send(request).expect(422); + }); + it('should reset password', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + username: 'test_user', + }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + const response = await client + .post(`/auth/forget-password`) + .send(reqData) + .expect(200); + const request = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + token: response.body.code, + password: 'test123', }; await client.patch(`/auth/reset-password`).send(request).expect(204); }); @@ -356,14 +394,49 @@ describe('Authentication microservice', () => { client_id: 'web', // eslint-disable-next-line client_secret: 'test', username: 'test_user', - password: 'test123@#', + password: 'test123', + }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + const reqForCode = await client + .post(`/auth/login`) + .send(reqData) + .expect(200); + const reqForToken = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }) + .expect(200); + await client + .post(`/logout`) + .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) + .send({ + refreshToken: reqForToken.body.refreshToken, + }) + .expect(200); + }); + it('should return error for wrong token on logout', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + username: 'test_user', + password: 'test123', }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; const reqForCode = await client .post(`/auth/login`) .send(reqData) .expect(200); const reqForToken = await client .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) .send({ clientId: 'web', code: reqForCode.body.code, @@ -372,11 +445,73 @@ describe('Authentication microservice', () => { await client .post(`/logout`) .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) + .send({ + refreshToken: 'aaaa', + }) + .expect(401); + }); + it('should return true on keycloak logout', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + username: 'test_user', + password: 'test123', + }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + const reqForCode = await client + .post(`/auth/login`) + .send(reqData) + .expect(200); + const reqForToken = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }) + .expect(200); + await client + .post(`/keycloak/logout`) + .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) .send({ refreshToken: reqForToken.body.refreshToken, }) .expect(200); }); + it('should return error for wrong token on keycloak logout', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + username: 'test_user', + password: 'test123', + }; + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + const reqForCode = await client + .post(`/auth/login`) + .send(reqData) + .expect(200); + const reqForToken = await client + .post(`/auth/token`) + .set(deviceIdName, deviceId) + .set(useragentName, useragent) + .send({ + clientId: 'web', + code: reqForCode.body.code, + }) + .expect(200); + await client + .post(`/keycloak/logout`) + .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) + .send({ + refreshToken: 'aaaa', + }) + .expect(401); + }); async function givenUserRepository() { userRepo = await app.getRepository(UserRepository); } @@ -458,9 +593,9 @@ describe('Authentication microservice', () => { ], }, ]); - await userRepo.create( + await userRepo.createAll([ { - id: '100', + id: '1', firstName: 'Test', lastName: 'User', username: 'test_user', @@ -469,22 +604,22 @@ describe('Authentication microservice', () => { email: 'xyz@gmail.com', }, { - id: '200', + id: '2', firstName: 'Test', lastName: 'Teacher', username: 'test_teacher', dob: '1996-11-05', authClientIds: `{1}`, }, - ); + ]); await userTenantRepo.create( { - userId: '100', + userId: '1', tenantId: '200', roleId: '2', }, { - userId: '200', + userId: '2', tenantId: '200', roleId: '2', }, @@ -499,5 +634,11 @@ describe('Authentication microservice', () => { authCodeExpiration: 180, secret: 'poiuytrewq', }); + await userCredentialsRepository.create({ + id: '1', + userId: '1', + authProvider: 'test_auth', + password: 'temp123!@', + }); } }); diff --git a/services/authentication-service/src/__tests__/acceptance/otp.controller.acceptance.ts b/services/authentication-service/src/__tests__/acceptance/otp.controller.acceptance.ts new file mode 100644 index 0000000000..d0f884f9e6 --- /dev/null +++ b/services/authentication-service/src/__tests__/acceptance/otp.controller.acceptance.ts @@ -0,0 +1,50 @@ +import {Client, expect} from '@loopback/testlab'; +import {OtpRepository} from '../../repositories'; +import {TestingApplication} from '../fixtures/application'; +import {setupApplication} from './test-helper'; + +describe('OTP Controller', () => { + let app: TestingApplication; + let client: Client; + let otpRepo: OtpRepository; + const basePath = '/otp-caches'; + + before('setupApplication', async () => { + ({app, client} = await setupApplication()); + }); + after(async () => app.stop()); + + before(givenRepositories); + afterEach(deleteMockData); + + it('gives status 200 when otp is added', async () => { + const reqToAddEntity = await addEntity(); + expect(reqToAddEntity.status).to.be.equal(204); + + const response = await client.get(`${basePath}/${reqToAddEntity.body.id}`); + + expect(response.status).to.be.equal(200); + }); + + it('deletes an otp successfully', async () => { + const reqToAddEntity = await addEntity(); + await client.del(`${basePath}/${reqToAddEntity.body.id}`).expect(204); + }); + + async function addEntity() { + const enitityToAdd = { + otp: 'test_otp', + username: 'test_username', + }; + + return client.post(basePath).send(enitityToAdd); + } + + async function deleteMockData() { + await otpRepo.deleteAll(); + } + + async function givenRepositories() { + otpRepo = await app.getRepository(OtpRepository); + } +}); diff --git a/services/authentication-service/src/__tests__/acceptance/signup-request.controller.acceptance.ts b/services/authentication-service/src/__tests__/acceptance/signup-request.controller.acceptance.ts new file mode 100644 index 0000000000..b4b7143a36 --- /dev/null +++ b/services/authentication-service/src/__tests__/acceptance/signup-request.controller.acceptance.ts @@ -0,0 +1,61 @@ +'use strict'; +import {Client, expect} from '@loopback/testlab'; +import {TestingApplication} from '../fixtures/application'; +import {setupApplication} from './test-helper'; + +describe('SignUp Request Controller', () => { + let app: TestingApplication; + let client: Client; + const basePath = '/auth/sign-up'; + const sampleEmail = 'xyz@gmail.com'; + const reqData = { + email: sampleEmail, + }; + + before('setupApplication', async () => { + ({app, client} = await setupApplication()); + }); + after(async () => app.stop()); + + afterEach(() => { + delete process.env.JWT_ISSUER; + delete process.env.JWT_SECRET; + }); + + it('gives status 200 when token is created', async () => { + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + await client.post(`${basePath}/create-token`).send(reqData).expect(200); + }); + + it('gives status 200 and code, email when token is created', async () => { + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + const response = await client + .post(`${basePath}/create-token`) + .send(reqData) + .expect(200); + expect(response.body).to.have.properties(['code', 'email']); + expect(response.body.email).to.be.equal(sampleEmail); + }); + + it('gives status 200 and user details for creating user', async () => { + process.env.JWT_ISSUER = 'test'; + process.env.JWT_SECRET = 'test'; + const respGot = await client + .post(`${basePath}/create-token`) + .send(reqData) + .expect(200); + const reqDta = { + email: sampleEmail, + password: 'test_password', + }; + const response = await client + .post(`/auth/sign-up/create-user`) + .set('Authorization', `Bearer ${respGot.body.code}`) + .send(reqDta) + .expect(200); + expect(response.body).to.have.properties(['user', 'email']); + expect(response.body.email).to.be.equal(sampleEmail); + }); +}); diff --git a/services/authentication-service/src/__tests__/acceptance/test-helper.ts b/services/authentication-service/src/__tests__/acceptance/test-helper.ts index 6b8919f8cc..326f4e6db5 100644 --- a/services/authentication-service/src/__tests__/acceptance/test-helper.ts +++ b/services/authentication-service/src/__tests__/acceptance/test-helper.ts @@ -3,9 +3,12 @@ import { givenHttpServerConfig, Client, } from '@loopback/testlab'; -import {AuthenticationBindings} from 'loopback4-authentication'; import {TestingApplication} from '../fixtures/application'; import {AuthDbSourceName, AuthCacheSourceName} from '../../types'; +import {AuthenticationBindings, Strategies} from 'loopback4-authentication'; +import {TestOauthPasswordVerifyProvider} from '../fixtures/providers/oauth-password-verifier.provider'; +import {TestPasswordVerifyProvider} from '../fixtures/providers/local-password.provider'; +import {TestResourceOwnerVerifyProvider} from '../fixtures/providers/resource-owner.provider'; export async function setupApplication(): Promise { const restConfig = givenHttpServerConfig({}); @@ -25,11 +28,6 @@ export async function setupApplication(): Promise { name: 'redis', connector: 'kv-memory', }); - app.bind(AuthenticationBindings.CURRENT_USER).to({ - id: 100, - username: 'test_user', - password: 'test_password', - }); app.bind(AuthenticationBindings.CURRENT_USER).to({ id: 1, @@ -37,6 +35,18 @@ export async function setupApplication(): Promise { password: 'temp123!@', }); + app + .bind(Strategies.Passport.LOCAL_PASSWORD_VERIFIER) + .toProvider(TestPasswordVerifyProvider); + + app + .bind(Strategies.Passport.OAUTH2_CLIENT_PASSWORD_VERIFIER) + .toProvider(TestOauthPasswordVerifyProvider); + + app + .bind(Strategies.Passport.RESOURCE_OWNER_PASSWORD_VERIFIER) + .toProvider(TestResourceOwnerVerifyProvider); + await app.start(); const client = createRestAppClient(app); diff --git a/services/authentication-service/src/__tests__/fixtures/application.ts b/services/authentication-service/src/__tests__/fixtures/application.ts index 91c375fd8b..e5369ec5fb 100644 --- a/services/authentication-service/src/__tests__/fixtures/application.ts +++ b/services/authentication-service/src/__tests__/fixtures/application.ts @@ -7,9 +7,14 @@ import { RestExplorerComponent, } from '@loopback/rest-explorer'; import {ServiceMixin} from '@loopback/service-proxy'; -import path from 'path'; +import {AuthenticationComponent, Strategies} from 'loopback4-authentication'; +import { + AuthorizationBindings, + AuthorizationComponent, +} from 'loopback4-authorization'; +import * as path from 'path'; +import {BearerTokenVerifyProvider} from './providers/bearer-token-verifier.provider'; import {AuthenticationServiceComponent} from '../../component'; -import '../../load-env'; export {ApplicationConfig}; @@ -22,13 +27,24 @@ export class TestingApplication extends BootMixin( // Set up default home page this.static('/', path.join(__dirname, '../public')); - // Customize @loopback/rest-explorer configuration here - this.configure(RestExplorerBindings.COMPONENT).to({ + // // Customize @loopback/rest-explorer configuration here + this.bind(RestExplorerBindings.CONFIG).to({ path: '/explorer', }); this.component(RestExplorerComponent); - this.component(AuthenticationServiceComponent); + // Add authentication component + this.component(AuthenticationComponent); + // Customize authentication verify handlers + this.bind(Strategies.Passport.BEARER_TOKEN_VERIFIER).toProvider( + BearerTokenVerifyProvider, + ); + + // Add authorization component + this.bind(AuthorizationBindings.CONFIG).to({ + allowAlwaysPaths: ['/explorer'], + }); + this.component(AuthorizationComponent); this.projectRoot = __dirname; // Customize @loopback/boot Booter Conventions here @@ -39,6 +55,11 @@ export class TestingApplication extends BootMixin( extensions: ['.controller.js'], nested: true, }, + repositories: { + dirs: ['repositories'], + extensions: ['.repository.js'], + nested: true, + }, }; } } diff --git a/services/authentication-service/src/__tests__/fixtures/globals.ts b/services/authentication-service/src/__tests__/fixtures/globals.ts new file mode 100644 index 0000000000..d60dbfe38e --- /dev/null +++ b/services/authentication-service/src/__tests__/fixtures/globals.ts @@ -0,0 +1 @@ +export const BINDING_PREFIX = 'sourcefuse.services.'; diff --git a/services/authentication-service/src/__tests__/fixtures/keys.ts b/services/authentication-service/src/__tests__/fixtures/keys.ts new file mode 100644 index 0000000000..a78847a007 --- /dev/null +++ b/services/authentication-service/src/__tests__/fixtures/keys.ts @@ -0,0 +1,41 @@ +import {IAuthUser} from 'loopback4-authentication'; +import {BindingKey} from '@loopback/context'; +import {BINDING_PREFIX} from './globals'; + +export namespace BearerVerifierBindings { + export const Config = BindingKey.create( + `${BINDING_PREFIX}.bearer-verfier.config`, + ); +} + +export enum BearerVerifierType { + service, + facade, +} +export interface BearerVerifierConfig { + type: BearerVerifierType; + authServiceUrl: string; +} + +export interface IUserPrefs { + locale?: string; +} + +export interface IAuthUserWithPermissions< + ID = string, + TID = string, + UTID = string, +> extends IAuthUser { + id?: string; + identifier?: ID; + permissions: string[]; + authClientId: number; + userPreferences?: IUserPrefs; + email?: string; + role: string; + firstName: string; + lastName: string; + middleName?: string; + tenantId?: TID; + userTenantId?: UTID; +} diff --git a/services/authentication-service/src/__tests__/fixtures/providers/bearer-token-verifier.provider.ts b/services/authentication-service/src/__tests__/fixtures/providers/bearer-token-verifier.provider.ts index 7152cfddb5..88aa0e0e5a 100644 --- a/services/authentication-service/src/__tests__/fixtures/providers/bearer-token-verifier.provider.ts +++ b/services/authentication-service/src/__tests__/fixtures/providers/bearer-token-verifier.provider.ts @@ -1,14 +1,24 @@ -import {Provider} from '@loopback/core'; +import {Provider} from '@loopback/context'; +import {verify} from 'jsonwebtoken'; import {VerifyFunction} from 'loopback4-authentication'; +import {IAuthUserWithPermissions} from '../keys'; export class BearerTokenVerifyProvider implements Provider { - constructor() {} - value(): VerifyFunction.BearerFn { return async (token: string) => { - return {id: 1, username: 'mayank'}; + /* + Implementing a basic JWT token decryption here + Leaving the additional security to the consumer of this application + + Suggestion: to revoke these tokens put them in redis or some in-memory + database. + Use global interceptor over this to apply that check on each api. + */ + return verify(token, 'test', { + issuer: 'test', + }) as IAuthUserWithPermissions; }; } } diff --git a/services/authentication-service/src/__tests__/fixtures/providers/local-password.provider.ts b/services/authentication-service/src/__tests__/fixtures/providers/local-password.provider.ts index 71facd6960..1aef27434e 100644 --- a/services/authentication-service/src/__tests__/fixtures/providers/local-password.provider.ts +++ b/services/authentication-service/src/__tests__/fixtures/providers/local-password.provider.ts @@ -4,11 +4,9 @@ import {VerifyFunction} from 'loopback4-authentication'; export class TestPasswordVerifyProvider implements Provider { - constructor() {} - value(): VerifyFunction.LocalPasswordFn { - return async (token: string) => { - return {id: 1, username: 'mayank', password: 'pass'}; + return async (username: string, password: string) => { + return {id: 1, username: 'test_user', password: 'temp123!@'}; }; } } diff --git a/services/authentication-service/src/__tests__/fixtures/providers/oauth-password-verifier.provider.ts b/services/authentication-service/src/__tests__/fixtures/providers/oauth-password-verifier.provider.ts new file mode 100644 index 0000000000..1dfabc3964 --- /dev/null +++ b/services/authentication-service/src/__tests__/fixtures/providers/oauth-password-verifier.provider.ts @@ -0,0 +1,19 @@ +import {Provider} from '@loopback/core'; +import {VerifyFunction} from 'loopback4-authentication'; + +export class TestOauthPasswordVerifyProvider + implements Provider +{ + value(): VerifyFunction.OauthClientPasswordFn { + return async (clientId: string, clientSecret: string) => { + return { + clientId: 'web', + clientSecret: 'test', + secret: 'poiuytrewq', + authCodeExpiration: 1800, + refreshTokenExpiration: 1800, + accessTokenExpiration: 1800, + }; + }; + } +} diff --git a/services/authentication-service/src/__tests__/fixtures/providers/resource-owner.provider.ts b/services/authentication-service/src/__tests__/fixtures/providers/resource-owner.provider.ts new file mode 100644 index 0000000000..a4782e9efc --- /dev/null +++ b/services/authentication-service/src/__tests__/fixtures/providers/resource-owner.provider.ts @@ -0,0 +1,30 @@ +import {Provider} from '@loopback/core'; +import {VerifyFunction} from 'loopback4-authentication'; + +export class TestResourceOwnerVerifyProvider + implements Provider +{ + value(): VerifyFunction.ResourceOwnerPasswordFn { + return async ( + clientId: string, + clientSecret: string, + username: string, + password: string, + ) => { + return { + client: { + clientId: 'web', + clientSecret: 'test', + refreshTokenExpiration: 1800, + accessTokenExpiration: 1800, + }, + user: { + id: 1, + username: 'test_user', + password: 'temp123!@', + authClientIds: '{1}', + }, + }; + }; + } +} diff --git a/services/authentication-service/src/__tests__/unit/apple-oauth2-signup.povider.unit.ts b/services/authentication-service/src/__tests__/unit/apple-oauth2-signup.povider.unit.ts new file mode 100644 index 0000000000..5f18914eeb --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/apple-oauth2-signup.povider.unit.ts @@ -0,0 +1,29 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {AppleOauth2SignupProvider} from '../../providers'; + +describe('Apple Oauth Signup Service', () => { + let appleOauth2SignupProvider: AppleOauth2SignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = {}; + + describe('Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = appleOauth2SignupProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and throws invalid credentials', async () => { + const func = appleOauth2SignupProvider.value(); + const result = await func(profile).catch(err => err.message); + expect(result).to.be.eql('Invalid Credentials'); + }); + }); + + function setUp() { + appleOauth2SignupProvider = new AppleOauth2SignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/apple-post-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/apple-post-verify.provider.unit.ts new file mode 100644 index 0000000000..3cd5466a97 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/apple-post-verify.provider.unit.ts @@ -0,0 +1,30 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {ApplePostVerifyProvider} from '../../providers'; + +describe('Apple Oauth Post Verify Service', () => { + let applePostVerifyProvider: ApplePostVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = {}; + const user = null; + + describe('Post Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = applePostVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and returns the user value', async () => { + const func = applePostVerifyProvider.value(); + const result = await func(profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + applePostVerifyProvider = new ApplePostVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/apple-pre-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/apple-pre-verify.provider.unit.ts new file mode 100644 index 0000000000..dd5f18bf55 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/apple-pre-verify.provider.unit.ts @@ -0,0 +1,32 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {ApplePreVerifyProvider} from '../../providers'; + +describe('Apple Oauth Pre Verify Service', () => { + let applePreVerifyProvider: ApplePreVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = {}; + const user = null; + + describe('Pre Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = applePreVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and it returns the user', async () => { + const func = applePreVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + applePreVerifyProvider = new ApplePreVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/bearer-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/bearer-verify.provider.unit.ts new file mode 100644 index 0000000000..26dd8a60f5 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/bearer-verify.provider.unit.ts @@ -0,0 +1,57 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {SignupBearerVerifyProvider} from '../../providers'; + +describe('Bearer Verify Signup Service', () => { + let bearerVerifyProvider: SignupBearerVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const logger = { + log, + info, + warn, + error, + debug, + }; + + const token = 'test_token'; + + describe('Bearer Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = bearerVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('chexk if provider funtion throws error for token expiration', async () => { + const func = bearerVerifyProvider.value(); + const result = await func(token).catch(err => err.message); + expect(result).to.be.eql('TokenExpired'); + }); + }); + + function log() { + // This is intentional + } + + function info() { + // This is intentional + } + + function warn() { + // This is intentional + } + + function error() { + // This is intentional + } + + function debug() { + // This is intentional + } + + function setUp() { + bearerVerifyProvider = new SignupBearerVerifyProvider(logger); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/code.reader.provider.unit.ts b/services/authentication-service/src/__tests__/unit/code.reader.provider.unit.ts new file mode 100644 index 0000000000..df4669894c --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/code.reader.provider.unit.ts @@ -0,0 +1,29 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {OauthCodeReaderProvider} from '../../providers'; + +describe('Code Reader Service', () => { + let oauthCodeReaderProvider: OauthCodeReaderProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const token = 'test_token'; + + describe('Code Reader Service', () => { + it('checks if provider returns a function', async () => { + const result = oauthCodeReaderProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise that returns the token', async () => { + const func = oauthCodeReaderProvider.value(); + const result = await func(token); + expect(result).to.be.eql(token); + }); + }); + + function setUp() { + oauthCodeReaderProvider = new OauthCodeReaderProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/code.writer.provider.unit.ts b/services/authentication-service/src/__tests__/unit/code.writer.provider.unit.ts new file mode 100644 index 0000000000..af0205abda --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/code.writer.provider.unit.ts @@ -0,0 +1,29 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {CodeWriterProvider} from '../../providers'; + +describe('Code Writer Service', () => { + let codeWriterProvider: CodeWriterProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const token = 'test_token'; + + describe('Code Writer Service', () => { + it('checks if provider returns a function', async () => { + const result = codeWriterProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and returns the token value', async () => { + const func = codeWriterProvider.value(); + const result = await func(token); + expect(result).to.be.eql(token); + }); + }); + + function setUp() { + codeWriterProvider = new CodeWriterProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/facebook-oauth2-signup.provider.unit.ts b/services/authentication-service/src/__tests__/unit/facebook-oauth2-signup.provider.unit.ts new file mode 100644 index 0000000000..90dbc3a7fc --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/facebook-oauth2-signup.provider.unit.ts @@ -0,0 +1,36 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {FacebookOauth2SignupProvider} from '../../providers'; + +describe('Facebook Oauth Signup Service', () => { + let facebookOauth2SignupProvider: FacebookOauth2SignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + id: 'test_id', + displayName: 'test_display_name', + birthday: 'test_date', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + + describe('Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = facebookOauth2SignupProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and throws invalid credentials', async () => { + const func = facebookOauth2SignupProvider.value(); + const result = await func(profile).catch(err => err.message); + expect(result).to.be.eql('Invalid Credentials'); + }); + }); + + function setUp() { + facebookOauth2SignupProvider = new FacebookOauth2SignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/facebook-post-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/facebook-post-verify.provider.unit.ts new file mode 100644 index 0000000000..f0c126cd0e --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/facebook-post-verify.provider.unit.ts @@ -0,0 +1,37 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {FacebookPostVerifyProvider} from '../../providers'; + +describe('Facebook Oauth Post Verify Service', () => { + let facebookPostVerifyProvider: FacebookPostVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + id: 'test_id', + displayName: 'test_display_name', + birthday: 'test_date', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Post Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = facebookPostVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which returns the user value', async () => { + const func = facebookPostVerifyProvider.value(); + const result = await func(profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + facebookPostVerifyProvider = new FacebookPostVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/facebook-pre-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/facebook-pre-verify.provider.unit.ts new file mode 100644 index 0000000000..f14c103e98 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/facebook-pre-verify.provider.unit.ts @@ -0,0 +1,39 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {FacebookPreVerifyProvider} from '../../providers'; + +describe('Facebook Oauth Pre Verify Service', () => { + let facebookPreVerifyProvider: FacebookPreVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: 'test_id', + displayName: 'test_display_name', + birthday: 'test_date', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Pre Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = facebookPreVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which is eql to user', async () => { + const func = facebookPreVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + facebookPreVerifyProvider = new FacebookPreVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/google-oauth2-signup.provider.unit.ts b/services/authentication-service/src/__tests__/unit/google-oauth2-signup.provider.unit.ts new file mode 100644 index 0000000000..1c5ae6514b --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/google-oauth2-signup.provider.unit.ts @@ -0,0 +1,36 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {GoogleOauth2SignupProvider} from '../../providers'; + +describe('Google Oauth Signup Service', () => { + let googleOauth2SignupProvider: GoogleOauth2SignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + id: 'test_id', + displayName: 'test_display_name', + profileUrl: 'test_profile_url', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + + describe('Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = googleOauth2SignupProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and throws invalid credentials', async () => { + const func = googleOauth2SignupProvider.value(); + const result = await func(profile).catch(err => err.message); + expect(result).to.be.eql('Invalid Credentials'); + }); + }); + + function setUp() { + googleOauth2SignupProvider = new GoogleOauth2SignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/google-post-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/google-post-verify.provider.unit.ts new file mode 100644 index 0000000000..ae32c0da2b --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/google-post-verify.provider.unit.ts @@ -0,0 +1,37 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {GooglePostVerifyProvider} from '../../providers'; + +describe('Google Oauth Post Verify Service', () => { + let googlePostVerifyProvider: GooglePostVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + id: 'test_id', + displayName: 'test_display_name', + profileUrl: 'test_profile_url', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Post Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = googlePostVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which returns the user', async () => { + const func = googlePostVerifyProvider.value(); + const result = await func(profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + googlePostVerifyProvider = new GooglePostVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/google-pre-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/google-pre-verify.provider.unit.ts new file mode 100644 index 0000000000..d007592a5f --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/google-pre-verify.provider.unit.ts @@ -0,0 +1,39 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {GooglePreVerifyProvider} from '../../providers'; + +describe('Google Oauth Pre Verify Service', () => { + let googlePreVerifyProvider: GooglePreVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: 'test_id', + displayName: 'test_display_name', + profileUrl: 'test_profiel_url', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Pre Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = googlePreVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which is eql to user', async () => { + const func = googlePreVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + googlePreVerifyProvider = new GooglePreVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/instagram-oauth2-signup.provider.unit.ts b/services/authentication-service/src/__tests__/unit/instagram-oauth2-signup.provider.unit.ts new file mode 100644 index 0000000000..2e6372ec59 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/instagram-oauth2-signup.provider.unit.ts @@ -0,0 +1,40 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {InstagramOauth2SignupProvider} from '../../providers'; + +describe('Instagram Oauth Signup Service', () => { + let instagramOauth2SignupProvider: InstagramOauth2SignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + id: 'test_id', + displayName: 'test_display_name', + username: 'test_user_name', + name: { + familyName: 'test_family_name', + givenName: 'test_given_name', + }, + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + + describe('Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = instagramOauth2SignupProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and throws invalid credentials', async () => { + const func = instagramOauth2SignupProvider.value(); + const result = await func(profile).catch(err => err.message); + expect(result).to.be.eql('Invalid Credentials'); + }); + }); + + function setUp() { + instagramOauth2SignupProvider = new InstagramOauth2SignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/instagram-post-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/instagram-post-verify.provider.unit.ts new file mode 100644 index 0000000000..4fa449aa42 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/instagram-post-verify.provider.unit.ts @@ -0,0 +1,41 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {InstagramPostVerifyProvider} from '../../providers'; + +describe('Instagram Oauth Post Verify Service', () => { + let instagramPostVerifyProvider: InstagramPostVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + id: 'test_id', + displayName: 'test_display_name', + username: 'test_user_name', + name: { + familyName: 'test_family_name', + givenName: 'test_given_name', + }, + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Post Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = instagramPostVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which is eql to user', async () => { + const func = instagramPostVerifyProvider.value(); + const result = await func(profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + instagramPostVerifyProvider = new InstagramPostVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/instagram-pre-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/instagram-pre-verify.provider.unit.ts new file mode 100644 index 0000000000..5e2591f946 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/instagram-pre-verify.provider.unit.ts @@ -0,0 +1,43 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {InstagramPreVerifyProvider} from '../../providers'; + +describe('Instagram Oauth Pre Verify Service', () => { + let instagramPreVerifyProvider: InstagramPreVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: 'test_id', + displayName: 'test_display_name', + username: 'test_user_name', + name: { + familyName: 'test_family_name', + givenName: 'test_given_name', + }, + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Pre Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = instagramPreVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which is eql to user', async () => { + const func = instagramPreVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + instagramPreVerifyProvider = new InstagramPreVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/jwt-payload.provider.unit.ts b/services/authentication-service/src/__tests__/unit/jwt-payload.provider.unit.ts new file mode 100644 index 0000000000..45ff549eb2 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/jwt-payload.provider.unit.ts @@ -0,0 +1,99 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import { + RoleRepository, + UserLevelPermissionRepository, + UserTenantRepository, + TenantConfigRepository, +} from '../../repositories'; +import sinon from 'sinon'; +import {JwtPayloadProvider} from '../../providers'; +import {UserPermission} from 'loopback4-authorization'; + +describe('JWT Payload Provider', () => { + let roleRepo: StubbedInstanceWithSinonAccessor; + let userLevelPermissionRepo: StubbedInstanceWithSinonAccessor; + let userTenantRepo: StubbedInstanceWithSinonAccessor; + let tenantConfigRepo: StubbedInstanceWithSinonAccessor; + let jwtPayloadProvider: JwtPayloadProvider; + + const authUserData = { + id: 'dummy', + username: 'test_username', + password: 'test_password', + }; + + const authClient = { + clientId: 'test_client_id', + clientSecret: 'test_client_secret', + }; + + const logger = { + log, + info, + warn, + error, + debug, + }; + + const userPermissions = ( + userPrm: UserPermission[], + rolePrm: string[], + ) => ['dummy']; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('jwt payload service', () => { + it('checks if provider returns a function', async () => { + const result = jwtPayloadProvider.value(); + expect(result).to.be.Function(); + }); + + it('returns error if no user exist', async () => { + const func = jwtPayloadProvider.value(); + const result = await func(authUserData, authClient).catch( + err => err.message, + ); + expect(result).to.be.eql('UserDoesNotExist'); + }); + }); + + function log() { + // This is intentional + } + + function info() { + // This is intentional + } + + function warn() { + // This is intentional + } + + function error() { + // This is intentional + } + + function debug() { + // This is intentional + } + + function setUp() { + roleRepo = createStubInstance(RoleRepository); + userLevelPermissionRepo = createStubInstance(UserLevelPermissionRepository); + userTenantRepo = createStubInstance(UserTenantRepository); + tenantConfigRepo = createStubInstance(TenantConfigRepository); + jwtPayloadProvider = new JwtPayloadProvider( + roleRepo, + userLevelPermissionRepo, + userTenantRepo, + tenantConfigRepo, + userPermissions, + logger, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/keycloak-post-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/keycloak-post-verify.provider.unit.ts new file mode 100644 index 0000000000..720f96b7cc --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/keycloak-post-verify.provider.unit.ts @@ -0,0 +1,42 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {KeyCloakPostVerifyProvider} from '../../providers'; + +describe('Keycloak Post Verify Service', () => { + let keycloakPostVerifyProvider: KeyCloakPostVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + keycloakId: 'test_id', + firstName: 'test_first_name', + lastName: 'test_last_name', + username: 'test_user_name', + fullName: 'test_full_name', + email: 'test_email', + avatar: 'test_avatar', + realm: 'test_realm', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Post Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = keycloakPostVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which is eql to user', async () => { + const func = keycloakPostVerifyProvider.value(); + const result = await func(profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + keycloakPostVerifyProvider = new KeyCloakPostVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/keycloak-pre-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/keycloak-pre-verify.provider.unit.ts new file mode 100644 index 0000000000..698d3ad1ef --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/keycloak-pre-verify.provider.unit.ts @@ -0,0 +1,44 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {KeyCloakPreVerifyProvider} from '../../providers'; + +describe('Keycloak Pre Verify Service', () => { + let keycloakPreVerifyProvider: KeyCloakPreVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + keycloakId: 'test_id', + firstName: 'test_first_name', + lastName: 'test_last_name', + username: 'test_user_name', + fullName: 'test_full_name', + email: 'test_email', + avatar: 'test_avatar', + realm: 'test_realm', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + const user = null; + + describe('Pre Verify Service', () => { + it('checks if provider returns a function', async () => { + const result = keycloakPreVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise which is eql to user', async () => { + const func = keycloakPreVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile, user); + expect(result).to.be.eql(user); + }); + }); + + function setUp() { + keycloakPreVerifyProvider = new KeyCloakPreVerifyProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/keycloak-signup.provider.unit.ts b/services/authentication-service/src/__tests__/unit/keycloak-signup.provider.unit.ts new file mode 100644 index 0000000000..9180008fb8 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/keycloak-signup.provider.unit.ts @@ -0,0 +1,41 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {KeyCloakSignupProvider} from '../../providers'; + +describe('Keycloak Oauth Signup Service', () => { + let keycloakOauth2SignupProvider: KeyCloakSignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + const profile = { + keycloakId: 'test_id', + firstName: 'test_first_name', + lastName: 'test_last_name', + username: 'test_user_name', + fullName: 'test_full_name', + email: 'test_email', + avatar: 'test_avatar', + realm: 'test_realm', + _raw: 'test_raw', + _json: 'test_json', + provider: 'test_provider', + }; + + describe('Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = keycloakOauth2SignupProvider.value(); + expect(result).to.be.Function(); + }); + + it('checks if provider function returns a promise and throws invalid credentials', async () => { + const func = keycloakOauth2SignupProvider.value(); + const result = await func(profile).catch(err => err.message); + expect(result).to.be.eql('Invalid Credentials'); + }); + }); + + function setUp() { + keycloakOauth2SignupProvider = new KeyCloakSignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/local-presignup.provider.unit.ts b/services/authentication-service/src/__tests__/unit/local-presignup.provider.unit.ts new file mode 100644 index 0000000000..c97913e3ca --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/local-presignup.provider.unit.ts @@ -0,0 +1,21 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {LocalPreSignupProvider} from '../../providers'; + +describe('Local Pre Signup Service', () => { + let localPresignupProvider: LocalPreSignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('Pre Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = localPresignupProvider.value(); + expect(result).to.be.Function(); + }); + }); + + function setUp() { + localPresignupProvider = new LocalPreSignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/local-signup.provider.unit.ts b/services/authentication-service/src/__tests__/unit/local-signup.provider.unit.ts new file mode 100644 index 0000000000..52475dc399 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/local-signup.provider.unit.ts @@ -0,0 +1,21 @@ +import {expect} from '@loopback/testlab'; +import sinon from 'sinon'; +import {LocalSignupProvider} from '../../providers'; + +describe('Local Signup Service', () => { + let localSignupProvider: LocalSignupProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('Signup Service', () => { + it('checks if provider returns a function', async () => { + const result = localSignupProvider.value(); + expect(result).to.be.Function(); + }); + }); + + function setUp() { + localSignupProvider = new LocalSignupProvider(); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/apple-oauth2-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/apple-oauth2-verify.provider.unit.ts new file mode 100644 index 0000000000..e29aaf5aa3 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/apple-oauth2-verify.provider.unit.ts @@ -0,0 +1,118 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {UserCredentialsRepository, UserRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {AppleOauth2VerifyProvider} from '../../../modules/auth/providers/apple-oauth2-verify.provider'; +import {AppleSignUpFn} from '../../../providers'; +import * as AppleStrategy from 'passport-apple'; +import { + User, + UserCredentials, + UserCredentialsWithRelations, + UserWithRelations, +} from '../../../models'; +import {IAuthUser} from 'loopback4-authentication'; + +describe('Apple Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userCredentialRepo: StubbedInstanceWithSinonAccessor; + let appleVerifyProvider: AppleOauth2VerifyProvider; + + const signupProvider: AppleSignUpFn = async (prof: AppleStrategy.Profile) => + null; + const preVerifyProvider = async ( + accToken: string, + refToken: string, + prof: AppleStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + const postVerifyProvider = async ( + prof: AppleStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: '1', + _json: { + email: 'xyz@gmail.com', + }, + }; + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + }); + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('apple oauth2 verify provider', () => { + it('checks if provider returns a function', async () => { + const result = appleVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return error for no user', async () => { + const func = appleVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + }); + + it('return error if there is no user cred', async () => { + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const func = appleVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + sinon.assert.calledOnce(findOne); + }); + + it('return user after post verification', async () => { + const userCred = new UserCredentials({ + id: '1', + userId: '1', + authProvider: 'apple', + authId: '1', + }); + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const findTwo = userCredentialRepo.stubs.findOne; + findTwo.resolves(userCred as UserCredentialsWithRelations); + const func = appleVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + sinon.assert.calledOnce(findOne); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userCredentialRepo = createStubInstance(UserCredentialsRepository); + appleVerifyProvider = new AppleOauth2VerifyProvider( + userRepo, + userCredentialRepo, + signupProvider, + preVerifyProvider, + postVerifyProvider, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/bearer-token-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/bearer-token-verify.provider.unit.ts new file mode 100644 index 0000000000..ce8b977d9c --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/bearer-token-verify.provider.unit.ts @@ -0,0 +1,67 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {RevokedTokenRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {BearerTokenVerifyProvider} from '../../../modules/auth/providers/bearer-token-verify.provider'; + +describe('Bearer Token Verify Provider', () => { + let revokedTokenRepo: StubbedInstanceWithSinonAccessor; + let bearerTokenVerifyProvider: BearerTokenVerifyProvider; + + const logger = { + log, + info, + warn, + error, + debug, + }; + + const token = 'test_token'; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('Bearer Token Verifier', () => { + it('checks if provider returns a function', async () => { + const result = bearerTokenVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return token expiry for expired token', async () => { + const func = bearerTokenVerifyProvider.value(); + const result = await func(token).catch(err => err.message); + expect(result).to.be.eql('TokenExpired'); + }); + }); + + function log() { + // This is intentional + } + + function info() { + // This is intentional + } + + function warn() { + // This is intentional + } + + function error() { + // This is intentional + } + + function debug() { + // This is intentional + } + + function setUp() { + revokedTokenRepo = createStubInstance(RevokedTokenRepository); + bearerTokenVerifyProvider = new BearerTokenVerifyProvider( + revokedTokenRepo, + logger, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/client-password-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/client-password-verify.provider.unit.ts new file mode 100644 index 0000000000..074017ffbf --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/client-password-verify.provider.unit.ts @@ -0,0 +1,46 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {AuthClientRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {ClientPasswordVerifyProvider} from '../../../modules/auth/providers/client-password-verify.provider'; +import {AuthClient} from '../../../models'; + +describe('Client Password Verify Provider', () => { + let authClientRepo: StubbedInstanceWithSinonAccessor; + let clientPasswordVerifyProvider: ClientPasswordVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('checks for client and returns it', () => { + it('returns the client', async () => { + const clientId = 'dummy'; + const clientSecret = 'dummy'; + const client = new AuthClient({ + id: 1, + clientId: clientId, + clientSecret: clientSecret, + secret: 'dummy', + accessTokenExpiration: 1800, + refreshTokenExpiration: 1800, + authCodeExpiration: 1800, + }); + const findOne = authClientRepo.stubs.findOne; + findOne.resolves(client); + const func = clientPasswordVerifyProvider.value(); + const result = await func(); + expect(result).to.eql(client); + sinon.assert.calledOnce(findOne); + }); + }); + + function setUp() { + authClientRepo = createStubInstance(AuthClientRepository); + clientPasswordVerifyProvider = new ClientPasswordVerifyProvider( + authClientRepo, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/facebook-oauth-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/facebook-oauth-verify.provider.unit.ts new file mode 100644 index 0000000000..3bc84b43ca --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/facebook-oauth-verify.provider.unit.ts @@ -0,0 +1,121 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {UserCredentialsRepository, UserRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {FacebookOauth2VerifyProvider} from '../../../modules/auth/providers/facebook-oauth-verify.provider'; +import {FacebookSignUpFn} from '../../../providers'; +import * as FacebookStrategy from 'passport-facebook'; +import { + User, + UserCredentials, + UserCredentialsWithRelations, + UserWithRelations, +} from '../../../models'; +import {IAuthUser} from 'loopback4-authentication'; + +describe('Facebook Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userCredentialRepo: StubbedInstanceWithSinonAccessor; + let facebookVerifyProvider: FacebookOauth2VerifyProvider; + + const signupProvider: FacebookSignUpFn = async ( + prof: FacebookStrategy.Profile, + ) => null; + const preVerifyProvider = async ( + accToken: string, + refToken: string, + prof: FacebookStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + const postVerifyProvider = async ( + prof: FacebookStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: '1', + _json: { + email: 'xyz@gmail.com', + }, + }; + + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + dob: new Date(), + }); + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('facebook oauth2 verify provider', () => { + it('checks if provider returns a function', async () => { + const result = facebookVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return error promise for no user', async () => { + const func = facebookVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + }); + + it('return error promise if there is no user cred', async () => { + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const func = facebookVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + sinon.assert.calledOnce(findOne); + }); + + it('return user after post verification', async () => { + const userCred = new UserCredentials({ + id: '1', + userId: '1', + authProvider: 'facebook', + authId: '1', + }); + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const findTwo = userCredentialRepo.stubs.findOne; + findTwo.resolves(userCred as UserCredentialsWithRelations); + const func = facebookVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + sinon.assert.calledOnce(findOne); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userCredentialRepo = createStubInstance(UserCredentialsRepository); + facebookVerifyProvider = new FacebookOauth2VerifyProvider( + userRepo, + userCredentialRepo, + signupProvider, + preVerifyProvider, + postVerifyProvider, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/google-oauth2-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/google-oauth2-verify.provider.unit.ts new file mode 100644 index 0000000000..12fb1a07c8 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/google-oauth2-verify.provider.unit.ts @@ -0,0 +1,120 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {UserCredentialsRepository, UserRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {GoogleOauth2VerifyProvider} from '../../../modules/auth/providers/google-oauth2-verify.provider'; +import {GoogleSignUpFn} from '../../../providers'; +import * as GoogleStrategy from 'passport-google-oauth20'; +import { + User, + UserCredentials, + UserCredentialsWithRelations, + UserWithRelations, +} from '../../../models'; +import {IAuthUser} from 'loopback4-authentication'; + +describe('Google Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userCredentialRepo: StubbedInstanceWithSinonAccessor; + let googleVerifyProvider: GoogleOauth2VerifyProvider; + + const signupProvider: GoogleSignUpFn = async (prof: GoogleStrategy.Profile) => + null; + const preVerifyProvider = async ( + accToken: string, + refToken: string, + prof: GoogleStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + const postVerifyProvider = async ( + prof: GoogleStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: '1', + _json: { + email: 'xyz@gmail.com', + }, + }; + + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + dob: new Date(), + }); + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('google oauth2 verify provider', () => { + it('checks if provider returns a function', async () => { + const result = googleVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return error promise for no user', async () => { + const func = googleVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + }); + + it('return error promise if there is no user cred', async () => { + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const func = googleVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + sinon.assert.calledOnce(findOne); + }); + + it('return user after post verification', async () => { + const userCred = new UserCredentials({ + id: '1', + userId: '1', + authProvider: 'google', + authId: '1', + }); + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const findTwo = userCredentialRepo.stubs.findOne; + findTwo.resolves(userCred as UserCredentialsWithRelations); + const func = googleVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + sinon.assert.calledOnce(findOne); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userCredentialRepo = createStubInstance(UserCredentialsRepository); + googleVerifyProvider = new GoogleOauth2VerifyProvider( + userRepo, + userCredentialRepo, + signupProvider, + preVerifyProvider, + postVerifyProvider, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/instagram-oauth2-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/instagram-oauth2-verify.provider.unit.ts new file mode 100644 index 0000000000..8312500ca9 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/instagram-oauth2-verify.provider.unit.ts @@ -0,0 +1,121 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {UserCredentialsRepository, UserRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {InstagramOauth2VerifyProvider} from '../../../modules/auth/providers/instagram-oauth2-verify.provider'; +import {InstagramSignUpFn} from '../../../providers'; +import * as InstagramStrategy from 'passport-instagram'; +import { + User, + UserCredentials, + UserCredentialsWithRelations, + UserWithRelations, +} from '../../../models'; +import {IAuthUser} from 'loopback4-authentication'; + +describe('Instagram Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userCredentialRepo: StubbedInstanceWithSinonAccessor; + let instagramVerifyProvider: InstagramOauth2VerifyProvider; + + const signupProvider: InstagramSignUpFn = async ( + prof: InstagramStrategy.Profile, + ) => null; + const preVerifyProvider = async ( + accToken: string, + refToken: string, + prof: InstagramStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + const postVerifyProvider = async ( + prof: InstagramStrategy.Profile, + usr: IAuthUser | null, + ) => usr; + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + id: '1', + _json: { + email: 'xyz@gmail.com', + }, + }; + + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + dob: new Date(), + }); + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('instagram oauth2 verify provider', () => { + it('checks if provider returns a function', async () => { + const result = instagramVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return error promise for no user', async () => { + const func = instagramVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + }); + + it('return error promise if there is no user cred', async () => { + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const func = instagramVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + sinon.assert.calledOnce(findOne); + }); + + it('return user after post verification', async () => { + const userCred = new UserCredentials({ + id: '1', + userId: '1', + authProvider: 'instagram', + authId: '1', + }); + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const findTwo = userCredentialRepo.stubs.findOne; + findTwo.resolves(userCred as UserCredentialsWithRelations); + const func = instagramVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + sinon.assert.calledOnce(findOne); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userCredentialRepo = createStubInstance(UserCredentialsRepository); + instagramVerifyProvider = new InstagramOauth2VerifyProvider( + userRepo, + userCredentialRepo, + signupProvider, + preVerifyProvider, + postVerifyProvider, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/keycloak-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/keycloak-verify.provider.unit.ts new file mode 100644 index 0000000000..bf5009c503 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/keycloak-verify.provider.unit.ts @@ -0,0 +1,117 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import {UserCredentialsRepository, UserRepository} from '../../../repositories'; +import sinon from 'sinon'; +import {KeycloakVerifyProvider} from '../../../modules/auth/providers/keycloak-verify.provider'; +import {KeyCloakSignUpFn} from '../../../providers'; +import {IAuthUser, KeycloakProfile} from 'loopback4-authentication'; +import { + User, + UserCredentials, + UserCredentialsWithRelations, + UserWithRelations, +} from '../../../models'; + +describe('Keycloak Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userCredentialRepo: StubbedInstanceWithSinonAccessor; + let keycloakVerifyProvider: KeycloakVerifyProvider; + + const signupProvider: KeyCloakSignUpFn = async (prof: KeycloakProfile) => + null; + const preVerifyProvider = async ( + accToken: string, + refToken: string, + prof: KeycloakProfile, + usr: IAuthUser | null, + ) => usr; + const postVerifyProvider = async ( + prof: KeycloakProfile, + usr: IAuthUser | null, + ) => usr; + + const accessToken = 'test_access_token'; + const refreshToken = 'test_refresh_token'; + const profile = { + email: 'test@gmail.com', + keycloakId: '1', + }; + + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + dob: new Date(), + }); + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('keycloak oauth2 verify provider', () => { + it('checks if provider returns a function', async () => { + const result = keycloakVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return error promise for no user', async () => { + const func = keycloakVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + }); + + it('return error promise if there is no user cred', async () => { + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const func = keycloakVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile).catch( + err => err.message, + ); + expect(result).to.be.eql('Invalid Credentials'); + sinon.assert.calledOnce(findOne); + }); + + it('return user after post verification', async () => { + const userCred = new UserCredentials({ + id: '1', + userId: '1', + authProvider: 'keycloak', + authId: '1', + }); + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const findTwo = userCredentialRepo.stubs.findOne; + findTwo.resolves(userCred as UserCredentialsWithRelations); + const func = keycloakVerifyProvider.value(); + const result = await func(accessToken, refreshToken, profile); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + sinon.assert.calledOnce(findOne); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userCredentialRepo = createStubInstance(UserCredentialsRepository); + keycloakVerifyProvider = new KeycloakVerifyProvider( + userRepo, + userCredentialRepo, + signupProvider, + preVerifyProvider, + postVerifyProvider, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/local-password-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/local-password-verify.provider.unit.ts new file mode 100644 index 0000000000..f243ebbed3 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/local-password-verify.provider.unit.ts @@ -0,0 +1,119 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import { + UserTenantRepository, + UserRepository, + OtpRepository, +} from '../../../repositories'; +import sinon from 'sinon'; +import {LocalPasswordVerifyProvider} from '../../../modules/auth/providers/local-password-verify.provider'; +import { + Otp, + User, + UserTenant, + UserTenantWithRelations, + UserWithRelations, +} from '../../../models'; +import {HttpErrors} from '@loopback/rest'; +import {UserStatus} from '@sourceloop/core'; + +describe('Local Password Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userTenantRepo: StubbedInstanceWithSinonAccessor; + let otpRepo: StubbedInstanceWithSinonAccessor; + let localPasswordVerifyProvider: LocalPasswordVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('local password verify provider', () => { + it('checks if provider returns a function', async () => { + const result = localPasswordVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return user if user is present', async () => { + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + dob: new Date(), + }); + const username = 'test_user'; + const password = 'test123!@'; + const findOne = userRepo.stubs.verifyPassword; + findOne.resolves(user as UserWithRelations); + const func = localPasswordVerifyProvider.value(); + const result = await func(username, password); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + }); + + it('returns user if verify password is not working', async () => { + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'test_user', + email: 'xyz@gmail.com', + authClientIds: '{1}', + dob: new Date(), + }); + const otpCreds = new Otp({ + username: 'test_user', + otp: 'test123!@', + }); + const userTenant = new UserTenant({ + id: '1', + userId: '1', + tenantId: '1', + status: UserStatus.ACTIVE, + roleId: '1', + }); + const err = new HttpErrors[400](); + const username = 'test'; + const password = 'test123!@'; + const findOne = userRepo.stubs.verifyPassword; + findOne.throws(err); + const findTwo = otpRepo.stubs.get; + findTwo.resolves(otpCreds); + const findThree = userRepo.stubs.findOne; + findThree.resolves(user as UserWithRelations); + const findFour = userTenantRepo.stubs.findOne; + findFour.resolves(userTenant as UserTenantWithRelations); + const func = localPasswordVerifyProvider.value(); + const result = await func(username, password); + expect(result).to.have.properties( + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + ); + expect(result?.username).to.be.eql('test_user'); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userTenantRepo = createStubInstance(UserTenantRepository); + otpRepo = createStubInstance(OtpRepository); + localPasswordVerifyProvider = new LocalPasswordVerifyProvider( + userRepo, + userTenantRepo, + otpRepo, + ); + } +}); diff --git a/services/authentication-service/src/__tests__/unit/modules-provider/resource-owner-verify.provider.unit.ts b/services/authentication-service/src/__tests__/unit/modules-provider/resource-owner-verify.provider.unit.ts new file mode 100644 index 0000000000..539a1ac598 --- /dev/null +++ b/services/authentication-service/src/__tests__/unit/modules-provider/resource-owner-verify.provider.unit.ts @@ -0,0 +1,147 @@ +import { + StubbedInstanceWithSinonAccessor, + createStubInstance, + expect, +} from '@loopback/testlab'; +import { + UserTenantRepository, + UserRepository, + OtpRepository, + AuthClientRepository, +} from '../../../repositories'; +import sinon from 'sinon'; +import {ResourceOwnerVerifyProvider} from '../../../modules/auth/providers/resource-owner-verify.provider'; +import { + AuthClient, + Otp, + User, + UserTenant, + UserTenantWithRelations, + UserWithRelations, +} from '../../../models'; +import {UserStatus} from '@sourceloop/core'; +import {HttpErrors} from '@loopback/rest'; + +describe('Resource Owner Verify Provider', () => { + let userRepo: StubbedInstanceWithSinonAccessor; + let userTenantRepo: StubbedInstanceWithSinonAccessor; + let otpRepo: StubbedInstanceWithSinonAccessor; + let authClientRepo: StubbedInstanceWithSinonAccessor; + let resourceOwnerVerifyProvider: ResourceOwnerVerifyProvider; + + afterEach(() => sinon.restore()); + beforeEach(setUp); + + describe('resource owner verify provider', () => { + it('checks if provider returns a function', async () => { + const result = resourceOwnerVerifyProvider.value(); + expect(result).to.be.Function(); + }); + + it('return user and client if both are present', async () => { + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'testuser', + email: 'xyz@gmail.com', + authClientIds: '{1}', + defaultTenantId: '1', + dob: new Date(), + }); + const userTenant = new UserTenant({ + id: '1', + userId: '1', + tenantId: '1', + status: UserStatus.ACTIVE, + roleId: '1', + }); + const authClient = new AuthClient({ + id: 1, + clientId: 'web', + clientSecret: 'test', + secret: 'test', + accessTokenExpiration: 1800, + refreshTokenExpiration: 1800, + authCodeExpiration: 1800, + }); + const username = 'testuser'; + const password = 'test123!@'; + const clientId = 'web'; + const clientSecret = 'test'; + const findOne = userRepo.stubs.verifyPassword; + findOne.resolves(user as UserWithRelations); + const findThree = userTenantRepo.stubs.findOne; + findThree.resolves(userTenant as UserTenantWithRelations); + const findFour = authClientRepo.stubs.findOne; + findFour.resolves(authClient); + const func = resourceOwnerVerifyProvider.value(); + const result = await func(clientId, clientSecret, username, password); + expect(result).to.have.properties('client', 'user'); + }); + + it('return user and client if verification for password is not done', async () => { + const user = new User({ + id: '1', + firstName: 'test', + lastName: 'test', + username: 'testuser', + email: 'xyz@gmail.com', + authClientIds: '{1}', + defaultTenantId: '1', + dob: new Date(), + }); + const userTenant = new UserTenant({ + id: '1', + userId: '1', + tenantId: '1', + status: UserStatus.ACTIVE, + roleId: '1', + }); + const authClient = new AuthClient({ + id: 1, + clientId: 'web', + clientSecret: 'test', + secret: 'test', + accessTokenExpiration: 1800, + refreshTokenExpiration: 1800, + authCodeExpiration: 1800, + }); + const otpCreds = new Otp({ + username: 'test_user', + otp: 'test123!@', + }); + const err = new HttpErrors[400](); + const username = 'testuser'; + const password = 'test123!@'; + const clientId = 'web'; + const clientSecret = 'test'; + const findFive = userRepo.stubs.verifyPassword; + findFive.throws(err); + const findOne = userRepo.stubs.findOne; + findOne.resolves(user as UserWithRelations); + const findTwo = otpRepo.stubs.get; + findTwo.resolves(otpCreds); + const findThree = userTenantRepo.stubs.findOne; + findThree.resolves(userTenant as UserTenantWithRelations); + const findFour = authClientRepo.stubs.findOne; + findFour.resolves(authClient); + const func = resourceOwnerVerifyProvider.value(); + const result = await func(clientId, clientSecret, username, password); + expect(result).to.have.properties('client', 'user'); + }); + }); + + function setUp() { + userRepo = createStubInstance(UserRepository); + userTenantRepo = createStubInstance(UserTenantRepository); + otpRepo = createStubInstance(OtpRepository); + authClientRepo = createStubInstance(AuthClientRepository); + resourceOwnerVerifyProvider = new ResourceOwnerVerifyProvider( + userRepo, + userTenantRepo, + authClientRepo, + otpRepo, + ); + } +});