Alexa skill that uses data from an external API with API-KEY - node.js

Im trying to access an external API from the alexa back end code using lambda that runs on node.js 8.1,the code can access any endpoint that doesnt require an api-key but i cant find a way to include my authoraztion (api-key) in the code so i can retrieve the data that im looking for.
the api documentation that im trying to access is as follows:
curl --request GET -H 'Authorization: Bearer ' -H 'Content-Type: application/json' "https://some-end-point/path/i/want"
this is for the alexa-skills-kit,it uses a lambda after the skill is invoked and tries to access an external api whith an api-key.The code can retrieve info to any endpoint that doesnt require any key.
I already tried including the key as a parameter in the URL (api key + URL),since im new to alexa,lambda,nodejs im not sure how to debug it but i just dont get the desire output(which is alexa turning the text to speech with the info that got from the external api).
pd:asuming my api key is: xxxx-xxxx-xxxx
// endpoint that i want
url = https://some-end-point/path/i/want
await getRemoteData(url)
.then((response) => {
const data = JSON.parse(response);
outputSpeech = `the data thati want is ${data.records.length} `;
for (let i = 0; i < data.records.length; i++) {
if (i === 0) {
//first record
outputSpeech = outputSpeech + data.records[i].fields.name + ', '
} else if (i === data.records.length - 1) {
//last record
outputSpeech = outputSpeech + 'y '+data.records[i].fields.name +
', '
} else {
//middle record(s)
outputSpeech = outputSpeech + data.records[i].fields.name + ', '
}
}
})
//function getRemoteData
const getRemoteData = function (url) {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? require('https') : require('http');
const request = client.get(url,(response) => {
if (response.statusCode < 200 || response.statusCode > 299) {
reject(new Error('Failed with status code: ' + response.statusCode));
}
const body = [];
response.on('data', (chunk) => body.push(chunk));
response.on('end', () => resolve(body.join('')));
});
request.on('error', (err) => reject(err))
})
};
the code above can acces any endpoint without errors but i dont know how to include the api key so it can acces the api,the output expected is to have access to the api by including the api-key
Any help on this problem would be gladly apreciated from this newbie ....

You need to pass a options object as the second parameter of client.get. For example:
const options = {
headers: {
'Authorization': 'Bearer <your API key>'
}
}
Then where you do the request:
const request = client.get(url, options, (response) => {
// Do the rest of your stuff here...
}
You can find more details on the options here.

Related

aws elasticsearch getting signature error on post request

Got a 403 signature error , when using the below fetch function:
function elasticsearchFetch(AWS, elasticsearchDomain, endpointPath, options = {}, region = process.env.AWS_REGION) {
return new Promise((resolve, reject) => {
const { body, method = 'GET' } = options;
const endpoint = new AWS.Endpoint(elasticsearchDomain);
const request = new AWS.HttpRequest(endpoint, region);
request.method = method;
request.path += endpointPath;
request.headers.host = elasticsearchDomain;
if (body) {
request.body = body;
request.headers['Content-Type'] = 'application/json';
request.headers['Content-Length'] = request.body.length;
}
const credentials = new AWS.EnvironmentCredentials('AWS');
const signer = new AWS.Signers.V4(request, 'es');
signer.addAuthorization(credentials, new Date());
const client = new AWS.HttpClient();
client.handleRequest(request, null, (res) => {
let chunks = '';
res.on('data', (chunk) => {
chunks += chunk;
});
res.on('end', () => {
if (res.statusCode !== 201) console.log('Got these options STATUSCODE', JSON.stringify(options, false, 2));
return resolve({ statusCode: res.statusCode, body: chunks });
});
}, (error) => {
console.log('Got these options ERROR', JSON.stringify(options, false, 2));
return reject(error);
});
});
}
This is the options used for the request in above function :
{
"method": "POST",
"body": "{\"prefix\":\"image_233/ArtService/articles-0/GB/ART-60297885/\",\"id\":\"ART-60297885\",\"retailUnit\":\"GB\",\"commercial\":{\"name\":{\"en-GB\":\"FÖRBÄTTRA\"}},\"schemaType\":\"product\",\"productType\":\"ART\"}"
}
and got this error :
{
"statusCode": 403,
"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.\"}"
}
This is the endpoint : 233/_doc/
I believe your Content-Length header is incorrect, causing the signature mismatch.
Your payload includes the string FÖRBÄTTRA, which has two double-byte characters.
You're setting the Content-Length to request.body.length, which comes to 186.
While this is the number of characters in the body, it is not the number of bytes in the body (188).
To calculate the Content-Length, use Buffer.byteLength(request.body). For a POST request like this, you can even remove that line of code altogether, and the request will succeed.
// Content-Length is only needed for DELETE requests that include a request
// body, but including it for all requests doesn't seem to hurt anything.
request.headers['Content-Length'] = Buffer.byteLength(request.body);
Source: https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-node
By the way, why not use elasticsearch client for nodejs to communicate with elasticsearch rather than writing your own logic. You can consider using http-aws-es which does the request signing part for you. The code will look like
const { Client } = require("elasticsearch");
const esConnectionClass = require("http-aws-es");
const elasticsearchConfig = {
host: "somePath",
connectionClass: esConnectionClass
};
const nativeClient = new Client(elasticsearchConfig);
const result = await nativeClient.search({});

Sending Form Data with the native node module

for my current project I have to send form-data from my lambda function to an api endpoint. The api endpoint essentially expects two images (that it compares with one another) and a key. As mentioned before, I somehow seem unable to send the correct form-data to the api endpoint. I checked out postman, and it seems to have worked alright, but something doesn't seem to work in my function. I presume it must be related the form-data string that I'm sending. Below you can find a shortened version of the function (I excluded the two image files), but somehow I'm getting an error back telling me that the api cannot read the key property:
const http = require('http');
const https = require('https');
const httpPromise = (protocol, params, postData) => {
return new Promise((resolve, reject) => {
const requestModule = protocol === 'http' ? http : https;
const req = requestModule.request(params, res => {
// grab request status
const statusCode = res.statusCode;
if(statusCode < 200 || statusCode > 299) {
throw new Error('Request Failed with Status Code:', statusCode);
}
let body = '';
// continuosly update data with incoming data
res.setEncoding('utf8');
res.on('data', data => body += data);
// once all data was received
res.on('end', () => resolve(body));
})
// write data to a post request
if(typeof(params.method) === 'string' && params.method === 'POST' && postData) {
req.write(postData)
}
// bind to the error event
req.on('error', err => reject(err));
// end the request
req.end();
})
}
const controller = async () => {
const apiKey = "00000000";
const options = {
hostname: '***"
port: 80,
path: '***'
method: 'POST',
headers: {"content-type": "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"}
}
const postData = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\00000000\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
let result = await httpPromise('http', options, postData)
console.log(result);
}
yeah, so somehow it just doesn't seem to recognise the key in the postData string. I have tried various different combinations but just can't seem to get this to work.
The default http and https libraries are kind of wordy and annoying.
Would recommend using the request library instead. Read more here
In which case, to make the request, you can simply write it as :
var request = require('request');
var formData = {
// Pass a simple key-value pair
my_field: 'my_value',
}
request.post({url:'http://service.com/upload', formData: formData}, (err, response, body) => {
// Handle response here
});
Alright, so for anyone who might also face the same issue, it took me a little but figured out what the issue was. I didn't set the Content-Length header, which then in turn meant that node automatically added the Transfer-Encoding Header and set its value to chunk. This broke the receiving api and resulted in the issue. Setting the Content-Length header to the correct length and setting the Transfer-Encoding Header to an empty string solved my issue here (but I think one could also simply omit the transfer-encoding header once you defined the Content-Length Header).

Passing parameters in angular4 services

I have been trying to pass two parameter from my angular frontend to node api. However I am getting error on my node console that paramter value is missing or invalid when I run my app.
Below here is node api code
app.post('/users', function(req, res) {
var username = req.body.username;
var orgName = req.body.orgName;
logger.debug('End point : /users');
logger.debug('User name : ' + username);
logger.debug('Org name : ' + orgName);
if (!username) {
res.json(getErrorMessage('\'username\''));
return;
}
if (!orgName) {
res.json(getErrorMessage('\'orgName\''));
return;
}
var token = jwt.sign({
exp: Math.floor(Date.now() / 1000) + parseInt(config.jwt_expiretime),
username: username,
orgName: orgName
}, app.get('secret'));
helper.getRegisteredUsers(username, orgName, true).then(function(response) {
if (response && typeof response !== 'string') {
response.token = token;
res.json(response);
} else {
res.json({
success: false,
message: response
});
}
});
});
Here is my angular service code . For the sake of demonstration , I am passing dummy values from angular service
getEnrollmentId(userName,org) {
let headers = new Headers({ 'Content-Type': 'x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
let body = "'username=Barry&orgName=org2'";
return this.http.post('http://localhost:4000/users', body, options )
.map((res: Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server error shit'));
}
When I try to acheive same thing using curl query by calling node api and passing paramters , I am successfully able to post data and return response from api. Below here is curl query
curl -s -X POST \
http://localhost:4000/users \
-H "content-type: application/x-www-form-urlencoded" \
-d 'username=Jim&orgName=org1'
What am I doing wrong in my angular services?
Pass the parameter as url search params ,
below code should help
let body = new URLSearchParams();
body.set('username', username);
body.set('orgName', orgName);

Angular2 POST Observable Request returns a not readable JSON Object

I'am using an Angular2 POST Request with Observable to a Node API.
I keep getting a strange JSON Object back as Response.
Response from Node API App - via Chrome Console:
{"_isScalar":false,"source":{"_isScalar":false,"source":
{"_isScalar":false,"source":{"_isScalar":false},"operator":
{"delay":500,"scheduler":{"actions":[],"active":false}}},"operator":
{}},"operator":{"caught":"[Circular]"}}
Angular2 Code:
login2(): Observable<any> {
this.url = 'http://localhost:3080/user/login';
this.obj.email = 'jhon.doe#foo.de';
this.obj.password = 'pass';
let formObj = this.form.getRawValue();
let serializedForm = JSON.stringify(formObj);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
var obsRequest = this.http.post(this.url, this.obj, options)
.map(res => res.json())
.catch( (error: any) => Observable.throw(error.json().error || 'Server error') );
console.log(JSON.stringify(obsRequest, this.avoidCircular(obsRequest)));
return obsRequest;
}
P.S. avoidCircular is an extra workaround for the javaScript built-in JSON.stringify, cause when I don't use it get the following error:
TypeError: Converting circular structure to JSON
avoidCircual Code:
avoidCircular(objCens) {
var i = 0;
return function(k, v) {
if(i !== 0 && typeof(objCens) === 'object' && typeof(v) == 'object' && objCens == v)
return '[Circular]';
if(i >= 29)
return '[Unknown]';
++i;
return v;
}
}
The API call is working fine via Postman!
Any idea how to fix this please?
obsRequest is not the response from your request, it is the request observable. In order to receive the response you need to subscribe to it.
var obsRequest = this.http.post(this.url, this.obj, options)
.map(res => res.json())
.catch( (error: any) => Observable.throw(error.json().error || 'Server error') );
obsRequest.subscribe((response)=> console.log(response));
I suggest you to read the http module of the official documentation: https://angular.io/guide/http

Podio API addItem call

I'm trying to implement https://developers.podio.com/doc/items/add-new-item-22362 Podio API addItem call in a nodejs module. Here is the code:
var _makeRequest = function(type, url, params, cb) {
var headers = {};
if(_isAuthenticated) {
headers.Authorization = 'OAuth2 ' + _access_token ;
}
console.log(url,params);
_request({method: type, url: url, json: true, form: params, headers: headers},function (error, response, body) {
if(!error && response.statusCode == 200) {
cb.call(this,body);
} else {
console.log('Error occured while launching a request to Podio: ' + error + '; body: ' + JSON.stringify (body));
}
});
}
exports.addItem = function(app_id, field_values, cb) {
_makeRequest('POST', _baseUrl + "/item/app/" + app_id + '/',{fields: {'title': 'fgdsfgdsf'}},function(response) {
cb.call(this,response);
});
It returns the following error:
{"error_propagate":false,"error_parameters":{},"error_detail":null,"error_description":"No matching operation could be found. No body was given.","error":"not_found"}
Only "title" attribute is required in the app - I checked that in Podio GUI. I also tried to remove trailing slash from the url where I post to, then a similar error occurs, but with the URL not found message in the error description.
I'm going to setup a proxy to catch a raw request, but maybe someone just sees the error in the code?
Any help is appreciated.
Nevermind on this, I found a solution. The thing is that addItem call was my first "real"-API method implementation with JSON parameters in the body. The former calls were authentication and getApp which is GET and doesn't have any parameters.
The problem is that Podio supports POST key-value pairs for authentication, but doesn't support this for all the calls, and I was trying to utilize single _makeRequest() method for all the calls, both auth and real-API ones.
Looks like I need to implement one for auth and one for all API calls.
Anyway, if someone needs a working proof of concept for addItem call on node, here it is (assuming you've got an auth token beforehand)
_request({method: 'POST', url: "https://api.podio.com/item/app/" + app_id + '/', headers: headers, body: JSON.stringify({fields: {'title': 'gdfgdsfgds'}})},function(error, response, body) {
console.log(body);
});
You should set content-type to application/json
send the body as stringfied json.
const getHeaders = async () => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
};
const token = "YOUR APP TOKEN HERE";
headers.Authorization = `Bearer ${token}`;
return headers;
}
const createItem = async (data) => {
const uri = `https://api.podio.com/item/app/${APP_ID}/`;
const payload = {
fields: {
[data.FIELD_ID]: [data.FIELD_VALUE],
},
};
const response = await fetch(uri, {
method: 'POST',
headers: await getHeaders(),
body: JSON.stringify(payload),
});
const newItem = await response.json();
return newItem;
}

Resources