I've deployed my Node.js Koa API app on Heroku. I'm making a call to a USAJOB.gov API and it works on my localhost. However, on Heroku I get this error:
You don't have permission to access "http://data.usajobs.gov/$(SERVE_403)/api/search?" on this server.
Here is my Koa router snippet:
router.get('/jobs', async (ctx) => {
const { keyword, location } = ctx.query;
const url = `https://data.usajobs.gov/api/search?
Keyword=${keyword}&LocationName=${location}`;
const host = 'data.usajobs.gov';
const userAgent = '<redacted>';
const authKey = '<redacted>';
const headers = {
Host: host,
'User-Agent': userAgent,
'Authorization-Key': authKey,
};
await fetch(url, {
headers,
method: 'GET',
});
}
So it turns out that whether or not cross-site Access-Control requests should be made using credentials was what was the issue. I wasn't able to figure out how to configure that with node-fetch but once I read the the solution (link below), switched to axios, configured withCredentials: true, and added headers: { 'X-Requested-With': 'XMLHttpRequest' }, then it all worked.
Source: axios delete method gives 403
Related
I have a local NodeJS app that uses Axios to connect to a local server and now I am trying to hit the deployed server. The call looks like:
let apiCall = "https://api.myproduct.io/api/all";
console.log("making axios call to " + apiCall);
const resp = await axios
.get(
apiCall,
{
headers: {
APIKey: config.API_KEY,
'Access-Control-Allow-Origin': '*'
},
},
{ crossdomain: true, credentials: "same-origin" }
)
.then((response) => {
console.log("axios response " + response);
return response;
})
.catch((e) => {
return e;
});
return resp;
}
And I have this in my vue.config.js:
module.exports = {
devServer: {
proxy: "https://api.myproduct.io",
},
transpileDependencies: ["vuetify"],
};
However, when I deploy this to AWS and try to GET it via Axios I get:
making axios call to https://api.myproduct.io/api/all
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at https://api.myproduct.io/api/all. (Reason:
CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 301.
These calls work from Postman and my mobile app - it's only from my local environment that this fails.
If I'm following correctly, you have deployed a website hosted at domain x.y and are trying to call an API at api.myproduct.io? If that's the case, the CORS headers that api.myproduct.io are responding with are what's causing the issue. You should either change the CORS headers in api.myproduct.io if you have control over that service. Or use a proxy of sorts to call it from a server you do control that can be registered to the same domain.
In short:
x.y calls to api.myproduct.io ❌
x.y calls to x.y/products ✅ (then service that handles /products calls api.myproducts.io)
You can learn more about CORS here
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
I am trying to upload image to my api. Uploading with postman works fine. When I previously used expo go all worked fine as well. Once I switched to react native native code I get following error.
I am sure my endpoint is correct and it is HTTPS. Other post request that are not multipart/form data/PUT requests or Get requests work perfectly. Here is code:
const openImagePickerAsync = async () => {
let permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("Permission to access camera roll is required!");
return;
}
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 1,
});
console.log(result)
const { cancelled } = result as ImageInfo
if(cancelled == false)
{
const { uri } = result as ImageInfo
const bodyy = new FormData();
const bod = JSON.parse(JSON.stringify({ uri: uri, type: 'image/jpeg', name: 'profilePicture'+userId+".jpg" }));
bodyy.append('demo_image', bod);
try{
const log = await fetch("https://endpoint/api/upload/picture/" + userId, {
method:'POST',
body: bodyy,
headers: {
'Content-Type': 'multipart/form-data',
Accept: "application/json",
}
})
console.log(log)
Alert.alert("Profile picture uploaded!")
}
catch (error){
console.log(error)
}
}
else{
Alert.alert("You did not choose picture to upload.")
}
}
And here is response:
Edit1: I have also tried running my server on localhost with http and writing IPv4 address of my PC I am running server on (besides localhost:8000) I wrote ip:8000. Did not help..
your problem is about CORS (Cross-Origin Resource Sharing)
you must be add this command in your server
npm install express cors
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
and cors() allowed the client to access this route
but if you didnt use express you can solve this problem with cors header request
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'multipart/form-data',
}```
Variations of my issue have been asked dozens of times, but nothing I’ve tried worked.
My Node.js API works as expected on localhost for GET and POST requests.
On my EC2/nginx instance, the server block was correctly configured and served a static index over https.
Then I configured this server block as a proxy to my API port (8000) and it also returns that the API is listening.
Then I ran a simple GET /index endpoint that is routed and works correctly.
However, a similar POST /checkEmail endpoint (which, remember, works on localhost) here times out with a 504 error. This is where it gets weird.
It won’t even console.log the payload, the first line of the function. That means it isn’t even routed correctly. Here are my routes:
const API = require("./controllers/api");
module.exports = [
{ method: 'POST', path: '/checkEmail', options: API.checkEmail },
{ method: 'POST', path: '/sendEmail', options: API.sendEmail },
{ method: 'POST', path: '/recaptcha', options: API.recaptcha },
{ method: 'GET', path: '/index', options: API.index }
]
Since that the GET request returns the expected response, then it means all of these are true:
The firewall rules are letting traffic through the 443 (HTTPS) port
That traffic is proxied through the 8000 (API) port
The server block is serving the files correctly
The ssl certificate works
The request is sent to the upstream server
The endpoints:
const axios = require("axios");
const env = require("dotenv").config();
const AWS = require("aws-sdk");
const subscriber = require("./middleware/subscriber");
const sanitizer = require("./middleware/sanitizer");
exports.index = {
cors: {
origin: ["*"]
},
handler: (request, h) => {
return "API Fluente";
}
}
exports.checkEmail = {
cors: {
origin: ["*"]
},
handler: async (request, h) => {
// This is never logged:
console.log(request.payload);
const payload = request.payload;
const email = await sanitizer.email(payload.email);
if(!email) throw new Error("Invalid email");
const response = await verifyEmail(email);
console.log("checkEmail attempt:", email, response.data)
if (response.status === 200) {
return h.response(response.data).code(200);
}
}
}
What I’ve tried:
Minimal/Full server block conf settings
curl the POST request from the EC2 CLI
Change ports from 8000 to something else (eg 8003)
Increasing timeout duration doesn’t make sense because these are simple requests that should return a response in a very short time.
None of these made any difference.
Found the problem! As it turns out, it had nothing to do with AWS services or Node.
I was using the deprecated hapi package, when I should have been using #hapi/hapi.
I am having a very weird issue by using the Nodejs https module.
What I was trying to do is, calling the 3rd party API for some service, the following is my code:
const https = require("https");
function request(accessId, secretKey, host, api, body, timeout=3000) {
let bodyString = JSON.stringify(body);
let time = Math.round(new Date().getTime()/1000).toString();
// I have implemented the signBody function
let sign = signBody(accessId, secretKey, time, bodyString);
let header = {
"Content-Type": "application/json",
"AccessId": accessId,
"TimeStamp": time,
"Sign": sign,
};
let options = {
method: 'POST',
timeout: timeout,
headers: header,
}
let url = new URL(api,host);
https.request(url, options, (res) => {...});
}
They weird part is, if I'm running the function by node xxx.js to trigger the request("MY_ACCESS_ID", "MY_SECRET_KEY", "https://api.xxxx.com", "/service/api/v3", MY_BODY) function, it works as expected. However, this request(...) function is part of my webserver, and it is used by a API (I'm using express.js), such as:
// the myService implemented the request() function
let myService = require("./myService.js")
router.get("/myAPI", (req, res, next) => {
request("MY_ACCESS_ID", "MY_SECRET_KEY", "https://api.xxxx.com", "/service/api/v3", MY_BODY)
})
it always shows the error: Error: connect ECONNREFUSED 127.0.0.1:443
I have no idea why the same code is behaving different. I thought it probably https.request issue. they I tried to use axios for the post request. The other weird things showed up. By using the exactly same header, https.request() returns success from the service provider and axios.post returns error message: Sign check error, please check the way to generate Sign.
This is so crazy....no idea of this issue. Any idea ??
BTW, I have solved the issue by implementing as:
const https = require("https");
function request(accessId, secretKey, host, api, body, timeout=3000) {
let bodyString = JSON.stringify(body);
let time = Math.round(new Date().getTime()/1000).toString();
// I have implemented the signBody function
let sign = signBody(accessId, secretKey, time, bodyString);
let header = {
"Content-Type": "application/json",
"AccessId": accessId,
"TimeStamp": time,
"Sign": sign,
};
let options = {
hostname: host,
path: api,
method: 'POST',
timeout: timeout,
headers: header,
}
https.request(options, (res) => {...});
}
But still no idea what is the difference.
I would check the final url constructed inside the https.request method. The non-working version makes a request to 127.0.0.1:443, which wouldn't work since your localhost doesn't support https(only http) and 443 is usually for https.
See https://nodejs.org/api/https.html#https_https_request_url_options_callback for default port number.
Axios has different implementation inside the post() method and it may manipulate your sign string by url encoding it before sending to the 3rd-party API.
Please read, this is different!
I've used Firebase Functions previously and solved this issue by adding this code:
const cors = require('cors')({ origin: true });
return cors(req, res, () => {
let format = req.query.format;
if (!format) {
format = req.body.format;
}
const formattedDate = moment().format(format);
console.log('Sending Formatted date:', formattedDate);
res.status(200).send(formattedDate);
});
But now I'm working on a new project, and I'm getting this error no matter what I try to do.
I have read and tried the solutions in over 20 other questions here on stackoverflow and around the internet, and none of them work now.
So I went to firebase's GitHub, downloaded the date example (has the recommended cors fix implemented) and deployed it.
And I still get the same error!
Access to fetch at 'https://us-central1-generation-y-members.cloudfunctions.net/date' from origin
'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The function can be called from postman and does work. https://i.imgur.com/YTi1PpQ.png
I've upgraded my project to the blaze plan (didn't help).
I've tried changing to origin: "http://localhost:3000" instead of origin: true, didn't help at all.
I've tried uploading my react app to a server and calling from there, same result (http, not https) - even when setting origin: "http://my-site.com"
Any pointers would be highly appreciated.
The issue is that the query being made from the localhost is missing the CORS headers.
On your client side application you need to add the following headers to be able to perform the CORS calls.
'Access-Control-Allow-Origin', '*'
'Access-Control-Allow-Headers', 'Content-Type'
If you are using jav ascript on the client side application this can be done with the following code, according to the libraries you are using.
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
xhr.setRequestHeader('Access-Control-Allow-Headers', 'Content-Type');
I found the solution so I'll post it here for anyone that has the same error and doesn't know why it's happening:
This is the code I'm using now, the issue was not using JSON.stringify() when setting the body for the request.
let body: any = {};
body.name = currentUser.name;
body.email = currentUser.email;
body.password = generatedPassword;
body.message = '';
body.number = randomNumber;
const requestOptions: any = {
method: 'POST',
mode: 'cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body), // ISSUE WAS HERE! I HAD 'body: body'
};
try {
let response = await (await fetch('https://us-central1-generation-y-members.cloudfunctions.net/register', requestOptions)).text();
// let response = await (await fetch('http://localhost:5001/generation-y-members/us-central1/register', requestOptions)).text();
console.log({ response: response });
sentEmails.push(currentUser.email);
} catch (e) {
console.log({ error: e });
}
Good luck!