From fc74d4ac1678553f2bcdc20902e576a5c586112c Mon Sep 17 00:00:00 2001 From: Tarik Gul <47201679+TarikGul@users.noreply.github.com> Date: Tue, 13 Sep 2022 12:08:16 -0400 Subject: [PATCH] feat: validateBooleanMiddleware for controllers (#1023) * add utility testing functions to util file * refactor address middleware tests with util functions * add validateBooleanMiddleware * validateBooleanMiddleware tests * export middleware * add validateBooleanMiddleware to controllers that use it * fix inline comments * remove hardcoded values * fix test * refactor middleware to be more efficient * fix query check --- .../accounts/AccountsBalanceInfoController.ts | 8 +- .../AccountsStakingPayoutsController.ts | 8 +- src/controllers/blocks/BlocksController.ts | 5 + .../blocks/BlocksTraceController.ts | 2 + .../node/NodeTransactionPoolController.ts | 2 + .../pallets/PalletsStorageController.ts | 3 +- src/controllers/paras/ParasController.ts | 5 + .../TransactionMaterialController.ts | 2 + src/middleware/validate/index.ts | 1 + src/middleware/validate/util.ts | 60 +++++++ .../validateAddressMiddleware.spec.ts | 158 +++++++++--------- .../validateBooleanMiddleware.spec.ts | 92 ++++++++++ .../validate/validateBooleanMiddleware.ts | 53 ++++++ 13 files changed, 315 insertions(+), 84 deletions(-) create mode 100644 src/middleware/validate/util.ts create mode 100644 src/middleware/validate/validateBooleanMiddleware.spec.ts create mode 100644 src/middleware/validate/validateBooleanMiddleware.ts diff --git a/src/controllers/accounts/AccountsBalanceInfoController.ts b/src/controllers/accounts/AccountsBalanceInfoController.ts index 6752a63ba..fcb2feb0a 100644 --- a/src/controllers/accounts/AccountsBalanceInfoController.ts +++ b/src/controllers/accounts/AccountsBalanceInfoController.ts @@ -18,7 +18,7 @@ import { ApiPromise } from '@polkadot/api'; import { RequestHandler } from 'express'; import { IAddressParam } from 'src/types/requests'; -import { validateAddress } from '../../middleware'; +import { validateAddress, validateBoolean } from '../../middleware'; import { AccountsBalanceInfoService } from '../../services'; import AbstractController from '../AbstractController'; @@ -66,7 +66,11 @@ export default class AccountsBalanceController extends AbstractController } protected initRoutes(): void { + this.router.use( + this.path, + validateBoolean(['eventDocs', 'extrinsicDocs', 'finalized']) + ); this.safeMountAsyncGetHandlers([ ['/', this.getBlocks], ['/head', this.getLatestBlock], diff --git a/src/controllers/blocks/BlocksTraceController.ts b/src/controllers/blocks/BlocksTraceController.ts index a02af4d43..806144ce9 100644 --- a/src/controllers/blocks/BlocksTraceController.ts +++ b/src/controllers/blocks/BlocksTraceController.ts @@ -17,6 +17,7 @@ import { ApiPromise } from '@polkadot/api'; import { RequestHandler } from 'express-serve-static-core'; +import { validateBoolean } from '../../middleware'; import { BlocksTraceService } from '../../services'; import AbstractController from '../AbstractController'; import BlocksController from './BlocksController'; @@ -28,6 +29,7 @@ export default class BlocksTraceController extends AbstractController { } protected initRoutes(): void { + this.router.use( + this.path + '/paras/leases/current', + validateBoolean(['currentLeaseHolders']) + ); this.safeMountAsyncGetHandlers([ ['/paras', this.getParas], ['/paras/crowdloans', this.getCrowdloans], diff --git a/src/controllers/transaction/TransactionMaterialController.ts b/src/controllers/transaction/TransactionMaterialController.ts index 93eec195f..02a1da710 100644 --- a/src/controllers/transaction/TransactionMaterialController.ts +++ b/src/controllers/transaction/TransactionMaterialController.ts @@ -18,6 +18,7 @@ import { ApiPromise } from '@polkadot/api'; import { RequestHandler } from 'express'; import { Log } from '../../logging/Log'; +import { validateBoolean } from '../../middleware'; import { TransactionMaterialService } from '../../services'; import AbstractController from '../AbstractController'; @@ -60,6 +61,7 @@ export default class TransactionMaterialController extends AbstractController. export { validateAddressMiddleware as validateAddress } from './validateAddressMiddleware'; +export { validateBooleanMiddleware as validateBoolean } from './validateBooleanMiddleware'; diff --git a/src/middleware/validate/util.ts b/src/middleware/validate/util.ts new file mode 100644 index 000000000..7aea84efb --- /dev/null +++ b/src/middleware/validate/util.ts @@ -0,0 +1,60 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate API Sidecar. +// +// Substrate API Sidecar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import { Request, Response } from 'express'; +import { RequestHandler } from 'express-serve-static-core'; + +/** + * Assert that a middleware does not error with the given request. + * + * @param name String for tests to log. + * @param req Express Request containing thing it errors on. + */ +export const doesNotErrorWith = ( + name: string, + req: Request, + middleware: RequestHandler +): void => { + it(`does not error with ${name}`, () => { + const next = jest.fn(); + middleware(req, null as unknown as Response, next); + expect(next).toBeCalledTimes(1); + expect(next).toBeCalledWith(); + }); +}; + +/** + * Assert that a middleware passes `err` to next with the given + * `req`. + * + * @param name String for tests to log. + * @param req Express Request containing thing it errors on. + * @param err Expected error that it passes to next. + */ +export const errorsWith = ( + name: string, + req: Request, + err: unknown, + middleware: RequestHandler +): void => { + it(`errors with ${name}`, () => { + const next = jest.fn(); + + middleware(req, null as unknown as Response, next); + expect(next).toBeCalledTimes(1); + expect(next).toBeCalledWith(err); + }); +}; diff --git a/src/middleware/validate/validateAddressMiddleware.spec.ts b/src/middleware/validate/validateAddressMiddleware.spec.ts index dc9401907..509582360 100644 --- a/src/middleware/validate/validateAddressMiddleware.spec.ts +++ b/src/middleware/validate/validateAddressMiddleware.spec.ts @@ -14,92 +14,88 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import { Request, Response } from 'express'; +import { Request } from 'express'; import { BadRequest } from 'http-errors'; +import { doesNotErrorWith, errorsWith } from './util'; import { validateAddressMiddleware } from './validateAddressMiddleware'; -/** - * Assert that `validateAddressMiddleware` does not error with a the given `req`. - * - * @param name thing it does not error on - * @param req Express Request containing thing it errors on - */ -function doesNotErrorWith(name: string, req: Request): void { - it(`does not error with ${name}`, () => { - const next = jest.fn(); - validateAddressMiddleware(req, null as unknown as Response, next); - expect(next).toBeCalledTimes(1); - expect(next).toBeCalledWith(); - }); -} - -/** - * Assert that `validateAddressMiddleware` passes `err` to next with the given - * `req`. - * - * @param name thing it errors on - * @param req Express Request containing thing it errors on - * @param err expected error that it passes to next - */ -function errorsWith(name: string, req: Request, err: unknown): void { - it(`errors with ${name}`, () => { - const next = jest.fn(); - - validateAddressMiddleware(req, null as unknown as Response, next); - expect(next).toBeCalledTimes(1); - expect(next).toBeCalledWith(err); - }); -} - describe('validateAddressMiddleware', () => { - doesNotErrorWith('no address in params', { - params: { - number: '1', - }, - } as unknown as Request); + doesNotErrorWith( + 'no address in params', + { + params: { + number: '1', + }, + } as unknown as Request, + validateAddressMiddleware + ); - doesNotErrorWith('a valid substrate address', { - params: { - number: '1', - address: '5EnxxUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG', - }, - } as unknown as Request); + doesNotErrorWith( + 'a valid substrate address', + { + params: { + number: '1', + address: '5EnxxUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG', + }, + } as unknown as Request, + validateAddressMiddleware + ); - doesNotErrorWith('a valid kusama address', { - params: { - number: '1', - address: 'DXgXPAT5zWtPHo6FhVvrDdiaDPgCNGxhJAeVBYLtiwW9hAc', - }, - } as unknown as Request); + doesNotErrorWith( + 'a valid kusama address', + { + params: { + number: '1', + address: 'DXgXPAT5zWtPHo6FhVvrDdiaDPgCNGxhJAeVBYLtiwW9hAc', + }, + } as unknown as Request, + validateAddressMiddleware + ); - doesNotErrorWith('a valid kulupu address', { - params: { - number: '1', - address: '2cYv9Gk6U4m4a7Taw9pG8qMfd1Pnxw6FLTvV6kYZNhGL6M9y', - }, - } as unknown as Request); + doesNotErrorWith( + 'a valid kulupu address', + { + params: { + number: '1', + address: '2cYv9Gk6U4m4a7Taw9pG8qMfd1Pnxw6FLTvV6kYZNhGL6M9y', + }, + } as unknown as Request, + validateAddressMiddleware + ); - doesNotErrorWith('a valid edgeware address', { - params: { - number: '1', - address: '5D24s4paTdVxddyeUzgsxGGiRd7SPhTnEvKu6XGPQvj1QSYN', - }, - } as unknown as Request); + doesNotErrorWith( + 'a valid edgeware address', + { + params: { + number: '1', + address: '5D24s4paTdVxddyeUzgsxGGiRd7SPhTnEvKu6XGPQvj1QSYN', + }, + } as unknown as Request, + validateAddressMiddleware + ); - doesNotErrorWith('a valid polkadot address', { - params: { - number: '1', - address: '1xN1Q5eKQmS5AzASdjt6R6sHF76611vKR4PFpFjy1kXau4m', - }, - } as unknown as Request); + doesNotErrorWith( + 'a valid polkadot address', + { + params: { + number: '1', + address: '1xN1Q5eKQmS5AzASdjt6R6sHF76611vKR4PFpFjy1kXau4m', + }, + } as unknown as Request, + validateAddressMiddleware + ); - doesNotErrorWith('a valid H160 address', { - params: { - number: '1', - address: '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac', - }, - } as unknown as Request); + doesNotErrorWith( + 'a valid H160 address', + { + params: { + number: '1', + address: '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac', + }, + } as unknown as Request, + validateAddressMiddleware + ); errorsWith( 'an address containing an invalid base58 char', @@ -109,7 +105,8 @@ describe('validateAddressMiddleware', () => { address: '5EnxIUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG', }, } as unknown as Request, - new BadRequest('Invalid base58 character "I" (0x49) at index 4') + new BadRequest('Invalid base58 character "I" (0x49) at index 4'), + validateAddressMiddleware ); errorsWith( @@ -120,7 +117,8 @@ describe('validateAddressMiddleware', () => { address: 'y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b', }, } as unknown as Request, - new BadRequest('Invalid address format') + new BadRequest('Invalid address format'), + validateAddressMiddleware ); errorsWith( @@ -131,7 +129,8 @@ describe('validateAddressMiddleware', () => { address: '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU', }, } as unknown as Request, - new BadRequest('Invalid decoded address checksum') + new BadRequest('Invalid decoded address checksum'), + validateAddressMiddleware ); errorsWith( @@ -142,6 +141,7 @@ describe('validateAddressMiddleware', () => { address: 'hello', }, } as unknown as Request, - new BadRequest('Invalid base58 character "l" (0x6c) at index 2') + new BadRequest('Invalid base58 character "l" (0x6c) at index 2'), + validateAddressMiddleware ); }); diff --git a/src/middleware/validate/validateBooleanMiddleware.spec.ts b/src/middleware/validate/validateBooleanMiddleware.spec.ts new file mode 100644 index 000000000..740a2d9ca --- /dev/null +++ b/src/middleware/validate/validateBooleanMiddleware.spec.ts @@ -0,0 +1,92 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate API Sidecar. +// +// Substrate API Sidecar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import { Request } from 'express'; +import { BadRequest } from 'http-errors'; + +import { doesNotErrorWith, errorsWith } from './util'; +import { validateBooleanMiddleware } from './validateBooleanMiddleware'; + +describe('validaeBooleanMiddleware', () => { + doesNotErrorWith( + 'no query params in path', + { + query: {}, + } as unknown as Request, + validateBooleanMiddleware([]) + ); + + doesNotErrorWith( + 'valid true and false query params', + { + query: { + finalized: 'true', + eventDocs: 'false', + }, + } as unknown as Request, + validateBooleanMiddleware(['finalized', 'eventDocs']) + ); + + doesNotErrorWith( + 'Non specified invalid query params', + { + query: { + invalid: 'truee', + }, + } as unknown as Request, + validateBooleanMiddleware([]) + ); + + errorsWith( + 'an invalid true query param', + { + query: { + finalized: 'truee', + }, + } as unknown as Request, + new BadRequest( + 'Query parameter: finalized has an invalid boolean value of truee' + ), + validateBooleanMiddleware(['finalized']) + ); + + errorsWith( + 'an invalid false query param', + { + query: { + finalized: 'falsee', + }, + } as unknown as Request, + new BadRequest( + 'Query parameter: finalized has an invalid boolean value of falsee' + ), + validateBooleanMiddleware(['finalized']) + ); + + errorsWith( + 'multiple invalid query params', + { + query: { + finalized: 'notTrue', + eventDocs: 'notFalse', + }, + } as unknown as Request, + new BadRequest( + 'Query parameter: finalized has an invalid boolean value of notTrue - Query parameter: eventDocs has an invalid boolean value of notFalse' + ), + validateBooleanMiddleware(['finalized', 'eventDocs']) + ); +}); diff --git a/src/middleware/validate/validateBooleanMiddleware.ts b/src/middleware/validate/validateBooleanMiddleware.ts new file mode 100644 index 000000000..ba01a67da --- /dev/null +++ b/src/middleware/validate/validateBooleanMiddleware.ts @@ -0,0 +1,53 @@ +// Copyright 2017-2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate API Sidecar. +// +// Substrate API Sidecar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import { RequestHandler } from 'express'; +import { BadRequest } from 'http-errors'; + +/** + * Validate that the given query params that are expected to be booleans are correct. + * + * @param queryParams An array of queryParams to check for. These are passed in at the controller level. + */ +export const validateBooleanMiddleware = ( + queryParams: string[] +): RequestHandler => { + return (req, _res, next) => { + const errQueryParams: string[] = []; + + for (const key of queryParams) { + if (req.query[key]) { + const queryParamVal = + typeof req.query[key] === 'string' + ? (req.query[key] as string).toLowerCase() + : ''; + if (!(queryParamVal === 'true' || queryParamVal === 'false')) { + errQueryParams.push( + `Query parameter: ${key} has an invalid boolean value of ${ + req.query[key] as string + }` + ); + } + } + } + + if (errQueryParams.length > 0) { + return next(new BadRequest(errQueryParams.join(' - '))); + } + + return next(); + }; +};