return new Verifier({             provider: 'post-...
# pact-js
s
return new Verifier({             provider: 'post-secrets-secret-store-proxy-api',             providerBaseUrl: process.env.API_URL,             logLevel: 'debug',             requestFilter: ((req) =>{                 delete req.headers['host'];                 delete req.headers['x-amz-date'];                 delete req.headers['x-amz-security-token'];                 delete req.headers['x-amz-content-sha256'];                 delete req.headers['authorization'];                 req.headers['host']= host;                 req.headers['x-amz-date']= date;                 req.headers['x-amz-security-token']= securityToken;                 req.headers['x-amz-content-sha256']= contentSha256;                 req.headers['authorization']= authorization;                 return req;             }),             stateHandlers: {                 ['I post credentials token']: async () => {                     await chai.request(requestUrl.toString())                         .post("/")                         .set('Content-Type', contentType)                         .set('host', host)                         .set('x-amz-date', date)                         .set('x-amz-security-token', securityToken)                         .set('x-amz-content-sha256', contentSha256)                         .set('authorization', authorization)                         .send({ "details": "something to store" })                 }             }
m
at the very least, you’re not calling
next()
which is probably why it’s hanging
I’m not sure what your state handler is doing there.
s
State handler does not need headers?
also this is the log I get
pact@12.1.0: incoming request: {"body":{"details":"user credential details as string"},"headers":{"content-type":"application/json","host":"iloveorange","x-amz-date":"iloveorange","authorization":"iloveorange","x-amz-content-sha256":"iloveorange","x-amz-security-token":"iloveorange","accept":"*/*","accept-encoding":"gzip, deflate","content-length":"47"},"method":"POST","path":"/"}
I want these to be substituted to my values from signed request
m
State handler does not need headers
no, I’m just saying I don’t know what it’s doing. The results of the call aren’t going anywhere, so I’m not sure the intent behind it
s
man I may have totally misunderstood this, GOD. ['I post credentials token'] this method name matches exactly to the one created in pact. and for some reason when I run this test i get 201
Please do let me know if I got it wrong altogether
I have checked in this test in a number of repos now
THe full test looks like this
it('validates expectations of consumers post', async () => { return new Verifier({ provider: 'post-secrets-secret-store-proxy-api', providerBaseUrl: process.env.API_URL, logLevel: 'debug', requestFilter: ((req, res, next) =>{ req.headers["Content-Type"]= '${contentType}'; req.headers["host"]= '${host}'; req.headers["x-amz-date"]= '${date}'; req.headers["x-amz-security-token"]= '${securityToken}'; req.headers["x-amz-content-sha256"]= '${contentSha256}'; req.headers["authorization"]= '${authorization}'; next(); }), stateHandlers: { ['I post credentials token']: async () => { await chai.request(requestUrl.toString()) .post("/") // .set('Content-Type', contentType) // .set('host', host) // .set('x-amz-date', date) // .set('x-amz-security-token', securityToken) // .set('x-amz-content-sha256', contentSha256) // .set('authorization', authorization) .send({ "details": "something to store" }) } }, pactBrokerUrl: ', pactBrokerUsername: '', pactBrokerPassword: '', providerVersion: 'latest', enablePending: true, consumerVersionSelectors: [ { latest: true, }, ], publishVerificationResult: true, }).verifyProvider(); }, 20000);
Hi any pointers for me please
m
what’s the actual problem, can we start with that?
s
hi
it('validates expectations of consumers post', async () => { return new Verifier({ provider: 'post-secrets-secret-store-proxy-api', providerBaseUrl: process.env.API_URL, logLevel: 'debug', requestFilter: ((req, res, next) =>{ req.headers["Content-Type"]= contentType; req.headers["host"]= host; req.headers["x-amz-date"]= date; req.headers["x-amz-security-token"]= securityToken; req.headers["x-amz-content-sha256"]= contentSha256; req.headers["authorization"]= authorization; next(); }), stateHandlers: { ['I post credentials token']: async () => { await chai.request(requestUrl.hostname) .post(requestUrl.pathname) .set('Content-Type', contentType) .set('host', host) .set('x-amz-date', date) .set('x-amz-security-token', securityToken) .set('x-amz-content-sha256', contentSha256) .set('authorization', authorization) .send({ "details": "something to store" }) } }, pactBrokerUrl: ', pactBrokerUsername: '', pactBrokerPassword: '', providerVersion: 'latest', enablePending: true, consumerVersionSelectors: [ { latest: true, }, ], publishVerificationResult: true, }).verifyProvider(); }, 20000);
In this test I have tried to modify the headers using request filters
however when the request gets created the log shows below
pact@12.1.0: incoming request: {"body":{"details":"user credential details as string"},"headers":{"host":"iloveorange","x-amz-security-token":"iloveorange","content-type":"application/json","x-amz-content-sha256":"iloveorange","authorization":"iloveorange","x-amz-date":"iloveorange","accept":"*/*","accept-encoding":"gzip, deflate","content-length":"47"},"method":"POST","path":"/"}
when I created PACT from the consumer side I used Matchers.string()
in all the headers
All I would like to do is substitute signed headers in my request
m
That’s the incoming request, before the request filter
what does it look like when it hits the target?
On the provider state handler. I can’t work out what it does. The idea of a provider state is that a consumer says “the provider must be in this state for the test to pass”. Think of it as a test precondition. When the provider test runs, the provider can (optionally) use that state handler, if needed, to prepare itself for that interaction. Normally, you would write them as you would a
given
statement in BDD e.g.
So in your case, it probably should be “the user is authenticated” or “the user has valid credentials” or something. The state handler would replace the valid credentials (that may be expired, as in the case of AWS) with a new set of valid ones.
Pact does not care what these state handlers do, only that they don’t error. It’s implementation detail
s
do you mean pact@12.1.0: incoming request: {"body":{"action":"teardown","params":{},"state":"I post credentials token"},"headers":{"content-type":"application/json","accept":"*/*","accept-encoding":"gzip, deflate","host":"127.0.0.1:59405","content-length":"68"},"method":"POST","path":"/_pactSetup"}
pact@12.1.0: outgoing response: {"body":"{\"message\":\"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\\n\\nThe Canonical String for this request should have been\\n'POST\\n/Prod/\\n\\ncontent-typeapplication/json\\nhostoewstupmv1.execute-api.eu-west-1.amazonaws.com\\nx-amz-content-sha256737eaae3db0b50b020ae2b8ebf63da3c0af99af40a11720528b528f5c5bf9ace\\nx amz date20230720T145622Z\\nx-amz-security-token:IQoJb3JpZ2luX2VjEB8aCWV1LXdlc3QtMSJHMEUCIADTmp8sqz11zeXXzZtPTZqMO+RF+73p0lV7HywYBZZXAiEAjY6UvXl3VfCFCK57LyMMV2TzWcbloWpshgzA2E9zeDUqwQMIp///////////ARAAGgw2ODgzOTU5MTI5MTciDIyf251wCtyK4ohHriqVA7M5o3/GjPo31BVVZLT0RU0n+GGm4gcPhOWSl8K6Jmfj3Aaqrc1zYD0BRGIiN8cpykZyj6a8YO2t7OEzKxIYK2cy6OmQuR2ig3HPgQBZcTTWMf16a98ueplUS7d73GQCk1OK+ZyZlRqxmTLiwqCLOujli5y7fqeh6Pr0Nq7lHK5PxaBEBHe2ep0WN9sD076XULD0xvPSL5TqwG0CzEe7jRb6Bk3++SelB9R17pJi2+MCMLORZUX7fIyBjwjkO3lnPa8+P5SAiurVOJ1SJ4tdYtOYtRa2t79nDgfHBLVzZY8nozwT+UpLleCZ/6VSru0G9Jz7Fqdi2+GnucpMRNAa84NhsFcdaVf8qK572dvfuwvhv30/6VNR4e8MdEf4sPkFnNcIt3QYFFYFcVuEAlc//K4F/1BEk8PIuU/oxT1qzmx+z9vE0BNSB4Vl6uwMRQsxsqd797Gd55FjpxPjyNpkB2+q46EzjGSmaAw8HLiOnYZ/NsWK2h4tcv4Kz+FeoIntP2+1T6//sZi4HGbPlEvRqgsDVASKljD9hOWlBjqmAbnhCuG6bpTlFgOYn6DXGGTtEAapjEqmzk9q1qIESUckH9eJ8MdxrffIDN/w5Vu3f/9bUdXmewDGU73N5G/sVQuPRRiZJDBPXMHlrrp6YKgkRwi5ezZuax+om69ef/4s0dOjI9x+nOMbVlQfngUPWzgP9jZtHxhjy73okCxgKSQK9gBmaKIy2HjxfUZWkEhEOx8Uv8Z6SMYKESluZ3554Kcv/yWLA64=\\n\\ncontent-type;host;x-amz-content-sha256;x-amz-date;x-amz-security-token\\n24eca5ec17f48bf6cc35f9e76f13ac867b004ce49201fd18ecf919a4296d8574'\\n\\nThe String-to-Sign should have been\\n'AWS4-HMAC-SHA256\\n20230720T145622Z\\n20230720/eu-west-1/execute-api/aws4_request\\n04817b18dac64be4ff8e2cc2487a059bec4a4ddc7593ae1c572b4233b571b4ee'\\n\"}","headers":{"x-powered-by":"Express","content-type":"application/json","content-length":"1789","connection":"close","date":"Thu, 20 Jul 2023 145937 GMT","x-amzn-requestid":"1102e99b-dd3b-42ca-a23e-804e6e099ae5","x-amzn-errortype":"InvalidSignatureException","x-amz-apigw-id":"IXjJ9GEkDoEFWZQ=","x-amzn-trace-id":"Root=1-64b94bd9-3140ac8033f7b55e0990a1e9","x-cache":"Error from cloudfront","via":"1.1 8eb76cf20c60ae36d4b347142eaf1140.cloudfront.net (CloudFront)","x-amz-
the above is the response I get
the headers are not getting substituted
please help me understand why are these values coming from the PACT
{"body":{"details":"user credential details as string"},"headers":{"host":"*iloveorange*","x-amz-security-token":"*iloveorange*","x-amz-content-sha256":"*iloveorange*","x-amz-date":"*iloveorange*","authorization":"*iloveorange*","content-type":"application/json","accept":"*/*","accept-encoding":"gzip, deflate","content-length":"47"},"method":"POST","path":"/"}
apologies
y
here is an example I did a little while back https://github.com/you54f/aws-auth-pact Not sure if its the correct way, but it worked for the demo
it came out the back of this feature req/convo https://github.com/pact-foundation/pact-js/issues/304
s
HI Still cant get it to work
it('validates expectations of consumers post', async () => { return new Verifier({ provider: 'post-secrets-secret-store-proxy-api', providerBaseUrl: process.env.API_URL, logLevel: 'debug', stateHandlers: { ['I post credentials token']: async () => { await chai.request(requestUrl.hostname) .post(requestUrl.pathname) .set(signedHeadersReq) .send(JSON.stringify({ "details": "something to store" })) } }, requestFilter: async (req, res, next) => { signRequest() .then((result) => { signedHeadersReq = result.headers; contentType = result.headers['Content-Type']; host = result.headers['host']; date = result.headers['x-amz-date']; securityToken = result.headers['x-amz-security-token']; contentSha256 = result.headers['x-amz-content-sha256']; authorization = result.headers['authorization']; }) .catch((error) => { console.error('Error signing request:', error); }); req.headers['Content-Type']= contentType; req.headers['host'] = host; req.headers['x-amz-date']= date; req.headers['x-amz-security-token']= securityToken; req.headers['x-amz-content-sha256']= contentSha256; req.headers['authorization']= authorization; next(); }, pactBrokerUrl: ', pactBrokerUsername: ', pactBrokerPassword: , providerVersion: 'latest', enablePending: true, consumerVersionSelectors: [ { latest: true, }, ], publishVerificationResult: true, }).verifyProvider(); }, 20000);
pact@12.1.0: incoming request: {"body":{"details":"user credential details as string"},"headers":{"authorization":"iloveorange","host":"iloveorange","content-type":"application/json","x-amz-security-token":"iloveorange","x-amz-content-sha256":"iloveorange","x-amz-date":"iloveorange","accept":"*/*","accept-encoding":"gzip, deflate","content-length":"47"},"method":"POST","path":"/"} [085407.181] DEBUG (11168): pact@12.1.0: Proxying POST: / [085407.245] DEBUG (11168): pact@12.1.0: outgoing response: {"body":"{\"message\":\"Authorization header requires 'Credential' parameter. Authorization header requires 'Signature' parameter. Authorization header requires 'SignedHeaders' parameter. Authorization=iloveorange\"}","headers":{"x-powered-by":"Express","content-type":"application/json","content-length":"203","connection":"close","date":"Mon, 07 Aug 2023 075407 GMT","x-amzn-requestid":"bb15f752-31ca-4869-815d-264ef1edf931","x-amzn-errortype":"IncompleteSignatureException","x-amz-apigw-id":"JR5s7FEGDoEFkzA=","x-amzn-trace-id":"Root=1-64d0a31f-21a9ac4f22af719f3e20ef67","x-cache":"Error from cloudfront","via":"1.1 99c9ffdbfc5207f9665251bb3284f588.cloudfront.net (CloudFront)","x-amz-cf-pop":"LHR50-P4","x-amz-cf-id":"dpUdL4OupofzBaPtohTGrUwkyf0xLx1U5x5jNVLzj34eNCsy_ixfWQ=="},"status":403}
Please advise
m
AT the very least
Copy code
signRequest()
              .then((result) => {
                signedHeadersReq = result.headers;
                contentType = result.headers['Content-Type'];
                host = result.headers['host'];
                date = result.headers['x-amz-date'];
                securityToken = result.headers['x-amz-security-token'];
                contentSha256 = result.headers['x-amz-content-sha256'];
                authorization = result.headers['authorization'];
              })
              .catch((error) => {
                console.error('Error signing request:', error);
              });
That’s a promise, and is currently not handled. So all of the code after it that relies on those values has already fired (probably) before this promise is complete. I’d look at that as a starting point
s
No luck bro it('validates expectations of consumers post', async () => { return new Verifier({ provider: 'post-secrets-secret-store-proxy-api', providerBaseUrl: process.env.API_URL, logLevel: 'debug', requestFilter: async (req, res, next) => { const sigv4 = new SignatureV4({ service: "execute-api", region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, sessionToken: process.env.AWS_SESSION_TOKEN, }, sha256: Sha256, }); const signed = await sigv4.sign({ method: "POST", hostname: requestUrl.host, path: requestUrl.pathname, protocol: requestUrl.protocol, headers: { "Content-Type": "application/json", host: requestUrl.hostname, // compulsory }, body: JSON.stringify({ "details": "something to store" }) }); contentType = signed.headers['Content-Type']; host = signed.headers['host']; date = signed.headers['x-amz-date']; securityToken = signed.headers['x-amz-security-token']; contentSha256 = signed.headers['x-amz-content-sha256']; authorization = signed.headers['authorization']; req.headers['Content-Type']= signed.headers['Content-Type']; req.headers['host'] = signed.headers['host']; req.headers['x-amz-date']= signed.headers['x-amz-date']; req.headers['x-amz-security-token']= signed.headers['x-amz-security-token']; req.headers['x-amz-content-sha256']= signed.headers['x-amz-content-sha256']; req.headers['authorization']= signed.headers['authorization']; next(); }, stateHandlers: { ['I post credentials token']: async () => { await chai.request(requestUrl.hostname) .post(requestUrl.pathname) .set('Content-Type', contentType) .set('host', host) .set('x-amz-date', date) .set('x-amz-security-token',securityToken) .set('x-amz-content-sha256', contentSha256) .set('authorization', authorization) .send(JSON.stringify({ "details": "something to store" })) } }, pactBrokerUrl: pactBrokerUsername: , pactBrokerPassword: , providerVersion: 'latest', enablePending: true, consumerVersionSelectors: [ { latest: true, }, ], publishVerificationResult: true, }).verifyProvider(); }, 20000);
Bro even if I am doing something fundamentally wrong I am not aware please advise.
m
I can’t speak for the AWS use case, but I’d look at the
DEBUG
(or perhaps even
TRACE
log level) to see what the request is sent to AWS
y
please format your code with three backs ticks it is painful trading this thread
i’ve provided an example, your code looks incorrect
👍 1
m
As for this:
Copy code
['I post credentials token']: async () => {
                await chai.request(requestUrl.hostname)
                          .post(requestUrl.pathname)
                          .set('Content-Type', contentType)
                          .set('host', host)
                          .set('x-amz-date', date)
                          .set('x-amz-security-token',securityToken)
                          .set('x-amz-content-sha256', contentSha256)
                          .set('authorization', authorization)
                          .send(JSON.stringify({ "details": "something to store" }))
             }
I’m not sure what this is doing. This will be executed before the provider test, but it’s just dangling. It shouldn’t be hitting your AWS service, the Pact verifier will do that
y
you need to be using values from the pact to send to the request sign, if that is all your code it’s not going to work
☝️ 1
please create a example repo, you can use my example i’ve linked in this thread as a start
👍 1
ie start here https://github.com/you54f/aws-auth-pact try and get that running and let us know if you have issues
we can collab around that.
👌 1
s
HI
All my attempts are failing bro
it('validates expectations of consumers post', async () => { return new Verifier({ provider: 'post-secrets-secret-store-proxy-api', providerBaseUrl: process.env.API_URL, logLevel: 'debug', stateHandlers: { ['I post credentials token']: async () => { await chai.request(requestUrl.hostname) .post(requestUrl.pathname) .set(signedHeadersReq) .send( JSON.stringify({ "details": "something to store" })) } }, requestFilter:async(req, res, next) => { const sigv4 = new SignatureV4({ service: "execute-api", region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, sessionToken: process.env.AWS_SESSION_TOKEN, }, sha256: Sha256, }); const signed = await sigv4.sign({ method: "POST", hostname: requestUrl.host, path: requestUrl.pathname, protocol: requestUrl.protocol, headers: { "Content-Type": "application/json", host: requestUrl.hostname, // compulsory }, body: JSON.stringify({ "details": "something to store" }) }); signedHeadersReq = signed.headers; // console.log(authHeaders) req.headers['host'] = signedHeadersReq && signedHeadersReq['host'] ? signedHeadersReq['host'].toString() : ''; req.headers['x-amz-date'] = signedHeadersReq && signedHeadersReq['x-amz-date'] ? signedHeadersReq['x-amz-date'].toString() : ''; req.headers['authorization'] = signedHeadersReq && signedHeadersReq['authorization'] ? signedHeadersReq['authorization'].toString() : ''; // The following is required if using AWS STS to assume a role req.headers['x-amz-security-token'] = signedHeadersReq && signedHeadersReq['x-amz-security-token'] ? signedHeadersReq['x-amz-security-token'].toString() : ''; req.headers['x-amz-content-sha256'] = signedHeadersReq && signedHeadersReq['x-amz-content-sha256'] ? signedHeadersReq['x-amz-content-sha256'].toString() : ''; next(); }, pactBrokerUrl: pactBrokerUsername: pactBrokerPassword: providerVersion: 'latest', enablePending: true, consumerVersionSelectors: [ { latest: true, }, ], publishVerificationResult: true, }).verifyProvider(); }, 20000);
not working for me sir
please advise
I think I get you now
Will report back
Bro its trying to hit localhost
Request Failed - error sending request for url (http://127.0.0.1:62608/Prod): operation timed out