diff --git a/packages/cactus-plugin-ledger-connector-ethereum/package.json b/packages/cactus-plugin-ledger-connector-ethereum/package.json index 21c835ccad..7eef38e4f8 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/package.json +++ b/packages/cactus-plugin-ledger-connector-ethereum/package.json @@ -65,11 +65,13 @@ "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js" }, "dependencies": { + "@apidevtools/json-schema-ref-parser": "12.0.1", "@ethereumjs/common": "4.0.0", "@ethereumjs/tx": "5.0.0", "@hyperledger/cactus-common": "2.1.0", "@hyperledger/cactus-core": "2.1.0", "@hyperledger/cactus-core-api": "2.1.0", + "ajv": "8.17.1", "axios": "1.8.4", "ethers": "6.8.1", "express": "5.1.0", diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts index b05396bdac..38e02412fa 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/main/typescript/web-services/deploy-contract-v1-endpoint.ts @@ -1,4 +1,6 @@ import { Express, Request, Response } from "express"; +import { Ajv } from "ajv"; +import RefParser from "@apidevtools/json-schema-ref-parser"; import { IWebServiceEndpoint, @@ -22,6 +24,53 @@ import OAS from "../../json/openapi.json"; import { ERR_INVALID_RESPONSE } from "web3"; import { isWeb3Error } from "../public-api"; +async function preprocessSchema() { + const resolvedSchema = await RefParser.dereference(OAS); + function adjustNullable(obj: Record) { + if (typeof obj !== "object" || obj === null) return; + if ("nullable" in obj && !("type" in obj)) { + delete obj.nullable; + } + if (obj.nullable === true && obj.type && typeof obj.type === "string") { + obj.type = [obj.type, "null"]; + delete obj.nullable; + } + if (obj.nullable === false) { + delete obj.nullable; + } + for (const key in obj) { + if (typeof obj[key] === "object" && obj[key] !== null) { + adjustNullable(obj[key] as Record); + } + } + } + + adjustNullable(resolvedSchema as Record); + return resolvedSchema; +} + +let validateDeployContract: ReturnType | undefined; +async function getValidateDeployContract(): Promise< + ReturnType +> { + if (validateDeployContract) { + return validateDeployContract; + } + + const ajv = new Ajv({ + allErrors: true, + strict: false, + allowMatchingProperties: true, + }); + const processedSchema = await preprocessSchema(); + ajv.addSchema(processedSchema, "openapi.json"); + + validateDeployContract = ajv.compile({ + $ref: "openapi.json#/components/schemas/DeployContractV1Request", + }); + return validateDeployContract; +} + export interface IDeployContractSolidityBytecodeOptions { logLevel?: LogLevelDesc; connector: PluginLedgerConnectorEthereum; @@ -88,6 +137,18 @@ export class DeployContractEndpoint implements IWebServiceEndpoint { public async handleRequest(req: Request, res: Response): Promise { const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; this.log.debug(reqTag); + + const validate = await getValidateDeployContract(); + const isValid = validate(req.body); + if (!isValid) { + const errorDetails = JSON.stringify(validate.errors); + this.log.debug(`Validation failed for ${reqTag}: ${errorDetails}`); + res.status(400).json({ + message: "Invalid request body", + errors: validate.errors, + }); + return; + } try { res .status(200) diff --git a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-json-object-v1.test.ts b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-json-object-v1.test.ts index 8c6815ad9a..c78f5a2a4e 100644 --- a/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-json-object-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-ethereum/src/test/typescript/integration/geth-contract-deploy-and-invoke-using-json-object-v1.test.ts @@ -243,8 +243,8 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { }); test("deployContract with additional parameters should fail", async () => { - try { - await apiClient.deployContract({ + await expect( + apiClient.deployContract({ contract: { contractJSON: HelloWorldContractJson, }, @@ -255,11 +255,15 @@ describe("Ethereum contract deploy and invoke using keychain tests", () => { }, gas: 1000000, fake: 4, - } as DeployContractV1Request); - fail("Expected deployContract call to fail but it succeeded."); - } catch (error) { - console.log("deployContract failed as expected"); - } + } as DeployContractV1Request), + ).rejects.toMatchObject({ + response: { + status: 400, + data: { + message: expect.stringContaining("Invalid request body"), + }, + }, + }); }); ////////////////////////////////// diff --git a/yarn.lock b/yarn.lock index 5945bb18e2..133fcbc818 100644 --- a/yarn.lock +++ b/yarn.lock @@ -805,6 +805,17 @@ __metadata: languageName: node linkType: hard +"@apidevtools/json-schema-ref-parser@npm:12.0.1": + version: 12.0.1 + resolution: "@apidevtools/json-schema-ref-parser@npm:12.0.1" + dependencies: + "@jsdevtools/ono": "npm:^7.1.3" + "@types/json-schema": "npm:^7.0.15" + js-yaml: "npm:^4.1.0" + checksum: 10/1ebe4b623dc841ac7137abb68e573a91350fcca38185379da52e1ab888fcb4cf706026bc41436dbb61a82ca830c8221e04b8432252bbb34815cfee5f290d0a22 + languageName: node + linkType: hard + "@apidevtools/json-schema-ref-parser@npm:^11.6.2": version: 11.6.4 resolution: "@apidevtools/json-schema-ref-parser@npm:11.6.4" @@ -10616,6 +10627,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperledger/cactus-plugin-ledger-connector-ethereum@workspace:packages/cactus-plugin-ledger-connector-ethereum" dependencies: + "@apidevtools/json-schema-ref-parser": "npm:12.0.1" "@ethereumjs/common": "npm:4.0.0" "@ethereumjs/tx": "npm:5.0.0" "@hyperledger/cactus-common": "npm:2.1.0" @@ -10630,6 +10642,7 @@ __metadata: "@types/minimist": "npm:1.2.2" "@types/sanitize-html": "npm:2.9.5" "@types/uuid": "npm:10.0.0" + ajv: "npm:8.17.1" axios: "npm:1.8.4" body-parser: "npm:1.20.3" chalk: "npm:4.1.2"