From f63f5a5bcba4099fe7975e07e88bf6b6adbab416 Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Fri, 15 May 2020 16:34:01 -0700 Subject: [PATCH] feat(plugin-web-service-consortium): add dedicated plugin for consortium management API Some other packages were heavily refactored to make this possible as well. The introduction of the web service plugin was just the beginning of the process of making it possible to host different API endpoints on custom TCP ports with unique configurations that might be necessary in a highly constrained enterprise environment for example. There's a test case specifically designed to showcase and verify the behavior where the consortium API is hosted separately from the rest of the APIs exposed by the cmd-api-server package. Signed-off-by: Peter Somogyvari --- package.json | 3 + packages/bif-cmd-api-server/package-lock.json | 26 +- packages/bif-cmd-api-server/package.json | 2 +- .../src/main/typescript/api-server.ts | 55 +- .../main/typescript/config/config-service.ts | 8 +- .../typescript/consortium/model/bif-node.ts | 4 - .../consortium/model/consortium-wrapper.ts | 4 - .../typescript/consortium/model/consortium.ts | 8 - .../routes/create-consortium-endpoint-v1.ts | 70 -- .../routes/sign-data-endpoint-v1.ts | 32 - .../src/main/typescript/openapi-spec.ts | 86 +- .../src/main/typescript/public-api.ts | 4 - packages/bif-cockpit/src/app/app.component.ts | 13 +- .../typescript/logging/logger-provider.ts | 17 + .../src/main/typescript/logging/logger.ts | 16 +- .../src/main/typescript/public-api.ts | 1 + .../main/typescript/plugin/plugin-registry.ts | 118 +++ .../web-service/i-plugin-web-service.ts | 17 +- .../src/main/typescript/public-api.ts | 1 + .../plugin-ledger-connector-quorum.ts | 21 +- .../package-lock.json | 947 ++++++++++++++++++ .../package.json | 73 ++ .../create-consortium-endpoint-v1.ts | 87 ++ .../openapi/typescript-axios/.gitignore | 4 + .../.openapi-generator-ignore | 23 + .../.openapi-generator/VERSION | 1 + .../generated/openapi/typescript-axios/api.ts | 254 +++++ .../openapi/typescript-axios/base.ts | 70 ++ .../openapi/typescript-axios/configuration.ts | 75 ++ .../openapi/typescript-axios/git_push.sh | 58 ++ .../openapi/typescript-axios/index.ts | 16 + .../src/main/typescript/index.ts | 1 + .../src/main/typescript/openapi-spec.ts | 134 +++ .../plugin-factory-web-service-consortium.ts | 16 + .../plugin-web-service-consortium.ts | 120 +++ .../src/main/typescript/public-api.ts | 7 + .../typescript/integration/api-surface.ts | 10 + .../integration/tap-parallel-not-ok | 0 ...security-isolation-via-api-server-ports.ts | 140 +++ .../src/test/typescript/unit/api-surface.ts | 10 + .../tsconfig.json | 10 + .../generated/openapi/typescript-axios/api.ts | 169 +--- .../deploy-contract-via-web-service.ts | 36 +- webpack.dev.web.js | 4 +- webpack.prod.web.js | 4 +- 45 files changed, 2400 insertions(+), 375 deletions(-) delete mode 100644 packages/bif-cmd-api-server/src/main/typescript/consortium/model/bif-node.ts delete mode 100644 packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium-wrapper.ts delete mode 100644 packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium.ts delete mode 100644 packages/bif-cmd-api-server/src/main/typescript/consortium/routes/create-consortium-endpoint-v1.ts delete mode 100644 packages/bif-cmd-api-server/src/main/typescript/consortium/routes/sign-data-endpoint-v1.ts create mode 100644 packages/bif-core-api/src/main/typescript/plugin/plugin-registry.ts create mode 100644 packages/bif-plugin-web-service-consortium/package-lock.json create mode 100755 packages/bif-plugin-web-service-consortium/package.json create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.gitignore create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/base.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/configuration.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/git_push.sh create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/index.ts create mode 100755 packages/bif-plugin-web-service-consortium/src/main/typescript/index.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts create mode 100755 packages/bif-plugin-web-service-consortium/src/main/typescript/public-api.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts create mode 100755 packages/bif-plugin-web-service-consortium/src/test/typescript/integration/tap-parallel-not-ok create mode 100644 packages/bif-plugin-web-service-consortium/src/test/typescript/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts create mode 100644 packages/bif-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts create mode 100644 packages/bif-plugin-web-service-consortium/tsconfig.json diff --git a/package.json b/package.json index 6342d9789b..4ef82148e8 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,11 @@ "build:frontend": "lerna exec --stream --scope '*/*cockpit' -- ng build --prod", "build:backend": "npm-run-all lint clean generate-sdk tsc webpack", "build:dev:pkg:cmd-api-server": "lerna exec --stream --scope '*/*api-server' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:pkg:common": "lerna exec --stream --scope '*/*common' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:pkg:core-api": "lerna exec --stream --scope '*/*core-api' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:pkg:test-tooling": "lerna exec --stream --scope '*/*test-tooling' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:pkg:bif-plugin-ledger-connector-quorum": "lerna exec --stream --scope '*/*connector-quorum' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:pkg:bif-plugin-web-service-consortium": "lerna exec --stream --scope '*/*web-service-consortium' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:pkg:sdk": "lerna exec --stream --scope '*/*sdk' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "webpack": "npm-run-all webpack:web:dev webpack:node:dev webpack:web:prod webpack:node:prod", "webpack:web:prod": "lerna exec --stream --ignore '*/*{cockpit,server,test-tooling}' -- webpack --env=prod --target=web --config ../../webpack.config.js", diff --git a/packages/bif-cmd-api-server/package-lock.json b/packages/bif-cmd-api-server/package-lock.json index 6fadd1571d..d6902965e3 100644 --- a/packages/bif-cmd-api-server/package-lock.json +++ b/packages/bif-cmd-api-server/package-lock.json @@ -63,23 +63,25 @@ } }, "@types/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", - "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", + "@types/qs": "*", "@types/serve-static": "*" } }, "@types/express-serve-static-core": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", - "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", + "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", "dev": true, "requires": { "@types/node": "*", + "@types/qs": "*", "@types/range-parser": "*" } }, @@ -90,9 +92,9 @@ "dev": true }, "@types/mime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", + "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", "dev": true }, "@types/multer": { @@ -110,6 +112,12 @@ "integrity": "sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==", "dev": true }, + "@types/qs": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.2.tgz", + "integrity": "sha512-a9bDi4Z3zCZf4Lv1X/vwnvbbDYSNz59h3i3KdyuYYN+YrLjSeJD0dnphdULDfySvUv6Exy/O0K6wX/kQpnPQ+A==", + "dev": true + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", diff --git a/packages/bif-cmd-api-server/package.json b/packages/bif-cmd-api-server/package.json index 964dc392d6..4810aebe57 100755 --- a/packages/bif-cmd-api-server/package.json +++ b/packages/bif-cmd-api-server/package.json @@ -70,7 +70,7 @@ "@types/compression": "1.7.0", "@types/convict": "5.2.0", "@types/cors": "2.8.6", - "@types/express": "4.17.3", + "@types/express": "4.17.6", "@types/joi": "14.3.4", "@types/multer": "1.4.2", "@types/secp256k1": "3.5.3", diff --git a/packages/bif-cmd-api-server/src/main/typescript/api-server.ts b/packages/bif-cmd-api-server/src/main/typescript/api-server.ts index 644a8ddf4b..fd83223283 100644 --- a/packages/bif-cmd-api-server/src/main/typescript/api-server.ts +++ b/packages/bif-cmd-api-server/src/main/typescript/api-server.ts @@ -5,7 +5,6 @@ import express, { Express, Request, Response, - NextFunction, RequestHandler, Application, } from "express"; @@ -21,8 +20,7 @@ import { isIPluginWebService, IPluginWebService, } from "@hyperledger-labs/bif-core-api"; -import { CreateConsortiumEndpointV1 } from "./consortium/routes/create-consortium-endpoint-v1"; -import { IBifApiServerOptions, ConfigService } from "./config/config-service"; +import { IBifApiServerOptions } from "./config/config-service"; import { BIF_OPEN_API_JSON } from "./openapi-spec"; import { Logger, LoggerProvider } from "@hyperledger-labs/bif-common"; import { Servers } from "./common/servers"; @@ -70,6 +68,21 @@ export class ApiServer { } public async shutdown(): Promise { + this.log.info(`Shutting down API server ...`); + const webServicesShutdown = this.options.plugins + .filter((pluginInstance) => isIPluginWebService(pluginInstance)) + .map((pluginInstance: ICactusPlugin) => { + return (pluginInstance as IPluginWebService).shutdown(); + }); + + this.log.info( + `Found ${webServicesShutdown.length} web service plugin(s), shutting them down ...` + ); + await Promise.all(webServicesShutdown); + this.log.info( + `Shut down ${webServicesShutdown.length} web service plugin(s) OK` + ); + if (this.httpServerApi) { this.log.info(`Closing HTTP server of the API...`); await Servers.shutdown(this.httpServerApi); @@ -124,31 +137,23 @@ export class ApiServer { const openApiValidator = this.createOpenApiValidator(); await openApiValidator.install(app); - app.get( - "/healthcheck", - (req: Request, res: Response, next: NextFunction) => { - res.json({ success: true, timestamp: new Date() }); - } - ); - - const storage: IPluginKVStorage = await this.createStoragePlugin(); - const configService = new ConfigService(); - const config = configService.getOrCreate(); - { - const endpoint = new CreateConsortiumEndpointV1({ storage, config }); - app.post(endpoint.getPath(), endpoint.handleRequest.bind(endpoint)); - } + const healthcheckHandler = (req: Request, res: Response) => { + res.json({ + success: true, + createdAt: new Date(), + memoryUsage: process.memoryUsage(), + }); + }; + app.get("/api/v1/api-server/healthcheck", healthcheckHandler); - this.options.plugins + this.log.info(`Starting to install web services...`); + const webServicesInstalled = this.options.plugins .filter((pluginInstance) => isIPluginWebService(pluginInstance)) - .forEach((pluginInstance: ICactusPlugin) => { - (pluginInstance as IPluginWebService).installWebService(app); + .map((pluginInstance: ICactusPlugin) => { + return (pluginInstance as IPluginWebService).installWebServices(app); }); - - // FIXME - // app.get('/api/v1/consortium/:consortiumId', (req: Request, res: Response, next: NextFunction) => { - // res.json({ swagger: 'TODO' }); - // }); + await Promise.all(webServicesInstalled); + this.log.info(`Installed ${webServicesInstalled.length} web services OK`); const apiPort: number = this.options.config.get("apiPort"); const apiHost: string = this.options.config.get("apiHost"); diff --git a/packages/bif-cmd-api-server/src/main/typescript/config/config-service.ts b/packages/bif-cmd-api-server/src/main/typescript/config/config-service.ts index 49dd94667b..9cd332589b 100644 --- a/packages/bif-cmd-api-server/src/main/typescript/config/config-service.ts +++ b/packages/bif-cmd-api-server/src/main/typescript/config/config-service.ts @@ -2,12 +2,16 @@ import { randomBytes } from "crypto"; import convict, { Schema, Config, SchemaObj } from "convict"; import secp256k1 from "secp256k1"; import { v4 as uuidV4 } from "uuid"; -import { LoggerProvider, Logger } from "@hyperledger-labs/bif-common"; +import { + LoggerProvider, + Logger, + LogLevelDesc, +} from "@hyperledger-labs/bif-common"; export interface IBifApiServerOptions { configFile: string; bifNodeId: string; - logLevel: string; + logLevel: LogLevelDesc; cockpitHost: string; cockpitPort: number; cockpitWwwRoot: string; diff --git a/packages/bif-cmd-api-server/src/main/typescript/consortium/model/bif-node.ts b/packages/bif-cmd-api-server/src/main/typescript/consortium/model/bif-node.ts deleted file mode 100644 index f50f7012ea..0000000000 --- a/packages/bif-cmd-api-server/src/main/typescript/consortium/model/bif-node.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface BifNode { - host: string; - publicKey: string; -} diff --git a/packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium-wrapper.ts b/packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium-wrapper.ts deleted file mode 100644 index c2afd45e5b..0000000000 --- a/packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium-wrapper.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IConsortiumWrapper { - signature: string; - consortiumJson: string; -} diff --git a/packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium.ts b/packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium.ts deleted file mode 100644 index 6ba1f36af2..0000000000 --- a/packages/bif-cmd-api-server/src/main/typescript/consortium/model/consortium.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { BifNode } from "./bif-node"; - -export interface IConsortium { - id: string; - name: string; - configurationEndpoint: string; - bifNodes: BifNode[]; -} diff --git a/packages/bif-cmd-api-server/src/main/typescript/consortium/routes/create-consortium-endpoint-v1.ts b/packages/bif-cmd-api-server/src/main/typescript/consortium/routes/create-consortium-endpoint-v1.ts deleted file mode 100644 index ef9be43c04..0000000000 --- a/packages/bif-cmd-api-server/src/main/typescript/consortium/routes/create-consortium-endpoint-v1.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Request, Response, NextFunction, Application } from "express"; -import secp256k1 from "secp256k1"; -import { keccak256 } from "js-sha3"; - -import { IPluginKVStorage } from "@hyperledger-labs/bif-core-api"; -import { IConsortium } from "../model/consortium"; -import { IConsortiumWrapper } from "../model/consortium-wrapper"; -import { Config } from "convict"; -import { IBifApiServerOptions } from "../../config/config-service"; - -export interface ICreateConsortiumEndpointOptions { - storage: IPluginKVStorage; - config: Config; -} - -export class CreateConsortiumEndpointV1 { - constructor(public readonly options: ICreateConsortiumEndpointOptions) { - if (!options) { - throw new Error(`CreateConsortiumEndpointV1#ctor options falsy.`); - } - if (!options.config) { - throw new Error(`CreateConsortiumEndpointV1#ctor options.config falsy.`); - } - if (!options.storage) { - throw new Error(`CreateConsortiumEndpointV1#ctor options.storage falsy.`); - } - } - - getPath(): string { - return "/api/v1/consortium"; - } - - async handleRequest( - req: Request, - res: Response, - next: NextFunction - ): Promise { - const consortium: IConsortium = req.body; - const idAlreadyExists = await this.options.storage.has(consortium.id); - if (idAlreadyExists) { - res.status(400); - res.json({ - success: false, - message: `Consortium with ID ${consortium.id} already exists.`, - }); - } else { - // FIXME: We need a library handling the crypto, how about NodeJS bindings for Ursa? - const privateKey = this.options.config.get("privateKey"); - const privateKeyBytes = Uint8Array.from(Buffer.from(privateKey, "hex")); - const consortiumJson: string = JSON.stringify(consortium); - const consortiumBytesHash = Uint8Array.from( - keccak256.array(consortiumJson) - ); - const signatureWrapper = secp256k1.ecdsaSign( - consortiumBytesHash, - privateKeyBytes - ); - const signature = Buffer.from(signatureWrapper.signature).toString("hex"); - const consortiumWrapper: IConsortiumWrapper = { - signature, - consortiumJson, - }; - const wrapperJson = JSON.stringify(consortiumWrapper); - // tslint:disable-next-line: no-console - await this.options.storage.set(consortium.id, wrapperJson); - res.status(201); - res.json({ success: true, consortiumWrapper }); - } - } -} diff --git a/packages/bif-cmd-api-server/src/main/typescript/consortium/routes/sign-data-endpoint-v1.ts b/packages/bif-cmd-api-server/src/main/typescript/consortium/routes/sign-data-endpoint-v1.ts deleted file mode 100644 index 6015d0b1e2..0000000000 --- a/packages/bif-cmd-api-server/src/main/typescript/consortium/routes/sign-data-endpoint-v1.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Request, Response, NextFunction, Application } from "express"; -import { - ConfigService, - IBifApiServerOptions, -} from "../../config/config-service"; -import { Config } from "convict"; - -export interface ISignDataEndpointOptions { - configService: ConfigService; -} - -export class SignDataEndpoint { - private readonly config: Config; - - constructor(public readonly options: ISignDataEndpointOptions) { - if (!options) { - throw new Error(`SignDataEndpoint#ctor options falsy.`); - } - if (!options.configService) { - throw new Error(`SignDataEndpoint#ctor options.configService falsy.`); - } - this.config = this.options.configService.getOrCreate(); - } - - async handleRequest( - req: Request, - res: Response, - next: NextFunction - ): Promise { - const privateKey = this.config.get("privateKey"); - } -} diff --git a/packages/bif-cmd-api-server/src/main/typescript/openapi-spec.ts b/packages/bif-cmd-api-server/src/main/typescript/openapi-spec.ts index 65c5570852..1f63115a9c 100644 --- a/packages/bif-cmd-api-server/src/main/typescript/openapi-spec.ts +++ b/packages/bif-cmd-api-server/src/main/typescript/openapi-spec.ts @@ -29,86 +29,64 @@ export const BIF_OPEN_API_JSON: OpenAPI.OpenAPIV3.Document = { ], components: { schemas: { - Consortium: { + MemoryUsage: { type: "object", properties: { - id: { - type: "string", + rss: { + title: "Resident Set Size", + type: "number", }, - name: { - type: "string", + heapTotal: { + title: "V8 memory usage - heap total", + type: "number", }, - configurationEndpoint: { - type: "string", + heapUsed: { + title: "V8 memory usage - heap used", + type: "number", }, - bifNodes: { - type: "array", - minItems: 1, - items: { - $ref: "#/components/schemas/BifNode", - }, + external: { + title: + "Memory usage of C++ objects bound to JavaScript objects managed by V8", + type: "number", + }, + arrayBuffers: { + title: + "Memory allocated for ArrayBuffers and SharedArrayBuffers, including all Node.js Buffers", + type: "number", }, }, - required: ["id", "name", "configurationEndpoint"], }, - BifNode: { + HealthCheckResponse: { type: "object", properties: { - host: { - type: "string", + success: { + type: "boolean", }, - publicKey: { + createdAt: { type: "string", }, + memoryUsage: { + $ref: "#/components/schemas/MemoryUsage", + }, }, - required: ["host", "publicKeyHex"], + required: ["createdAt", "memoryUsage"], }, }, }, paths: { - "/api/v1/consortium": { - post: { - summary: - "Creates a new consortium from scratch based on the provided parameters.", - requestBody: { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/Consortium", - }, - }, - }, - }, - responses: { - "201": { - description: "Created", - }, - }, - }, - }, - "/api/v1/consortium/{consortiumId}": { + "/api/v1/api-server/healthcheck": { get: { - summary: "Retrieves a consortium", + summary: "Can be used to verify liveness of an API server instance", description: - "The metadata of the consortium (minus the sensitive data)", - parameters: [ - { - in: "path", - name: "consortiumId", - required: true, - schema: { - type: "string", - }, - }, - ], + "Returns the current timestamp of the API server as proof of health/liveness", + parameters: [], responses: { "200": { description: "OK", content: { "application/json": { schema: { - $ref: "#/components/schemas/Consortium", + $ref: "#/components/schemas/HealthCheckResponse", }, }, }, diff --git a/packages/bif-cmd-api-server/src/main/typescript/public-api.ts b/packages/bif-cmd-api-server/src/main/typescript/public-api.ts index ad3587fbc6..9bd1e7e99e 100755 --- a/packages/bif-cmd-api-server/src/main/typescript/public-api.ts +++ b/packages/bif-cmd-api-server/src/main/typescript/public-api.ts @@ -1,6 +1,2 @@ export { ApiServer, IApiServerConstructorOptions } from "./api-server"; export { ConfigService, IBifApiServerOptions } from "./config/config-service"; -export { - CreateConsortiumEndpointV1, - ICreateConsortiumEndpointOptions, -} from "./consortium/routes/create-consortium-endpoint-v1"; diff --git a/packages/bif-cockpit/src/app/app.component.ts b/packages/bif-cockpit/src/app/app.component.ts index cfaa2a8256..3db5630342 100644 --- a/packages/bif-cockpit/src/app/app.component.ts +++ b/packages/bif-cockpit/src/app/app.component.ts @@ -85,16 +85,7 @@ export class AppComponent implements OnInit { const BIF_API_HOST = "http://localhost:4000"; const configuration = new Configuration({ basePath: BIF_API_HOST }); const api = new DefaultApi(configuration); - const response = await api.apiV1ConsortiumPost({ - configurationEndpoint: "domain-and-an-http-endpoint", - id: "asdf", - name: "asdf", - bifNodes: [ - { - host: "BIF-NODE-HOST-1", - publicKey: "FAKE-PUBLIC-KEY", - }, - ], - }); + const response = await api.apiV1ApiServerHealthcheckGet(); + this.logger.info(`HealthcheckResponse: `, response.data); } } diff --git a/packages/bif-common/src/main/typescript/logging/logger-provider.ts b/packages/bif-common/src/main/typescript/logging/logger-provider.ts index 1683b438f8..13e9d4f701 100644 --- a/packages/bif-common/src/main/typescript/logging/logger-provider.ts +++ b/packages/bif-common/src/main/typescript/logging/logger-provider.ts @@ -1,9 +1,14 @@ import { Logger, ILoggerOptions } from "./logger"; +import { LogLevelDesc } from "loglevel"; export class LoggerProvider { private static loggers: Map = new Map(); + private static logLevel: LogLevelDesc = "warn"; public static getOrCreate(loggerOptions: ILoggerOptions) { + // make sure log level is set to global default if otherwise wasn't provided + loggerOptions.level = loggerOptions.level || LoggerProvider.logLevel; + let logger: Logger | undefined = LoggerProvider.loggers.get( loggerOptions.label ); @@ -13,4 +18,16 @@ export class LoggerProvider { } return logger; } + + public static setLogLevel( + logLevel: LogLevelDesc, + applyToCachedLoggers: boolean = true + ) { + LoggerProvider.logLevel = logLevel; + if (applyToCachedLoggers) { + LoggerProvider.loggers.forEach((logger: Logger) => + logger.setLogLevel(logLevel as any) + ); + } + } } diff --git a/packages/bif-common/src/main/typescript/logging/logger.ts b/packages/bif-common/src/main/typescript/logging/logger.ts index 94a0d9a9bc..2ffddf025e 100644 --- a/packages/bif-common/src/main/typescript/logging/logger.ts +++ b/packages/bif-common/src/main/typescript/logging/logger.ts @@ -1,4 +1,8 @@ -import libLogLevel, { Logger as LogLevelLogger, levels } from "loglevel"; +import libLogLevel, { + Logger as LogLevelLogger, + levels, + LogLevelDesc, +} from "loglevel"; import prefix from "loglevel-plugin-prefix"; prefix.reg(libLogLevel); @@ -18,7 +22,7 @@ prefix.apply(libLogLevel, { export interface ILoggerOptions { label: string; - level?: string; + level?: LogLevelDesc; } /** @@ -33,9 +37,13 @@ export class Logger { private readonly backend: LogLevelLogger; constructor(public readonly options: ILoggerOptions) { - const level: string = options.level || "warn"; + const level: LogLevelDesc = options.level || "warn"; this.backend = libLogLevel.getLogger(options.label); - this.backend.setLevel(level.toUpperCase() as any); + this.backend.setLevel(level); + } + + public setLogLevel(logLevel: LogLevelDesc): void { + this.backend.setLevel(logLevel); } public async shutdown(gracePeriodMillis: number = 60000): Promise { diff --git a/packages/bif-common/src/main/typescript/public-api.ts b/packages/bif-common/src/main/typescript/public-api.ts index afcbe4b97f..8f9a958b01 100755 --- a/packages/bif-common/src/main/typescript/public-api.ts +++ b/packages/bif-common/src/main/typescript/public-api.ts @@ -1,2 +1,3 @@ export { LoggerProvider } from "./logging/logger-provider"; export { Logger, ILoggerOptions } from "./logging/logger"; +export { LogLevelDesc } from "loglevel"; diff --git a/packages/bif-core-api/src/main/typescript/plugin/plugin-registry.ts b/packages/bif-core-api/src/main/typescript/plugin/plugin-registry.ts new file mode 100644 index 0000000000..463fdcbbe3 --- /dev/null +++ b/packages/bif-core-api/src/main/typescript/plugin/plugin-registry.ts @@ -0,0 +1,118 @@ +import { Optional } from "typescript-optional"; +import { ICactusPlugin, isICactusPlugin } from "../plugin/i-cactus-plugin"; +import { PluginAspect } from "../plugin/plugin-aspect"; + +/** + * This interface describes the constructor options object that can be used to provide configuration parameters to + * the `PluginRegistry` class instances. + */ +export interface IPluginRegistryOptions { + plugins?: ICactusPlugin[]; +} + +/** + * The plugin registry exists so that plugins can use other plugins as their dependencies in a convenient way where + * we can pass around the plugin registry itself as a simplified and not overly opinionated inversion of control + * container. + * Did consider using libraries made for this specific purpose but they are quite heavy handed and usually require + * decorators on classes. Also, they do not work with interfaces so we also intend to avoid being forced to use actual + * classes in place of the interfaces currently describing the plugin architecture. + */ +export class PluginRegistry { + public readonly plugins: ICactusPlugin[]; + + constructor(public readonly options: IPluginRegistryOptions = {}) { + if (!options) { + throw new TypeError(`PluginRegistry#ctor options falsy`); + } + if (options.plugins && !Array.isArray(options.plugins)) { + throw new TypeError( + `PluginRegistry#ctor options.plugins truthy but non-Array` + ); + } + this.plugins = options.plugins || []; + } + + public getPlugins(): ICactusPlugin[] { + return this.plugins; + } + + /** + * The main difference between this method and `findOneById` is that this throws an Error if there was nothing to + * return. It is recommended to use this method over `findOneById` if you have a hard dependency on a certain + * plugin being loaded for your code. + * + * @param id The ID of the plugin that you are looking to obtain an instance of from the registry. + * @throws If there is no plugin in the registry by the ID specificed. + */ + public getOneById(id: string): T { + return this.findOneById(id).orElseThrow( + () => new Error(`Plugin ${id} not present in registry`) + ) as T; + } + + public getOneByAspect(aspect: PluginAspect): T { + return this.findOneByAspect(aspect).orElseThrow( + () => new Error(`No plugin with aspect: ${aspect}`) + ) as T; + } + + public findOneById(pluginId: string): Optional { + const plugin = this.getPlugins().find((p) => p.getId() === pluginId); + return Optional.ofNullable(plugin as T); + } + + public findManyById(id: string): T[] { + return this.getPlugins().filter((p) => p.getId() === id) as T[]; + } + + public findOneByAspect( + aspect: PluginAspect + ): Optional { + const plugin = this.getPlugins().find((p) => p.getAspect() === aspect); + return Optional.ofNullable(plugin as T); + } + + public findManyByAspect(aspect: PluginAspect): T[] { + return this.getPlugins().filter((p) => p.getAspect() === aspect) as T[]; + } + + public hasByAspect(aspect: PluginAspect): boolean { + return this.findOneByAspect(aspect).isPresent(); + } + + public hasById(id: string): boolean { + return this.findOneById(id).isPresent(); + } + + public deleteById(id: string): [number] { + let deleteCount: number = 0; + this.plugins.forEach((p, i) => { + if (p.getId() === id) { + this.plugins.splice(i, 1); + deleteCount++; + } + }); + return [deleteCount]; + } + + public add( + plugin: ICactusPlugin, + replaceOnConflict: boolean = false + ): [number] { + if (!isICactusPlugin(plugin)) { + throw new Error(`PluginRegistry#add() plugin not an ICactusPlugin`); + } + const id = plugin.getId(); + const hasConfclit = this.hasById(id); + if (hasConfclit && !replaceOnConflict) { + throw new Error(`PluginRegistry#add() already have plugin: ${id}`); + } + let deleteCount: number = 0; + if (replaceOnConflict) { + [deleteCount] = this.deleteById(plugin.getId()); + } + this.getPlugins().push(plugin); + return [deleteCount]; + } +} diff --git a/packages/bif-core-api/src/main/typescript/plugin/web-service/i-plugin-web-service.ts b/packages/bif-core-api/src/main/typescript/plugin/web-service/i-plugin-web-service.ts index 156a42f9d3..c43fa3dd67 100644 --- a/packages/bif-core-api/src/main/typescript/plugin/web-service/i-plugin-web-service.ts +++ b/packages/bif-core-api/src/main/typescript/plugin/web-service/i-plugin-web-service.ts @@ -1,12 +1,25 @@ import { IWebServiceEndpoint } from "./i-web-service-endpoint"; import { ICactusPlugin } from "../i-cactus-plugin"; +import { Server } from "http"; +import { Server as SecureServer } from "https"; +import { Optional } from "typescript-optional"; export interface IPluginWebService extends ICactusPlugin { - installWebService(expressApp: any): IWebServiceEndpoint[]; + installWebServices(expressApp: any): Promise; + getHttpServer(): Optional; + shutdown(): Promise; } export function isIPluginWebService( pluginInstance: any ): pluginInstance is IPluginWebService { - return typeof pluginInstance.installWebService === "function"; + return ( + pluginInstance && + typeof (pluginInstance as IPluginWebService).installWebServices === + "function" && + typeof (pluginInstance as IPluginWebService).getHttpServer === "function" && + typeof (pluginInstance as IPluginWebService).getId === "function" && + typeof (pluginInstance as IPluginWebService).getAspect === "function" && + typeof (pluginInstance as IPluginWebService).shutdown === "function" + ); } diff --git a/packages/bif-core-api/src/main/typescript/public-api.ts b/packages/bif-core-api/src/main/typescript/public-api.ts index 0446eafb95..1265f92bf9 100755 --- a/packages/bif-core-api/src/main/typescript/public-api.ts +++ b/packages/bif-core-api/src/main/typescript/public-api.ts @@ -10,3 +10,4 @@ export { IWebServiceEndpoint } from "./plugin/web-service/i-web-service-endpoint export { PluginFactory } from "./plugin/plugin-factory"; export { ICactusPlugin, isICactusPlugin } from "./plugin/i-cactus-plugin"; export { PluginAspect } from "./plugin/plugin-aspect"; +export { PluginRegistry } from "./plugin/plugin-registry"; diff --git a/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts b/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts index 967f1abc18..2eba8a72d1 100644 --- a/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts +++ b/packages/bif-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts @@ -6,6 +6,10 @@ import { } from "@hyperledger-labs/bif-core-api"; import { Logger, LoggerProvider } from "@hyperledger-labs/bif-common"; +import { promisify } from "util"; +import { Optional } from "typescript-optional"; +import { Server } from "http"; +import { Server as SecureServer } from "https"; import Web3 from "web3"; import { Contract, @@ -41,6 +45,7 @@ export class PluginLedgerConnectorQuorum implements IPluginLedgerConnector, IPluginWebService { private readonly web3: Web3; private readonly log: Logger; + private httpServer: Server | SecureServer | null = null; constructor(public readonly options: IPluginLedgerConnectorQuorumOptions) { if (!options) { @@ -61,7 +66,21 @@ export class PluginLedgerConnectorQuorum }); } - public installWebService(expressApp: any): IWebServiceEndpoint[] { + public getHttpServer(): Optional { + return Optional.ofNullable(this.httpServer); + } + + public async shutdown(): Promise { + const serverMaybe = this.getHttpServer(); + if (serverMaybe.isPresent()) { + const server = serverMaybe.get(); + await promisify(server.close.bind(server))(); + } + } + + public async installWebServices( + expressApp: any + ): Promise { const endpoints: IWebServiceEndpoint[] = []; { const pluginId = this.getId(); // @hyperledger/cactus-plugin-ledger-connector-quorum diff --git a/packages/bif-plugin-web-service-consortium/package-lock.json b/packages/bif-plugin-web-service-consortium/package-lock.json new file mode 100644 index 0000000000..560f2205da --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/package-lock.json @@ -0,0 +1,947 @@ +{ + "name": "@hyperledger-labs/bif-plugin-web-service-consortium", + "version": "0.2.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-8.0.0.tgz", + "integrity": "sha512-n4YBtwQhdpLto1BaUCyAeflizmIbaloGShsPyRtFf5qdFJxfssj+GgLavczgKJFa3Bq+3St2CKcpRJdjtB4EBw==", + "requires": { + "@jsdevtools/ono": "^7.1.0", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, + "@jsdevtools/ono": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz", + "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==" + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", + "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/joi": { + "version": "14.3.4", + "resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.3.4.tgz", + "integrity": "sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A==", + "dev": true + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, + "@types/multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-tWsKbF5LYtXrJ7eOfI0aLBgEv9B7fnJe1JRXTj5+Z6EMfX0yHVsRFsNGnKyN8Bs0gtDv+JR37xAqsPnALyVTqg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/node": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.1.tgz", + "integrity": "sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA==", + "dev": true + }, + "@types/qs": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.2.tgz", + "integrity": "sha512-a9bDi4Z3zCZf4Lv1X/vwnvbbDYSNz59h3i3KdyuYYN+YrLjSeJD0dnphdULDfySvUv6Exy/O0K6wX/kQpnPQ+A==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/secp256k1": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz", + "integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "deasync": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.20.tgz", + "integrity": "sha512-E1GI7jMI57hL30OX6Ht/hfQU8DO4AuB9m72WFm4c38GNbUD4Q03//XZaOIHZiY+H1xUaomcot5yk2q/qIZQkGQ==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "node-addon-api": "^1.7.1" + }, + "dependencies": { + "node-addon-api": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz", + "integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ==", + "optional": true + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-openapi-validator": { + "version": "3.12.9", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-3.12.9.tgz", + "integrity": "sha512-b88G97lb2E3j0f5BHhu7nvwFWOl6ZUOnkxMPilUInKl4pHea63+C8QbG+pQHvYNamowpggMpwpVdlHu7cm9vOQ==", + "requires": { + "ajv": "^6.12.2", + "content-type": "^1.0.4", + "deasync": "^0.1.19", + "js-yaml": "^3.13.1", + "json-schema-ref-parser": "^8.0.0", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0", + "lodash.zipobject": "^4.1.3", + "media-typer": "^1.1.0", + "multer": "^1.4.2", + "ono": "^7.1.2", + "path-to-regexp": "^6.1.0" + }, + "dependencies": { + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "path-to-regexp": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.1.0.tgz", + "integrity": "sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==" + } + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-ref-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-8.0.0.tgz", + "integrity": "sha512-2P4icmNkZLrBr6oa5gSZaDSol/oaBHYkoP/8dsw63E54NnHGRhhiFuy9yFoxPuSm+uHKmeGxAAWMDF16SCHhcQ==", + "requires": { + "@apidevtools/json-schema-ref-parser": "8.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "lodash.zipobject": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lodash.zipobject/-/lodash.zipobject-4.1.3.tgz", + "integrity": "sha1-s5n1q6j/YqdG9peb8gshT5ZNvvg=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + }, + "node-gyp-build": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.2.tgz", + "integrity": "sha512-Lqh7mrByWCM8Cf9UPqpeoVBBo5Ugx+RKu885GAzmLBVYjeywScxHXPGLa4JfYNZmcNGwzR0Glu5/9GaQZMFqyA==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "ono": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ono/-/ono-7.1.2.tgz", + "integrity": "sha512-es7Gfr+OGNFwiYpyHCLgBF+p/RA0qYbWysQKlZbLvvUBis5BygEs8TVJ4r+SgHDfagOgONhaAl6Y4JLy++0MTw==", + "requires": { + "@jsdevtools/ono": "7.1.2" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "secp256k1": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.1.tgz", + "integrity": "sha512-iGRjbGAKfXMqhtdkkuNxsgJQfJO8Oo78Rm7DAvsG3XKngq+nJIOGqrCSXcQqIVsmCj0wFanE5uTKFxV3T9j2wg==", + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typescript-optional": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/typescript-optional/-/typescript-optional-2.0.1.tgz", + "integrity": "sha512-xuwmqsCjE4OeeMKxbNX3jjNcISGzYh5Q9R1rM5OyxEVNIr94CB5llCkfKW+1nZTKbbUV0axN3QAUuX2fus/DhQ==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/packages/bif-plugin-web-service-consortium/package.json b/packages/bif-plugin-web-service-consortium/package.json new file mode 100755 index 0000000000..5cbba71304 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/package.json @@ -0,0 +1,73 @@ +{ + "name": "@hyperledger-labs/bif-plugin-web-service-consortium", + "version": "0.2.0", + "description": "A web service plugin that provides management capabilities on a Cactus consortium as a whole for administrative purposes.", + "main": "dist/bif-plugin-web-service-consortium.node.umd.js", + "mainMinified": "dist/bif-plugin-web-service-consortium.node.umd.min.js", + "browser": "dist/bif-plugin-web-service-consortium.web.umd.js", + "browserMinified": "dist/bif-plugin-web-service-consortium.web.umd.min.js", + "module": "dist/lib/main/typescript/index.js", + "types": "dist/types/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "export-open-api-spec": "ts-node -e 'import(\"./src/main/typescript/openapi-spec\").then((x) => x.exportToFileSystemAsJson());'", + "pregenerate-sdk": "npm-run-all export-open-api-spec", + "generate-sdk": "openapi-generator generate --input-spec generated-sources/cactus-openapi-spec-plugin-web-service-consortium.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/", + "tsc": "tsc --project ./tsconfig.json", + "posttsc": "npm run generate-sdk" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger-labs/blockchain-integration-framework.git" + }, + "keywords": [ + "Hyperledger", + "Blockchain", + "Interoperability", + "Integration" + ], + "author": { + "name": "Peter Somogyvari", + "email": "peter.somogyvari@accenture.com", + "url": "https://accenture.com" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + } + ], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/hyperledger-labs/blockchain-integration-framework/issues" + }, + "homepage": "https://github.com/hyperledger-labs/blockchain-integration-framework#readme", + "dependencies": { + "@hyperledger-labs/bif-common": "0.2.0", + "@hyperledger-labs/bif-core-api": "^0.2.0", + "axios": "0.19.2", + "body-parser": "1.19.0", + "express": "4.17.1", + "express-openapi-validator": "3.12.9", + "joi": "14.3.1", + "js-sha3": "0.8.0", + "secp256k1": "4.0.1", + "typescript-optional": "2.0.1" + }, + "devDependencies": { + "@hyperledger-labs/bif-cmd-api-server": "0.2.0", + "@hyperledger-labs/bif-plugin-kv-storage-memory": "0.2.0", + "@hyperledger-labs/bif-sdk": "0.2.0", + "@types/express": "4.17.6", + "@types/joi": "14.3.4", + "@types/multer": "1.4.3", + "@types/secp256k1": "4.0.1" + } +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts new file mode 100644 index 0000000000..40c44cedfc --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts @@ -0,0 +1,87 @@ +import { Request, Response, NextFunction } from "express"; +import secp256k1 from "secp256k1"; +import { keccak256 } from "js-sha3"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + ICactusPlugin, +} from "@hyperledger-labs/bif-core-api"; +import { IPluginKVStorage } from "@hyperledger-labs/bif-core-api"; +import { Consortium } from "../generated/openapi/typescript-axios"; + +export interface ICreateConsortiumEndpointOptions { + storage: IPluginKVStorage; + privateKey: string; + path: string; + hostPlugin: ICactusPlugin; +} + +export class CreateConsortiumEndpointV1 implements IWebServiceEndpoint { + constructor(public readonly options: ICreateConsortiumEndpointOptions) { + if (!options) { + throw new Error(`CreateConsortiumEndpointV1#ctor options falsy.`); + } + if (!options.privateKey) { + throw new Error( + `CreateConsortiumEndpointV1#ctor options.privateKey falsy.` + ); + } + if (!options.storage) { + throw new Error(`CreateConsortiumEndpointV1#ctor options.storage falsy.`); + } + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + getPath(): string { + return this.options.path; + } + + async handleRequest( + req: Request, + res: Response, + next: NextFunction + ): Promise { + try { + const consortium: Consortium = req.body; + const idAlreadyExists = await this.options.storage.has(consortium.id); + if (idAlreadyExists) { + res.status(400); + res.json({ + success: false, + message: `Consortium with ID ${consortium.id} already exists.`, + }); + } else { + // FIXME: We need a library handling the crypto, how about NodeJS bindings for Ursa? + const privateKey = this.options.privateKey; + const privateKeyBytes = Uint8Array.from(Buffer.from(privateKey, "hex")); + const consortiumJson: string = JSON.stringify(consortium); + const consortiumBytesHash = Uint8Array.from( + keccak256.array(consortiumJson) + ); + const signatureWrapper = secp256k1.ecdsaSign( + consortiumBytesHash, + privateKeyBytes + ); + const signature = Buffer.from(signatureWrapper.signature).toString( + "hex" + ); + const consortiumWrapper = { + signature, + consortiumJson, + }; + const wrapperJson = JSON.stringify(consortiumWrapper); + // tslint:disable-next-line: no-console + await this.options.storage.set(consortium.id, wrapperJson); + res.status(201); + res.json({ success: true, consortiumWrapper }); + } + } catch (ex) { + res.status(500); + res.json({ error: ex.stack }); + } + } +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.gitignore b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.gitignore new file mode 100644 index 0000000000..149b576547 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore new file mode 100644 index 0000000000..7484ee590a --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 0000000000..ec87108d82 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION @@ -0,0 +1 @@ +4.2.3 \ No newline at end of file diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 0000000000..517a9c22ab --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,254 @@ +// tslint:disable +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative priviliges. + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import * as globalImportUrl from 'url'; +import { Configuration } from './configuration'; +import globalAxios, { AxiosPromise, AxiosInstance } from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base'; + +/** + * + * @export + * @interface BifNode + */ +export interface BifNode { + /** + * + * @type {string} + * @memberof BifNode + */ + host: string; + /** + * + * @type {string} + * @memberof BifNode + */ + publicKey?: string; +} +/** + * + * @export + * @interface Consortium + */ +export interface Consortium { + /** + * + * @type {string} + * @memberof Consortium + */ + id: string; + /** + * + * @type {string} + * @memberof Consortium + */ + name: string; + /** + * + * @type {string} + * @memberof Consortium + */ + configurationEndpoint: string; + /** + * + * @type {Array} + * @memberof Consortium + */ + bifNodes?: Array; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * The metadata of the consortium (minus the sensitive data) + * @summary Retrieves a consortium + * @param {string} consortiumId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options: any = {}): RequestArgs { + // verify required parameter 'consortiumId' is not null or undefined + if (consortiumId === null || consortiumId === undefined) { + throw new RequiredError('consortiumId','Required parameter consortiumId was null or undefined when calling apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet.'); + } + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium/{consortiumId}` + .replace(`{${"consortiumId"}}`, encodeURIComponent(String(consortiumId))); + const localVarUrlObj = globalImportUrl.parse(localVarPath, true); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + delete localVarUrlObj.search; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + + return { + url: globalImportUrl.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Creates a new consortium from scratch based on the provided parameters. + * @param {Consortium} consortium + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options: any = {}): RequestArgs { + // verify required parameter 'consortium' is not null or undefined + if (consortium === null || consortium === undefined) { + throw new RequiredError('consortium','Required parameter consortium was null or undefined when calling apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost.'); + } + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium`; + const localVarUrlObj = globalImportUrl.parse(localVarPath, true); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + delete localVarUrlObj.search; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + const needsSerialization = (typeof consortium !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json'; + localVarRequestOptions.data = needsSerialization ? JSON.stringify(consortium !== undefined ? consortium : {}) : (consortium || ""); + + return { + url: globalImportUrl.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + return { + /** + * The metadata of the consortium (minus the sensitive data) + * @summary Retrieves a consortium + * @param {string} consortiumId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { + const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId, options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, + /** + * + * @summary Creates a new consortium from scratch based on the provided parameters. + * @param {Consortium} consortium + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { + const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium, options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + return { + /** + * The metadata of the consortium (minus the sensitive data) + * @summary Retrieves a consortium + * @param {string} consortiumId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options?: any): AxiosPromise { + return DefaultApiFp(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId, options)(axios, basePath); + }, + /** + * + * @summary Creates a new consortium from scratch based on the provided parameters. + * @param {Consortium} consortium + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options?: any): AxiosPromise { + return DefaultApiFp(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium, options)(axios, basePath); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * The metadata of the consortium (minus the sensitive data) + * @summary Retrieves a consortium + * @param {string} consortiumId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options?: any) { + return DefaultApiFp(this.configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId, options)(this.axios, this.basePath); + } + + /** + * + * @summary Creates a new consortium from scratch based on the provided parameters. + * @param {Consortium} consortium + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options?: any) { + return DefaultApiFp(this.configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium, options)(this.axios, this.basePath); + } + +} + + diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 0000000000..0bb8c113dd --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,70 @@ +// tslint:disable +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative priviliges. + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { Configuration } from "./configuration"; +// Some imports not used depending on template conditions +// @ts-ignore +import globalAxios, { AxiosPromise, AxiosInstance } from 'axios'; + +export const BASE_PATH = "https://www.hlbif.win".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: any; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 0000000000..52fb28e9d8 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,75 @@ +// tslint:disable +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative priviliges. + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | ((name: string) => string); + username?: string; + password?: string; + accessToken?: string | ((name?: string, scopes?: string[]) => string); + basePath?: string; + baseOptions?: any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | ((name: string) => string); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | ((name?: string, scopes?: string[]) => string); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + } +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/git_push.sh b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/git_push.sh new file mode 100644 index 0000000000..ced3be2b0c --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/git_push.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 0000000000..eab00b6508 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,16 @@ +// tslint:disable +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative priviliges. + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/index.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts new file mode 100644 index 0000000000..42925fea4b --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts @@ -0,0 +1,134 @@ +import * as OpenAPI from "express-openapi-validator/dist/framework/types"; + +export const BIF_OPEN_API_JSON: OpenAPI.OpenAPIV3.Document = { + openapi: "3.0.3", + info: { + title: "Hyperledger Cactus Plugin - Consortium Web Service", + description: + "Manage a Cactus consortium through the APIs. Needs administrative priviliges.", + version: "0.0.1", + }, + servers: [ + { + url: "https://www.hlbif.win/{basePath}", + description: "Public test instance", + variables: { + basePath: { + default: "", + }, + }, + }, + { + url: "http://localhost:4000/{basePath}", + description: "Local test instance", + variables: { + basePath: { + default: "", + }, + }, + }, + ], + components: { + schemas: { + Consortium: { + type: "object", + properties: { + id: { + type: "string", + }, + name: { + type: "string", + }, + configurationEndpoint: { + type: "string", + }, + bifNodes: { + type: "array", + minItems: 1, + items: { + $ref: "#/components/schemas/BifNode", + }, + }, + }, + required: ["id", "name", "configurationEndpoint"], + }, + BifNode: { + type: "object", + properties: { + host: { + type: "string", + }, + publicKey: { + type: "string", + }, + }, + required: ["host", "publicKeyHex"], + }, + }, + }, + paths: { + "/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium": { + post: { + summary: + "Creates a new consortium from scratch based on the provided parameters.", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Consortium", + }, + }, + }, + }, + responses: { + "201": { + description: "Created", + }, + }, + }, + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium/{consortiumId}": { + get: { + summary: "Retrieves a consortium", + description: + "The metadata of the consortium (minus the sensitive data)", + parameters: [ + { + in: "path", + name: "consortiumId", + required: true, + schema: { + type: "string", + }, + }, + ], + responses: { + "200": { + description: "OK", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Consortium", + }, + }, + }, + }, + }, + }, + }, + }, +}; + +export async function exportToFileSystemAsJson(): Promise { + const fs = await import("fs"); + const destination = + process.argv[2] || + "./generated-sources/cactus-openapi-spec-plugin-web-service-consortium.json"; + + // tslint:disable-next-line: no-console + console.log( + `OpenApiSpec#exportToFileSystemAsJson() destination=${destination}` + ); + fs.writeFileSync(destination, JSON.stringify(BIF_OPEN_API_JSON, null, 4)); +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts new file mode 100644 index 0000000000..18e84b40a8 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts @@ -0,0 +1,16 @@ +import { PluginFactory } from "@hyperledger-labs/bif-core-api"; +import { + IPluginWebServiceConsortiumOptions, + PluginWebServiceConsortium, +} from "./plugin-web-service-consortium"; + +export class PluginFactoryWebService extends PluginFactory< + PluginWebServiceConsortium, + IPluginWebServiceConsortiumOptions +> { + async create( + options: IPluginWebServiceConsortiumOptions + ): Promise { + return new PluginWebServiceConsortium(options); + } +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts new file mode 100644 index 0000000000..b2e0de2072 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts @@ -0,0 +1,120 @@ +import { Server } from "http"; +import { Server as SecureServer } from "https"; +import { Optional } from "typescript-optional"; +import { promisify } from "util"; +import express, { Express } from "express"; +import bodyParser from "body-parser"; + +import { + IPluginWebService, + PluginAspect, + IPluginKVStorage, + PluginRegistry, + IWebServiceEndpoint, +} from "@hyperledger-labs/bif-core-api"; +import { Logger, LoggerProvider } from "@hyperledger-labs/bif-common"; +import { CreateConsortiumEndpointV1 } from "./consortium/create-consortium-endpoint-v1"; + +export interface IWebAppOptions { + port: number; + hostname: string; +} + +export interface IPluginWebServiceConsortiumOptions { + privateKey: string; + pluginRegistry: PluginRegistry; + logLevel?: string; + webAppOptions?: IWebAppOptions; +} + +export class PluginWebServiceConsortium implements IPluginWebService { + private readonly log: Logger; + private httpServer: Server | SecureServer | null = null; + + constructor(public readonly options: IPluginWebServiceConsortiumOptions) { + if (!options) { + throw new Error(`PluginWebServiceConsortium#ctor options falsy.`); + } + this.log = LoggerProvider.getOrCreate({ + label: "plugin-web-service-consortium", + }); + } + + public async shutdown(): Promise { + this.log.info(`Shutting down...`); + const serverMaybe = this.getHttpServer(); + if (serverMaybe.isPresent()) { + this.log.info(`Awaiting server.close() ...`); + const server = serverMaybe.get(); + await promisify(server.close.bind(server))(); + this.log.info(`server.close() OK`); + } else { + this.log.info(`No HTTP server found, skipping...`); + } + } + + public async installWebServices( + expressApp: any + ): Promise { + this.log.info(`Installing web services for plugin ${this.getId()}...`); + const webApp: Express = this.options.webAppOptions ? express() : expressApp; + + // presence of webAppOptions implies that caller wants the plugin to configure it's own express instance on a custom + // host/port to listen on + if (this.options.webAppOptions) { + this.log.info(`Creating dedicatd HTTP server...`); + const { port, hostname } = this.options.webAppOptions; + + webApp.use(bodyParser.json({ limit: "50mb" })); + + const address = await new Promise((resolve, reject) => { + const httpServer = webApp.listen(port, hostname, (err: any) => { + if (err) { + reject(err); + this.log.error(`Failed to create dedicatd HTTP server`, err); + } else { + this.httpServer = httpServer; + const theAddress = this.httpServer.address(); + resolve(theAddress); + } + }); + }); + this.log.info(`Creation of HTTP server OK`, { address }); + } + + const endpoints: IWebServiceEndpoint[] = []; + { + const pluginId = this.getId(); + const path = `/api/v1/plugins/${pluginId}/consortium/`; + const storage = this.options.pluginRegistry.getOneByAspect< + IPluginKVStorage + >(PluginAspect.KV_STORAGE); + const endpoint: IWebServiceEndpoint = new CreateConsortiumEndpointV1({ + path, + hostPlugin: this, + privateKey: this.options.privateKey, + storage, + }); + webApp.post(endpoint.getPath(), endpoint.getExpressRequestHandler()); + endpoints.push(endpoint); + this.log.info(`Registered contract deployment endpoint at ${path}`); + } + + this.log.info(`Installed web services for plugin ${this.getId()} OK`, { + endpoints, + }); + return endpoints; + } + + public getHttpServer(): Optional { + return Optional.ofNullable(this.httpServer); + } + + public getId(): string { + return `@hyperledger/cactus-plugin-web-service-consortium`; + } + + public getAspect(): PluginAspect { + return PluginAspect.WEB_SERVICE; + } +} diff --git a/packages/bif-plugin-web-service-consortium/src/main/typescript/public-api.ts b/packages/bif-plugin-web-service-consortium/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..bbe4ae4a03 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/main/typescript/public-api.ts @@ -0,0 +1,7 @@ +export { + PluginWebServiceConsortium, + IPluginWebServiceConsortiumOptions, + IWebAppOptions, +} from "./plugin-web-service-consortium"; +export { PluginFactoryWebService } from "./plugin-factory-web-service-consortium"; +export * from "./generated/openapi/typescript-axios/index"; diff --git a/packages/bif-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts b/packages/bif-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts new file mode 100644 index 0000000000..73a563e812 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts @@ -0,0 +1,10 @@ +// tslint:disable-next-line: no-var-requires +const tap = require("tap"); +import * as publicApi from "../../../main/typescript/public-api"; + +tap.pass("Test file can be executed"); + +tap.test("Library can be loaded", (assert: any) => { + assert.plan(1); + assert.ok(publicApi); +}); diff --git a/packages/bif-plugin-web-service-consortium/src/test/typescript/integration/tap-parallel-not-ok b/packages/bif-plugin-web-service-consortium/src/test/typescript/integration/tap-parallel-not-ok new file mode 100755 index 0000000000..e69de29bb2 diff --git a/packages/bif-plugin-web-service-consortium/src/test/typescript/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts b/packages/bif-plugin-web-service-consortium/src/test/typescript/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts new file mode 100644 index 0000000000..7c04ced5ab --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/test/typescript/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts @@ -0,0 +1,140 @@ +// tslint:disable-next-line: no-var-requires +const tap = require("tap"); +import { AxiosResponse } from "axios"; +import { Server } from "http"; +import { Logger, LoggerProvider } from "@hyperledger-labs/bif-common"; +import { + ApiServer, + ConfigService, + IBifApiServerOptions, +} from "@hyperledger-labs/bif-cmd-api-server"; +import { ICactusPlugin, PluginRegistry } from "@hyperledger-labs/bif-core-api"; +import { PluginKVStorageMemory } from "@hyperledger-labs/bif-plugin-kv-storage-memory"; +import { + DefaultApi, + Configuration, + HealthCheckResponse, +} from "@hyperledger-labs/bif-sdk"; +import { + DefaultApi as DefaultApiPlugin, + Configuration as ConfigurationPlugin, + PluginWebServiceConsortium, + IPluginWebServiceConsortiumOptions, +} from "../../../main/typescript/"; + +LoggerProvider.setLogLevel("TRACE"); +const log: Logger = LoggerProvider.getOrCreate({ + label: "security-isolation-via-api-server-ports", +}); + +tap.test( + "pulls up API server with consortium web service on different port", + async (assert: any) => { + // 1. Instantiate a key value storage plugin that works in memory (good here because we don't need persistence) + const kvStoragePlugin = new PluginKVStorageMemory({ backend: new Map() }); + + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [kvStoragePlugin] }); + + // 3. Instantiate the web service consortium plugin which will host itself on a new TCP port for isolation/security + // Note that if we omitted the `webAppOptions` object that the web service plugin would default to installing itself + // on the default port of the API server. This allows for flexibility in deployments. + const options: IPluginWebServiceConsortiumOptions = { + pluginRegistry, + privateKey: + "4eb8be4f03c19623c884c584e7b1baacf352bf7bf399330a212d90e32fff64da", + logLevel: "trace", + webAppOptions: { + hostname: "127.0.0.1", + port: 0, + }, + }; + const webServiceConsortiumPlugin = new PluginWebServiceConsortium(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const bifApiServerOptions: IBifApiServerOptions = configService.newExampleConfig(); + bifApiServerOptions.configFile = ""; + bifApiServerOptions.apiCorsDomainCsv = "*"; + bifApiServerOptions.apiPort = 0; + const config = configService.newExampleConfigConvict(bifApiServerOptions); + + const plugins: ICactusPlugin[] = []; + plugins.push(webServiceConsortiumPlugin); + plugins.push(kvStoragePlugin); + + const apiServer = new ApiServer({ config, plugins }); + assert.tearDown(() => apiServer.shutdown()); + + // 5. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 6. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + const httpServer = apiServer.getHttpServerApi(); + const addressInfo: any = httpServer?.address(); + log.debug(`AddressInfo: `, addressInfo); + const CACTUS_API_HOST = `http://${addressInfo.address}:${addressInfo.port}`; + + const configuration = new Configuration({ basePath: CACTUS_API_HOST }); + const api = new DefaultApi(configuration); + + // 7. Issue an API call to the API server via the main SDK verifying that the SDK and the API server both work + const healthcheckResponse: AxiosResponse = await api.apiV1ApiServerHealthcheckGet(); + assert.ok(healthcheckResponse); + assert.ok(healthcheckResponse.data); + assert.ok(healthcheckResponse.data.success); + assert.ok(healthcheckResponse.data.memoryUsage); + assert.ok(healthcheckResponse.data.createdAt); + + // 8. Get the dedicated HTTP server of the web service plugin + const httpServerConsortium: Server = webServiceConsortiumPlugin + .getHttpServer() + .orElseThrow( + () => new Error("webServiceConsortiumPlugin HTTP server is not present") + ); + assert.ok(httpServerConsortium, "Get the plugin specific HTTP server"); + const addressInfoConsortium: any = httpServerConsortium.address(); + assert.ok( + addressInfoConsortium, + "Get the plugin specific AddressInfo object" + ); + assert.ok( + addressInfoConsortium.port, + "plugin specific web app address info has a port" + ); + assert.ok( + addressInfoConsortium.address, + "plugin specific web app address info has an address (host)" + ); + + // 9. Verify that the web service plugin is on a different port for security isolation + assert.ok( + addressInfoConsortium.port !== addressInfo.port, + "plugin specific and API server base port are different" + ); + + const CACTUS_API_HOST2 = `http://${addressInfoConsortium.address}:${addressInfoConsortium.port}`; + const configuration2 = new ConfigurationPlugin({ + basePath: CACTUS_API_HOST2, + baseOptions: { timeout: 2000 }, + }); + const api2 = new DefaultApiPlugin(configuration2); + + const consortium = { + configurationEndpoint: "fake", + id: "adsf", + name: "asdf", + bifNodes: [{ host: "asdf", publicKey: "adsf" }], + }; + const response: AxiosResponse = await api2.apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost( + consortium + ); + assert.ok( + response, + "expect a truthy response object from consortium POST endpoint" + ); + assert.ok(response.status === 201, "expect HTTP status code to equal 201"); + + assert.end(); + } +); diff --git a/packages/bif-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts b/packages/bif-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts new file mode 100644 index 0000000000..73a563e812 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts @@ -0,0 +1,10 @@ +// tslint:disable-next-line: no-var-requires +const tap = require("tap"); +import * as publicApi from "../../../main/typescript/public-api"; + +tap.pass("Test file can be executed"); + +tap.test("Library can be loaded", (assert: any) => { + assert.plan(1); + assert.ok(publicApi); +}); diff --git a/packages/bif-plugin-web-service-consortium/tsconfig.json b/packages/bif-plugin-web-service-consortium/tsconfig.json new file mode 100644 index 0000000000..d90c929ef9 --- /dev/null +++ b/packages/bif-plugin-web-service-consortium/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist/lib/", /* Redirect output structure to the directory. */ + "declarationDir": "dist/types", + }, + "include": [ + "./src" + ] +} \ No newline at end of file diff --git a/packages/bif-sdk/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/bif-sdk/src/main/typescript/generated/openapi/typescript-axios/api.ts index 9e3f6fa9a4..6e8d067960 100644 --- a/packages/bif-sdk/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/bif-sdk/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -22,52 +22,64 @@ import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } fr /** * * @export - * @interface BifNode + * @interface HealthCheckResponse */ -export interface BifNode { +export interface HealthCheckResponse { /** * - * @type {string} - * @memberof BifNode + * @type {boolean} + * @memberof HealthCheckResponse */ - host: string; + success?: boolean; /** * * @type {string} - * @memberof BifNode + * @memberof HealthCheckResponse + */ + createdAt: string; + /** + * + * @type {MemoryUsage} + * @memberof HealthCheckResponse */ - publicKey?: string; + memoryUsage: MemoryUsage; } /** * * @export - * @interface Consortium + * @interface MemoryUsage */ -export interface Consortium { +export interface MemoryUsage { /** * - * @type {string} - * @memberof Consortium + * @type {number} + * @memberof MemoryUsage */ - id: string; + rss?: number; /** * - * @type {string} - * @memberof Consortium + * @type {number} + * @memberof MemoryUsage */ - name: string; + heapTotal?: number; /** * - * @type {string} - * @memberof Consortium + * @type {number} + * @memberof MemoryUsage */ - configurationEndpoint: string; + heapUsed?: number; /** * - * @type {Array} - * @memberof Consortium + * @type {number} + * @memberof MemoryUsage */ - bifNodes?: Array; + external?: number; + /** + * + * @type {number} + * @memberof MemoryUsage + */ + arrayBuffers?: number; } /** @@ -77,19 +89,13 @@ export interface Consortium { export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { return { /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId + * Returns the current timestamp of the API server as proof of health/liveness + * @summary Can be used to verify liveness of an API server instance * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiV1ConsortiumConsortiumIdGet(consortiumId: string, options: any = {}): RequestArgs { - // verify required parameter 'consortiumId' is not null or undefined - if (consortiumId === null || consortiumId === undefined) { - throw new RequiredError('consortiumId','Required parameter consortiumId was null or undefined when calling apiV1ConsortiumConsortiumIdGet.'); - } - const localVarPath = `/api/v1/consortium/{consortiumId}` - .replace(`{${"consortiumId"}}`, encodeURIComponent(String(consortiumId))); + apiV1ApiServerHealthcheckGet(options: any = {}): RequestArgs { + const localVarPath = `/api/v1/api-server/healthcheck`; const localVarUrlObj = globalImportUrl.parse(localVarPath, true); let baseOptions; if (configuration) { @@ -106,44 +112,6 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati delete localVarUrlObj.search; localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; - return { - url: globalImportUrl.format(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1ConsortiumPost(consortium: Consortium, options: any = {}): RequestArgs { - // verify required parameter 'consortium' is not null or undefined - if (consortium === null || consortium === undefined) { - throw new RequiredError('consortium','Required parameter consortium was null or undefined when calling apiV1ConsortiumPost.'); - } - const localVarPath = `/api/v1/consortium`; - const localVarUrlObj = globalImportUrl.parse(localVarPath, true); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; - // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 - delete localVarUrlObj.search; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; - const needsSerialization = (typeof consortium !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json'; - localVarRequestOptions.data = needsSerialization ? JSON.stringify(consortium !== undefined ? consortium : {}) : (consortium || ""); - return { url: globalImportUrl.format(localVarUrlObj), options: localVarRequestOptions, @@ -159,28 +127,13 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati export const DefaultApiFp = function(configuration?: Configuration) { return { /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId + * Returns the current timestamp of the API server as proof of health/liveness + * @summary Can be used to verify liveness of an API server instance * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiV1ConsortiumConsortiumIdGet(consortiumId: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { - const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1ConsortiumConsortiumIdGet(consortiumId, options); - return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { - const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; - return axios.request(axiosRequestArgs); - }; - }, - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1ConsortiumPost(consortium: Consortium, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { - const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1ConsortiumPost(consortium, options); + apiV1ApiServerHealthcheckGet(options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { + const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1ApiServerHealthcheckGet(options); return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; return axios.request(axiosRequestArgs); @@ -196,24 +149,13 @@ export const DefaultApiFp = function(configuration?: Configuration) { export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { return { /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId + * Returns the current timestamp of the API server as proof of health/liveness + * @summary Can be used to verify liveness of an API server instance * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiV1ConsortiumConsortiumIdGet(consortiumId: string, options?: any): AxiosPromise { - return DefaultApiFp(configuration).apiV1ConsortiumConsortiumIdGet(consortiumId, options)(axios, basePath); - }, - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1ConsortiumPost(consortium: Consortium, options?: any): AxiosPromise { - return DefaultApiFp(configuration).apiV1ConsortiumPost(consortium, options)(axios, basePath); + apiV1ApiServerHealthcheckGet(options?: any): AxiosPromise { + return DefaultApiFp(configuration).apiV1ApiServerHealthcheckGet(options)(axios, basePath); }, }; }; @@ -226,27 +168,14 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa */ export class DefaultApi extends BaseAPI { /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof DefaultApi - */ - public apiV1ConsortiumConsortiumIdGet(consortiumId: string, options?: any) { - return DefaultApiFp(this.configuration).apiV1ConsortiumConsortiumIdGet(consortiumId, options)(this.axios, this.basePath); - } - - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium + * Returns the current timestamp of the API server as proof of health/liveness + * @summary Can be used to verify liveness of an API server instance * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi */ - public apiV1ConsortiumPost(consortium: Consortium, options?: any) { - return DefaultApiFp(this.configuration).apiV1ConsortiumPost(consortium, options)(this.axios, this.basePath); + public apiV1ApiServerHealthcheckGet(options?: any) { + return DefaultApiFp(this.configuration).apiV1ApiServerHealthcheckGet(options)(this.axios, this.basePath); } } diff --git a/packages/bif-test-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-via-web-service.ts b/packages/bif-test-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-via-web-service.ts index ef59a769c7..7b550fd158 100644 --- a/packages/bif-test-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-via-web-service.ts +++ b/packages/bif-test-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/deploy-contract-via-web-service.ts @@ -1,6 +1,6 @@ // tslint:disable-next-line: no-var-requires const tap = require("tap"); -import axios, { AxiosPromise, AxiosInstance } from "axios"; +import axios, { AxiosPromise, AxiosInstance, AxiosResponse } from "axios"; import { QuorumTestLedger, IQuorumGenesisOptions, @@ -8,12 +8,7 @@ import { } from "@hyperledger-labs/bif-test-tooling"; import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; import { Logger, LoggerProvider } from "@hyperledger-labs/bif-common"; -import { - Web3EthContract, - IQuorumDeployContractOptions, - PluginLedgerConnectorQuorum, - PluginFactoryLedgerConnector, -} from "@hyperledger-labs/bif-plugin-ledger-connector-quorum"; +import { PluginLedgerConnectorQuorum } from "@hyperledger-labs/bif-plugin-ledger-connector-quorum"; import { ApiServer, ConfigService, @@ -21,7 +16,11 @@ import { } from "@hyperledger-labs/bif-cmd-api-server"; import { ICactusPlugin } from "@hyperledger-labs/bif-core-api"; import { PluginKVStorageMemory } from "@hyperledger-labs/bif-plugin-kv-storage-memory"; -import { DefaultApi, Configuration } from "@hyperledger-labs/bif-sdk"; +import { + DefaultApi, + Configuration, + HealthCheckResponse, +} from "@hyperledger-labs/bif-sdk"; const log: Logger = LoggerProvider.getOrCreate({ label: "test-deploy-contract-via-web-service", @@ -90,19 +89,12 @@ tap.test( const api = new DefaultApi(configuration); // 7. Issue an API call to the API server via the SDK verifying that the SDK and the API server both work - const response = await api.apiV1ConsortiumPost({ - configurationEndpoint: "domain-and-an-http-endpoint", - id: "asdf", - name: "asdf", - bifNodes: [ - { - host: "BIF-NODE-HOST-1", - publicKey: "FAKE-PUBLIC-KEY", - }, - ], - }); - assert.ok(response); - assert.ok(response.status > 199 && response.status < 300); + const healthcheckResponse: AxiosResponse = await api.apiV1ApiServerHealthcheckGet(); + assert.ok(healthcheckResponse); + assert.ok(healthcheckResponse.data); + assert.ok(healthcheckResponse.data.success); + assert.ok(healthcheckResponse.data.memoryUsage); + assert.ok(healthcheckResponse.data.createdAt); // 8. Assemble request to invoke the deploy contract method of the quorum ledger connector plugin via the REST API const bodyObject = { @@ -117,7 +109,7 @@ tap.test( const response2 = await axios.post(url, bodyObject, {}); assert.ok(response2, "Response for contract deployment is truthy"); assert.ok( - response2.status > 199 && response.status < 300, + response2.status > 199 && healthcheckResponse.status < 300, "Response status code for contract deployment is 2xx" ); assert.end(); diff --git a/webpack.dev.web.js b/webpack.dev.web.js index 81e50f544a..3f5349e013 100644 --- a/webpack.dev.web.js +++ b/webpack.dev.web.js @@ -55,5 +55,7 @@ module.exports = { umdNamedDefine: true, globalObject: "this", }, - externals: {}, + externals: { + express: "express", + }, }; diff --git a/webpack.prod.web.js b/webpack.prod.web.js index 97dbdf562d..172f9ab9cc 100644 --- a/webpack.prod.web.js +++ b/webpack.prod.web.js @@ -59,5 +59,7 @@ module.exports = { umdNamedDefine: true, globalObject: "this", }, - externals: {}, + externals: { + express: "express", + }, };