From 7da5b0b3e12980c94a1dbc1cb89742178e087bb5 Mon Sep 17 00:00:00 2001 From: samarpan-b <39519829+samarpan-b@users.noreply.github.com> Date: Sun, 16 Jan 2022 22:58:39 +0530 Subject: [PATCH] fix(authentication-service): security issue with forget password and login api (#466) BREAKING CHANGE: response type of forget password and login api changed gh-0 --- services/authentication-service/.nycrc | 9 + .../authentication-service/.prettierignore | 1 + .../.vscode/launch.json | 39 ++ .../authentication-service/.vscode/tasks.json | 14 + services/authentication-service/README.md | 2 + .../authentication-service/package-lock.json | 611 +++++++++++++++++- services/authentication-service/package.json | 4 +- .../acceptance/login.controller.acceptance.ts | 286 ++++---- .../src/__tests__/acceptance/test-helper.ts | 20 +- .../src/__tests__/fixtures/application.ts | 21 +- .../src/__tests__/fixtures/providers/index.ts | 5 +- .../providers/local-password.provider.ts | 16 - .../oauth-password-verifier.provider.ts | 19 - .../providers/resource-owner.provider.ts | 30 - .../authentication-service/src/component.ts | 38 +- .../controllers/forget-password.controller.ts | 26 +- services/authentication-service/src/index.ts | 5 +- .../authentication-service/src/load-env.ts | 2 +- .../src/models/forget-password-dto.model.ts | 4 +- .../src/models/index.ts | 56 +- .../reset-password-with-client.model.ts | 3 +- .../src/modules/auth/login.controller.ts | 69 +- .../src/providers/types.ts | 3 +- .../src/repositories/user.repository.ts | 14 +- .../src/services/index.ts | 4 + .../src/services/login-helper.service.ts | 65 ++ services/authentication-service/src/types.ts | 8 + services/authentication-service/tsconfig.json | 4 +- 28 files changed, 1046 insertions(+), 332 deletions(-) create mode 100644 services/authentication-service/.nycrc create mode 100644 services/authentication-service/.vscode/launch.json delete mode 100644 services/authentication-service/src/__tests__/fixtures/providers/local-password.provider.ts delete mode 100644 services/authentication-service/src/__tests__/fixtures/providers/oauth-password-verifier.provider.ts delete mode 100644 services/authentication-service/src/__tests__/fixtures/providers/resource-owner.provider.ts create mode 100644 services/authentication-service/src/services/index.ts create mode 100644 services/authentication-service/src/services/login-helper.service.ts diff --git a/services/authentication-service/.nycrc b/services/authentication-service/.nycrc new file mode 100644 index 0000000000..c81cdf54f3 --- /dev/null +++ b/services/authentication-service/.nycrc @@ -0,0 +1,9 @@ +{ + "extends": "@istanbuljs/nyc-config-typescript", + "all": true, + "reporter": [ + "html", + "json-summary", + "text-summary" + ] +} diff --git a/services/authentication-service/.prettierignore b/services/authentication-service/.prettierignore index c6911da9e1..bfc57b89fa 100644 --- a/services/authentication-service/.prettierignore +++ b/services/authentication-service/.prettierignore @@ -1,2 +1,3 @@ dist *.json +coverage \ No newline at end of file diff --git a/services/authentication-service/.vscode/launch.json b/services/authentication-service/.vscode/launch.json new file mode 100644 index 0000000000..4dc10270b6 --- /dev/null +++ b/services/authentication-service/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Service", + "program": "${workspaceFolder}/dist/index.js", + "preLaunchTask": "Build Project" + }, + { + "type": "node", + "request": "launch", + "name": "Mocha Service", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/dist/__tests__" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "preLaunchTask": "Build Project" + }, + { + "type": "node", + "request": "launch", + "name": "Mocha Current File", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": ["--timeout", "999999", "--colors", "${file}"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} diff --git a/services/authentication-service/.vscode/tasks.json b/services/authentication-service/.vscode/tasks.json index 004717d8e8..89622580b2 100644 --- a/services/authentication-service/.vscode/tasks.json +++ b/services/authentication-service/.vscode/tasks.json @@ -14,6 +14,20 @@ }, "problemMatcher": "$tsc-watch" }, + { + "label": "Build Project", + "type": "shell", + "command": "npm", + "args": [ + "run", + "build" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$tsc" + }, { "label": "Build, Test and Lint", "type": "shell", diff --git a/services/authentication-service/README.md b/services/authentication-service/README.md index 95e2f06e88..f9ee71dcf7 100644 --- a/services/authentication-service/README.md +++ b/services/authentication-service/README.md @@ -8,6 +8,8 @@ ![npm (prod) dependency version (scoped)](https://img.shields.io/npm/dependency-version/@sourceloop/authentication-service/@loopback/core) +![check-code-coverage](https://img.shields.io/badge/code--coverage-76.35%25-yellow) + ## Overview A Loopback Microservice for handling authentications. It provides - diff --git a/services/authentication-service/package-lock.json b/services/authentication-service/package-lock.json index e9ce3ecc8a..fcbe8e572d 100644 --- a/services/authentication-service/package-lock.json +++ b/services/authentication-service/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@sourceloop/authentication-service", - "version": "3.0.1", + "version": "3.0.2", "hasInstallScript": true, "dependencies": { "@loopback/boot": "^4.0.0", @@ -38,6 +38,7 @@ "tslib": "^2.3.1" }, "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.2", "@loopback/build": "^8.0.0", "@loopback/eslint-config": "^12.0.0", "@loopback/testlab": "^4.0.0", @@ -54,6 +55,7 @@ "@types/passport-google-oauth20": "^2.0.3", "@types/passport-instagram": "^1.0.0", "@types/sinon": "^10.0.6", + "check-code-coverage": "^1.10.0", "db-migrate": "^0.11.12", "db-migrate-pg": "^1.2.2", "eslint": "^7.32.0", @@ -771,6 +773,21 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "node_modules/@istanbuljs/nyc-config-typescript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", + "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "nyc": ">=15" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1254,6 +1271,18 @@ "fast-deep-equal": "^3.1.3" } }, + "node_modules/@sindresorhus/is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", + "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -1322,6 +1351,18 @@ "node": "12 || 14 || 16 || 17" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@types/base-64": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@types/base-64/-/base-64-0.1.3.tgz", @@ -1343,6 +1384,18 @@ "@types/node": "*" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -1409,6 +1462,12 @@ "@types/node": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, "node_modules/@types/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz", @@ -1434,6 +1493,15 @@ "@types/node": "*" } }, + "node_modules/@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.178", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", @@ -1599,6 +1667,15 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -2022,6 +2099,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2416,6 +2499,48 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, + "node_modules/cacheable-lookup": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-4.3.0.tgz", + "integrity": "sha512-PTUoCeIjj2awloqyVRUL33SjquU1Qv5xuDalYY8WAzd9NnUMUivZnGsOzVsMfg2YuMsWXaXkd/hjnsVoWc/3YA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -2573,6 +2698,41 @@ "node": "*" } }, + "node_modules/check-code-coverage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/check-code-coverage/-/check-code-coverage-1.10.0.tgz", + "integrity": "sha512-GUVIczwuUTMcHlSw6t+dYQj/8cBKnnfL+O7dk6sZOFcKHybOGrY8q2X8LszKVv80aGGGTfT2TXWBtuuDNnTVqw==", + "dev": true, + "dependencies": { + "arg": "4.1.3", + "debug": "4.1.1", + "got": "11.1.0", + "lodash": "4.17.15" + }, + "bin": { + "check-coverage": "bin/check-coverage.js", + "check-total": "bin/check-total.js", + "only-covered": "bin/only-covered.js", + "set-gh-status": "bin/set-gh-status.js", + "update-badge": "bin/update-badge.js" + } + }, + "node_modules/check-code-coverage/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/check-code-coverage/node_modules/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, "node_modules/chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -2649,6 +2809,15 @@ "node": ">=0.8" } }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", @@ -3112,6 +3281,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-response": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", + "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "dev": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3147,6 +3340,15 @@ "clone": "^1.0.2" } }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4474,6 +4676,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/got": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.1.0.tgz", + "integrity": "sha512-9lZDzFe43s6HH60tSurUk04kEtssfLiIfMiY5lSE0+vVaDCmT7+0xYzzlHY5VArSiz41mQQC38LefW2KoE9erw==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^2.1.0", + "@szmarczak/http-timer": "^4.0.0", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^4.1.1", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "get-stream": "^5.0.0", + "http2-wrapper": "^1.0.0-beta.4.4", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -4741,6 +4984,12 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, "node_modules/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -4784,6 +5033,19 @@ "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -5526,6 +5788,12 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-merge-patch": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-merge-patch/-/json-merge-patch-1.0.2.tgz", @@ -5700,6 +5968,15 @@ "node": ">= 0.6" } }, + "node_modules/keyv": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.5.tgz", + "integrity": "sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -6119,6 +6396,15 @@ "tslib": "^2.0.3" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6381,6 +6667,15 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -7025,6 +7320,18 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -7408,6 +7715,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -8209,6 +8525,18 @@ } ] }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", @@ -8528,6 +8856,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -8537,6 +8871,15 @@ "node": ">=8" } }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, "node_modules/retry": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.6.0.tgz", @@ -11302,6 +11645,15 @@ } } }, + "@istanbuljs/nyc-config-typescript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", + "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2" + } + }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -11668,6 +12020,12 @@ "fast-deep-equal": "^3.1.3" } }, + "@sindresorhus/is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", + "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "dev": true + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -11733,6 +12091,15 @@ "winston": "^3.3.3" } }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, "@types/base-64": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@types/base-64/-/base-64-0.1.3.tgz", @@ -11754,6 +12121,18 @@ "@types/node": "*" } }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -11820,6 +12199,12 @@ "@types/node": "*" } }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, "@types/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz", @@ -11845,6 +12230,15 @@ "@types/node": "*" } }, + "@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/lodash": { "version": "4.14.178", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", @@ -12008,6 +12402,15 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -12298,6 +12701,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -12605,6 +13014,38 @@ } } }, + "cacheable-lookup": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-4.3.0.tgz", + "integrity": "sha512-PTUoCeIjj2awloqyVRUL33SjquU1Qv5xuDalYY8WAzd9NnUMUivZnGsOzVsMfg2YuMsWXaXkd/hjnsVoWc/3YA==", + "dev": true + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -12734,6 +13175,35 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, + "check-code-coverage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/check-code-coverage/-/check-code-coverage-1.10.0.tgz", + "integrity": "sha512-GUVIczwuUTMcHlSw6t+dYQj/8cBKnnfL+O7dk6sZOFcKHybOGrY8q2X8LszKVv80aGGGTfT2TXWBtuuDNnTVqw==", + "dev": true, + "requires": { + "arg": "4.1.3", + "debug": "4.1.1", + "got": "11.1.0", + "lodash": "4.17.15" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -12795,6 +13265,15 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, "cluster-key-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", @@ -13165,6 +13644,23 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==" }, + "decompress-response": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", + "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + }, + "dependencies": { + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + } + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -13194,6 +13690,12 @@ "clone": "^1.0.2" } }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -14215,6 +14717,37 @@ "slash": "^3.0.0" } }, + "got": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.1.0.tgz", + "integrity": "sha512-9lZDzFe43s6HH60tSurUk04kEtssfLiIfMiY5lSE0+vVaDCmT7+0xYzzlHY5VArSiz41mQQC38LefW2KoE9erw==", + "dev": true, + "requires": { + "@sindresorhus/is": "^2.1.0", + "@szmarczak/http-timer": "^4.0.0", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^4.1.1", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "get-stream": "^5.0.0", + "http2-wrapper": "^1.0.0-beta.4.4", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -14426,6 +14959,12 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, "http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -14459,6 +14998,16 @@ "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -15009,6 +15558,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-merge-patch": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-merge-patch/-/json-merge-patch-1.0.2.tgz", @@ -15158,6 +15713,15 @@ "tsscmp": "1.0.6" } }, + "keyv": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.5.tgz", + "integrity": "sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -15506,6 +16070,12 @@ "tslib": "^2.0.3" } }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -15719,6 +16289,12 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -16204,6 +16780,12 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -16504,6 +17086,12 @@ "mem": "^5.0.0" } }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -17095,6 +17683,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, "ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", @@ -17348,12 +17942,27 @@ "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, "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 }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, "retry": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.6.0.tgz", diff --git a/services/authentication-service/package.json b/services/authentication-service/package.json index 2f929c61b7..4d0b6ae8d7 100644 --- a/services/authentication-service/package.json +++ b/services/authentication-service/package.json @@ -26,7 +26,7 @@ "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": "lb-mocha --allow-console-logs \"dist/__tests__\"", - "coverage": "nyc npm run test", + "coverage": "nyc npm run test && npx update-badge", "posttest": "npm run lint", "prepublishOnly": "npm run test", "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest", @@ -83,6 +83,7 @@ "tslib": "^2.3.1" }, "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.2", "@loopback/build": "^8.0.0", "@loopback/eslint-config": "^12.0.0", "@loopback/testlab": "^4.0.0", @@ -99,6 +100,7 @@ "@types/passport-google-oauth20": "^2.0.3", "@types/passport-instagram": "^1.0.0", "@types/sinon": "^10.0.6", + "check-code-coverage": "^1.10.0", "db-migrate": "^0.11.12", "db-migrate-pg": "^1.2.2", "eslint": "^7.32.0", diff --git a/services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts b/services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts index 629e468542..2668133479 100644 --- a/services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts +++ b/services/authentication-service/src/__tests__/acceptance/login.controller.acceptance.ts @@ -1,6 +1,7 @@ 'use strict'; import {Client, createRestAppClient, expect} from '@loopback/testlab'; -import {AuthenticateErrorKeys, RoleTypes} from '@sourceloop/core'; +import {AuthenticateErrorKeys, AuthProvider, RoleTypes} from '@sourceloop/core'; +import {AuthErrorKeys} from 'loopback4-authentication'; import { AuthClientRepository, RefreshTokenRepository, @@ -10,10 +11,10 @@ import { UserRepository, UserTenantRepository, } from '../../repositories'; -import {setupApplication} from './test-helper'; import {TestingApplication} from '../fixtures/application'; -import {TestHelperService} from '../fixtures/services'; import {TestHelperKey} from '../fixtures/keys'; +import {TestHelperService} from '../fixtures/services'; +import {setupApplication} from './test-helper'; describe('Authentication microservice', () => { let app: TestingApplication; @@ -52,12 +53,12 @@ describe('Authentication microservice', () => { delete process.env.JWT_SECRET; helper.reset(); }); - it('gives status 422 for login request with no client credentials', async () => { + it('should give status 422 for login request with no client credentials', async () => { const reqData = {}; const response = await client.post(`/auth/login`).send(reqData).expect(422); expect(response).to.have.property('error'); }); - it('gives status 422 for login request with no user credentials', async () => { + it('should give status 422 for login request with no user credentials', async () => { const reqData = { clientId: 'web', clientSecret: 'blah', @@ -65,7 +66,7 @@ describe('Authentication microservice', () => { const response = await client.post(`/auth/login`).send(reqData).expect(422); expect(response).to.have.property('error'); }); - it('gives status 401 for login request with wrong client credentials', async () => { + it('should give status 401 for login request with wrong client credentials', async () => { const reqData = { // eslint-disable-next-line client_id: 'web1', // eslint-disable-next-line @@ -76,7 +77,7 @@ describe('Authentication microservice', () => { const response = await client.post(`/auth/login`).send(reqData).expect(401); expect(response).to.have.property('error'); }); - it('gives status 401 for login request with wrong user credentials', async () => { + it('should give status 401 for login request with wrong user credentials', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -87,7 +88,21 @@ describe('Authentication microservice', () => { const response = await client.post(`/auth/login`).send(reqData).expect(401); expect(response).to.have.property('error'); }); - it('gives status 200 for login request', async () => { + it('should give status 401 for login request with user credentials not belonging to client', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'mobile', // eslint-disable-next-line + client_secret: 'test', + username: 'test_user', + password: 'temp123!@', + }; + const response = await client.post(`/auth/login`).send(reqData).expect(401); + expect(response).to.have.property('error'); + expect(response.body.error.message).to.be.equal( + AuthErrorKeys.ClientInvalid, + ); + }); + it('should give status 200 for login request', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -98,6 +113,7 @@ describe('Authentication microservice', () => { process.env.JWT_ISSUER = 'test'; await client.post(`/auth/login`).send(reqData).expect(200); }); + it('should return code in response', async () => { const reqData = { // eslint-disable-next-line @@ -113,6 +129,7 @@ describe('Authentication microservice', () => { .expect(200); expect(reqForCode.body).to.have.property('code'); }); + it('should return refresh token, access token, expires in response', async () => { const reqData = { // eslint-disable-next-line @@ -141,7 +158,8 @@ describe('Authentication microservice', () => { 'expires', ]); }); - it('should return 401 for incorrect moment creation', async () => { + + it('should return refresh token and access token for token refresh request', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -151,19 +169,31 @@ describe('Authentication microservice', () => { }; process.env.JWT_ISSUER = 'test'; process.env.JWT_SECRET = 'test'; - await client - .post(`/auth/login-token`) + 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(reqData) - .expect(401); + .send({ + clientId: 'web', + code: reqForCode.body.code, + }); + const response = await client + .post(`/auth/token-refresh`) + .send({refreshToken: reqForToken.body.refreshToken}) + .set('Authorization', `Bearer ${reqForToken.body.accessToken}`); + expect(response.body).to.have.properties(['accessToken', 'refreshToken']); }); - it('should change password successfully for internal user', async () => { + + it('should throw error when login for external user', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line client_secret: 'test', - username: 'test_user', + username: 'test_teacher', password: 'temp123!@', }; process.env.JWT_ISSUER = 'test'; @@ -171,28 +201,19 @@ describe('Authentication microservice', () => { 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, - }); - await client - .patch(`/auth/change-password`) - .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) - .send({ - username: 'test_user', - password: 'new_test_password', - refreshToken: reqForToken.body.refreshToken, - }) - .expect(200); + .expect(401); + + expect(reqForCode.body.error.message.message).to.equal( + AuthErrorKeys.InvalidCredentials, + ); }); - it('should throw error when changing password for external user', async () => { + it('should change password successfully for internal user', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line client_secret: 'test', - username: 'test_teacher', + username: 'test_user', password: 'temp123!@', }; process.env.JWT_ISSUER = 'test'; @@ -205,22 +226,18 @@ describe('Authentication microservice', () => { clientId: 'web', code: reqForCode.body.code, }); - const response = await client + await client .patch(`/auth/change-password`) .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) .send({ - username: 'test_teacher', + username: 'test_user', password: 'new_test_password', refreshToken: reqForToken.body.refreshToken, }) - .expect(401); - - expect(response.body.error.message).to.equal( - AuthenticateErrorKeys.PasswordCannotBeChanged, - ); + .expect(200); }); - it('should return refresh token and access token for token refresh request', async () => { + it('should return refresh token and access token for token refresh request with new password', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -248,6 +265,36 @@ describe('Authentication microservice', () => { .set('Authorization', `Bearer ${reqForToken.body.accessToken}`); expect(response.body).to.have.properties(['accessToken', 'refreshToken']); }); + + it('should revert to previous password successfully for internal user', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web', // eslint-disable-next-line + client_secret: 'test', + 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, + }); + await client + .patch(`/auth/change-password`) + .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) + .send({ + username: 'test_user', + password: 'temp123!@', + refreshToken: reqForToken.body.refreshToken, + }) + .expect(200); + }); + it('should return 401 for token refresh request when Authentication token invalid', async () => { const reqData = { // eslint-disable-next-line @@ -303,6 +350,24 @@ describe('Authentication microservice', () => { .send({refreshToken: reqForToken.body.refreshToken}) .expect(401); }); + it('should throw error if user does not belong to client id in forgot password request', async () => { + const reqData = { + // eslint-disable-next-line + client_id: 'web1', // 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(401); + expect(response.body.error.message.message).to.be.equal( + AuthErrorKeys.ClientInvalid, + ); + }); + it('should send forgot password request successfully', async () => { const reqData = { // eslint-disable-next-line @@ -336,7 +401,7 @@ describe('Authentication microservice', () => { ); }); - it('should return error user does not exist', async () => { + it('should return error user does not exist for invalid user in forget password', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -346,10 +411,12 @@ describe('Authentication microservice', () => { const response = await client .post(`/auth/forget-password`) .send(reqData) - .expect(404); - expect(response.body).to.have.properties('error'); + .expect(401); + expect(response.body.error.message).to.be.equal( + AuthErrorKeys.InvalidCredentials, + ); }); - it('should verify token successfully', async () => { + it('should verify reset password token successfully', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -362,13 +429,12 @@ describe('Authentication microservice', () => { const token = helper.get('TOKEN'); expect(token).to.be.String(); expect(token).to.not.be.equal(''); - const responseToken = await client + await client .get(`/auth/verify-reset-password-link?token=${token}`) .send() .expect(200); - expect(responseToken.body).to.have.properties('success'); }); - it('should give token missing error', async () => { + it('should give token missing error when no token passed in verify reset password', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -384,7 +450,7 @@ describe('Authentication microservice', () => { .expect(400); expect(responseToken.body).to.have.properties('error'); }); - it('return error for token missing', async () => { + it('should return error for token missing when no token passed in reset password', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -402,7 +468,7 @@ describe('Authentication microservice', () => { }; await client.patch(`/auth/reset-password`).send(request).expect(422); }); - it('return error for password missing', async () => { + it('should return error for password missing when new password not sent in reset password', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -411,19 +477,17 @@ describe('Authentication microservice', () => { }; process.env.JWT_ISSUER = 'test'; process.env.JWT_SECRET = 'test'; - const response = await client - .post(`/auth/forget-password`) - .send(reqData) - .expect(204); + await client.post(`/auth/forget-password`).send(reqData).expect(204); + const token = helper.get('TOKEN'); const request = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line client_secret: 'test', - token: response.body.code, + token, }; await client.patch(`/auth/reset-password`).send(request).expect(422); }); - it('should reset password', async () => { + it('should throw error when reset password to previous password', async () => { const reqData = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line @@ -438,80 +502,40 @@ describe('Authentication microservice', () => { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line client_secret: 'test', - token: token, - password: 'test123', + token, + password: 'temp123!@', }; - await client.patch(`/auth/reset-password`).send(request).expect(204); + await client.patch(`/auth/reset-password`).send(request).expect(401); }); - it('should return true on logout', async () => { + + it('should reset password successfully', 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(`/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 = { + await client.post(`/auth/forget-password`).send(reqData).expect(204); + const token = helper.get('TOKEN'); + const request = { // eslint-disable-next-line client_id: 'web', // eslint-disable-next-line client_secret: 'test', - username: 'test_user', - password: 'test123', + token, + 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: 'aaaa', - }) - .expect(401); + await client.patch(`/auth/reset-password`).send(request).expect(204); }); - it('should return true on keycloak logout', async () => { + + it('should return true on logout', async () => { const reqData = { // eslint-disable-next-line 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'; @@ -529,20 +553,20 @@ describe('Authentication microservice', () => { }) .expect(200); await client - .post(`/keycloak/logout`) + .post(`/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 () => { + 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', + password: 'test123#@', }; process.env.JWT_ISSUER = 'test'; process.env.JWT_SECRET = 'test'; @@ -560,13 +584,14 @@ describe('Authentication microservice', () => { }) .expect(200); await client - .post(`/keycloak/logout`) + .post(`/logout`) .set('Authorization', `Bearer ${reqForToken.body.accessToken}`) .send({ refreshToken: 'aaaa', }) .expect(401); }); + async function givenUserRepository() { userRepo = await app.getRepository(UserRepository); } @@ -648,16 +673,17 @@ describe('Authentication microservice', () => { ], }, ]); + process.env.USER_TEMP_PASSWORD = 'temp123!@'; + await userRepo.create({ + id: '1', + firstName: 'Test', + lastName: 'User', + username: 'test_user', + dob: '1996-11-05', + authClientIds: `{1}`, + email: 'xyz@gmail.com', + }); await userRepo.createAll([ - { - id: '1', - firstName: 'Test', - lastName: 'User', - username: 'test_user', - dob: '1996-11-05', - authClientIds: `{1}`, - email: 'xyz@gmail.com', - }, { id: '2', firstName: 'Test', @@ -690,17 +716,21 @@ describe('Authentication microservice', () => { authCodeExpiration: 180, secret: 'poiuytrewq', }); - await userCredentialsRepository.create({ - id: '1', - userId: '1', - authProvider: 'internal', - password: 'temp123!@', + await authClientRepository.create({ + id: 2, + clientId: 'mobile', + clientSecret: 'test', + redirectUrl: 'http://localhost:4200/login/success', + accessTokenExpiration: 900, + refreshTokenExpiration: 86400, + authCodeExpiration: 180, + secret: 'poiuytrewq', }); + await userCredentialsRepository.create({ id: '2', userId: '2', - authProvider: 'external', - password: 'temp123!@', + authProvider: AuthProvider.KEYCLOAK, }); } }); diff --git a/services/authentication-service/src/__tests__/acceptance/test-helper.ts b/services/authentication-service/src/__tests__/acceptance/test-helper.ts index 326f4e6db5..881aedfac4 100644 --- a/services/authentication-service/src/__tests__/acceptance/test-helper.ts +++ b/services/authentication-service/src/__tests__/acceptance/test-helper.ts @@ -1,14 +1,16 @@ import { + Client, createRestAppClient, givenHttpServerConfig, - Client, } from '@loopback/testlab'; -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'; +import { + ClientPasswordVerifyProvider, + LocalPasswordVerifyProvider, + ResourceOwnerVerifyProvider, +} from '../../modules/auth'; +import {AuthCacheSourceName, AuthDbSourceName} from '../../types'; +import {TestingApplication} from '../fixtures/application'; export async function setupApplication(): Promise { const restConfig = givenHttpServerConfig({}); @@ -37,15 +39,15 @@ export async function setupApplication(): Promise { app .bind(Strategies.Passport.LOCAL_PASSWORD_VERIFIER) - .toProvider(TestPasswordVerifyProvider); + .toProvider(LocalPasswordVerifyProvider); app .bind(Strategies.Passport.OAUTH2_CLIENT_PASSWORD_VERIFIER) - .toProvider(TestOauthPasswordVerifyProvider); + .toProvider(ClientPasswordVerifyProvider); app .bind(Strategies.Passport.RESOURCE_OWNER_PASSWORD_VERIFIER) - .toProvider(TestResourceOwnerVerifyProvider); + .toProvider(ResourceOwnerVerifyProvider); await app.start(); diff --git a/services/authentication-service/src/__tests__/fixtures/application.ts b/services/authentication-service/src/__tests__/fixtures/application.ts index e31b3e4d20..313ad11c43 100644 --- a/services/authentication-service/src/__tests__/fixtures/application.ts +++ b/services/authentication-service/src/__tests__/fixtures/application.ts @@ -7,20 +7,16 @@ import { RestExplorerComponent, } from '@loopback/rest-explorer'; import {ServiceMixin} from '@loopback/service-proxy'; -import {AuthenticationComponent, Strategies} from 'loopback4-authentication'; -import { - AuthorizationBindings, - AuthorizationComponent, -} from 'loopback4-authorization'; +import {Strategies} from 'loopback4-authentication'; import * as path from 'path'; -import {BearerTokenVerifyProvider} from './providers/bearer-token-verifier.provider'; import {AuthenticationServiceComponent} from '../../component'; +import {AuthServiceBindings} from '../../keys'; +import {SignUpBindings} from '../../providers'; import { TestForgotPasswordTokenHandlerProvider, TestSignupTokenHandlerProvider, } from './providers'; -import {AuthServiceBindings} from '../../keys'; -import {SignUpBindings} from '../../providers'; +import {BearerTokenVerifyProvider} from './providers/bearer-token-verifier.provider'; import {TestHelperService} from './services'; export {ApplicationConfig}; @@ -40,8 +36,7 @@ export class TestingApplication extends BootMixin( }); 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, @@ -54,12 +49,6 @@ export class TestingApplication extends BootMixin( ); this.service(TestHelperService, {defaultScope: BindingScope.SINGLETON}); - // Add authorization component - this.bind(AuthorizationBindings.CONFIG).to({ - allowAlwaysPaths: ['/explorer'], - }); - this.component(AuthorizationComponent); - this.projectRoot = __dirname; // Customize @loopback/boot Booter Conventions here this.bootOptions = { diff --git a/services/authentication-service/src/__tests__/fixtures/providers/index.ts b/services/authentication-service/src/__tests__/fixtures/providers/index.ts index 2e133a79fc..bae807a26a 100644 --- a/services/authentication-service/src/__tests__/fixtures/providers/index.ts +++ b/services/authentication-service/src/__tests__/fixtures/providers/index.ts @@ -1,6 +1,3 @@ export * from './bearer-token-verifier.provider'; -export * from './local-password.provider'; -export * from './oauth-password-verifier.provider'; -export * from './resource-owner.provider'; -export * from './signup-handler.provider'; export * from './forgot-password-handler.provider'; +export * from './signup-handler.provider'; 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 deleted file mode 100644 index 21233cc6ad..0000000000 --- a/services/authentication-service/src/__tests__/fixtures/providers/local-password.provider.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Provider} from '@loopback/core'; -import {VerifyFunction} from 'loopback4-authentication'; - -export class TestPasswordVerifyProvider - implements Provider -{ - value(): VerifyFunction.LocalPasswordFn { - return async (username: string, password: string) => { - if (username === 'test_user') { - return {id: 1, username: 'test_user', password: 'temp123!@'}; - } else { - return {id: 2, username: 'test_teacher', 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 deleted file mode 100644 index 1dfabc3964..0000000000 --- a/services/authentication-service/src/__tests__/fixtures/providers/oauth-password-verifier.provider.ts +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index a4782e9efc..0000000000 --- a/services/authentication-service/src/__tests__/fixtures/providers/resource-owner.provider.ts +++ /dev/null @@ -1,30 +0,0 @@ -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/component.ts b/services/authentication-service/src/component.ts index 66b3224a0b..691586f226 100644 --- a/services/authentication-service/src/component.ts +++ b/services/authentication-service/src/component.ts @@ -14,51 +14,51 @@ import { AuthorizationBindings, AuthorizationComponent, } from 'loopback4-authorization'; - import {controllers} from './controllers'; import {AuthServiceBindings} from './keys'; import {models} from './models'; import { + AppleOauth2VerifyProvider, BearerTokenVerifyProvider, ClientPasswordVerifyProvider, + FacebookOauth2VerifyProvider, GoogleOauth2VerifyProvider, LocalPasswordVerifyProvider, ResourceOwnerVerifyProvider, - AppleOauth2VerifyProvider, - FacebookOauth2VerifyProvider, } from './modules/auth'; -import {repositories} from './repositories'; -import {MySequence} from './sequence'; -import {IAuthServiceConfig} from './types'; import {KeycloakVerifyProvider} from './modules/auth/providers/keycloak-verify.provider'; import { + AppleOauth2SignupProvider, + ApplePostVerifyProvider, + ApplePreVerifyProvider, AuthCodeBindings, CodeWriterProvider, + FacebookOauth2SignupProvider, + FacebookPostVerifyProvider, + FacebookPreVerifyProvider, + ForgotPasswordProvider, GoogleOauth2SignupProvider, GooglePostVerifyProvider, GooglePreVerifyProvider, InstagramOauth2SignupProvider, InstagramPostVerifyProvider, InstagramPreVerifyProvider, - AppleOauth2SignupProvider, - ApplePostVerifyProvider, - ApplePreVerifyProvider, - FacebookOauth2SignupProvider, - FacebookPostVerifyProvider, - FacebookPreVerifyProvider, JwtPayloadProvider, KeyCloakPostVerifyProvider, KeyCloakPreVerifyProvider, SignUpBindings, - VerifyBindings, SignupTokenHandlerProvider, - ForgotPasswordProvider, + VerifyBindings, } from './providers'; -import {KeyCloakSignupProvider} from './providers/keycloak-signup.provider'; -import {LocalSignupProvider} from './providers/local-signup.provider'; -import {LocalPreSignupProvider} from './providers/local-presignup.provider'; import {SignupBearerVerifyProvider} from './providers/bearer-verify.provider'; import {OauthCodeReaderProvider} from './providers/code-reader.provider'; +import {KeyCloakSignupProvider} from './providers/keycloak-signup.provider'; +import {LocalPreSignupProvider} from './providers/local-presignup.provider'; +import {LocalSignupProvider} from './providers/local-signup.provider'; +import {repositories} from './repositories'; +import {MySequence} from './sequence'; +import {LoginHelperService} from './services'; +import {IAuthServiceConfig} from './types'; export class AuthenticationServiceComponent implements Component { constructor( @@ -99,7 +99,9 @@ export class AuthenticationServiceComponent implements Component { } this.repositories = repositories; - + this.application + .bind('services.LoginHelperService') + .toClass(LoginHelperService); this.models = models; this.controllers = controllers; diff --git a/services/authentication-service/src/controllers/forget-password.controller.ts b/services/authentication-service/src/controllers/forget-password.controller.ts index 524078e760..061115c3c1 100644 --- a/services/authentication-service/src/controllers/forget-password.controller.ts +++ b/services/authentication-service/src/controllers/forget-password.controller.ts @@ -3,13 +3,14 @@ import {repository} from '@loopback/repository'; import {get, HttpErrors, param, patch, post, requestBody} from '@loopback/rest'; import { AuthenticateErrorKeys, + AuthProvider, ErrorCodes, + OPERATION_SECURITY_SPEC, STATUS_CODE, SuccessResponse, - OPERATION_SECURITY_SPEC, - AuthProvider, } from '@sourceloop/core'; import * as jwt from 'jsonwebtoken'; +import {omit} from 'lodash'; import { authenticateClient, AuthenticationBindings, @@ -18,24 +19,25 @@ import { STRATEGY, } from 'loopback4-authentication'; import {authorize} from 'loopback4-authorization'; -import {ForgotPasswordHandlerFn} from '../providers'; import {AuthServiceBindings} from '../keys'; - import { AuthClient, ForgetPasswordDto, ResetPasswordWithClient, User, } from '../models'; +import {ForgotPasswordHandlerFn} from '../providers'; import {RevokedTokenRepository, UserRepository} from '../repositories'; -import {omit} from 'lodash'; +import {LoginHelperService} from '../services'; export class ForgetPasswordController { constructor( @repository(UserRepository) - public userRepo: UserRepository, + private readonly userRepo: UserRepository, @repository(RevokedTokenRepository) - public revokedTokensRepo: RevokedTokenRepository, + private readonly revokedTokensRepo: RevokedTokenRepository, + @inject('services.LoginHelperService') + private readonly loginHelperService: LoginHelperService, ) {} @authenticateClient(STRATEGY.CLIENT_PASSWORD) @@ -63,6 +65,7 @@ export class ForgetPasswordController { }, include: ['credentials'], }); + await this.loginHelperService.verifyClientUserLogin(req, client, user); if (!user || !user.id) { throw new HttpErrors.NotFound('User not found !'); } @@ -170,7 +173,7 @@ export class ForgetPasswordController { throw new HttpErrors.BadRequest(AuthenticateErrorKeys.PasswordInvalid); } - let payload; + let payload: ClientAuthCode; const isRevoked = await this.revokedTokensRepo.get(req.token); if (isRevoked?.token) { @@ -189,6 +192,13 @@ export class ForgetPasswordController { if (!payload.clientId || !payload.user) { throw new HttpErrors.Unauthorized(AuthErrorKeys.TokenInvalid); } + const user = await this.userRepo.findOne({ + where: { + username: payload.user.username, + }, + }); + await this.loginHelperService.verifyClientUserLogin(req, client, user); + await this.userRepo.changePassword(payload.user.username, req.password); await this.revokedTokensRepo.set(req.token, { diff --git a/services/authentication-service/src/index.ts b/services/authentication-service/src/index.ts index 6db7a18107..0437c3c299 100644 --- a/services/authentication-service/src/index.ts +++ b/services/authentication-service/src/index.ts @@ -1,7 +1,8 @@ import './load-env'; export * from './component'; export * from './keys'; -export * from './types'; -export * from './providers'; export * from './models'; +export * from './providers'; export * from './repositories'; +export * from './services'; +export * from './types'; diff --git a/services/authentication-service/src/load-env.ts b/services/authentication-service/src/load-env.ts index 3e3116d5b9..531fa659aa 100644 --- a/services/authentication-service/src/load-env.ts +++ b/services/authentication-service/src/load-env.ts @@ -4,6 +4,6 @@ import * as dotenvExt from 'dotenv-extended'; dotenv.config(); dotenvExt.load({ schema: '.env.example', - errorOnMissing: true, + errorOnMissing: false, includeProcessEnv: true, }); diff --git a/services/authentication-service/src/models/forget-password-dto.model.ts b/services/authentication-service/src/models/forget-password-dto.model.ts index b40512dc96..346e95cd29 100644 --- a/services/authentication-service/src/models/forget-password-dto.model.ts +++ b/services/authentication-service/src/models/forget-password-dto.model.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ - import {Model, model, property} from '@loopback/repository'; +import {IAuthClientDTO} from '..'; @model() -export class ForgetPasswordDto extends Model { +export class ForgetPasswordDto extends Model implements IAuthClientDTO { @property({ type: 'string', required: true, diff --git a/services/authentication-service/src/models/index.ts b/services/authentication-service/src/models/index.ts index d2d4409e64..fb34d4def3 100644 --- a/services/authentication-service/src/models/index.ts +++ b/services/authentication-service/src/models/index.ts @@ -1,47 +1,47 @@ -import {User} from './user.model'; -import {Tenant} from './tenant.model'; -import {Role} from './role.model'; import {AuthClient} from './auth-client.model'; -import {UserLevelPermission} from './user-level-permission.model'; -import {RefreshToken} from './refresh-token.model'; -import {RevokedToken} from './revoked-token.model'; -import {UserCredentials} from './user-credentials.model'; +import {ForgetPasswordDto} from './forget-password-dto.model'; +import {ForgetPasswordResponseDto} from './forget-password-response-dto.model'; +import {LocalUserProfileDto} from './local-user-profile'; import {Otp} from './otp.model'; -import {TenantConfig} from './tenant-config.model'; -import {UserTenant} from './user-tenant.model'; import {RefreshTokenRequest} from './refresh-token-request.model'; -import {ForgetPasswordResponseDto} from './forget-password-response-dto.model'; -import {ForgetPasswordDto} from './forget-password-dto.model'; +import {RefreshToken} from './refresh-token.model'; import {ResetPasswordWithClient} from './reset-password-with-client.model'; -import {UserLevelResource} from './user-level-resource.model'; +import {RevokedToken} from './revoked-token.model'; +import {Role} from './role.model'; import {SignupRequestDto} from './signup-request-dto.model'; import {SignupRequestResponseDto} from './signup-request-response-dto.model'; import {SignupRequest} from './signup-request.model'; import {SignupWithTokenReponseDto} from './signup-with-token-response-dto.model'; -import {LocalUserProfileDto} from './local-user-profile'; +import {TenantConfig} from './tenant-config.model'; +import {Tenant} from './tenant.model'; +import {UserCredentials} from './user-credentials.model'; +import {UserLevelPermission} from './user-level-permission.model'; +import {UserLevelResource} from './user-level-resource.model'; +import {UserTenant} from './user-tenant.model'; +import {User} from './user.model'; -export * from './user.model'; -export * from './tenant.model'; -export * from './role.model'; -export * from './user-level-resource.model'; -export * from './user-level-permission.model'; +export * from './'; export * from './auth-client.model'; -export * from './refresh-token.model'; -export * from './revoked-token.model'; -export * from './user-credentials.model'; +export * from './forget-password-dto.model'; +export * from './forget-password-response-dto.model'; +export * from './local-user-profile'; export * from './otp.model'; -export * from './tenant-config.model'; -export * from './user-tenant.model'; export * from './refresh-token-request.model'; -export * from './forget-password-response-dto.model'; -export * from './forget-password-dto.model'; +export * from './refresh-token.model'; export * from './reset-password-with-client.model'; +export * from './revoked-token.model'; +export * from './role.model'; +export * from './signup-request-dto.model'; export * from './signup-request-response-dto.model'; export * from './signup-request.model'; export * from './signup-with-token-response-dto.model'; -export * from './signup-request-dto.model'; -export * from './local-user-profile'; -export * from './'; +export * from './tenant-config.model'; +export * from './tenant.model'; +export * from './user-credentials.model'; +export * from './user-level-permission.model'; +export * from './user-level-resource.model'; +export * from './user-tenant.model'; +export * from './user.model'; export const models = [ User, diff --git a/services/authentication-service/src/models/reset-password-with-client.model.ts b/services/authentication-service/src/models/reset-password-with-client.model.ts index bfdcf7c60a..8ce31c33e5 100644 --- a/services/authentication-service/src/models/reset-password-with-client.model.ts +++ b/services/authentication-service/src/models/reset-password-with-client.model.ts @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {Model, model, property} from '@loopback/repository'; +import {IAuthClientDTO} from '..'; @model() -export class ResetPasswordWithClient extends Model { +export class ResetPasswordWithClient extends Model implements IAuthClientDTO { @property({ type: 'string', required: true, diff --git a/services/authentication-service/src/modules/auth/login.controller.ts b/services/authentication-service/src/modules/auth/login.controller.ts index 6b78a230b1..11d110119d 100644 --- a/services/authentication-service/src/modules/auth/login.controller.ts +++ b/services/authentication-service/src/modules/auth/login.controller.ts @@ -35,7 +35,6 @@ import { } from 'loopback4-authentication'; import {authorize, AuthorizeErrorKeys} from 'loopback4-authorization'; import moment from 'moment-timezone'; - import {AuthServiceBindings} from '../../keys'; import {AuthClient, RefreshToken, User} from '../../models'; import {AuthCodeBindings, CodeReaderFn, JwtPayloadFn} from '../../providers'; @@ -50,6 +49,7 @@ import { UserTenantRepository, } from '../../repositories'; import {TenantConfigRepository} from '../../repositories/tenant-config.repository'; +import {LoginHelperService} from '../../services'; import {AuthRefreshTokenRequest, AuthTokenRequest, LoginRequest} from './'; import {AuthUser, DeviceInfo} from './models/auth-user.model'; import {ResetPassword} from './models/reset-password.dto'; @@ -86,6 +86,8 @@ export class LoginController { @inject(LOGGER.LOGGER_INJECT) public logger: ILogger, @inject(AuthServiceBindings.JWTPayloadProvider) private readonly getJwtPayload: JwtPayloadFn, + @inject('services.LoginHelperService') + private readonly loginHelperService: LoginHelperService, ) {} @authenticateClient(STRATEGY.CLIENT_PASSWORD) @@ -108,27 +110,23 @@ export class LoginController { async login( @requestBody() req: LoginRequest, + @inject(AuthenticationBindings.CURRENT_CLIENT) + client: AuthClient | undefined, + @inject(AuthenticationBindings.CURRENT_USER) + user: AuthUser | undefined, ): Promise<{ code: string; }> { - const userStatus = await this.userTenantRepo.findOne({ - where: { - userId: this.user?.id, - }, - fields: { - status: true, - }, - }); - if (!this.client || !this.user) { - throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid); - } else if (!req.client_secret) { - throw new HttpErrors.BadRequest(AuthErrorKeys.ClientSecretMissing); - } else if (userStatus?.status === UserStatus.REGISTERED) { - throw new HttpErrors.BadRequest('User not active yet'); - } else { - // Do nothing and move ahead - } + await this.loginHelperService.verifyClientUserLogin(req, client, user); + try { + if (!this.user || !this.client) { + // Control should never reach here + this.logger.error( + `${AuthErrorKeys.ClientInvalid} :: Control should never reach here`, + ); + throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid); + } const codePayload: ClientAuthCode = { clientId: req.client_id, userId: this.user.id, @@ -170,29 +168,20 @@ export class LoginController { @requestBody() req: LoginRequest, @param.header.string('device_id') deviceId?: string, ): Promise { - const userStatus = await this.userTenantRepo.findOne({ - where: { - userId: this.user?.id, - }, - fields: { - status: true, - }, - }); - if (!this.client || !this.user) { - throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid); - } else if ( - !this.user.authClientIds || - this.user.authClientIds.length === 0 - ) { - throw new HttpErrors.UnprocessableEntity(AuthErrorKeys.ClientUserMissing); - } else if (!req.client_secret) { - throw new HttpErrors.BadRequest(AuthErrorKeys.ClientSecretMissing); - } else if (userStatus?.status === UserStatus.REGISTERED) { - throw new HttpErrors.BadRequest('Your Sign-Up request is in process'); - } else { - // Do nothing and move ahead - } + await this.loginHelperService.verifyClientUserLogin( + req, + this.client, + this.user, + ); + try { + if (!this.user || !this.client) { + // Control should never reach here + this.logger.error( + `${AuthErrorKeys.ClientInvalid} :: Control should never reach here`, + ); + throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid); + } const payload: ClientAuthCode = { clientId: this.client.clientId, user: this.user, diff --git a/services/authentication-service/src/providers/types.ts b/services/authentication-service/src/providers/types.ts index e051fe566e..44d0bd846c 100644 --- a/services/authentication-service/src/providers/types.ts +++ b/services/authentication-service/src/providers/types.ts @@ -4,7 +4,6 @@ import * as AppleStrategy from 'passport-apple'; import * as FacebookStrategy from 'passport-facebook'; import * as GoogleStrategy from 'passport-google-oauth20'; import * as InstagramStrategy from 'passport-instagram'; - import { ForgetPasswordResponseDto, SignupRequestResponseDto, @@ -134,7 +133,7 @@ export interface JwtPayloadFn { } export interface ForgotPasswordHandlerFn { - (dto: DataObject): Promise; + (dto: DataObject): Promise; } export interface SignupTokenHandlerFn { diff --git a/services/authentication-service/src/repositories/user.repository.ts b/services/authentication-service/src/repositories/user.repository.ts index bcef68d619..fbe83f46c1 100644 --- a/services/authentication-service/src/repositories/user.repository.ts +++ b/services/authentication-service/src/repositories/user.repository.ts @@ -14,11 +14,12 @@ import { AuthProvider, DefaultUserModifyCrudRepository, IAuthUserWithPermissions, + ILogger, + LOGGER, UserStatus, } from '@sourceloop/core'; import * as bcrypt from 'bcrypt'; import {AuthenticationBindings, AuthErrorKeys} from 'loopback4-authentication'; - import { Tenant, User, @@ -64,6 +65,7 @@ export class UserRepository extends DefaultUserModifyCrudRepository< protected tenantRepositoryGetter: Getter, @repository.getter('UserTenantRepository') protected userTenantRepositoryGetter: Getter, + @inject(LOGGER.LOGGER_INJECT) private readonly logger: ILogger, ) { super(User, dataSource, getCurrentUser); this.userTenants = this.createHasManyRepositoryFactoryFor( @@ -124,9 +126,15 @@ export class UserRepository extends DefaultUserModifyCrudRepository< where: {username: username.toLowerCase()}, }); const creds = user && (await this.credentials(user.id).get()); - if (!user || user.deleted || !creds || !creds.password) { + if (!user || user.deleted) { throw new HttpErrors.Unauthorized(AuthenticateErrorKeys.UserDoesNotExist); - } else if (!(await bcrypt.compare(password, creds.password))) { + } else if ( + !creds || + !creds.password || + creds.authProvider !== AuthProvider.INTERNAL || + !(await bcrypt.compare(password, creds.password)) + ) { + this.logger.error('User creds not found in DB or is invalid'); throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials); } else { return user; diff --git a/services/authentication-service/src/services/index.ts b/services/authentication-service/src/services/index.ts new file mode 100644 index 0000000000..72d91760db --- /dev/null +++ b/services/authentication-service/src/services/index.ts @@ -0,0 +1,4 @@ +import {LoginHelperService} from './login-helper.service'; +export * from './login-helper.service'; + +export const services = [LoginHelperService]; diff --git a/services/authentication-service/src/services/login-helper.service.ts b/services/authentication-service/src/services/login-helper.service.ts new file mode 100644 index 0000000000..109f7c55de --- /dev/null +++ b/services/authentication-service/src/services/login-helper.service.ts @@ -0,0 +1,65 @@ +import {inject} from '@loopback/context'; +import {BindingScope, injectable} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import {HttpErrors} from '@loopback/rest'; +import {ILogger, LOGGER, UserStatus} from '@sourceloop/core'; +import {AuthErrorKeys} from 'loopback4-authentication'; +import {AuthClient, IAuthClientDTO, UserTenant} from '..'; +import {AuthUser} from '../modules/auth/models/auth-user.model'; +import {UserTenantRepository} from '../repositories'; + +@injectable({scope: BindingScope.TRANSIENT}) +export class LoginHelperService { + constructor( + @repository(UserTenantRepository) + private readonly userTenantRepo: UserTenantRepository, + @inject(LOGGER.LOGGER_INJECT) private readonly logger: ILogger, + ) {} + + async verifyClientUserLogin( + req: IAuthClientDTO, + client?: AuthClient, + reqUser?: Pick | null, + ): Promise | null> { + const currentUser = reqUser; + if (!client) { + this.logger.error('Auth client not found or invalid'); + throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid); + } + if (!currentUser) { + this.logger.error('Auth user not found or invalid'); + throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials); + } + + const userStatus: Pick | null = + await this.userTenantRepo.findOne({ + where: { + userId: currentUser.id, + }, + fields: { + status: true, + }, + }); + + if (!currentUser.authClientIds || currentUser.authClientIds.length === 0) { + this.logger.error('No allowed auth clients found for this user in DB'); + throw new HttpErrors.UnprocessableEntity(AuthErrorKeys.ClientUserMissing); + } else if (!req.client_secret) { + this.logger.error('client secret key missing from request object'); + throw new HttpErrors.BadRequest(AuthErrorKeys.ClientSecretMissing); + } else if ( + currentUser.authClientIds.indexOf((client.id ?? 0).toString()) < 0 + ) { + this.logger.error( + 'User is not allowed to access client id passed in request', + ); + throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid); + } else if (userStatus?.status === UserStatus.REGISTERED) { + this.logger.error('User is in registered state'); + throw new HttpErrors.BadRequest('User not active yet'); + } else { + // Do nothing and move ahead + } + return userStatus; + } +} diff --git a/services/authentication-service/src/types.ts b/services/authentication-service/src/types.ts index a1d30d26ae..cf656f5b42 100644 --- a/services/authentication-service/src/types.ts +++ b/services/authentication-service/src/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import {AnyObject} from '@loopback/repository'; import {IServiceConfig} from '@sourceloop/core'; import {LocalUserProfileDto} from './models'; @@ -15,3 +16,10 @@ export interface PreSignupFn { export interface UserSignupFn { (model: T & LocalUserProfileDto, tokenInfo?: AnyObject): Promise; } + +export interface IAuthClientDTO { + // sonarignore:start + client_id: string; + client_secret: string; + // sonarignore:end +} diff --git a/services/authentication-service/tsconfig.json b/services/authentication-service/tsconfig.json index 704f00e6b0..dd0dfe3786 100644 --- a/services/authentication-service/tsconfig.json +++ b/services/authentication-service/tsconfig.json @@ -6,9 +6,7 @@ "rootDir": "src", "composite": true }, - "include": [ - "src" - ], + "include": ["src"], "references": [ { "path": "../../packages/core/tsconfig.json"