Skip to content

Commit

Permalink
Merge pull request #44 from pactflow/feat/token_auth
Browse files Browse the repository at this point in the history
Feat: Bearer token based auth for Pact Brokers
  • Loading branch information
YOU54F authored Oct 8, 2024
2 parents 714f8e7 + ed4726f commit 32eb466
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 6 deletions.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,24 @@ 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`

Note: command line options will take precedence over environment variables.

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, --user [USERNAME:PASSWORD] The basic auth username and password to access the pact broker (env - PACT_BROKER_USERNAME:PACT_BROKER_PASSWORD)
-u, --token [string] The bearer token to access the pact broker (env - PACT_BROKER_TOKEN)
-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
Expand Down Expand Up @@ -356,6 +369,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.
Expand Down
29 changes: 26 additions & 3 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ program
.arguments('<swagger> <mock>')
.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('-u, --user [USERNAME:PASSWORD]', 'The basic auth username and password to access the pact broker (env - PACT_BROKER_USERNAME:PACT_BROKER_PASSWORD)')
.option('-b, --token [string]', 'The bearer token to access the pact broker (env - PACT_BROKER_TOKEN)')
.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. ' +
Expand All @@ -72,11 +73,33 @@ json file. Optionally, pass a --tag option alongside a --provider option to filt
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.`
(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
Note: command line options will take precedence over environment variables.
`
)
.action(async (swagger, mock, options) => {
try {
const swaggerMockValidator = SwaggerMockValidatorFactory.create(options.user);
if (
options.user == undefined &&
process.env.PACT_BROKER_USERNAME != undefined &&
process.env.PACT_BROKER_PASSWORD != undefined
) {
options.user = process.env.PACT_BROKER_USERNAME + ':' + process.env.PACT_BROKER_PASSWORD;
} else if (options.token == undefined && process.env.PACT_BROKER_TOKEN != undefined) {
options.token = process.env.PACT_BROKER_TOKEN;
}
const swaggerMockValidator = SwaggerMockValidatorFactory.create(options.user ?? options.token);

const result = await swaggerMockValidator.validate({
analyticsUrl: options.analyticsUrl,
Expand Down
12 changes: 11 additions & 1 deletion lib/swagger-mock-validator/clients/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ import axios from 'axios';

export class HttpClient {
public async get(url: string, auth?: string): Promise<string> {
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,
Expand Down
88 changes: 87 additions & 1 deletion test/e2e/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {expectToFail} from '../helpers/expect-to-fail';
interface InvokeCommandOptions {
analyticsUrl?: string;
auth?: string;
token?: string;
mock: string;
providerName?: string;
swagger: string;
tag?: string;
outputDepth?: string;
envVars?: string;
}

const execute = (command: string): Promise<string> => {
Expand All @@ -28,7 +30,9 @@ const execute = (command: string): Promise<string> => {
};

const invokeCommand = (options: InvokeCommandOptions): Promise<string> => {
let command = `./bin/swagger-mock-validator.mjs ${options.swagger} ${options.mock}`;
let command = `${options.envVars ? `${options.envVars} ` : ''}./bin/swagger-mock-validator.mjs ${options.swagger} ${
options.mock
}`;

if (options.providerName) {
command += ` --provider ${options.providerName}`;
Expand All @@ -46,6 +50,10 @@ const invokeCommand = (options: InvokeCommandOptions): Promise<string> => {
command += ` --user ${options.auth}`;
}

if (options.token) {
command += ` --token ${options.token}`;
}

if (options.outputDepth) {
command += ` --outputDepth ${options.outputDepth}`;
}
Expand Down Expand Up @@ -387,7 +395,85 @@ 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 token = 'token';

await invokeCommand({
token,
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 make an authenticated request with a user/pass combo read from PACT_BROKER_USERNAME/PACT_BROKER_PASSWORD variable', async () => {
const user = 'user';
const pass = 'pass';

await invokeCommand({
envVars: `PACT_BROKER_USERNAME=${user} PACT_BROKER_PASSWORD=${pass}`,
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: 'Basic dXNlcjpwYXNz'}),
jasmine.stringMatching('test/e2e/fixtures/pact-broker.json')
);
}, 30000);
it('should make an authenticated request with a bearer token read from PACT_BROKER_TOKEN variable', async () => {
const token = 'token';

await invokeCommand({
envVars: `PACT_BROKER_TOKEN=${token}`,
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 prefer user variable, even if PACT_BROKER_USERNAME/PACT_BROKER_PASSWORD variable are set', async () => {
const user = 'user';
const pass = 'pass';

await invokeCommand({
auth: `${user}:${pass}`,
envVars: `PACT_BROKER_USERNAME=${user}_env PACT_BROKER_PASSWORD=${pass}_env`,
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: 'Basic dXNlcjpwYXNz'}),
jasmine.stringMatching('test/e2e/fixtures/pact-broker.json')
);
}, 30000);
it('should prefer user variable, even if PACT_BROKER_TOKEN variable are set', async () => {
const token = 'token';

await invokeCommand({
token,
envVars: `PACT_BROKER_TOKEN=${token}_env`,
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({
mock: 'test/e2e/fixtures/pact-working-consumer.json',
Expand Down

0 comments on commit 32eb466

Please sign in to comment.