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", + }, };