diff --git a/pages/api/address/balance/[address].ts b/pages/api/address/balance/[address].ts index 93801e956..c0b79af29 100644 --- a/pages/api/address/balance/[address].ts +++ b/pages/api/address/balance/[address].ts @@ -6,7 +6,7 @@ import { runMiddleware, satoshisToUnit } from 'utils/index' import xecaddr from 'xecaddrjs' import { multiBlockchainClient } from 'services/chronikService' -const { ADDRESS_NOT_PROVIDED_400 } = RESPONSE_MESSAGES +const { ADDRESS_NOT_PROVIDED_400, INVALID_ADDRESS_400 } = RESPONSE_MESSAGES const cors = Cors({ methods: ['GET', 'HEAD'] }) @@ -24,6 +24,9 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise case ADDRESS_NOT_PROVIDED_400.message: res.status(ADDRESS_NOT_PROVIDED_400.statusCode).json(ADDRESS_NOT_PROVIDED_400) break + case INVALID_ADDRESS_400.message: + res.status(INVALID_ADDRESS_400.statusCode).json(INVALID_ADDRESS_400) + break default: res.status(500).json({ statusCode: 500, message: err.message }) } diff --git a/pages/api/address/transactions/count/[address].ts b/pages/api/address/transactions/count/[address].ts index e7bd9a62e..a2a70bebc 100644 --- a/pages/api/address/transactions/count/[address].ts +++ b/pages/api/address/transactions/count/[address].ts @@ -20,6 +20,9 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise case RESPONSE_MESSAGES.MULTIPLE_USER_IDS_PROVIDED_400.message: res.status(400).json(RESPONSE_MESSAGES.MULTIPLE_USER_IDS_PROVIDED_400) break + case RESPONSE_MESSAGES.INVALID_ADDRESS_400.message: + res.status(RESPONSE_MESSAGES.INVALID_ADDRESS_400.statusCode).json(RESPONSE_MESSAGES.INVALID_ADDRESS_400) + break default: res.status(500).json({ statusCode: 500, message: err.message }) } diff --git a/tests/integration-tests/api.test.ts b/tests/integration-tests/api.test.ts index 2fca22012..ca3592289 100644 --- a/tests/integration-tests/api.test.ts +++ b/tests/integration-tests/api.test.ts @@ -8,6 +8,7 @@ import walletsEndpoint from 'pages/api/wallets/index' import walletEndpoint from 'pages/api/wallet/index' import walletIdEndpoint from 'pages/api/wallet/[id]' import transactionsEndpoint from 'pages/api/address/transactions/[address]' +import transactionCountEndpoint from 'pages/api/address/transactions/count/[address]' import balanceEndpoint from 'pages/api/address/balance/[address]' import dashboardEndpoint from 'pages/api/dashboard/index' import paymentsEndpoint from 'pages/api/payments/index' @@ -1285,6 +1286,24 @@ describe('GET /api/address/transactions/[address]', () => { }) }) +describe('GET /api/address/transactions/count/[address]', () => { + it('HTTP 400 when multiple address query parameters are provided', async () => { + const requestOptions: RequestOptions = { + method: 'GET' as RequestMethod, + headers: { + 'Content-Type': 'application/json' + }, + query: { + address: ['address1', 'address2'] + } + } + const res = await testEndpoint(requestOptions, transactionCountEndpoint) + const responseData = res._getJSONData() + expect(res.statusCode).toBe(RESPONSE_MESSAGES.INVALID_ADDRESS_400.statusCode) + expect(responseData.message).toBe(RESPONSE_MESSAGES.INVALID_ADDRESS_400.message) + }) +}) + describe('GET /api/address/balance/[address]', () => { const baseRequestOptions: RequestOptions = { method: 'GET' as RequestMethod, @@ -1300,6 +1319,22 @@ describe('GET /api/address/balance/[address]', () => { const responseData = res._getJSONData() expect(responseData.message).toBe(RESPONSE_MESSAGES.ADDRESS_NOT_PROVIDED_400.message) }) + + it('Should return HTTP 400 when multiple address query parameters are provided', async () => { + const requestOptions: RequestOptions = { + method: 'GET' as RequestMethod, + headers: { + 'Content-Type': 'application/json' + }, + query: { + address: ['address1', 'address2'] + } + } + const res = await testEndpoint(requestOptions, balanceEndpoint) + const responseData = res._getJSONData() + expect(res.statusCode).toBe(RESPONSE_MESSAGES.INVALID_ADDRESS_400.statusCode) + expect(responseData.message).toBe(RESPONSE_MESSAGES.INVALID_ADDRESS_400.message) + }) }) describe('GET /api/payments', () => { diff --git a/tests/unittests/validators.test.ts b/tests/unittests/validators.test.ts index 6bbf8dd2e..934400eeb 100644 --- a/tests/unittests/validators.test.ts +++ b/tests/unittests/validators.test.ts @@ -109,6 +109,12 @@ describe('parseAddress', () => { v.parseAddress('') }).toThrow(RESPONSE_MESSAGES.ADDRESS_NOT_PROVIDED_400.message) }) + + it('Reject repeated query parameters passed as an array', () => { + expect(() => { + v.parseAddress([exampleAddresses.ecash, exampleAddresses.bitcoincash]) + }).toThrow(RESPONSE_MESSAGES.INVALID_ADDRESS_400.message) + }) }) describe('parseError', () => { diff --git a/utils/validators.ts b/utils/validators.ts index 6f4c2eaa6..f8b809817 100644 --- a/utils/validators.ts +++ b/utils/validators.ts @@ -28,7 +28,10 @@ const parseAddressTextBlock = function (addressBlock: string): string[] { .filter((value, index, self) => self.indexOf(value) === index) } -export const parseAddress = function (addressString: string | undefined): string { +export const parseAddress = function (addressString: string | string[] | undefined): string { + // Repeated query keys arrive as arrays. String methods like includes() then + // check membership instead of a substring, so reject non-strings before use. + if (Array.isArray(addressString)) throw new Error(RESPONSE_MESSAGES.INVALID_ADDRESS_400.message) if (addressString === '' || addressString === undefined) throw new Error(RESPONSE_MESSAGES.ADDRESS_NOT_PROVIDED_400.message) let parsedAddress: string if (