From 7f2844107daea5ef9a40df4f24b3123a6920f6bb Mon Sep 17 00:00:00 2001 From: MCatherine Date: Tue, 30 Apr 2024 12:39:55 -0700 Subject: [PATCH] feat: #82 allow search by user guid (#83) * chore: fix dependabot warnings * feat(72): added endpoint for verifying bceid, refs: #72 * feat(72): add test for verifying bceid user, but skip it for now, refs: #72 * merge: update package-lock.json to be the same as main branch, refs: #72 * fix(72): fix return type, refs: #72 * fix(72): fix type in test, refs: #72 * refactor(72): refactor the service method and add comments for clarification, refs: #72 * feat(82): create a new api route to allow search bceid user by both userid and userguid, refs: #82 * fix: upgrade eslint-plugin-n * fix dependency conflict * fix dependency conflict * fix(82): fix controller parameter type, refs: #82 --- backend/package-lock.json | 121 +----------------- backend/package.json | 10 +- .../idim-webservice.controller.ts | 27 +++- .../idim-webservice/idim-webservice.dto.ts | 5 + .../idim-webservice.service.ts | 110 ++++++++++++++++ 5 files changed, 147 insertions(+), 126 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 2754600..7310a43 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "backend", "license": "Apache-2.0", "dependencies": { "@nestjs/cli": "^10.0.0", @@ -36,7 +35,7 @@ "eslint-config-prettier": "^8.6.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-node": "^11.1.0", + "eslint-plugin-n": "^16.0.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", "istanbul-badges-readme": "^1.8.4", @@ -3362,7 +3361,6 @@ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, - "peer": true, "engines": { "node": ">=6" }, @@ -3375,7 +3373,6 @@ "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", "dev": true, - "peer": true, "dependencies": { "semver": "^7.0.0" } @@ -4409,7 +4406,6 @@ "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", "dev": true, - "peer": true, "engines": { "node": ">=12" }, @@ -4532,31 +4528,11 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, "node_modules/eslint-plugin-es-x": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", "@eslint-community/regexpp": "^4.6.0", @@ -4699,7 +4675,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", @@ -4728,56 +4703,12 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/eslint-plugin-n/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-node/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", @@ -4789,15 +4720,6 @@ "node": "*" } }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -4856,30 +4778,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -5717,7 +5615,6 @@ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", "dev": true, - "peer": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -6178,7 +6075,6 @@ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, - "peer": true, "dependencies": { "builtin-modules": "^3.3.0" }, @@ -8919,18 +8815,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -9006,7 +8890,6 @@ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -10984,4 +10867,4 @@ } } } -} \ No newline at end of file +} diff --git a/backend/package.json b/backend/package.json index c5f482c..ef2abec 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,13 +28,13 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.10", "dotenv": "^16.1.4", + "helmet": "^7.0.0", + "nest-winston": "^1.9.1", "rimraf": "^5.0.0", "rxjs": "^7.8.0", "soap": "^1.0.0", "swagger-ui-express": "^5.0.0", - "winston": "^3.8.2", - "nest-winston": "^1.9.1", - "helmet": "^7.0.0" + "winston": "^3.8.2" }, "devDependencies": { "@nestjs/schematics": "^10.0.0", @@ -50,7 +50,7 @@ "eslint-config-prettier": "^8.6.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-node": "^11.1.0", + "eslint-plugin-n": "^16.0.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", "istanbul-badges-readme": "^1.8.4", @@ -102,4 +102,4 @@ "overrides": { "minimist@<1.2.6": "1.2.6" } -} \ No newline at end of file +} diff --git a/backend/src/modules/idim-webservice/idim-webservice.controller.ts b/backend/src/modules/idim-webservice/idim-webservice.controller.ts index 871c0fb..bcd0819 100644 --- a/backend/src/modules/idim-webservice/idim-webservice.controller.ts +++ b/backend/src/modules/idim-webservice/idim-webservice.controller.ts @@ -10,6 +10,7 @@ import { ApiResponse, ApiQuery, ApiTags, ApiSecurity } from '@nestjs/swagger'; import { AuthGuard } from '../auth/auth.guard'; import { IdimWebserviceService } from './idim-webservice.service'; import { + SearchUserParameterType, IDIRUserResponse, BCEIDUserResponse, RequesterAccountTypeCode, @@ -34,7 +35,7 @@ export class IdimWebserviceController { @Query('userId') userId: string, @Query('requesterUserId') requesterUserId: string, @Query('requesterAccountTypeCode') - requesterAccountTypeCode: string + requesterAccountTypeCode: RequesterAccountTypeCode ): Promise { return this.idimWebserviceService.verifyIdirUser( userId, @@ -53,7 +54,7 @@ export class IdimWebserviceController { @Query('userId') userId: string, @Query('requesterUserGuid') requesterUserGuid: string, @Query('requesterAccountTypeCode') - requesterAccountTypeCode: string + requesterAccountTypeCode: RequesterAccountTypeCode ): Promise { return this.idimWebserviceService.verifyBceidUser( userId, @@ -61,4 +62,26 @@ export class IdimWebserviceController { requesterAccountTypeCode ); } + + @Get('businessBceid') + @ApiResponse({ status: HttpStatus.OK, type: BCEIDUserResponse }) + @ApiQuery({ + name: 'requesterAccountTypeCode', + enum: RequesterAccountTypeCode, + }) + @ApiQuery({ name: 'searchUserBy', enum: SearchUserParameterType }) + async verifyBusinessBceidUser( + @Query('searchUserBy') searchUserBy: SearchUserParameterType, + @Query('searchValue') searchValue: string, + @Query('requesterUserGuid') requesterUserGuid: string, + @Query('requesterAccountTypeCode') + requesterAccountTypeCode: RequesterAccountTypeCode + ): Promise { + return this.idimWebserviceService.verifyBusinessBceidUser( + searchUserBy, + searchValue, + requesterUserGuid, + requesterAccountTypeCode + ); + } } diff --git a/backend/src/modules/idim-webservice/idim-webservice.dto.ts b/backend/src/modules/idim-webservice/idim-webservice.dto.ts index 5031798..2476e6b 100644 --- a/backend/src/modules/idim-webservice/idim-webservice.dto.ts +++ b/backend/src/modules/idim-webservice/idim-webservice.dto.ts @@ -1,5 +1,10 @@ import { ApiProperty } from '@nestjs/swagger'; +export enum SearchUserParameterType { + UserGuid = "userGuid", + UserId = "userId" + } + export enum RequesterAccountTypeCode { Internal = 'Internal', Business = 'Business' diff --git a/backend/src/modules/idim-webservice/idim-webservice.service.ts b/backend/src/modules/idim-webservice/idim-webservice.service.ts index 96f6a0a..3c0d92d 100644 --- a/backend/src/modules/idim-webservice/idim-webservice.service.ts +++ b/backend/src/modules/idim-webservice/idim-webservice.service.ts @@ -1,5 +1,6 @@ import { Injectable, HttpStatus, HttpException } from '@nestjs/common'; import { + SearchUserParameterType, IDIRUserResponse, BCEIDUserResponse, RequesterAccountTypeCode, @@ -255,4 +256,113 @@ export class IdimWebserviceService { ); } } + + async verifyBusinessBceidUser( + searchUserBy: string, + searchValue: string, + requesterUserGuid: string, + requesterAccountTypeCode: string + ): Promise { + this.checkRequiredIDIMCredentials(); + try { + const client = await this.getSoapClient(); + // set xml schema parameters for the request + const requestData = { + accountDetailRequest: { + onlineServiceId: this.idimWebServiceID, + // who is sending the request + requesterAccountTypeCode, + requesterUserGuid, + // who we search for, exact match by userID or userGuid + [searchUserBy]: searchValue, + accountTypeCode: RequesterAccountTypeCode.Business, + }, + }; + + return new Promise((resolve, reject) => { + client.BCeIDService.BCeIDServiceSoap.getAccountDetail( + requestData, + function (error, foundUser) { + // this will be any error from the IDIM server side + if (error) { + return reject( + new HttpException( + { + error: + 'IDIM web service call error: ' + + error, + }, + HttpStatus.INTERNAL_SERVER_ERROR + ) + ); + } + + // this will be any error return by the web service + // for example if we provided an non existing requestor id, or permission issue + if ( + foundUser.getAccountDetailResult.code == 'Failed' && + foundUser.getAccountDetailResult.failureCode !== + 'NoResults' + ) { + return reject( + new HttpException( + { + status: HttpStatus.BAD_REQUEST, + code: foundUser.getAccountDetailResult + .code, + failureCode: + foundUser.getAccountDetailResult + .failureCode, + message: + foundUser.getAccountDetailResult + .message, + }, + HttpStatus.BAD_REQUEST + ) + ); + } + + // user not found case + // getAccountDetail method returns code Failed with failureCode NoResults + // which is different than the not found case of searchInternalAccount method that we used above for searching idir user + if ( + foundUser.getAccountDetailResult.code == 'Failed' && + foundUser.getAccountDetailResult.failureCode == + 'NoResults' + ) { + const response = new BCEIDUserResponse(); + response.found = false; + if ( + searchUserBy == SearchUserParameterType.UserGuid + ) + response.guid = searchValue; + else response.userId = searchValue; + return resolve(response); + } + + const response = new BCEIDUserResponse(); + const userInfo = + foundUser.getAccountDetailResult.account; + response.found = true; + response.userId = userInfo.userId.value; + response.guid = userInfo.guid.value; + response.businessGuid = userInfo.business.guid.value; + response.businessLegalName = + userInfo.business.legalName.value; + response.firstName = + userInfo.individualIdentity.name.firstname.value; + response.lastName = + userInfo.individualIdentity.name.surname.value; + response.email = userInfo.contact.email.value; + return resolve(response); + } + ); + }); + } catch (error) { + return new HttpException( + { error: 'Error happened when call verifyBceidUser: ' + error }, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } }