From af3ac52045e6893aa4e82f2a16964d4826641c87 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 7 Oct 2024 15:04:37 +0200 Subject: [PATCH] feat: add validation for amq connection uri (#844) --- packages/rabbitmq/src/amqp/utils.ts | 25 +++++++++++++++- packages/rabbitmq/src/rabbitmq.module.ts | 4 +++ .../rabbitmq/src/tests/rabbitmq.utils.spec.ts | 29 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/rabbitmq/src/amqp/utils.ts b/packages/rabbitmq/src/amqp/utils.ts index 313446f02..366b093c6 100644 --- a/packages/rabbitmq/src/amqp/utils.ts +++ b/packages/rabbitmq/src/amqp/utils.ts @@ -1,6 +1,6 @@ export function matchesRoutingKey( routingKey: string, - pattern: string[] | string | undefined + pattern: string[] | string | undefined, ): boolean { // An empty string is a valid pattern therefore // we should only exclude null values and empty array @@ -27,3 +27,26 @@ export function matchesRoutingKey( return false; } + +const rabbitMQRegex = + /^amqp:\/\/(([^:]+):([^@]+)@)?([^:/]+)(:[0-9]+)?(\/[^\/]+)?$/; + +/** + * Validates a rabbitmq uri + * @see https://www.rabbitmq.com/docs/uri-spec#the-amqps-uri-scheme + * @param uri + * @returns + */ +export const assertRabbitMqUri = (uri: string | string[]) => { + if (Array.isArray(uri)) { + for (const u of uri) { + assertRabbitMqUri(u); + } + return; + } + + const valid = rabbitMQRegex.test(uri); + if (!valid) { + throw new Error(`Invalid RabbitMQ connection uri, received: ${uri}`); + } +}; diff --git a/packages/rabbitmq/src/rabbitmq.module.ts b/packages/rabbitmq/src/rabbitmq.module.ts index daed432a0..737c57730 100644 --- a/packages/rabbitmq/src/rabbitmq.module.ts +++ b/packages/rabbitmq/src/rabbitmq.module.ts @@ -18,6 +18,7 @@ import { ExternalContextCreator } from '@nestjs/core/helpers/external-context-cr import { groupBy } from 'lodash'; import { AmqpConnection } from './amqp/connection'; import { AmqpConnectionManager } from './amqp/connectionManager'; +import { assertRabbitMqUri } from './amqp/utils'; import { RABBIT_CONFIG_TOKEN, RABBIT_CONTEXT_TYPE_KEY, @@ -89,6 +90,9 @@ export class RabbitMQModule ); return undefined; } + + assertRabbitMqUri(config.uri); + const connection = new AmqpConnection(config); this.connectionManager.addConnection(connection); await connection.init(); diff --git a/packages/rabbitmq/src/tests/rabbitmq.utils.spec.ts b/packages/rabbitmq/src/tests/rabbitmq.utils.spec.ts index 9fc1eda97..590b4aa92 100644 --- a/packages/rabbitmq/src/tests/rabbitmq.utils.spec.ts +++ b/packages/rabbitmq/src/tests/rabbitmq.utils.spec.ts @@ -1,4 +1,4 @@ -import { matchesRoutingKey } from '../amqp/utils'; +import { assertRabbitMqUri, matchesRoutingKey } from '../amqp/utils'; describe(matchesRoutingKey.name, () => { const userCreated = 'user.created'; @@ -58,4 +58,31 @@ describe(matchesRoutingKey.name, () => { const result = matchesRoutingKey(routingKey, pattern); expect(result).toBe(expectedResult); }); + + describe(assertRabbitMqUri.name, () => { + it('should not throw with valid uris', () => { + expect(() => + assertRabbitMqUri('amqp://rabbitmq:rabbitmq@localhost:4444'), + ).not.toThrowError(); + + expect(() => + assertRabbitMqUri([ + 'amqp://rabbitmq:rabbitmq@localhost:4444', + 'amqp://rabbitmq:rabbitmq@localhost:1234', + ]), + ).not.toThrowError(); + }); + + it('should throw when malformed uris are provided', () => { + expect(() => + assertRabbitMqUri('xamqp://rabbitmq:rabbitmq@localhost:4444'), + ).toThrowError(); + expect(() => + assertRabbitMqUri([ + 'amqp://rabbitmq:rabbitmq@localhost:hello', + 'superbawl://rabbitmq:rabbitmq@localhost:4444', + ]), + ).toThrowError(); + }); + }); });