diff --git a/vehicles/package-lock.json b/vehicles/package-lock.json index 7e112c88a..9e090b108 100644 --- a/vehicles/package-lock.json +++ b/vehicles/package-lock.json @@ -39,6 +39,7 @@ "nest-winston": "^1.10.0", "nestjs-cls": "^4.4.1", "nestjs-typeorm-paginate": "^4.0.4", + "onroute-policy-engine": "0.4.2", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "response-time": "^2.3.2", @@ -5540,6 +5541,11 @@ "node": ">=6" } }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -6436,6 +6442,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-it": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hash-it/-/hash-it-6.0.0.tgz", + "integrity": "sha512-KHzmSFx1KwyMPw0kXeeUD752q/Kfbzhy6dAZrjXV9kAIXGqzGvv8vhkUqj+2MGZldTo0IBpw6v7iWE7uxsvH0w==" + }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -7822,6 +7833,25 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "node_modules/json-rules-engine": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/json-rules-engine/-/json-rules-engine-6.6.0.tgz", + "integrity": "sha512-jJ4eVCPnItetPiU3fTIzrrl3d2zeIXCcCy11dwWhN72YXBR2mByV1Vfbrvt6y2n+VFmxc6rtL/XhDqLKIwBx6g==", + "dependencies": { + "clone": "^2.1.2", + "eventemitter2": "^6.4.4", + "hash-it": "^6.0.0", + "jsonpath-plus": "^7.2.0" + } + }, + "node_modules/json-rules-engine/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7860,6 +7890,14 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpath-plus": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", + "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -9118,6 +9156,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onroute-policy-engine": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/onroute-policy-engine/-/onroute-policy-engine-0.4.2.tgz", + "integrity": "sha512-LTkCy8omP/UHO+Qw0UowiDGDBfqCPTXNmrXeYftkCQ9QBDulBQiHDLFP+W569mFsFqz4I/Cy0I40NdoiP5IUqA==", + "dependencies": { + "dayjs": "^1.11.10", + "json-rules-engine": "^6.5.0" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", diff --git a/vehicles/package.json b/vehicles/package.json index 2e9e7fa73..3505bb3f3 100644 --- a/vehicles/package.json +++ b/vehicles/package.json @@ -78,7 +78,8 @@ "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.20", "uuid": "^9.0.1", - "winston": "^3.14.2" + "winston": "^3.14.2", + "onroute-policy-engine": "0.4.2" }, "devDependencies": { "@golevelup/ts-jest": "^0.5.4", diff --git a/vehicles/src/app.service.ts b/vehicles/src/app.service.ts index bcee88529..d2c29221c 100644 --- a/vehicles/src/app.service.ts +++ b/vehicles/src/app.service.ts @@ -12,6 +12,8 @@ import { PaymentService } from './modules/permit-application-payment/payment/pay import { LogAsyncMethodExecution } from './common/decorator/log-async-method-execution.decorator'; import { FeatureFlagsService } from './modules/feature-flags/feature-flags.service'; import { ApplicationService } from './modules/permit-application-payment/application/application.service'; +import { HttpService } from '@nestjs/axios'; +import { getActivePolicyDefinitions } from './common/helper/policy-engine.helper'; @Injectable() export class AppService { @@ -27,6 +29,7 @@ export class AppService { private paymentService: PaymentService, private featureFlagsService: FeatureFlagsService, private applicationService: ApplicationService, + private readonly httpService: HttpService, ) {} getHello(): string { @@ -128,6 +131,16 @@ export class AppService { createCacheMap(permitApprovalSource, 'id', 'code'), ); + const policyConfigs = await getActivePolicyDefinitions( + this.httpService, + this.cacheManager, + ); + await addToCache( + this.cacheManager, + CacheKey.POLICY_CONFIGURATIONS, + createCacheMap(policyConfigs, '??', 'policyJson'), + ); + const endDateTime = new Date(); const processingTime = endDateTime.getTime() - startDateTime.getTime(); this.logger.log( diff --git a/vehicles/src/common/enum/cache-key.enum.ts b/vehicles/src/common/enum/cache-key.enum.ts index 82100c8cc..fd58b6416 100644 --- a/vehicles/src/common/enum/cache-key.enum.ts +++ b/vehicles/src/common/enum/cache-key.enum.ts @@ -20,4 +20,6 @@ export enum CacheKey { FEATURE_FLAG_TYPE = 'FEATURE_FLAG_TYPE', PERMIT_APPLICATION_ORIGIN = 'PERMIT_APPLICATION_ORIGIN', PERMIT_APPROVAL_SOURCE = 'PERMIT_APPROVAL_SOURCE', + POLICY_CONFIGURATIONS = 'POLICY_CONFIGURATIONS', + ORBC_SERVICE_ACCOUNT_ACCESS_TOKEN = 'ORBC_SERVICE_ACCOUNT_ACCESS_TOKEN', } diff --git a/vehicles/src/common/enum/gov-common-services.enum.ts b/vehicles/src/common/enum/gov-common-services.enum.ts index 6a44a473f..acc92f93d 100644 --- a/vehicles/src/common/enum/gov-common-services.enum.ts +++ b/vehicles/src/common/enum/gov-common-services.enum.ts @@ -2,4 +2,5 @@ export enum GovCommonServices { COMMON_HOSTED_EMAIL_SERVICE = 'CHES', COMMON_DOCUMENT_GENERATION_SERVICE = 'CDOGS', CREDIT_ACCOUNT_SERVICE = 'CREDIT_ACCOUNT', + ORBC_SERVICE_ACCOUNT = 'SA', } diff --git a/vehicles/src/common/helper/gov-common-services.helper.ts b/vehicles/src/common/helper/gov-common-services.helper.ts index be81e942b..d453be518 100644 --- a/vehicles/src/common/helper/gov-common-services.helper.ts +++ b/vehicles/src/common/helper/gov-common-services.helper.ts @@ -124,7 +124,12 @@ function getTokenCredentials(govCommonServices: GovCommonServices): { username = process.env.CFS_CREDIT_ACCOUNT_CLIENT_ID; password = process.env.CFS_CREDIT_ACCOUNT_CLIENT_SECRET; break; - + case GovCommonServices.ORBC_SERVICE_ACCOUNT: + tokenCacheKey = CacheKey.ORBC_SERVICE_ACCOUNT_ACCESS_TOKEN; + tokenUrl = process.env.ORBC_SERVICE_ACCOUNT_TOKEN_URL; + username = process.env.ORBC_SERVICE_ACCOUNT_CLIENT_ID; + password = process.env.ORBC_SERVICE_ACCOUNT_CLIENT_SECRET; + break; default: break; } diff --git a/vehicles/src/common/helper/policy-engine.helper.ts b/vehicles/src/common/helper/policy-engine.helper.ts index fcf3fd3ba..3716baece 100644 --- a/vehicles/src/common/helper/policy-engine.helper.ts +++ b/vehicles/src/common/helper/policy-engine.helper.ts @@ -1,6 +1,13 @@ import { Permit } from 'src/modules/permit-application-payment/permit/entities/permit.entity'; import { PolicyApplication } from '../interface/policy-application.interface'; import { PermitData } from '../interface/permit.template.interface'; +import { getAccessToken } from './gov-common-services.helper'; +import { GovCommonServices } from '../enum/gov-common-services.enum'; +import { HttpService } from '@nestjs/axios'; +import { Cache } from 'cache-manager'; +import { AxiosResponse } from 'axios'; +import { ReadPolicyConfigDto } from '../../modules/policy/dto/response/read-policy-config.dto'; +import { Policy, ValidationResults } from 'onroute-policy-engine'; export const convertToPolicyApplication = ( application: Permit, @@ -10,3 +17,41 @@ export const convertToPolicyApplication = ( permitData: JSON.parse(application.permitData.permitData) as PermitData, }; }; + +export const getActivePolicyDefinitions = async ( + httpService: HttpService, + cacheManager: Cache, +) => { + const token = await getAccessToken( + GovCommonServices.ORBC_SERVICE_ACCOUNT, + httpService, + cacheManager, + ); + const response = await httpService.axiosRef.get< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + AxiosResponse, + Request + >(process.env.ORBC_POLICY_URL + '/policy-configurations', { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + return (await response.data.json()) as ReadPolicyConfigDto[]; +}; + +export const validateWithPolicyEngine = async ( + permitApplication: unknown, + cacheManager: Cache, +): Promise => { + const policyDefinitions: ReadPolicyConfigDto[] = await cacheManager.get( + 'active-policy-definitions', + ); + const policy = new Policy(policyDefinitions); + const validationResults: ValidationResults = + await policy.validate(permitApplication); + + return validationResults.violations.length > 0; +}; diff --git a/vehicles/src/modules/policy/dto/response/read-policy-config.dto.ts b/vehicles/src/modules/policy/dto/response/read-policy-config.dto.ts new file mode 100644 index 000000000..21a3bf049 --- /dev/null +++ b/vehicles/src/modules/policy/dto/response/read-policy-config.dto.ts @@ -0,0 +1,54 @@ +import { AutoMap } from '@automapper/classes'; +import { ApiProperty } from '@nestjs/swagger'; +import { PolicyDefinition } from 'onroute-policy-engine/dist/types'; + +export class ReadPolicyConfigDto { + /** + * Unique identifier for the policy configuration. + */ + @AutoMap() + @ApiProperty({ + example: '1', + description: 'Unique identifier for the policy configuration.', + }) + policyConfigId: number; + + /** + * JSON data representing the policy configuration. + */ + @AutoMap() + @ApiProperty({ + description: 'Policy configuration in JSON format.', + }) + policy: PolicyDefinition; + + /** + * Configuration effective date. + */ + @AutoMap() + @ApiProperty({ + example: '2023-07-13T17:31:17.470Z', + description: 'Policy Configuration effective date.', + }) + effectiveDate: string; + + /** + * Indicates if the configuration is currently a draft version. + */ + @AutoMap() + @ApiProperty({ + example: true, + description: 'Indicates if the configuration is currently a draft.', + }) + isDraft: boolean; + + /** + * Description of changes made in the configuration. + */ + @AutoMap() + @ApiProperty({ + example: 'Initial release of policy configuration with updated rules', + description: 'Description of changes made in the configuration.', + }) + changeDescription: string; +} diff --git a/vehicles/tsconfig.json b/vehicles/tsconfig.json index fd39c3271..c153b4495 100644 --- a/vehicles/tsconfig.json +++ b/vehicles/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "CommonJS", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true,