From f42f3edd34d1eeb8565494ab8f9b7fd9abdcce35 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Thu, 26 Sep 2024 19:07:03 +0100 Subject: [PATCH] feat: Bearer token based auth for PactFlow Broker --- README.md | 27 +++++++++++++++++++ lib/cli.ts | 8 +++++- .../clients/http-client.ts | 12 ++++++++- test/e2e/cli.spec.ts | 15 +++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e554058..ef817fa 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,22 @@ pacts from the broker by Pact Broker version tags. If the pact broker has basic auth enabled, pass a --user option with username and password joined by a colon (i.e. THE_USERNAME:THE_PASSWORD) to access the pact broker resources. +If the pact broker has bearer token auth enabled, pass a --token option along with the token to access the pact broker resources. + +You can also set the following environment variables + +- Basic Auth + - `PACT_BROKER_USERNAME` + - `PACT_BROKER_PASSWORD` +- Bearer Auth + - `PACT_BROKER_TOKEN` + Options: -V, --version output the version number -p, --provider [string] The name of the provider in the pact broker -t, --tag [string] The tag to filter pacts retrieved from the pact broker -u, --user [USERNAME:PASSWORD] The basic auth username and password to access the pact broker + -u, --token [string] The bearer token to access the pact broker -a, --analyticsUrl [string] The url to send analytics events to as a http post -o, --outputDepth [integer] Specifies the number of times to recurse while formatting the output objects. This is useful in case of large complicated objects or schemas. (default: 4) -A, --additionalPropertiesInResponse [boolean] allow additional properties in response bodies, default false @@ -356,6 +367,22 @@ If the Pact Broker is behind basic auth, you can pass credentials with the `--us swagger-mock-validator /path/to/swagger.json https://pact-broker.com --provider my-provider-name --user BASIC_AUTH_USER:BASIC_AUTH_PASSWORD ``` +You can also use environment variables +``` +PACT_BROKER_USERNAME=BASIC_AUTH_USER PACT_BROKER_PASSWORD=BASIC_AUTH_PASSWORD swagger-mock-validator /path/to/swagger.json https://pact-broker.com --provider my-provider-name +``` + +If the Pact Broker is behind bearer auth, you can pass credentials with the `--token` option while invoking the tool. + +``` +swagger-mock-validator /path/to/swagger.json https://pact-broker.com --provider my-provider-name --token bar +``` + +You can also use environment variables +``` +PACT_BROKER_TOKEN=bar swagger-mock-validator /path/to/swagger.json https://pact-broker.com --provider my-provider-name +``` + ### Analytics (Opt-In) The tool can be configured to send analytics events to a server of your choosing. Use the `--analyticsUrl` flag to pass a url that the tool should post the event to. The tool will send this event via a http post request and will timeout after 5 seconds. See [analytics.ts](lib/swagger-mock-validator/analytics.ts) for the post body schema. diff --git a/lib/cli.ts b/lib/cli.ts index 07b6678..2bddb23 100644 --- a/lib/cli.ts +++ b/lib/cli.ts @@ -48,6 +48,7 @@ program .option('-p, --provider [string]', 'The name of the provider in the pact broker') .option('-t, --tag [string]', 'The tag to filter pacts retrieved from the pact broker') .option('-u, --user [USERNAME:PASSWORD]', 'The basic auth username and password to access the pact broker') + .option('-b, --token [string]', 'The bearer token to access the pact broker') .option('-a, --analyticsUrl [string]', 'The url to send analytics events to as a http post') .option('-o, --outputDepth [integer]', 'Specifies the number of times to recurse ' + 'while formatting the output objects. ' + @@ -76,7 +77,12 @@ If the pact broker has basic auth enabled, pass a --user option with username an ) .action(async (swagger, mock, options) => { try { - const swaggerMockValidator = SwaggerMockValidatorFactory.create(options.user); + if (!options.user && process.env.PACT_BROKER_USERNAME != '' && process.env.PACT_BROKER_PASSWORD != '') { + options.user = process.env.PACT_BROKER_USERNAME + ':' + process.env.PACT_BROKER_PASSWORD; + } else if (!options.token && process.env.PACT_BROKER_TOKEN != '') { + options.token = process.env.PACT_BROKER_TOKEN; + } + const swaggerMockValidator = SwaggerMockValidatorFactory.create(options.user ?? options.token); const result = await swaggerMockValidator.validate({ analyticsUrl: options.analyticsUrl, diff --git a/lib/swagger-mock-validator/clients/http-client.ts b/lib/swagger-mock-validator/clients/http-client.ts index 3a3ff10..c31ed72 100644 --- a/lib/swagger-mock-validator/clients/http-client.ts +++ b/lib/swagger-mock-validator/clients/http-client.ts @@ -2,9 +2,19 @@ import axios from 'axios'; export class HttpClient { public async get(url: string, auth?: string): Promise { + let authHeader: string | undefined; + + if (auth) { + if (auth.includes(':')) { + authHeader = 'Basic ' + Buffer.from(auth).toString('base64'); + } else { + authHeader = 'Bearer ' + auth; + } + } + const response = await axios.get(url, { headers: { - ...(auth ? {authorization: 'Basic ' + Buffer.from(auth).toString('base64')} : {}) + ...(authHeader ? { Authorization: authHeader } : {}) }, timeout: 30000, transformResponse: (data) => data, diff --git a/test/e2e/cli.spec.ts b/test/e2e/cli.spec.ts index 320ba20..387d263 100644 --- a/test/e2e/cli.spec.ts +++ b/test/e2e/cli.spec.ts @@ -387,6 +387,21 @@ describe('swagger-mock-validator/cli', () => { jasmine.stringMatching('test/e2e/fixtures/pact-broker.json') ); }, 30000); + it('should make an bearer token authenticated request to the provided pact broker url when asked to do so', async () => { + const auth = 'token'; + + await invokeCommand({ + auth, + mock: urlTo('test/e2e/fixtures/pact-broker.json'), + providerName: 'provider-1', + swagger: urlTo('test/e2e/fixtures/swagger-provider.json') + }); + + expect(mockPactBroker.get).toHaveBeenCalledWith( + jasmine.objectContaining({authorization: 'Bearer token'}), + jasmine.stringMatching('test/e2e/fixtures/pact-broker.json') + ); + }, 30000); it('should format output objects to depth 0', async () => { const result = await invokeCommand({