Express.js routes responding with 504 error when deployed to AWS - amazon-web-services

I'm serving an application via AWS Elastic Beanstalk. localhost and ngrok serve the webpage and additional api calls as expected. When uploaded to AWS-ELB a 504 error is the response for any additional api calls after the webpage is served.
The application serves the base index.html file via:
BASIC SETUP AND SERVING INDEX.HTML
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.all('/', ensureSecure)
function ensureSecure(req, res, next){
if(req.headers['x-forwarded-proto'] === 'https'){
// OK, continue
return next()
};
res.redirect('https://' + req.headers.host)
}
app.use(express.static(__dirname + '/../web'), () => {}) // SERVE THE WEBPAGE
var port = process.env.PORT || 443
var router = express.Router()
router.use(function(req, res, next) {
console.log('here')
res.setHeader("Access-Control-Allow-Methods", "POST, PUT, OPTIONS, DELETE, GET");
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next(); // make sure we go to the next routes and don't stop here
});
It then has additional routes below it:
SUBSEQUENT API CALL
app.get('/user/:type', function(req, res) {
res.setHeader("Access-Control-Allow-Methods", "POST, PUT, OPTIONS, DELETE, GET");
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
var dbparams = {};
dbparams.TableName = 'FFUserTable';
dbparams.KeyConditionExpression = 'uid = :uid'
dbparams.ExpressionAttributeValues = {':uid':req.query.uid};
ddb_calls.awsDynamoQuery(dbparams, function(data){
res.json(data);
})
})
The index page serves and renders just fine. But any of the routes below it return a 504 gateway error when the page makes calls to them. The strange thing is that this only happens when deployed to AWS-ELB. Everything works as expected when using localhost or when using ngrok. Even if I take out the https redirect I get the same issue.
AWS-ELB ssl is setup and routed correctly via AWS-Route53.

This is the problem:
app.use(express.static(__dirname + '/../web'), () => {})
I'm not sure why you added that last function there, but it's an (incomplete) middleware function that will stall all requests because it's not doing anything.
It should be this:
app.use(express.static(__dirname + '/../web'))

Related

Cookies not settings when sending request between frontend and backend on different IPs

I am making an authorization part of app, where the frontend is being run on ip1, while backend is on ip2, which is written using NestJS
Here is the code of main.ts file in my backend
const file = readFileSync(
path.join(os.homedir(), '.local/folder/folder/iplist.txt'),
'utf-8',
);
const whitelist = file.split('\n');
app.enableCors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
console.log('allowed cors for:', origin);
callback(null, true);
} else {
console.log('blocked cors for:', origin);
callback(new Error('Not allowed by CORS'));
}
},
allowedHeaders:
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe',
methods: 'GET,PUT,POST,DELETE,UPDATE,OPTIONS',
credentials: true,
});
app.use(cookieParser());
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
Basically, what it does, is reading a file with IPs and then making an array of whitelist ips, allowing those with cors.
When setting the cookie, this is what I do:
const domain = request.headers.origin.slice(7).split(':')[0];
response
.cookie('access_token', await this.authService.signIn(dto), {
httpOnly: true,
domain: domain,
sameSite: 'lax',
expires: new Date(Date.now() + 1000 * 60 * 10),
})
This approach gives me the following error:
Set-Cookie was blocked because its Domain attribute was invalid with regards to the current host url

How to call a server from localhost VueJS with Axios?

I'm trying to call an AWS hosted API from my VueJS app, which is running on my localhost:8080. I have used this blog post to setup the vue.config.js with this block:
module.exports = {
devServer: {
proxy: 'https://0123456789.execute-api.eu-west-1.amazonaws.com/'
},
...
}
With this in place, I can use this code to make a GET request to an endpoint at that host:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/mock/api/endpoint',
{
headers: {
'Content-Type': 'application/json'
}})
This is because I have configured the AWS API Gateway mock endpoint to return these headers for the OPTIONS method:
Access-Control-Allow-Headers: 'Cache-Control,Expires,Pragma,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
Access-Control-Allow-Methods: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'
Access-Control-Allow-Origin: '*'
However, I cannot make this call:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
This endpoint is a Lambda integration and also has an OPTIONS method with the same headers as above.
Why should both endpoints, configured the same way, have different responses for axios?
UPDATE
As advised by #deniz, I have updated the .env.development file to contain:
VUE_APP_API_URI=https://0123456789.execute-api.eu-west-1.amazonaws.com/
I have also updated the axios requests to:
let url = 'mock/api/endpoint'
let headers = {
headers: {
'Content-Type': 'application/json',
},
}
this.$axios
.get(url, headers)
...and...
let url = 'lambda/api/function'
let headers = {
headers: {
'Content-Type': 'application/json',
},
}
this.$axios
.get(url, headers)
The result I get for the first GET request is:
200 OK
However the second request's response is:
Access to XMLHttpRequest at 'https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Your config for your dev env. as a proxy setup is doing nothing else then pretend to be someone else.
Thats why you dont get any CORS issues when you work with a proxy. its a kinda bottleneck which acts like "i am someone else, not localhost"
module.exports = {
devServer: {
proxy: 'https://0123456789.execute-api.eu-west-1.amazonaws.com/'
},
...
}
from now on all your requests came from this very proxy based URL
https://0123456789.execute-api.eu-west-1.amazonaws.com/
if you try to access the api like this:
this.$axios
.get('https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
you should keep in mind that you are already pretend that your proxy is doing his desguise stuff and still acts like its from a other source.
your URL when you call the API looks like this now, if i am not completely wrong:
https://0123456789.execute-api.eu-west-1.amazonaws.com/https://0123456789.execute-api.eu-west-1.amazonaws.com/lambda/api/function
all you have to do is change the axios url in your request to:
this.$axios
.get('lambda/api/function',
{
headers: {
'Content-Type': 'application/json'
}})
and try again.
UPDATE
VUE_APP_API_URI=https://0123456789.execute-api.eu-west-1.amazonaws.com/
wrap your URL string into quotes, like this and remove the last slash.
VUE_APP_API_URI='https://0123456789.execute-api.eu-west-1.amazonaws.com'
thats a common practice to handle .env vars.
2.
the CORS error you get is a result of not using proxy anymore.
your requesting data from a other source now and this is no allowed on modern browsers like FireFox or Chrome etc.
here you have to handle the server side configs in your API:
https://0123456789.execute-api.eu-west-1.amazonaws.com
because if you go like that you need to give your localhost and your backend the permission to handle requests if the requests are made from different sources, like in your case:
i am localhost and i request data from https://0123456789.execute-api.eu-west-1.amazonaws.com
normally this is forbidden and is a highly risk on security
But the solution is...
As you did before in your AWS API
Access-Control-Allow-Origin: '*' is the important part which handles your "CORS" issues.
make sure it is setup correct and works as intended. maybe play around with that and set localhost instead of * (allow for all)
3.
i highly recommend you to use the proxy way on development and use the non proxy way only for production, and just allow CORS for your frontend only.

Nuxt.js and forwarding cookie to backend

I have site on nuxt.js (example.com) and backend on PHP (example.com/api/).
Some page get data from /api/some:
asyncData() {
return axios.get("http://example.com/api/some").then((response) => {
return response.data;
});
},
In PHP-handler of "/api/some" I write to the log recieved cookies.
If I go to some-page by link (browser ajax-request) cookies exist.
If I refresh page (server-side rendering) then cookies is empty.
Cookie reach to nuxt (context.req.headers.cookie is not empty) but don't transfer to backend.
How I can fix it?
Sorry, it work for axios.
const headers = {};
if (context.req) {
headers.Cookie = context.req.headers.coookie;
}
return axios({
url: url,
method: "get",
headers: headers,
}).then(/* ... */);

2 docker containers (Django & React), isomorphic-fetch and a login request

My setup is running on Docker with a frontend (React) as well as a backend (Django) container.
I'm using the login-form component of the drf-react-app below in another project and am clueless as to how the api fetch request in the loginUser action creator (src/actions/user.js) knows which URL it is supposed to use..?
user.js:22 POST http://localhost:3000/api/obtain-auth-token/ 404 (Not Found)
I want it to send the request to the server at port 8000. I took the code from this drf-react boilerplate: https://github.com/moritz91/drf-react-login
export function loginUser(username, password) {
return (dispatch, getState) => {
const payload = {username, password};
dispatch({type: LOGIN_USER_REQUEST, payload});
return fetch(`/api/obtain-auth-token/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
})
.then(handleResponse(dispatch, LOGIN_USER_RESPONSE))
.then((json) => {
saveUser(json);
return json;
})
.catch(handleError(dispatch, LOGIN_USER_RESPONSE))
}
}
What am I missing?
In your package.json you have a proxy property set to "http://backend:8000". The proxy is used to redirect requests to a given url when you make a request against your local server http://localhost:3000. So if that's not working then you might be missing a step that enables the proxy.

Google Cloud Functions enable CORS?

I just finished the Hello World Google Cloud Functions tutorial and received the following response headers:
Connection → keep-alive
Content-Length → 14
Content-Type → text/plain; charset=utf-8
Date → Mon, 29 Feb 2016 07:02:37 GMT
Execution-Id → XbT-WC9lXKL-0
Server → nginx
How can I add the CORS headers to be able to call my function from my website?
here we go:
exports.helloWorld = function helloWorld(req, res) {
res.set('Access-Control-Allow-Origin', "*")
res.set('Access-Control-Allow-Methods', 'GET, POST');
if (req.method === "OPTIONS") {
// stop preflight requests here
res.status(204).send('');
return;
}
// handle full requests
res.status(200).send('weeee!);
};
then you can jquery/whatever it as usual:
$.get(myUrl, (r) => console.log(r))
I'm the product manager for Google Cloud Functions. Thanks for your question, this has been a popular request.
We don't have anything to announce just yet, but we're aware of several enhancements that need to be made to the HTTP invocation capabilities of Cloud Functions and we'll be rolling out improvements to this and many other areas in future iterations.
UPDATE:
We've improved the way you deal with HTTP in Cloud Functions. You now have full access to the HTTP Request/Response objects so you can set the appropriate CORS headers and respond to pre-flight OPTIONS requests (https://cloud.google.com/functions/docs/writing/http)
UPDATE (2022):
Just noticed there was a question about docs, and our docs have moved. Updated docs for CORS are here:
https://cloud.google.com/functions/docs/samples/functions-http-cors
You can use the CORS express middleware.
package.json
npm install express --save
npm install cors --save
index.js
'use strict';
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({origin: true});
const app = express();
app.use(cors);
app.get('*', (req, res) => {
res.send(`Hello, world`);
});
exports.hello = functions.https.onRequest(app);
I've just created webfunc. It's a lightweight HTTP server that supports CORS as well as routing for Google Cloud Functions. Example:
const { serveHttp, app } = require('webfunc')
exports.yourapp = serveHttp([
app.get('/', (req, res) => res.status(200).send('Hello World')),
app.get('/users/{userId}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}`)),
app.get('/users/{userId}/document/{docName}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}. I like your document ${params.docName}`)),
])
In your project's root, simply add a appconfig.json that looks like this:
{
"headers": {
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, POST",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Access-Control-Allow-Origin": "*",
"Access-Control-Max-Age": "1296000"
}
}
Hope this helps.
In the python environment, you can use the flask request object to manage CORS requests.
def cors_enabled_function(request):
if request.method == 'OPTIONS':
# Allows GET requests from any origin with the Content-Type
# header and caches preflight response for an 3600s
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
# Set CORS headers for the main request
headers = {
'Access-Control-Allow-Origin': '*'
}
return ('Hello World!', 200, headers)
See the gcloud docs for more.
You need to send an 'OPTIONS' response by setting its header as follows:
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', '*');
res.set('Access-Control-Allow-Headers', '*');
res.status(204).send('');
}
Runtime: NodeJS 10
If you tried the accepted answer but encountered a preflight error, the docs offer examples of handling it in multiple languages, with the caveat that it only works on public functions, i.e. deployed with --allow-unauthenticated:
exports.corsEnabledFunction = (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
/* handle preflight OPTIONS request */
res.set("Access-Control-Allow-Methods", "GET, POST");
res.set("Access-Control-Allow-Headers", "Content-Type");
// cache preflight response for 3600 sec
res.set("Access-Control-Max-Age", "3600");
return res.sendStatus(204);
}
// handle the main request
res.send("main response");
};
Another option is to use Express as shown in this post, complete with cross-origin enabled.
You must enable CORS within all your functions, for example hello function:
index.js
const cors = require('cors')();
// My Hello Function
function hello(req, res) {
res.status(200)
.send('Hello, Functions');
};
// CORS and Cloud Functions export
exports.hello = (req, res) => {
cors(req, res, () => {
hello(req, res);
});
}
Don't forget about package.json
package.json
{
"name": "function-hello",
"version": "0.1.0",
"private": true,
"dependencies": {
"cors": "^2.8.5"
}
}
After applying your favourite answer from here, if you're still getting this error, check for uncaught errors in your cloud function. This can result in the browser receiving a CORS error, even when your error has nothing to do with CORS
After CORS enabled if you send POST request to your function also check for your request Content-Type header, mine was set it to "text/plain" and my browser was constantly triggering CORS errors, after setting the header to "application/json" everything worked properly.