Authorization issue when downloading AMZ Advertising Report - node.js

I have the following app to download productAds from amazons Sponsored Products API
Docs: https://advertising.amazon.com/API/docs/reference/reports
When I try to fun it, I get the response from requesting the report, and obtain a file in an s3 Bucket.
server is running at localhost:3000
{"reportId":"amzn1.clicPI.v1.p7.5B7EE.d141f-e5-4b-a8-6a4e712","status":"SUCCESS","statusDetails":"Report has been successfully generated","location":"https://advertising-api.amazon.com/v1/reports/amzn1.clsAPI.v1.p7.5B7EE.d151f-e5-4b-a8-699e712/download","fileSize":22}
Next I try to download the report
and the repose I get the following erro:
donwloading <?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidArgument</Code><Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message><ArgumentName>Authorization</ArgumentName><ArgumentValue>bearer Atza|IwEBIJvCt20TUi122srkN4JCOdUxlBNuLJBrtbIGF9x5QbKG67f-K-0L4RkLzeyouXWy_U_-VscaCe1aFqOJK55X9Mu2X6nwWkAWRyhc6cCMfPjKpyyVjKtPqC8Plme84om1dqtmIqC93yUVc_clHimQqmnl262te2EXyUhYoVQg8hK2nlDG67Iw7xjsLK4rgl3E4RR36DHnZkEOnVQZtfjIkIbcYtsCSAdpRZRazF4FQfpS-jHvMlwuH8TZfY9tRpmBEx5fjJw1WZ14Dejqti23mZ7yt-MjNkUuD-DdPXs3fek1ZJePlHEVzcI2y_WzCnwJnoSVp5a5w1WgNco8YqEGEuLsT9S0dxQRluTiw8f4b4lx2FFFm9jz0K7pqo1Mvs6DZOVCDJzE-xJ_VLlWoE5QDMUAMor4AEQH44_0NWBjJDrYaVvn1vZLCER1uxW4jgr127W5yXaj4y1vj_vADwFq9a3330hAc73EWwL6FFSfoTQZyNc4Fh1d3DXfVHpXFk6cv0bHt4cV_OotGwGHat5fv75VHX5K3al3Xd5-QJv2cTiQ9srY5oqKsdbxptGaxAdrdMQaUlFHhyHEGbwED9xYoCw6-IauN15gvMAei9wz2kzRCA</ArgumentValue><RequestId>BDCE812C4363E9D2</RequestId><HostId>4ixH24mPtMBt+FDnI3rM9UJP95toaNBmmR1v0uQJ5XkyiXbtLEuZ8d+vDI0+gquwhn6/Fkz6/+o=</HostId></Error>
Here is the app. How can I download the report and pass the headers correctly? I found this repo in PHP that has the download function: https://github.com/dbrent-amazon/amazon-advertising-api-php/blob/master/AmazonAdvertisingApi/Client.php
const express = require('express')
const app = express();
const request = require('request');
const moment = require('moment');
const fs = require('fs')
const path = require('path')
const config = require('./config')
const auth = require('./helpers/auth');
const cron = require('./helpers/cron');
const con = require('./helpers/database');
const getProfiles = (headers) => {
return new Promise((resolve, reject) => {
request({
url: config.ad_url + '/v1/profiles',
method: "GET",
headers: headers
}, (err, httpResponse, body) => {
if (err) {
reject(err);
}
resolve(body);
});
})
}
const createReport = (params, recordType, headers) => {
return new Promise((resolve, reject) => {
request({
url: config.ad_url + '/v1/' + recordType + '/report',
method: "POST",
headers: headers,
json: params
}, (err, httpResponse, body) => {
if (err) {
reject(err);
}
resolve(body);
});
})
}
const getReport = (reportId, headers) => {
return new Promise((resolve, reject) => {
request({
url: config.ad_url + '/v1/reports/' + reportId,
method: "GET",
headers: headers
}, (err, httpResponse, body) => {
if (err) {
reject(err);
}
resolve(body);
});
})
}
function download(fileUrl, apiPath, callback) {
var url = require('url'),
http = require('http'),
p = url.parse(fileUrl),
timeout = 10000;
var file = fs.createWriteStream(apiPath);
var timeout_wrapper = function (req) {
return function () {
console.log('abort');
req.abort();
callback("File transfer timeout!");
};
};
console.log('before');
var request = http.get(fileUrl).on('response', function (res) {
console.log('in cb');
var len = parseInt(res.headers['content-length'], 10);
var downloaded = 0;
res.on('data', function (chunk) {
file.write(chunk);
downloaded += chunk.length;
process.stdout.write("Downloading " + (100.0 * downloaded / len).toFixed(2) + "% " + downloaded + " bytes" + isWin ? "\033[0G" : "\r");
// reset timeout
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, timeout);
}).on('end', function () {
// clear timeout
clearTimeout(timeoutId);
file.end();
console.log(file_name + ' downloaded to: ' + apiPath);
callback(null);
}).on('error', function (err) {
// clear timeout
clearTimeout(timeoutId);
callback(err.message);
});
});
// generate timeout handler
var fn = timeout_wrapper(request);
// set initial timeout
var timeoutId = setTimeout(fn, timeout);
}
const recordType = 'productAds';
const campaignType = 'sponsoredProducts';
const reportDate = moment().format('YYYYMMDD');
const metrics = 'campaignId,sku,currency,attributedConversions1d';
const reportParam = {
campaignType: campaignType,
// segment: "query",
reportDate: reportDate,
metrics: metrics
}
auth().then(res => {
const headers = {
'Content-Type': 'application/json',
'Authorization': config.token_type + ' ' + res.access_token,
'Amazon-Advertising-API-Scope': '196354651614',
}
// getProfiles(headers).then(res => {})
const reportId = 'amzn1.clicksAI.1.p.5B057EE.d41f-e5-4a-a8-699e712';
getReport(reportId, headers).then(res => {
console.log(res)
const file = JSON.parse(res);
// console.log(file.location )
// download
request({
url: file.location,
method: 'GET',
headers: {
'Authorization': headers.Authorization,
'Amazon-Advertising-API-Scope': '196335474',
'Allow': 'GET, HEAD, PUT, DELETE'
}
}, (err, response, body) => {
console.log('donwloading', body)
})
})
createReport(reportParam, recordType, headers).then(res => {
const reportId = 'amzn1.clicksI.v1.p7.5B07EE.d1541f-e5-eb-a68-6996e712';
})
})
app.listen(3000, function(err) {
console.log("server is running at localhost:3000");
})

This is caused by sending the "Authorization" header to S3 and it doesn't want that. The reason is because the /download response from the API is a 307 redirect so the client you are using is redirecting with full API headers. To get around this, prevent your client from following redirects and parse the S3 location from the header of the response instead.

Related

Api to accept written number as option?

I have using azcapture api which only accepts number as id at the end of 'path' Options of Fetch/Request api and works when I type in the id e.g.
Var options = {
'path':'url+key+1234455
}
When 1234455 is typed like above it works. But since this is the resp I cannot beforehand know the id so I pass it from the req result which was a POST and now I do a GET, effectively I have chained them without using Promises:
Function secondCall(id)
Console.log (id)
Var options = { 'path': url+key+id
}
This above always fails even if I parse id with parseInt or Number () or if I parse or coerce then
id.toString()
since ClientRequestArgs.path is a string (ClientRequestArgs.path?: string), I believe, it always resolves to a string.
Am I seeing double here or is there a fundamental issue?
POSTMAN works fine btw and the code I have below is exported from POSTMAN except in chainResolve function the first 4 lines are my conversion code.
If I change this line and replace the resolvedID to a pre generated id it will work:
url: 'http://azcaptcha.com/res.php?key=kowg1cjodmtlyiyqheuzjfzta4ki0vwn&action=get&id=335439890',
But as resolvedID the converted string (pre generated id) into an int it won't work.
Full code with keys omitted:
var request = require('request');
var fs = require('fs');
var http = require('follow-redirects').http;
var axios = require('axios');
var options = {
'method': 'POST',
'url': 'http://azcaptcha.com/in.php?key=key&method=post',
'headers': {
},
formData: {
'file': {
'value': fs.createReadStream('C:/Users/jsonX/Documents/fiverr/captchatest.png'),
'options': {
'filename': 'C:/Users/jsonX/Documents/fiverr/captchatest.png',
'contentType': null
}
}
}
};
//let respondedID;
convertToInt = (x) => {
var converted=parseInt(x[1], 10);
return converted;
}
request(options, function (error, response) {
if (error) throw new Error(error);
var respondedID = response.body;
console.log('line 26 '+respondedID);
chainResolve(respondedID);
});
chainResolve = (id) => {
var sid = id.split('|');
var resolvedID=parseInt(sid[1], 10)
console.log(parseInt(sid[1], 10));
console.log('line 40 '+convertToInt(sid));
var config = {
method: 'get',
url: 'http://azcaptcha.com/res.php?key=key&action=get&id=resolvedID',
headers: { }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
}
Solved it! It turns out this API will not give you back any result if the resp is CAPTCHA_NOT_READY. So the solution was to set a timeout and push this with a callback in my response block of the second request:
axios(config)
.then(function (response) {
if (result === 'CAPCHA_NOT_READY'){
console.log('Captcha is being processed');
var startTime = setTimeout(function() {
waitR(id);
clearTimeout(startTime);
},5000);
} else {
console.log(result.split('|')[1]);
}
})
.catch(function (error) {
console.log(error);
});
waitR = (id) => {
console.log('The result is being processed ...');
chainResolve(id);
}

Consuming get API in nodejs with authentication using https

How can I send Id and password means basic authentication to below code. I mean where I need to put id/pass in below code? My API needs basic authentication
const https = require('https');
https.get('https://rws322s213.infosys.com/AIAMFG/rest/v1/describe', (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
console.log(JSON.parse(data).explanation);
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
To request Basic authentication, a client passes a http Authorization header to the server. That header takes the form
Authorization: Basic Base64EncodedCredentials
Therefore, your question is "how to pass a header with a https.get() request?"
It goes in options.headers{} and you can put it there like this:
const encodedCredentials = /* whatever your API requires */
const options = {
headers: {
'Authorization' : 'Basic ' + encodedCredentials
}
}
const getUrl = https://rws322s213.infosys.com/AIAMFG/rest/v1/describe'
https.get (getUrl, options, (resp) => {
/* handle the response here. */
})
const https = require('https');
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
// Allow SELF_SIGNED_CERT, aka set rejectUnauthorized: false
let options = {
agent: httpsAgent
}
let address = "10.10.10.1";
let path = "/api/v1/foo";
let url = new URL(`https://${address}${path}`);
url.username = "joe";
url.password = "password123";
let apiCall = new Promise(function (resolve, reject) {
var data = '';
https.get(url, options, res => {
res.on('data', function (chunk){ data += chunk })
res.on('end', function () {
resolve(data);
})
}).on('error', function (e) {
reject(e);
});
});
try {
let result = await apiCall;
} catch (e) {
console.error(e);
} finally {
console.log('We do cleanup here');
}

How to send a HTTP/2.0 request with node.js

How can i send a httpVersion 2.0 request in nodejs?
I have tried almost every request module there is, and all of them is httpVersion 1.1
Get request:
const http2 = require("http2");
const client = http2.connect("https://www.google.com");
const req = client.request({
":path": "/"
});
let data = "";
req.on("response", (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
}
});
req.on("data", chunk => {
data += chunk;
});
req.on("end", () => {
console.log(data);
client.close();
});
req.end();
POST Request
let res = "";
let postbody = JSON.stringify({
key: value
});
let baseurl = 'baseurl'
let path = '/any-path'
const client = http2.connect(baseurl);
const req = client.request({
":method": "POST",
":path": path,
"content-type": "application/json",
"content-length": Buffer.byteLength(postbody),
});
req.on("response", (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
}
});
req.on("data", chunk => {
res = res + chunk;
});
req.on("end", () => {
client.close();
});
req.end(postbody)
For more details pls see this official documents:
https://nodejs.org/api/http2.html#http2_client_side_example
Since Node.js 8.4.0, you can use built-in http2 module to implement an http2 server. Or if you want to use http2 with Express, here's a great module on npm: spdy.
Here're some code from express-spdy:
const fs = require('fs');
const path = require('path');
const express = require('express');
const spdy = require('spdy');
const CERTS_ROOT = '../../certs/';
const app = express();
app.use(express.static('static'));
const config = {
cert: fs.readFileSync(path.resolve(CERTS_ROOT, 'server.crt')),
key: fs.readFileSync(path.resolve(CERTS_ROOT, 'server.key')),
};
spdy.createServer(config, app).listen(3000, (err) => {
if (err) {
console.error('An error occured', error);
return;
}
console.log('Server listening on https://localhost:3000.')
});
Just to add more note cause this is the main point behind using HTTP2 in case you have multiple requests to the same endpoint
var payload1 = JSON.stringify({});
var payload2 = JSON.stringify({});
const client = http2.connect('server url');
const request = client.request({
":method": "POST",
':path': 'some path',
'authorization': `bearer ${token}`,
"content-type": "application/json",
});
request.on('response', (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
}
});
request.setEncoding('utf8');
let data = ''
request.on('data', (chunk) => { data += chunk; });
request.on('end', () => {
console.log(`\n${data}`);
client.close();
});
// send as many request you want and then close the connection
request.write(payload1)
request.write(payload2)
request.end();
Hope to help someone

Azure Function ignoring https.request

I have an azure function with this line of code.
var myReq = https.request(options, function(res) {
context.log('STATUS: ' + res.statusCode);
context.log('HEADERS: ' + JSON.stringify(res.headers));
body += res.statusCode
res.on('data', function (chunk) {
context.log('BODY: ' + chunk);
});
});
myReq.on('error', function(e) {
context.log('problem with request: ' + e.message);
});
myReq.write(postData);
myReq.end();
But my code seems to just skip this part of code, with no errors. I am new to Azure and node.js so I might have missed some basic parts in setting this up.
Any ideas?
Edit:
Here is my full code
const https = require('https');
const querystring = require('querystring');
module.exports = async function (context, req) {
if (req.query.accessCode || (req.body && req.body.accessCode)) {
context.log('JavaScript HTTP trigger function processed a request.');
var options = {
host: 'httpbin.org',
port: 80,
path: '/post',
method: 'POST'
};
var postData = querystring.stringify({
client_id : '1234',
client_secret: 'xyz',
code: req.query.accessCode
});
var body = "";
var myReq = https.request(options, function(res) {
context.log('STATUS: ' + res.statusCode);
context.log('HEADERS: ' + JSON.stringify(res.headers));
body += res.statusCode
res.on('data', function (chunk) {
context.log('BODY: ' + chunk);
});
});
myReq.on('error', function(e) {
context.log('problem with request: ' + e.message);
});
myReq.write(postData);
myReq.end();
context.log("help");
context.res = {
status: 200,
body: "Hello " + (body)
};
} else {
context.res = {
status: 400,
body: "Please pass a name on the query string or in the request body"
};
}
};
Ideally it should work. You can also try using request module like below
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
Try and see if it helps.
Solved by doing await properly. Used this as guide.
var https = require('https');
var util = require('util');
const querystring = require('querystring');
var request = require('request')
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
/*if (req.query.name || (req.body && req.body.name)) {*/
var getOptions = {
contentType: 'application/json',
headers: {
'Authorization': <bearer_token>
},
};
var postData = {
"key": "value"
};
var postOptions = {
method: 'post',
body: postData,
json: true,
url: <post_url>,
headers: {
'Authorization': <bearer_token>
},
};
try{
var httpPost = await HttpPostFunction(context, postOptions);
var httpGet = await HttpGetFunction(context, <get_url>, getOptions);
return {
res: httpPost
};
}catch(err){
//handle errr
console.log(err);
};
};
async function HttpPostFunction(context, options) {
context.log("Starting HTTP Post Call");
return new Promise((resolve, reject) => {
var data = '';
request(options, function (err, res, body) {
if (err) {
console.error('error posting json: ', err)
reject(err)
}
var headers = res.headers;
var statusCode = res.statusCode;
//context.log('headers: ', headers);
//context.log('statusCode: ', statusCode);
//context.log('body: ', body);
resolve(body);
})
});
};
async function HttpGetFunction(context, url, options) {
context.log("Starting HTTP Get Call");
return new Promise((resolve, reject) => {
var data = '';
https.get(url, options, (resp) => {
// A chunk of data has been recieved.
resp.on('data', (chunk) => {
data += chunk;
})
// The whole response has been received. Print out the result.
resp.on('end', () => {
resolve(JSON.parse(data));
});
}).on("error", (err) => {
console.log("Error: " + err.message);
reject(err.message);
});
});
};

Does AWS Lambda (NodeJS) not allow http.request or https.request?

I am attempting to make a request to another API from a Lambda. I am finding that using the NodeJS http and https modules allow for GET requests but any others (e.g. POST) do not work; POST coincidentally is the only method I need to work for the service I am attempting to call.
Here is a working example of Lambda performing a GET and receiving a 200 response:
const https = require('https')
function handler(event, context, callback) {
const options = {
hostname: 'encrypted.google.com'
}
https
.get(options, (res) => {
console.log('statusCode:', res.statusCode);
res.on('end', callback.bind(null, null))
})
.on('error', callback);
}
exports.handler = handler
So that proves that he request is allowed. However, if the script attempts to make the same request using the .request() method of the https (or https) lib/module the request never finishes and the Lambda times out.
const https = require('https')
function handler(event, context, callback) {
const options = {
hostname: 'encrypted.google.com',
method: 'GET'
}
https
.request(options, (res) => {
console.log('statusCode:', res.statusCode);
res.on('end', callback.bind(null, null))
})
.on('error', callback);
}
exports.handler = handler
I don't know what I am doing wrong. The call https.request() silently fails - doesn't throw an error - and nothing is reported in the log.
The problem was that I was never completing the request with req.end().
const https = require('https')
function handler(event, context, callback) {
const options = {
hostname: 'encrypted.google.com',
method: 'GET'
}
https
.request(options, (res) => {
console.log('statusCode:', res.statusCode);
res.on('end', callback.bind(null, null))
})
.on('error', callback)
.end(); // <--- The important missing piece!
}
exports.handler = handler
Please try this one if your API is HTTPS,
var url = 'HTTPS URL HERE';
var req = https.get(url, (res) => {
var body = "";
res.on("data", (chunk) => {
body += chunk
});
res.on("end", () => {
var result = JSON.parse(body);
callBack(result)
});
}).on("error", (error) => {
callBack(err);
});
}
And if it is HTTP then,
var url = 'HTTP URL HERE';
var req = http.get(url, (res) => {
var body = "";
res.on("data", (chunk) => {
body += chunk
});
res.on("end", () => {
var result = JSON.parse(body);
callBack(result)
});
}).on("error", (error) => {
callBack(err);
});
}
Please don't fogot to add package require('https') / require('http')
The POST method is done by the request method.
This is the lambda code:
const https = require('https');
const options = {
hostname: 'Your host name',
path: '/api/v1/Login/Login',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body : JSON.stringify({
'email': 'hassan.uzair9#gmail.com',
'password': 'Asdf1234.',
})
};
var result;
try{
result = await https.request(options);
console.log("result.....",result);
}catch(err){
console.log("err......",err);
}

Resources