aww4 credentials not accepted when sent via pactjs - javascript

I am attempting to verify my pact.json that has been generated by my consumer. However for verifying I need to include AWS4 credentials in order to be able to get a response from my provider. I am attempting to do this using customProviderHeaders. I am using the library AWS4(https://github.com/mhart/aws4) to generate the token. Below is my code:
const aws4 = require('aws4');
const path = require('path');
import { before, beforeEach, describe, it } from 'mocha';
const {
Verifier
} = require('../../../node_modules/#pact-foundation/pact');
function getToken() {
const opts: any = {
method: 'GET',
region: 'us-east-2',
service: 'execute-api',
path: '/qa/api/',
host: '123456789.execute-api.us-east-2.amazonaws.com',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
aws4.sign(opts, {accessKeyId: '$AWSACCESSKEY', secretAccessKey: '$AWSSECRETKEY'});
return opts.headers;
}
describe('Pact Verification', () => {
it('should validate the watchlist expectations', () => {
let headers = getToken();
let authToken = headers.Authorization;
let date = headers[`X-Amz-Date`];
let opts = {
provider: 'DealerBlock',
providerBaseUrl: 'https://3ua1cprd53.execute-api.us-east-2.amazonaws.com',
pactUrls: [path.resolve(process.cwd(), 'src/test/pact/path_to_my_json')],
customProviderHeaders: [`Authorization: ${authToken}`, `X-Amz-Date: ${date}`]
};
return new Verifier().verifyProvider(opts)
.then(output => {
console.log('STARTED');
console.log(opts.pactUrls);
console.log('Pact Verification Complete');
console.log(output);
});
});
});
The function getToken() generates a new token and I then grab the token and date and insert them into my request using the customer provider headers.
I see the following:
INFO: Replacing header 'Authorization: ' with 'Authorization: AWS4-HMAC-SHA256 Credential=AKIAJ5FTCODVMSUTEST/2018908/us-east-2/execute-api/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ceea9aac0303769da58357cb37cb849cb0bbfc13ff0a25cea977385368531349'
INFO: Replacing header 'X-Amz-Date: ' with 'X-Amz-Date: 20180528T184202Z'
However I get the following error:
Actual: {"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."}
Am I using the customProviderHeaders in the correct manner? Or does anyone have any suggestions as to what I should do differently? I am able to send a request using the same credentials via Postman so not sure whats going on here.
Thanks!

It looks OK to my eyes.
Could it be that you're not interpolating the variables (that also appear not to be defined anywhere) in the following statement:
aws4.sign(opts, {accessKeyId: '$AWSACCESSKEY', secretAccessKey: '$AWSSECRETKEY'});

Was able to get this working when I passed in headers of: 'Content-Type': 'application/x-www-form-urlencoded' via customProviderHeaders.
Even though this header was listed in my consumer generated json contract, the pact provider did not seem to see it.

Related

Okta: How to retrieve Access Token via SessionToken (JavaScript)

Im able to fetch the "sessionToken" via the Okta API, however how am I able to retrieve the AccessToken with the "sessionToken"?
I am using the JS SDK: https://github.com/okta/okta-auth-js
let oktaConfig2 = {
issuer: "https://{OKTA_DOMAIN}/oauth2/default/v1/authorize"
};
const oktaAuth2 = new OktaAuth(oktaConfig2);
oktaAuth2.token
.getWithoutPrompt({
responseType: "id_token",
sessionToken: sessionToken2,
})
.then(function (data2) {
console.log(data2);
});
I receive an error 404 in the response.
Any ideas?
TIA
Remove the /v1/authorize from the issuer value and it should take care of the 404.

How to solve 'Access-Control-Allow-Origin' error when trying to access api from localhost

I've seen a lot of topics on this error, but I'm very new to js and apis, so I couldn't really understand much, so I apologize for noob status.
I'm trying to access an api from sportradar. I'm testing it out on a simple react project created from create-react-app and using axios. When I console.log the data I get these errors: https://dzwonsemrish7.cloudfront.net/items/372w3g2b3F141t2P0K1G/Image%202018-09-04%20at%2011.33.38%20AM.png
I guess I can't access it from a local host? here is my function with the request:
getPlayerInfo() {
const apiKey = "my-api-key";
const playerID = "41c44740-d0f6-44ab-8347-3b5d515e5ecf";
const url = `http://api.sportradar.us/nfl/official/trial/v5/en/players/${playerID}/profile.json?api_key=${apiKey}`;
axios.get(url).then(response => console.log(response));
}
If the backend support CORS, you probably need to add to your request this header:
headers: {"Access-Control-Allow-Origin": "*"}
Your code would be like this:
getPlayerInfo() {
const apiKey = "my-api-key";
const playerID = "41c44740-d0f6-44ab-8347-3b5d515e5ecf";
const url = `http://api.sportradar.us/nfl/official/trial/v5/en/players/${playerID}/profile.json?api_key=${apiKey}`;
const config = {
headers: {'Access-Control-Allow-Origin': '*'}
};
axios.get(url,config).then(response => console.log(response));
}
Hope this helps !

How to integrate Dialogflow v2 with javascript frontend (Vue.js)

I'm trying to integrate Dialogflow with Vue.js (and axios) according to the documentation's sample HTTP request: https://dialogflow.com/docs/reference/v2-auth-setup and detectIntent: https://dialogflow.com/docs/reference/api-v2/rest/v2beta1/projects.agent.sessions/detectIntent.
I have a service account set up with sufficient permissions, and given it the path parameters and request body as shown in the documentation, but I keep getting 'Error: Request failed with status code 400' when calling the detectIntent API.
There are a few things I'm not sure of, though:
How do I get a sessionId? Currently I just copy the sessionId from Firebase Function logs which shows up when entering a query through the Dialogflow console directly.
How do I actually implement $(gcloud auth print-access-token) in javascript code? Currently I'm running the command in the terminal and pasting the token in the code, just to test if the API works, but I have no clue how it should be implemented.
(Perhaps useful, I have fulfillment set up in a functions folder, and that is working nicely.)
Thanks in advance!
<script>
import axios from 'axios'
export default {
name: 'myChatBot',
mounted () {
// Authorization: Bearer $(gcloud auth print-access-token)
const session = 'projects/mychatbot/agent/sessions/some-session-id'
const token = 'xxxxxxxxxxxx'
axios.defaults.baseURL = 'https://dialogflow.googleapis.com'
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios
.post(`/v2beta1/${session}:detectIntent`, {
"queryInput": {
"text": "add buy milk to inbox",
"languageCode": "en-US"
}
})
.then(response => console.log(response))
.catch(error => console.log(error))
}
}
</script>
You can use JWT authorization to handle your #2 question. You just need to put your JSON file someplace safe. https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth
The reason you are getting the code 400 is because your params are a little off. Here is how your post should look (I've also added some extra code to handle token generation):
<script>
import axios from 'axios'
import { KJUR } from 'jsrsasign'
const creds = require('./YOUR_JSON_FILE')
export default {
name: 'myChatBot',
data() {
return {
token: undefined,
tokenInterval: undefined
}
},
created() {
// update the tokens every hour
this.tokenInterval = setInterval(this.generateToken, 3600000)
this.generateToken()
},
mounted () {
this.detectIntent('add buy milk to inbox')
},
beforeDestroy() {
clearInterval(this.tokenInterval)
},
methods: {
generateToken() {
// Header
const header = {
alg: 'RS256',
typ: 'JWT',
kid: creds.private_key_id
}
// Payload
const payload = {
iss: creds.client_email,
sub: creds.client_email,
iat: KJUR.jws.IntDate.get('now'),
exp: KJUR.jws.IntDate.get('now + 1hour'),
aud: 'https://dialogflow.googleapis.com/google.cloud.dialogflow.v2.Sessions'
}
const stringHeader = JSON.stringify(header)
const stringPayload = JSON.stringify(payload)
this.token = KJUR.jws.JWS.sign('RS256', stringHeader, stringPayload, creds.private_key)
},
detectIntent(text, languageCode = 'en-US') {
if (!this.token) {
// try again
setTimeout(this.detectIntent, 300, text, languageCode)
return
}
// error check for no text, etc.
const session = 'projects/mychatbot/agent/sessions/some-session-id'
axios.defaults.baseURL = 'https://dialogflow.googleapis.com'
axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}`
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios
.post(`/v2beta1/${session}:detectIntent`, {
queryInput: {
text: {
text,
languageCode
}
}
})
.then(response => console.log(response))
.catch(error => console.log(error))
}
}
}
</script>
You can see that in QueryInput it's taking 1 of 3 different types of objects ("text" being one of those).
In the link, it's stated under the HTTP request session path parameters that "It's up to the API caller to choose an appropriate session id.
It can be a random number or some type of user identifier (preferably hashed).
For integration with Dialogflow V2, here's an example for doing with third-party tools that are easy to integrate and start using.
The sessionId is an identifier you can provide that will indicate to Dialogflow whether subsequent requests belong to the same "session" of user interaction (see docs).
For a client's first request to the API, you could just generate a random number to use as a session ID. For subsequent requests from the same client (e.g. if a user is continuing to converse with your agent) you can reuse the same number.
Your implementation of the token management looks fine, as long as the service account you are using has appropriately limited access (since this token could potentially allow anyone to make requests to Google Cloud APIs). For additional security, you could consider proxying the request to Dialogflow through your own server rather than making the call from the client.

In firebase - How to generate an idToken on the server for testing purposes?

I want to test a a cloud function that creates users.
In normal cases, inside the browser i generate an idToken and i send it to server via headers: Authorization : Bearer etcIdToken
But I want to test this function without the browser. In my mocha tests i have:
before(done => {
firebase = require firebase.. -- this is suppose to be like the browser lib.
admin = require admin..
idToken = null;
uid = "AY8HrgYIeuQswolbLl53pjdJw8b2";
admin.auth()
.createCustomToken(uid) -- admin creates a customToken
.then(customToken => {
return firebase.auth() -- this is like browser code. customToken get's passed to the browser.
.signInWithCustomToken(customToken) -- browser signs in.
.then(signedInUser => firebase.auth() -- now i want to get an idToken. But this gives me an error.
.currentUser.getIdToken())
})
.then(idToken_ => {
idToken = idToken_
done();
})
.catch(err => done(err));
})
The error i'm getting is:
firebase.auth(...).currentUser.getIdToken is not a function - getting the idToken like this works on client - and is documented here.
I tried directly with signedInUser.getIdToken(). Same problem:
signedInUser.getIdToken is not a function - not documented. just a test.
I think this is because firebase object is not intended for node.js use like i'm doing here. When signing in - stuff get's saved in browser local storage - and maybe this is why.
But the question still remains. How can i get an idToken inside node.js in order to be able to test:
return chai.request(myFunctions.manageUsers)
.post("/create")
.set("Authorization", "Bearer " + idToken) --- i need the idToken here - like would be if i'm getting it from the browser.
.send({
displayName: "jony",
email: "jony#gmail.com",
password: "123456"
})
am I approaching this wrong? I know that if i can get the idToken it will work. Do i rely need the browser for this? Thanks :)
From Exchange custom token for an ID and refresh token, you can transform a custom token to an id token with the api. Hence, you just have to generate a custom token first from the uid, then transform it in a custom token. Here is my sample:
const admin = require('firebase-admin');
const config = require('config');
const rp = require('request-promise');
module.exports.getIdToken = async uid => {
const customToken = await admin.auth().createCustomToken(uid)
const res = await rp({
url: `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${config.get('firebase.apiKey')}`,
method: 'POST',
body: {
token: customToken,
returnSecureToken: true
},
json: true,
});
return res.idToken;
};
L. Meyer's Answer Worked for me.
But, the rp npm package is deprecated and is no longer used.
Here is the modified working code using axios.
const axios = require('axios').default;
const admin = require('firebase-admin');
const FIREBASE_API_KEY = 'YOUR_API_KEY_FROM_FIREBASE_CONSOLE';
const createIdTokenfromCustomToken = async uid => {
try {
const customToken = await admin.auth().createCustomToken(uid);
const res = await axios({
url: `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${FIREBASE_API_KEY}`,
method: 'post',
data: {
token: customToken,
returnSecureToken: true
},
json: true,
});
return res.data.idToken;
} catch (e) {
console.log(e);
}
}
curl 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=<FIREBASE_KEY>' -H 'Content-Type: application/json'--data-binary '{"email": "test#test.com","password":"test","returnSecureToken":true}'
If this curl doesn't run, try running the same thing on Postman. It works!

Enable CORS from a Node.JS Callback Function

I'm attempting to use Twilio Functions to handle token generation for my Twilio application. I previously was using a Node.js + Express Server to accomplish this, but I do not know how to figure out enable CORS in this type of environment.
My Client Code looks like this:
$('#new-twilio').click(function(){
var toNum = this.value;
if(token == undefined) {
$.getJSON('https://my-twilio-function/endpoint').done(function(data){
token = data.token;
Twilio.Device.setup(token, {debug: true});
Twilio.Device.ready(function(device){
Twilio.Device.connect({"PhoneNumber": toNum});
});
}).fail(function(error){
alert("Failure!");
alert(JSON.stringify(error));
});
} else {
Twilio.Device.connect({"PhoneNumber": toNum});
}
});
My function code looks like this:
exports.handler = function(context, event, callback) {
const client = context.getTwilioClient();
const ClientCapability = require('twilio').jwt.ClientCapability;
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST",
"Access-Control-Allow-Headers": "content-type, accept",
"Content-Type": "application/json"
};
let identity = "sampleIdentity";
const capability = new ClientCapability({
accountSid: context.ACCOUNT_SID,
authToken: context.AUTH_TOKEN
});
capability.addScope(new ClientCapability.IncomingClientScope(identity));
capability.addScope(new ClientCapability.OutgoingClientScope({
applicationSid: context.TWILIO_TWIML_APP_SID
}));
console.log(capability.toJwt());
callback(null, {headers: responseHeaders, identity: identity, token: capability.toJwt()});
};
Worth noting, the console.log proves that this function is returning the exact token I need, but I continue to get this error:
XMLHttpRequest cannot load https://my-twilio-function/endpoint. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
Obviously, my twilio function is at a real URL. As much as I google, I cannot find how to allow access control to this type of node method.
This client code ended up working:
exports.handler = function(context, event, callback) {
const client = context.getTwilioClient();
const ClientCapability = require('twilio').jwt.ClientCapability;
const response = new Twilio.Response();
response.appendHeader('Access-Control-Allow-Origin', '*');
response.appendHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
response.appendHeader('Content-Type', 'application/json');
let identity = "sampleIdentity";
const capability = new ClientCapability({
accountSid: context.ACCOUNT_SID,
authToken: context.AUTH_TOKEN
});
capability.addScope(new ClientCapability.IncomingClientScope(identity));
capability.addScope(new ClientCapability.OutgoingClientScope({
applicationSid: context.TWILIO_TWIML_APP_SID
}));
response.setBody({identity: identity, token: capability.toJwt()})
console.log(capability.toJwt());
callback(null, response);
};
Twilio developer evangelist here.
I'm glad to see that K. Rhoda sorted out the issue. I just wanted to make obvious what made it work.
There is a custom response you can access from Twilio.Response within the Function. The response is initialized like:
const response = new Twilio.Response();
and then has the following useful methods:
// set the body of the response
response.setBody(body);
// set a header for the response
response.appendHeader(key, value);
// set all the headers for the response
response.setHeaders(obj);
// set the status code for the response
response.setStatusCode(200);
You can then send that response using the callback function, like so:
callback(null, response);
In my case everything above didn't work because i had "Check for valid Twilio signature" checked in my fuction (by default) and made requests without signature.
After I unchecked it, answers above worked. So pay attention to whether you have this checked and if so whether your request has proper signature.

Categories