IBM Cloud Functions Calling API with authentication / node.js - node.js

For most of you reading this, it is portably the most basic question and done within 2 minutes..
Maybe someone got the time to provide the code for me or can recommend a resource where this is being explained for an absolute beginner.
I want to call an API from IBM cloud functions that requires authentication.
I got this code from an IBM video tutorial with that I can call any open API:
let rp = require('request-promise')
function main(params) {
if (params.actionA == 'joke') {
const options = {
uri: "http://api.icndb.com/jokes/random",
json: true
}
return rp(options)
.then(res => {
return { response: res }
})
} else if (params.actionB == 'fact') {
const options = {
uri: "https://catfact.ninja/fact",
json: true
}
return rp(options)
.then(res => {
return { response: res }
})
}
}
I want to keep the joke API but want to exchange the Cat fact API with this inspirational quote API (which needs authenticaion): https://english.api.rakuten.net/HealThruWords/api/universal-inspirational-quotes/details
I can get this node.js code from rakuten to call the quote api.
var http = require("https");
var options = {
"method": "GET",
"hostname": "healthruwords.p.rapidapi.com",
"port": null,
"path": "/v1/quotes/?id=731&t=Wisdom&maxR=1&size=medium",
"headers": {
"x-rapidapi-host": "healthruwords.p.rapidapi.com",
"x-rapidapi-key": "api key here",
"useQueryString": true
}
};
var req = http.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function () {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
});
req.end();
How can I incorporate it into the if function? I want to use that function with watson assistant , which works well with the current code. I just need the catfact api exchanged by the quote api.

The two code snippets that you have provided use different modules to do the http request. That is probably why it looks a bit complicated.
The first step to change the behaviour in a way that you have described, is to replace the complete options in the else branch with the options from you second code snippet.
The second step is to provide the necessary api key for the quote URL as a parameter to the function.
Can you give this code snippet below a try, after adding an additional parameter apiKey to the function. I have not tested the code below, but that's how I would do it. Please let me know if it worked for you and I can improve the answer on your feedback.
let rp = require('request-promise')
function main(params) {
if (params.actionA == 'joke') {
const options = {
uri: "http://api.icndb.com/jokes/random",
json: true
}
return rp(options)
.then(res => {
return { response: res }
})
} else if (params.actionB == 'fact') {
const options = {
"method": "GET",
"hostname": "healthruwords.p.rapidapi.com",
"port": null,
"uri": "/v1/quotes/?id=731&t=Wisdom&maxR=1&size=medium",
"headers": {
"x-rapidapi-host": "healthruwords.p.rapidapi.com",
"x-rapidapi-key": params.apiKey,
"useQueryString": true
}
}
return rp(options)
.then(res => {
return { response: res }
})
}
}

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)

Loop through axios data and perform another axios request and concat the two results into one JSON

So I have an axios request to a rapid API, my function looks like this...
//Initialize the lookup API that utalizes rapidAPI to get breach data
app.get("/lookup/:email/:function", (req, res) => {
var options = {
method: "GET",
url: "https://breachdirectory.p.rapidapi.com/",
params: { func: `${req.params.function}`, term: `${req.params.email}` },
headers: {
"x-rapidapi-host": "breachdirectory.p.rapidapi.com",
"x-rapidapi-key": `${config.RAPID_API_KEY}`,
},
};
axios
.request(options)
.then(function (response) {
res.json(response.data);
})
.catch(function (error) {
console.error(error);
});
}
});
The res.json(response.data); will show on the page a result like this:
{
"disclaimer": "This data is aggregated from BreachDirectory, HaveIBeenPwned, and Vigilante.pw.",
"info": "For full source info, request e.g. https://breachdirectory.tk/api/source?name=Animoto",
"sources": [
"123RF",
"500px",
"Adobe",
"AntiPublic",
"Apollo",
"Bitly",
"Dave",
"Disqus",
"Dropbox",
"ExploitIn",
"ShareThis",
"Straffic",
"Ticketfly",
"Tumblr",
"VerificationsIO"
]
}
I want to loop through everything in the "sources" array, and call upon the following:
https://haveibeenpwned.com/api/v3/breach/[ITEM]
So, the first one will call upon https://haveibeenpwned.com/api/v3/breach/123RF
So each result from that call will look like this:
{
"Name": "123RF",
"Title": "123RF",
"Domain": "123rf.com",
"BreachDate": "2020-03-22",
"AddedDate": "2020-11-15T00:59:50Z",
"ModifiedDate": "2020-11-15T01:07:10Z",
"PwnCount": 8661578,
"Description": "In March 2020, the stock photo site 123RF suffered a data breach which impacted over 8 million subscribers and was subsequently sold online. The breach included email, IP and physical addresses, names, phone numbers and passwords stored as MD5 hashes. The data was provided to HIBP by dehashed.com.",
"LogoPath": "https://haveibeenpwned.com/Content/Images/PwnedLogos/123RF.png",
"DataClasses": [
"Email addresses",
"IP addresses",
"Names",
"Passwords",
"Phone numbers",
"Physical addresses",
"Usernames"
],
"IsVerified": true,
"IsFabricated": false,
"IsSensitive": false,
"IsRetired": false,
"IsSpamList": false
}
I want to make my res.json send over a JSON string that will have all the sources still there, along with the "Title","Description", and "LogoPath" from the API calls that it pulled for each one of the sources. So I will have a JSON string with the sources along with the title of each source, description of each source, and LogoPath of each source.
You have two options:
Create an array of promises and run with Promise.all
app.get('/lookup/:email/:function', async (req, res) => {
var options = {
method: 'GET',
url: 'https://breachdirectory.p.rapidapi.com/',
params: { func: `${req.params.function}`, term: `${req.params.email}` },
headers: {
'x-rapidapi-host': 'breachdirectory.p.rapidapi.com',
'x-rapidapi-key': `${config.RAPID_API_KEY}`,
},
};
axios.request(options)
.then((response) => {
const requestTasks = [];
for (let item of response.data.sources) {
const itemOption = {
method: 'GET',
url: `https://haveibeenpwned.com/api/v3/breach/${item}`,
headers: {
'content-type': 'application/json; charset=utf-8'
}
};
requestTasks.push(axios.request(itemOption));
}
return Promise.all(requestTasks);
})
.then((responseList) => {
for (let response of responseList) {
console.log(response.data);
}
})
.catch((error) => {
console.error(error);
});
});
Use async/await (promise) and for await for get data from for loop
app.get('/lookup/:email/:function', async (req, res) => {
try {
var options = {
method: 'GET',
url: 'https://breachdirectory.p.rapidapi.com/',
params: { func: `${req.params.function}`, term: `${req.params.email}` },
headers: {
'x-rapidapi-host': 'breachdirectory.p.rapidapi.com',
'x-rapidapi-key': `${config.RAPID_API_KEY}`,
},
};
const response = await axios.request(options);
for await (let item of response.data.sources) {
const itemOption = {
method: 'GET',
url: `https://haveibeenpwned.com/api/v3/breach/${item}`,
headers: {
'content-type': 'application/json; charset=utf-8'
}
};
const itemResponse = await axios.request(itemOption);
console.log(itemResponse.data);
}
} catch (error) {
console.error(error);
}
});
This how I managed to make it works.
first: I didn't had any APi key (and didn't want to register to get one) So i used a dummy Api.
although the logic stay the same as i have tested the result.
second i kept all your initial url just next to the one i used.so you can easily switch back to your original url.
finally i put comment to any critical part, and i named variable in a
way that they almost describe what they do.
so you can copy past test it to understand my logic then adapt it to your use case.
here the code
// make sure to replace /lookup by /lookup/:email/:function after testing my logic
app.get('/lookup', async (req, res) => {
try {
// in this options no change just switch back to your url
var options = {
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/albums',
// url: "https://breachdirectory.p.rapidapi.com/",
// params: { func: `${req.params.function}`, term: `${req.params.email}` },
// headers: {
// 'x-rapidapi-host': 'breachdirectory.p.rapidapi.com',
// 'x-rapidapi-key': `${config.RAPID_API_KEY}`,
// },
};
// here you get all your sources list (in my case it an array of object check picture 1 bellow)
const allSources = await axios.request(options)
console.log(allSources.data);
// because my dummy api response is a huge array i slice to limited number
const reduceAllsource = allSources.data.slice(0,5);
console.log(reduceAllsource);
// note here you need to replace reduceAllsource.map by allSources.data.map
// because you don't need a sliced array
const allSourcesWithDetails = reduceAllsource.map(async (_1sourceEachtime)=>{
// here you can switch back to your original url
// make sure to replace [ITEM] by ${_1sourceEachtime}
const itemOption = await axios({
method: 'GET',
url: `https://jsonplaceholder.typicode.com/albums/${_1sourceEachtime.id}/photos`,
// url:`https://haveibeenpwned.com/api/v3/breach/[ITEM]`
headers: {
'content-type': 'application/json; charset=utf-8'
}
});
// this the place you can mix the 2 result.
const mixRes1AndRes2 ={
sources:_1sourceEachtime.title,
details:itemOption.data.slice(0,1)
}
return mixRes1AndRes2;
})
// final result look like the picture 2 below
finalRes= await Promise.all(allSourcesWithDetails);
return res.status(200).json({response: finalRes});
}
catch (error) {
console.log(error);
}
});
Picture 1
Picture 2

Make a HTTP Post call within Azure Functions using Nodejs

I am trying to make a HTTP Post call from within Azure Functions using nodejs. The payload which needs to be passed is in JSON with two properties title and description. For some reason, the below code doesn't work and I don't get any errors in the log too. The POST operation works in a Postman rest client. Can someone point me in the right direction? Thanks
var http = require('https');
module.exports = function (context, req) {
var body = {
"title": "Sunday",
"description": "Last day of the week"
}
const options = {
hostname: 'capservice-xxxxxxxx.ondemand.com',
port: 443,
path: '/incident/SafetyIncidents',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': body.length
}
}
var response = '';
const request = http.request(options, (res) => {
context.log(`statusCode: ${res.statusCode}`)
res.on('data', (d) => {
response += d;
})
res.on('end', (d) => {
context.res = {
body: response
}
context.done();
})
})
request.on('error', (error) => {
context.log.error(error)
context.done();
})
request.write(body);
request.end();
};
Had a bit more chance to investigate the issue. I've actually tested it on Azure Functions now - and got it to work.
Your body variable is incorrectly typed:
// This assumes that the body type is JSON.
var body = {
"title": "Sunday",
"description": "Last day of the week"
}
See the reference here: https://nodejs.dev/learn/make-an-http-post-request-using-nodejs
const https = require('https')
// See the JSON.stringify
const data = JSON.stringify({
todo: 'Buy the milk'
})
When I try to execute your function above as it is (with changing the URL) - I'm receiving a parameter error as below
FailureException: TypeError [ERR_INVALID_ARG_TYPE]: The first argument
must be of type string or an instance of Buffer. Received an instance
of ObjectStack
Adding the JSON.stringify to the body object solved the problem for me.

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({});

How to update a document based on query using elasticsearch-js (or other means)?

I want to perform a update-by-query, specifically update a specific document that where field name that contains Gary. I am using the latest version of elasticsearch (2.3) I am using the official ES client for nodejs. Providing an alternative means to do this (find a document, update a specific field in the document) would be acceptable as a correct answer.
This has not yet been released in the JavaScript client. This will be available in the 2.3 API version of the Javascript client library. Right now, the JS client only supports up to apiVersion: 2.2
You can use any HTTP client (Postman, curl, /head/, Sense, ...) in order to hit the REST endpoint and carry out what you need.
If you do need to do this through Node.js, you can use the http module like this:
var http = require('http');
var options = {
host: 'localhost',
port: 9200,
path: '/your_index/your_type/_update_by_query',
method: 'POST'
};
var req = http.request(options, function(resp){
resp.on('data', function(chunk){
// check update response
});
}).on("error", function(e){
console.log("Got error: " + e.message);
});
var query = {
"script": {
"inline": "ctx._source.field = 'value2'"
},
"query": {
"term": {
"field": "value1"
}
}
};
// write data to request body
req.write(JSON.stringify(query));
req.write('\n');
req.end();
I did similar, may helpful for others
async function updateBranches(req){
try{
let query = '';
for (const key in req) {
if (Object.hasOwnProperty.call(req, key)) {
query = `${query}ctx._source["${key}"] = "${req[key]}";`
}
}
const res = await esclient.updateByQuery({
index: branchesIndex,
refresh: true,
body: {
query: {
match: {
branchKey: req.branchKey
}
},
script: {
lang: 'painless',
source: query
},
}
})
return res;
} catch(e){
console.log(e,'error');
}}

Resources