Proxying an endpoint to avoid CORS issues - javascript

I am using an external api that doesn't allow client side POST request. I can make a POST request using node.js and I am getting my desired response on the server. I am stuck trying to figure out how to get the response from the server into my HTML file.
const https = require("https");
const data = JSON.stringify({
key: "value"
});
const options = {
hostname: "url",
port: 443,
path: "/path",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": data.length
}
};
const req = https.request(options, res => {
console.log(
`statusCode: ${res.statusCode} statusMessage: ${res.statusMessage}`
);
res.setEncoding("utf8");
res.on("data", chunk => {
console.log(chunk);
});
});
req.on("error", error => {
console.error(error);
});
req.write(data)
req.end();
This is my server.js file, I'm not sure what the next step is to get in a file.

Related

Azure AD publisher's authorization token request returned 302

In an Azure Function as a backend for my webpage, I requested an Azure AD publisher's authorization token as per this page instructed.
This is the line of codes of my Azure Functions:
// Stringfy request body
const postData = querystring.stringify({
'grant_type': 'client_credentials',
'client_id': client_id,
'client_secret': client_secret,
'resource': resource,
});
// Initiate options
var httpAgent = new http.Agent();
httpAgent.maxSockets = 200;
const options = {
hostname: 'login.microsoftonline.com',
path: `/${tenantId}/oauth2/token`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
agent: httpAgent,
}
const tokenReq = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf-8')
res.on('data', (chunk) => {
console.log(chunk);
body += chunk;
});
res.on('end', () => {
console.log('No more data in response.');
console.log("body:" + body);
context.res = {
status: 200,
body: body,
};
});
});
tokenReq.on('error', (e) => {
console.log(`problem with request: ${e.message}`);
context.res = {
status: 500,
body: `problem with request: ${e.message}`,
}
});
// write data to request body
tokenReq.write(postData);
tokenReq.end();
The expected response was the access token that I require, however running it locally I got STATUS 302, and a header containing a location and some other parameters, as a response. In my understanding STATUS 302 states that the URL is moved temporarily to the location provided in the header. Now, I don't know what I'm supposed to do, the request that I have to make is supposed to be a POST request so a redirection would not work. I've also tried to make a new request after receiving the redirect URL, but I got an error message saying: getaddrinfo ENOTFOUND {redirect URL from header}. What did I do wrong here?
The 302 error was caused by http module, you use require('http'); and http.request(options, (res).... to do the request, so it shows 302 error.
I suggest you to use var request = require('request'); to do the request, below is my function code for your reference (before use request module, you need to run npm install request to install it first):
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
var result = await generatetoken(context);
context.res = {
body: result
};
}
function generatetoken(context){
var request = require('request');
var options = {
'method': 'POST',
'url': 'https://login.microsoftonline.com/<your tenant id>/oauth2/token',
'headers': {
'Content-Type': 'application/x-www-url-form-urlencoded'
},
form: {
'client_id': 'xxxxxx',
'grant_type': 'client_credentials',
'resource': 'xxxxx',
'client_secret': 'xxxxx'
}
};
return new Promise(function(resolve, reject) {
request(options, function(err, res) {
if (err) {
reject(err);
} else {
context.log(res.body);
resolve(res.body);
}
})
})
}

Using node-fetch effectively, getting invalid json response body error *only* the first time I query the server

I have been attempting to collect an API token from an undocumented API of a Ubiquiti EdgeMax Network switch and use it to check the status of the ports then eventually make some changes to the status of them when I receive the correct information.
I have put together some code using NodeJS and node-fetch that allows me to grab the API authentication token then use it to get some information on the interfaces.
It all seems to be working fine but I have noticed that the first time I run the NodeJS application and query it it would throw an
UnhandledPromiseRejectionWarning: FetchError: invalid json response body at... reason: Unexpected end of JSON input
This happens when I request the data to the switch with the API using the token. If I try the second time it works fine, even on multiple consecutive times.
I tried getting tweaking the async functions but the behavior is the same, is as if the token is not gathered quickly enough the first time the request is done. I am not as good with async JS code conventions so I wonder if is something wrong with my approach.
I just want to be able to make each of these request actions one after another effectively and return back the result to the server I'm making the query from. Any insights on this would be greatly appreciated.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const express = require("express");
const bodyParser = require("body-parser");
const fetch = require("node-fetch");
const app = express();
const PORT = process.env.PORT;
app.use(bodyParser.json());
const url = "https://example.com:9443/";
const credentials = { username: "ubnt", password: "ubnt" };
let status;
let token;
async function postData(url = '', path = '', body = {}) {
const response = await fetch(`${url}${path}`, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
Referer: url,
accept: "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9,es;q=0.8",
"content-type": "application/json;charset=UTF-8",
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: JSON.stringify(body)
}).then(response => response.json().then(json => ({
authToken: response.headers.get("x-auth-token"),
json
})))
return response;
}
async function getData(url = '', path = '', token = '') {
const response = await fetch(`${url}${path}`, {
method: 'GET',
mode: 'cors',
cache: 'no-cache',
headers: {
Referer: url,
accept: "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9,es;q=0.8",
"content-type": "application/json;charset=UTF-8",
"x-auth-token": token
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: null
}).then(response => response.json())
return response;
}
const statusHandler = (req, res) => {
const {
seed
} = req.body.input;
postData(url, 'api/v1.0/user/login', credentials)
.then(response => {
console.log(response.authToken);
token = response.authToken;
}).then(getData(url, 'api/v1.0/interfaces', token)
.then(response => {
console.log(response[0].status);
status = response[0].status;
}));
console.log(seed);
return res.json({
status,
});
};
app.post("/status", statusHandler);
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}...`);
});
Turns out I was using the functions incorrectly. I did not use an async function for the statusHandler. I was was not waiting for the reply from the api before proceeding with the next request. It seems that with the first requests the API responds a lot slower.
I fixed by waiting for a reply on each of the function declarations and their calls on the handler using async / await. Doing this allowed me to clean up the code a lot more making it more readable.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const express = require("express");
const bodyParser = require("body-parser");
const fetch = require("node-fetch");
const app = express();
const PORT = process.env.PORT;
app.use(bodyParser.json());
const url = "https://example.com:9443/";
const credentials = { username: "ubnt", password: "ubnt" };
let status;
let token;
async function postData(url = '', path = '', body = {}) {
const response = await fetch(`${url}${path}`, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
Referer: url,
accept: "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9,es;q=0.8",
"content-type": "application/json;charset=UTF-8",
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: JSON.stringify(body)
})
const json = await response.json()
.then(json => ({
authToken: response.headers.get("x-auth-token"),
json
}));
return json
}
async function getData(url = '', path = '', token = '') {
const response = await fetch(`${url}${path}`, {
method: 'GET',
mode: 'cors',
cache: 'no-cache',
headers: {
Referer: url,
accept: "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9,es;q=0.8",
"content-type": "application/json;charset=UTF-8",
"x-auth-token": token
},
redirect: 'follow',
referrerPolicy: 'no-referrer',
body: null
})
const json = await response.json()
return json
}
const statusHandler = async (req, res) => {
const {
seed
} = req.body.input;
await postData(url, 'api/v1.0/user/login', credentials)
.then(response => {
console.log(response.authToken);
token = response.authToken;
})
await getData(url, 'api/v1.0/interfaces', token)
.then(response => {
console.log(response[0].status);
status = response[0].status;
});
console.log(seed);
return res.json({
status,
});
};
app.post("/status", statusHandler);
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}...`);
});

Http GET Request from NodeJS to external API with http.get()

I am trying to do an HTTP GET request to an external API with NodeJS (using Express), but I am not getting any data back. My code is the nextone:
import * as http from "http";
const options = {
host: "EXAMPLE.COM",
path: "/MY/PATH",
headers: {
"Content-Type": "application/json",
"Authorization": "Basic XXXXXXXXXXXXXXXXXX"
}
};
const req = http.get(options, function(res) {
console.log("statusCode: " + res.statusCode);
res.on("data", function (chunk) {
console.log("BODY: " + chunk);
});
});
But the response I get is:
statusCode : 302 and BODY is empty.
The external API works properly (I have tried doing a http GET Request with INSOMNIA and returns data)
The request I am doing NEEDS an Authorization Token
What am I doing wrong? or what can I do to get the data back?
Cheers
You are just throwing data to console.log and not responding to request.
You did not mention if what http server you are using with node. In case you are using express.js (most common one) you should have something like:
const express = require("express");
const app = express();
const port = 3003;
const http = require("http");
// your webserver url localhost:3003/fetch-something
app.get("/fetch-something", (req, res) => {
const options = {
host: "EXAMPLE.COM",
path: "/MY/PATH",
headers: {
"Content-Type": "application/json",
Authorization: "Basic XXXXXXXXXXXXXXXXXX"
}
};
const httpReq = http.get(options, function(httpRes) {
//output status code to your console
console.log("statusCode: " + httpRes.statusCode);
httpRes.on("data", function(chunk) {
// still nothing happens on client - this will also just print to server console
console.log("data", chunk);
// return some data for requested route
return res.send(chunk);
});
});
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Getting invalid JSON on API call

I'm trying to use GoToMeeting's API and making a POST request to create a meeting. At the moment, I'm just trying to hardcode the body of the meeting and send headers but I'm receiving and I'm invalid JSON error and not sure why. Here's the code for that route:
app.post('/new-meeting', (req, res) => {
const headers = {
'Content-Type': 'application/json',
Accept: 'application / json',
Authorization: 'OAuth oauth_token=' + originalToken
};
console.log('-----------------------------------------------------------')
console.log('Acess Token:');
console.log('OAuth oauth_token=' + originalToken);
console.log('-----------------------------------------------------------')
const meetingBody = {
subject: 'string',
starttime: '2018-03-20T08:15:30-05:00',
endtime: '2018-03-20T09:15:30-05:00',
passwordrequired: true,
conferencecallinfo: 'string',
timezonekey: 'string',
meetingtype: 'immediate'
};
return fetch('https://api.getgo.com/G2M/rest/meetings', {
method: 'POST',
body: meetingBody,
headers: headers
}).then(response => {
console.log('response:');
console.log(response);
response
.json()
.then(json => {
res.send(json);
console.log(req.headers);
})
.catch(err => {
console.log(err);
});
});
});
When I hit that router, I get the following error:
{
"error": {
"resource": "/rest/meetings",
"message": "invalid json"
}
}
Any advice would be appreciated!
tl;dr
You are passing fetch a value for the body represented by a JavaScript object. It is converting it to a string by (implicitly) calling its .toString() method. This doesn't give you JSON. The API you are calling then complains and tells you that it isn't JSON.
You need to convert your object to JSON using:
body: JSON.stringify(meetingBody),
Test case
This demonstrates the problem and the solution.
Server
This is designed to be a very primitive and incomplete mock of GoToMeeting's API. It just echos back the request body.
const express = require("express");
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.text({ type: "*/*" }));
app.post("/", (req, res) => {
console.log(req.body);
res.send(req.body)
});
app.listen(7070, () => console.log('Example app listening on port 7070!'))
Client
This represents your code, but with the Express server stripped out. Only the code relevant for sending the request to GoToMeeting's API is preserved.
const url = "http://localhost:7070/";
const fetch = require("node-fetch");
const headers = {
'Content-Type': 'application/json',
Accept: 'application / json',
Authorization: 'OAuth oauth_token=foobarbaz'
};
const meetingBody = {
subject: 'string',
starttime: '2018-03-20T08:15:30-05:00',
endtime: '2018-03-20T09:15:30-05:00',
passwordrequired: true,
conferencecallinfo: 'string',
timezonekey: 'string',
meetingtype: 'immediate'
};
fetch(url, {
method: 'POST',
body: meetingBody,
headers: headers
})
.then(res => res.text())
.then(body => console.log(body));
Results of running the test case
The logs of both server and client show:
[object Object]
This is what you get when you call meetingBody.toString().
If you change the code as described at the top of this answer, you get:
{"subject":"string","starttime":"2018-03-20T08:15:30-05:00","endtime":"2018-03-20T09:15:30-05:00","passwordrequired":true,"conferencecallinfo":"string","timezonekey":"string","meetingtype":"immediate"}
This is JSON, which is what the API is expecting.
Aside
MIME types do not have spaces in them. Accept: 'application / json', should be Accept: 'application/json',. This probably isn't causing you any problems though.
I believe the header is incorrect.
You need 'Accept: application/json' without space.

Setup Node.js HTTPS to work with HAPROXY

I'm trying to make my nodejs app to communicate with HAPROXY via https. The idea is that nodejs sends message to haproxy via https, haproxy routes message forward.
I used request.js library and all worked fine, but now I need to perform this task without any libraries. The scenario is following. If environment variable is 1, I should use HTTP, in other cases -HTTPS. The problem is that when I use https and haproxy, I get "Socket hangup error", but everything works fine with request.js. Here is my code.
const protocol = process.env.NODE_ENV === 1 ? require('http') : require('https');
then I configure HTTPS
this.api = url.parse(app.get('API_HAPROXY'));
this.options = {
port: this.api.port,
hostname: this.api.hostname,
path: '/api/report',
method: 'POST',
headers: {
"Content-Type": "application/json",
},
rejectUnauthorized: false,
requestCert: true,
agent: false
};
Because I don't want to use ca to validate ssh keys I use NODE_TLS_REJECT_UNAUTHORIZED=0
reportData(json) {
const req = protocol.request(this.options, (res) => {
res.on('error', (err) => {
this.logger.error(`Failed to report ${err.message}`)
})
});
req.write(JSON.stringify(json));
req.end();
req.on('error', (err) => {
this.logger.error(`Failed to report ${err.message}`);
});
}
In this case I get socket hangup error while using HTTPS
Here is my request configuration
request({
uri: `${this.api}/api/report`,
method: 'POST',
json,
}, (err, response) => {
if (err || response.statusCode !== 200) {
this.logger.error(`Failed to report : ${err ? err.message : response.statusCode}`);
} else {
this.logger.info(`Report was sent`);
}
});
The issue was fixed by adding content-length header to the options.headers.
this.api = url.parse(app.get('API_HAPROXY')); this.options = {
port: this.api.port,
hostname: this.api.hostname,
path: '/api/report',
method: 'POST',
headers: {
"Content-Type": "application/json",
"Content-Length: <calculated length of the object you want to send in bytes >
},
rejectUnauthorized: false,
requestCert: true,
agent: false
};

Categories