Got - HTTPS Request - HTTPError - node.js

I'm trying to hit a URL protected by basic auth using the snippet below.
const got = require('got');
export const getToken = async () => {
const payload = new URLSearchParams({grant_type: 'client_credentials'}).toString();
const auth = "Bearer " + Buffer.from(process.env.username + ":" + process.env.password).toString('base64');
try{
const response = await got(process.env.url, {
body: payload,
method: "POST",
headers: {
"Authorization" : auth,
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": payload.length.toString()
}
});
}catch(error){
console.log(`ERROR: ${JSON.stringify(error)}`);
}
};
But this results in an error and there are no specifics provided either.
{"name":"HTTPError","timings":{"start":1634484011218,"socket":1634484011219,"lookup":1634484011230,"connect":1634484011248,"secureConnect":1634484011288,"upload":1634484011288,"response":1634484011307,"end":1634484011310,"abort":1634484011314,"phases":{"wait":1,"dns":11,"tcp":18,"tls":40,"request":0,"firstByte":19,"download":3,"total":96}}}
Any advice on resolving the issue would be greatly appreciated. Thanks.

It's happening on got.
You need to parse the error and see what the error is. You can follow this at error.
const { HTTPError } = require('got')
const parsedError = new HTTPError(err)
console.log('---err--->', parsedError.response.body)
You will see what the exact issue is.

Related

Authenticating FTX API SHA256 HMAC with Node

I am lost with using HMAC SHA256 for api authentication. This is my first time using it and I'm not sure what I am missing although I suspect it has to do with the timestamp. Can someone please help me identify what it is I am missing?
Everytime I try and make an API call I get a response stating
data: { success: false, error: 'Not logged in: Invalid signature' }
Here are the requirements for making the API call including the HMAC SHA256.
Here is the code I am using currently:
const axios = require('axios');
var forge = require('node-forge');
require('dotenv').config()
// get timestamp
var time = new Date().getTime();
// generate and return hash
function generateHash(plainText,secretKey)
{
var hmac = forge.hmac.create();
hmac.start('sha256', secretKey);
hmac.update(plainText);
var hashText = hmac.digest().toHex();
return hashText
}
// set axios config
var config = {
url:"https://ftx.us/api/wallet/all_balances",
method:"GET",
headers :{
"FTXUS-KEY":process.env.FTX_API_KEY,
"FTXUS-TS":time,
"FTXUS-SIGN":generateHash(`${new Date()}${"GET"}${"/wallet/all_balances"}`,process.env.FTX_API_SECRET)
}
}
axios(config)
.then(response => {
console.log(response.data)
}).catch(function (error) {
console.log(error);
})
I had to go through the same issue, so here goes my code.
import * as crypto from "crypto";
import fetch from "node-fetch";
// a function to call FTX (US)
async function callFtxAPIAsync(secrets, method, requestPath, body) {
const timestamp = Date.now();
const signaturePayload = timestamp + method.toUpperCase() + "/api" + requestPath + (method.toUpperCase() == "POST" ? JSON.stringify(body) : "");
const signature = crypto.createHmac('sha256', secrets.secret)
.update(signaturePayload)
.digest('hex');
const response = await fetch("https://ftx.us/api" + requestPath, {
method: method,
body: body != null ? JSON.stringify(body) : "",
headers: {
'FTXUS-KEY': secrets.key,
'FTXUS-TS': timestamp.toString(),
'FTXUS-SIGN': signature,
"Content-Type": "application/json",
"Accepts": "application/json"
}
});
return await response.json();
}
then call a post endpoint as for example:
let resultQuote = await callFtxAPIAsync(secrets, "post", "/otc/quotes",
{
"fromCoin": "USD",
"toCoin": "ETH",
"size": usd
});
or a get one:
let resultQuote = await callFtxAPIAsync(secrets, "get", "/otc/quotes/1234");
I hope it helps 😄
You need to add the full URL path, except the domain, in your case /api is missing. Try this:
"FTXUS-SIGN":generateHash(`${new Date()}${"GET"}${"/api/wallet/all_balances"}`,process.env.FTX_API_SECRET)

Creating Wallet Address with coinbase API Using Axios Request

Hello I'm trying to use the coinase api using axios to make request. I have set up the neccessary api authentication using SHA256 HMAC. I have been able to make some GET Request and got response. I have been trying to make a POST Request but i have been getting 401 status code.
const name = "Test BTC Address";
const body = {
name: name
}
var encHash = {
baseUrl: 'https://api.coinbase.com',
method: 'POST',
path: '/v2/accounts/bbc2e3f7-a851-50ab-b4b3-a0f2a700846f/addresses',
body: body,
scopes: "wallet:addresses:create"
};
const sign = generateHashKey(encHash, key.APISECRET);
console.log(sign);
const config = {
headers: {
'CB-ACCESS-SIGN': sign.signature,
'CB-ACCESS-TIMESTAMP': sign.timestamp,
'CB-ACCESS-KEY': key.APIKEY,
'CB-VERSION': '2021-10-15'
}
}
const url = `${encHash.baseUrl}${encHash.path}`
console.log(url);
var options = await axios.post(url, body, config);
return res.send({data: options.data})
} catch (error) {
// console.error(error);
return res.send({error})
} ```

x-www-form-urlencoded format - using https in node.js

I am currently writing to an API to try and get a token. I'm nearly there but fallen at the last hurdle..
const fs = require('fs');
const https = require('https');
const ConfigParams = JSON.parse(fs.readFileSync('Config.json', 'utf8'));
const jwt = require('jsonwebtoken');
const apikey = ConfigParams.client_id;
var privateKey = fs.readFileSync(**MY KEY**);
var tkn;
const jwtOptions = {
algorithm: 'RS512',
header: { kid: 'test-1' }
}
const jwtPayload = {
iss: apikey,
sub: apikey,
aud: **API TOKEN ENDPOINT**,
jti: '1',
exp: 300
}
jwt.sign(jwtPayload,
privateKey,
jwtOptions,
(err, token) => {
console.log(err);
//console.log(token);
tkn = token;
let = tokenPayload = {
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer/',
client_assertion: tkn
}
tokenAuthOptions = {
payload: tokenPayload,
host: **HOST**,
path: **PATH**,
method: 'POST',
}
https.request(
tokenAuthOptions,
resp => {
var body = '';
resp.on('data', function (chunk) {
body += chunk;
});
resp.on('end', function () {
console.log(body);
console.log(resp.statusCode);
});
}
).end();
}
)
the encoded token comes back fine for the first part, the https request though returns a problem.
the response I get back is grant_type is missing, so I know I have a formatting problem due to this x-www-form-urlencoded, but I can't figure out how to fix it.
here is what the website said:
You need to include the following data in the request body in
x-www-form-urlencoded format:
grant_type = client_credentials client_assertion_type =
urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion = <your signed JWT from step 4> Here's a complete
example, as a CURL command:
curl -X POST -H "content-type:application/x-www-form-urlencoded"
--data \ "grant_type=client_credentials\ &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion="
END POINT
Ideally I want a solution using the https request, but if that's not possible I'm open to other solutions.
Any help is greatly appreciated.
Thanks,
Craig
Edit - I updated my code based on a suggestion to:
const params = new url.URLSearchParams({
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer/',
client_assertion: tkn
});
axios.post("URL", params.toString()).then(resp => {
console.log("response was : " + resp.status);
}).catch(err => {
console.log("there was an error: " + err);
})
But I'm still getting an error code 400, but now with less detail as to why. (error code 400 has multiple message failures)
Postman is the best.
Thank for #Anatoly for your support which helped to point me in the right direction. I had no luck so used postman for the first time, and found it had a code snippet section, with four different ways of achieving this using node.js.
The solution with Axion was:
const axios = require('axios').default;
const qs = require('qs');
var data = qs.stringify({
'grant_type': 'client_credentials',
'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': tkn
});
var config = {
method: 'post',
url: '',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.status));
})
.catch(function (error) {
console.log(error);
});
I believe the issue was that I was not passing the information into 'data:' in combination with the querystring problem. Using qs.stringify to format the object, then passing this into the data: key solved the problem.

I am getting a 404 from the Walmart Canada Maketplace API when bulk feeding new item

I am able to GET data from Walmart Canada Marketplace API, using Nodejs, but I am unable to post a new item with their bulk feed endpoint. I get back a 404 'bad request'. I am not sure if the error is in the POST request or in the JSON. I am also not sure if Walmart Canada really accepts JSON files or just claims to.
Here is my code:
import { createSign, randomBytes } from 'crypto'
import fetch from 'node-fetch';
import { resolve } from 'url';
import fs from 'fs';
const method = 'post';
const BASE_URL = 'https://marketplace.walmartapis.com/'
const PK_HEADER = '\n-----BEGIN PRIVATE KEY-----\n'
const PK_FOOTER = '\n-----END PRIVATE KEY-----\n'
const WALMART_CONSUMER = "consumer";
const WALMART_CHANNEL = 'channel';
const WALMART_SECRET = 'secret';
function generateCorrelationId () {
return randomBytes(16).toString('hex')
}
function generateSignature (url, method, timestamp) {
const privateKey = `${PK_HEADER}${WALMART_SECRET}${PK_FOOTER}`
const stringToSign = WALMART_CONSUMER + '\n' +
url + '\n' +
method.toUpperCase() + '\n' +
timestamp + '\n'
const sign = createSign('RSA-SHA256')
sign.update(stringToSign)
return sign.sign(privateKey, 'base64')
}
const url = resolve(BASE_URL, "/v3/ca/feeds?feedType=item")
const timestamp = Date.now()
const signature = generateSignature(url, method, timestamp)
const headers = {
'WM_SVC.NAME': 'Walmart Marketplace',
'WM_CONSUMER.ID': WALMART_CONSUMER,
'WM_SEC.TIMESTAMP': timestamp,
'WM_SEC.AUTH_SIGNATURE': signature,
'WM_QOS.CORRELATION_ID': generateCorrelationId(),
'WM_CONSUMER.CHANNEL.TYPE': WALMART_CHANNEL,
'WM_TENANT_ID' : 'WALMART.CA',
'WM_LOCALE_ID' : 'en_CA',
'Accept': 'application/xml',
'Content-Type': 'multipart/form-data'
}
fetch(url, {
method: method,
headers: headers,
formData: {
'file': {
'value': fs.createReadStream('wm_new_item.json'),
'options': {
'filename': 'wm_new_item.json'
}
}
}
})
.then(response => console.log(response))
.catch(function (error) {
console.log(error)
});
and here is my json file:
{
"MPItemFeedHeader":{
"subset":"INTERNAL",
"mart":"WALMART_CA",
"sellingChannel":"marketplace",
"subCategory":"toys_other",
"locale":"en",
"version":"3.2",
"requestId": "1606485958",
"requestBatchId": "1606485958",
"processMode":"REPLACE"
},
"MPItem":[
{
"Orderable":{
"sku":"2663d9467e64a0a94ab0eeeccd9f37dd",
"productIdentifiers":{
"productIdType":"UPC",
"productId":"078257310609"
},
"productName":"abc",
"brand":"abc",
"price":1.00,
"fulfillmentLagTime":6,
"floorPrice":1.00,
"startDate":"2021-04-20T08:37:30Z",
"endDate":"2021-04-20T08:37:30Z",
"ProductIdUpdate":"No",
"SkuUpdate":"No",
"ShippingWeight":1
},
"Visible":{
"Toys":{
"shortDescription":"abc",
"mainImageUrl":"https://images-na.ssl-images-amazon.com/images/I/71X4z76HUTS._AC_SL1500_.jpg",
"productSecondaryImageURL":[
"https://images-na.ssl-images-amazon.com/images/I/71X4z76HUTS._AC_SL1500_.jpg",
"https://images-na.ssl-images-amazon.com/images/I/71X4z76HUTS._AC_SL1500_.jpg"
],
"msrp":1.00,
"variantGroupId":"abc",
"variantAttributeNames":[
"color"
],
"isPrimaryVariant":"Yes"
}
}
}
]
}
Thanks!
I figured it out with the help of this answer on another question.
You are better off referencing this than Walmarts official documentation
You need to submit a post request to the "https://marketplace.walmartapis.com/v3/feeds endpoint appending your type on the url ?feedType=[something]
You need to make sure that your "Content-Type" is "application/json" if you are sending them JSON.
You do not need to send it multipart like the documentation suggests, just send your entire JSON file as the body and it should work correctly.

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