Pact-js: Proposal: Allow customProviderHeaders to be dynamically added to different interactions

Created on 24 Aug 2018  路  13Comments  路  Source: pact-foundation/pact-js

Issue

It appears customProviderHeaders currently adds the headers for all the interactions in a pact file. But in certain situations you may want the header to be relevant to a specific interaction (and, in fact, may need information about that interaction itself to create the header). For example, for authentication, it may change between requests.

I am writing this here as a request because Matt Fellows asked me on the pact-js slack channel to raise a request .

Thank you!

enhancement help wanted

Most helpful comment

I've spiked locally making the verification system more flexible, whilst I don't think the API is perfect yet, it already feels so much nicer now that the user can just pass around functions, and doesn't need to provide a /setup like endpoint:

const { Verifier } = require('@pact-foundation/pact')
const { server, importData, animalRepository } = require('../provider.js')

// Start the actual provider
server.listen(8081, () => {
  console.log('Animal Profile Service listening on http://localhost:8081')
})

// Verify that the provider meets all consumer expectations
describe('Pact Verification', () => {
  it('should validate the expectations of Matching Service', () => {
    // Authentication header we'll manipulate for different requests
    let token = "INVALID TOKEN"

    // Verification options
    let opts = {
      provider: 'Animal Profile Service',
      providerBaseUrl: 'http://localhost:8081',
      pactBrokerUrl: 'https://test.pact.dius.com.au/',
      pactBrokerUsername: 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M',
      pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1',
      publishVerificationResult: true,
      providerVersion: "1.0.0",

      // Optional middleware to intercept the request _before_ the provider is invoked
      // Implements the 'request filter' pattern as per JVM
      // e.g. ADD Bearer token
      beforeHook: (req, res, next) => {
        console.log('Middleware invoked before provider API - chance to modify request')
        // Set's the auth token for each request
        // State handlers will ensure it is correct/incorrect later on
        req.headers['Authorization'] = `Bearer: ${token}`
        next()
      },

      // Optional middleware to intercept the request _after_ the provider is invoked, but _before_
      // the response is passed to the verifier itself
      // Very advanced, with narrow real-world use cases, but can be useful if e.g. verifying a 3rd party service
      afterHook: (req, res, next) => {
        console.log(`Middleware invoked after provider API - chance to modify response... but probably don't`)
        token = "INVALID TOKEN AGAIN"
        next()
      },

      // Register handler functions for any known provider states
      // Could we use decorators as an alternative to this to keep things DRY
      stateHandlers: {
        "Has no animals": () => {
          animalRepository.clear()
          return Promise.resolve(`Animals added to the db`)
        },
        "is authenticated": () => {
          token = "generate a valid token for this state"
          return Promise.resolve(`Valid bearer token generated`)}
      },

    }

    return new Verifier(opts)
      .verifyProvider()
      .then(output => {
        console.log('Pact Verification Complete!')
        console.log(output)
      })
  })
})

cc: @lirantal @TimothyJones

Worth adding/thinking about in #215

All 13 comments

I've implemented this. Just posting this to remind myself to post the docs here.

Ping me if I forget.

Ok, you can write your own middleware. Unfortunately, it will need to be in Ruby, not js (otherwise, you'll have to write your own independent proxy app in JS).

Here's an intro to Rack middleware. Rack is the bomb.

https://www.engineyard.com/blog/understanding-rack-apps-and-middleware

Here's an example spec using --custom-middleware

https://github.com/pact-foundation/pact-provider-verifier/blob/master/spec/integration_with_custom_middleware_spec.rb#L8

Here's the example custom middleware (note that it has to inherit from Pact::ProviderVerifier::CustomMiddleware)

https://github.com/pact-foundation/pact-provider-verifier/blob/master/spec/support/custom_middleware.rb

Matt, I've just released 1.60.0 of the standalone, can you update the js please?

Thanks Beth, yes will upgrade soon.

That being said, a more ideal solution might be following a similar strategy to the message pact, whereby we spin up a server in JS land that intercepts requests from the verifier and can add the custom headers to the Provider. This way it will leverage native JS code, rather than having to create custom Ruby middleware.

At least now we have the infrastructure to do all of these things.

Yes, I said that :P

(otherwise, you'll have to write your own independent proxy app in JS)

But we could make the experience nicer.

I've spiked locally making the verification system more flexible, whilst I don't think the API is perfect yet, it already feels so much nicer now that the user can just pass around functions, and doesn't need to provide a /setup like endpoint:

const { Verifier } = require('@pact-foundation/pact')
const { server, importData, animalRepository } = require('../provider.js')

// Start the actual provider
server.listen(8081, () => {
  console.log('Animal Profile Service listening on http://localhost:8081')
})

// Verify that the provider meets all consumer expectations
describe('Pact Verification', () => {
  it('should validate the expectations of Matching Service', () => {
    // Authentication header we'll manipulate for different requests
    let token = "INVALID TOKEN"

    // Verification options
    let opts = {
      provider: 'Animal Profile Service',
      providerBaseUrl: 'http://localhost:8081',
      pactBrokerUrl: 'https://test.pact.dius.com.au/',
      pactBrokerUsername: 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M',
      pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1',
      publishVerificationResult: true,
      providerVersion: "1.0.0",

      // Optional middleware to intercept the request _before_ the provider is invoked
      // Implements the 'request filter' pattern as per JVM
      // e.g. ADD Bearer token
      beforeHook: (req, res, next) => {
        console.log('Middleware invoked before provider API - chance to modify request')
        // Set's the auth token for each request
        // State handlers will ensure it is correct/incorrect later on
        req.headers['Authorization'] = `Bearer: ${token}`
        next()
      },

      // Optional middleware to intercept the request _after_ the provider is invoked, but _before_
      // the response is passed to the verifier itself
      // Very advanced, with narrow real-world use cases, but can be useful if e.g. verifying a 3rd party service
      afterHook: (req, res, next) => {
        console.log(`Middleware invoked after provider API - chance to modify response... but probably don't`)
        token = "INVALID TOKEN AGAIN"
        next()
      },

      // Register handler functions for any known provider states
      // Could we use decorators as an alternative to this to keep things DRY
      stateHandlers: {
        "Has no animals": () => {
          animalRepository.clear()
          return Promise.resolve(`Animals added to the db`)
        },
        "is authenticated": () => {
          token = "generate a valid token for this state"
          return Promise.resolve(`Valid bearer token generated`)}
      },

    }

    return new Verifier(opts)
      .verifyProvider()
      .then(output => {
        console.log('Pact Verification Complete!')
        console.log(output)
      })
  })
})

cc: @lirantal @TimothyJones

Worth adding/thinking about in #215

That looks amazing @mefellows! (Just like the Ruby one ;) )

As an aside, I've published this to a tag proxy-spike if people would like to test, and all code is in the branch https://github.com/pact-foundation/pact-js/tree/spike/proxy-system.

Install with npm i @pact-foundation/pact@proxy-spike and use the API as per above.

@mefellows this is really cool, both the hooks and the state handlers!

Some comments:

  1. the state handlers should always return a promise, right?
  2. if you refer to decorators in terms of also adding transpiling requirement then I'd choose not to opt in to that.
  3. when this lands can we make sure we also document it? :-)
  1. Yes, at least that's the current thinking
  2. It would either be a) for versions of Node\TS that support it or b) fall back to other options. I've been thinking about it, and even did a bit of a spike. It's a bit weird, because the decorator proposal is really only for Classes, Class methods etc. Really, all we need is a wrapper function that can decorate an existing function. In pure JS, this is pretty easy. A DSL might look like this (simplified for readability):
import { registerStateHandler } from "pact"

const loadUsers = () => { ... }
const removeUsers = () => { ... }

registerStateHandler('User A exists', loadUsers)
registerStateHandler('User A does not exist', removeUsers)


// Do provider verification

Where registerStateHandler takes the function and stores it away in the subsystem somewhere for use during verification. I'm not convinced the interface is much easier to use as a consumer, but something like this might be useful if other UIs are built on top of the Pact library (e.g. a Mocha/Jest/Karma/Ava/... DSL)

  1. Yes 馃槀

That HOF looks nice.

This is now in the 8.x.x line 馃帀

Hey @mefellows

Great work on this proposal, it has come in useful as I need to sign requests for an AWS API Gateway endpoint as my providers are protected. I am using https://github.com/mhart/aws4

For a GET request, it needs the path, I hardcoded pathname in the state handler and it worked great for a single test.

For a POST request, it needs the path and body

Each pact test, may have a different path and body, and currently my stateHandler Is authenticated can't access the body, or pathname.

`` let signedHost: string; let signedXAmzSecurityToken: string; let signedXAmzDate: string; let signedAuthorization: string; opts = { stateHandlers: { "Is authenticated": () => { const requestUrl = PACT_PROVIDER_URL; const host = new url.URL(requestUrl).host; const pathname = '/path/that/the/pact/test/is/calling'; const options = { host, path:pathname, headers: {} }; console.log('creating AWS signed headers') aws4.sign(options); console.log('created AWS signed headers') authHeaders = options.headers signedHost = authHeaders['Host'] signedXAmzSecurityToken = authHeaders['X-Amz-Security-Token'] signedXAmzDate = authHeaders['X-Amz-Date'] signedAuthorization = authHeaders['Authorization'] return Promise.resolve(AWS signed headers created`)
}
},
requestFilter: (req, res, next) => {
// over-riding request headers with AWS credentials
console.log('req.path',req.path)
console.log('req.body',req.body)
req.headers['Host'] = signedHost
req.headers['X-Amz-Security-Token'] = signedXAmzSecurityToken
req.headers['X-Amz-Date'] = signedXAmzDate
req.headers['Authorization'] = signedAuthorization
next()
},
provider: PACT_PROVIDER_NAME
providerBaseUrl: PACT_PROVIDER_URL,
pactUrls: [
path.resolve(
process.cwd(),
"pact",
"pacts",
"consumer-service-provider-service.json"
),
],
publishVerificationResult: publishResultsFlag || false, // ONLY SET THIS TRUE IN CI!
validateSSL: true,
changeOrigin: true,
providerVersion, // the application version of the provider
pactBrokerUsername: PACT_BROKER_BASIC_AUTH_USERNAME,
pactBrokerPassword: PACT_BROKER_BASIC_AUTH_PASSWORD,
tags: tagsArray
};
}


I can get access to the path and body under test  in `requestFilter` with `req.path` & `req.body`, where I can see the state`Is authenticated`, and then the path and body under test.

Log output of `requestFilter`

req.path /_pactSetup
req.body { consumer: 'consumer-service',
state: 'Is authenticated',
states: [ 'Is authenticated' ],
params: {} }
creating AWS signed headers
created AWS signed headers
req.path /path/that/the/pact/test/is/calling
req.body undefined
```

So I suppose I could check if the state is authenticated in the requestFilter function, and then call a authentication function that has access to the path and body.

It would be nice if the stateHandler function can easily access the path and body for the current pact test being verified, it would make it cleaner. I might just be being dim as to how to achieve this!

I'll get a proper example up in a repo shortly

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MichaelSu1983 picture MichaelSu1983  路  7Comments

mefellows picture mefellows  路  7Comments

eauc picture eauc  路  6Comments

mefellows picture mefellows  路  6Comments

BenSayers picture BenSayers  路  8Comments