express.static() and sendFile() problem... creating a dynamic host with nodejs - node.js

After configure my web server with nginx, i redirected all *.example.com to my nodejs server.
But before, i handle the http request, i check the url and host to see if it is correct or not.
For example, if the user writes something like what.ever.example.com
I redirect him to the main website because that host is not valid.
otherwise if the user writes something like mydomain.example.com
The user should access to this website and receive the angular APP.
So i am doing something like this.
UPDATED CODE
const express = require('express');
const cors = require('cors');
const mongoose = require('./server/database');
const bodyParser = require('body-parser');
const app = express();
var path = require('path');
// Settings
app.set('port', process.env.PORT || 4000)
// Middlewares
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json());
app.use(cors());
// Routes API
app.use('/api/users', require('./server/routes/usuarios.routes'));
app.use('/api/almacenes', require('./server/routes/almacen.routes'))
app.use('/api/updates', require('./server/routes/update.routes'))
app.use('/api/dominios', require('./server/routes/dominios.routes'))
app.get('/', checkHost);
app.get('/', express.static('../nginx/app'));
app.get('/*', checkPath);
function checkHost(req, res, next) { //With this function what i pretend is check the subdomain that the user send, and if it doesn't exist. redirect it.
var domain = req.headers.host
subDomain = domain.split('.')
if (subDomain.length == 3) {
subDomain = subDomain[0].split("-").join(" ");
let query = { dominio: subDomain }
var dominiosModel = mongoose.model('dominios');
dominiosModel.findOne(query).exec((err, response) => {
if (response != null) {
if (response.dominio == subDomain) {
next();
} else {
res.writeHead(303, {
location: 'http://www.example.com/index.html'
})
res.end()
}
} else {
res.writeHead(303, {
location: 'http://www.example.com/index.html'
})
res.end()
}
})
} else {
res.writeHead(303, {
location: 'http://www.example.com/index.html'
})
res.end()
}
}
function checkPath(req, res, next) { //With this function what i want to do is.. if the user send *.example.com/whatever, i redirect it to *.example.com
if (req.url !== '/') {
res.writeHead(303, {
location: `http://${req.headers.host}`
})
res.end()
} else {
next()
}
}
// Starting Server.
app.listen(app.get('port'), () => {
console.log('Server listening on port', app.get('port'));
});
All redirects are working well, but when in checkHost the subDomain matched, it doesnt send nothing to the front... so what can i do here?

Try removing the response.end(). Since .sendFile() accepts a callback, it is most likely an async function, which means that calling .end() right after .sendFile() will most probably result in a blank response.

The sendFile function requires absolute path of the file to be sent, if root is not provided. If root is provided, a relative path could be used, but the root itself should be absolute. Check documentation here: https://expressjs.com/en/api.html#res.sendFile
You should try to send your index.html in following manner:
app.get('*', checkPath, checkHost, function (req, response) {
response.sendFile('index.html', { root: path.join(__dirname, '../nginx/app') });
}
This should work provided that the path ../nginx/app/index.html is valid, relative to the file in which this code is written.
Additionally, based on the sample code (and the comments), you probably don't need the express.static(...) at all. Unless, you need to serve 'other' files statically.
If it is needed, then the app.use(express.static('../nginx/app')) should be outside the controller. It should probably be added before the bodyParser, but since you are concerned about someone being able to access 'index.html' via the static middleware, you can consider following order for your middlewares:
//existing body parser and cors middlewares
// existing /api/* api middlewares.
app.use(checkPath);
app.use(checkHost);
app.use(express.static('../nginx/app'));
If the checkPath middleware is modified slightly to redirect to /index.html, the middleware with '*' path might not be required at all with this setup.

Related

CORS anywhere returning proxy text, not desired target resource

I am trying to set up a proxy using node, express, and an instance of cors-anywhere for my arcgis-js-api app. My server file looks like this:
import express from 'express';
import cors from 'cors';
import corsAnywhere from 'cors-anywhere';
const { PORT } = process.env;
const port = PORT || 3030;
var app = express();
let proxy = corsAnywhere.createServer({
originWhitelist: [], // Allow all origins
requireHeaders: [], // Do not require any headers.
removeHeaders: [], // Do not remove any headers.
});
app.use(cors());
app.get('/proxy/:proxyUrl*', (req, res) => {
req.url = req.url.replace('/proxy/', '/');
proxy.emit('request', req, res);
});
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'index.html'));
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
When I go to http://localhost:3030/proxy/https://maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json, I get to my target json no problem, with access-control-allow-origin: * correctly tacked on.
In my front end html (an arcgis-js-api app), I am calling that same url:
var layer = new MapImageLayer({
url: 'http://localhost:3030/proxy/https://maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json',
});
My network tab shows a response not of the expected JSON, but of the text of the cors-anywhere proxy:
For those familiar with the arcgis-js-api, you can also preconfigure use of a proxy:
urlUtils.addProxyRule({
urlPrefix: 'maps.disasters.nasa.gov',
proxyUrl: 'http://localhost:3030/proxy/',
});
If I do it this way, the network tab shows that the call to the localhost:3030/proxy/<url> is returning the index.html page, not the desired json.
Why is the proxy giving the expected/required result when I access the url directly through the browser, but not when being called from my front end file? Thanks for reading.
I checked the browser console and noticed that the url being sent to the proxy instead of this
http://localhost:3030/proxy/https://maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json
looks like this
http://localhost:3030/proxy/https:/maps.disasters.nasa.gov/ags04/rest/services/ca_fires_202008/sentinel2/MapServer?f=json
Not sure why it's happening, but as a quick fix you can replace
req.url = req.url.replace('/proxy/', '/');
with
req.url = req.url.replace('/proxy/https:/', '/https://');

Preventing posts from origin other than domain with axios

I'm working on my first website, and am using axios to send post/get requests to the backend. I'm using React on the front-end and node/express on the back-end. I'm wondering if there is a way to prevent posts from a source other than my site.
For example, if I make this exact request through postman I am still be able to post comments, meaning that someone could post with names and ID's other than themselves.
Here is a typical post request made on the front-end:
axios.post('/api/forumActions/postComment', {}, {
params: {
postUserID: this.props.auth.user.id,
name: `${this.props.auth.user.firstName} ${this.props.auth.user.lastName}`,
commentContent: this.state.commentContent,
respondingToPost: this.state.postID,
respondingToComment: this.state.postID
}
})
And here is how it gets processed on the back-end
app.use(
bodyParser.urlencoded({
extended: false
})
);
app.use(bodyParser.json());
app.use(passport.initialize());
require("./config/passport")(passport);
app.post('/postComment', (req, res)=>{
var commentData={
postUserID: req.query.postUserID,
name: req.query.name,
commentContent: req.query.commentContent,
respondingToPost: req.query.respondingToPost,
respondingToComment: req.query,respondingToComment
}
//Write commentData to database
})
const port = process.env.PORT || 80;
const server = app.listen(port, () => console.log(`Server running on port ${port} !`));
I'm wondering if there is anything I can do to ramp up security to prevent post requests being made from anywhere?
You can use cors to accomplish this. This is a pretty good guide on how to configure it, specifically this section. You can configure it for certain routes, or all across the board.
CORS sets the Access-Control-Allow-Origin header, which you can read more about here - it only allows requests from specified origins.
Keep in mind you don't need that package to accomplish this.. you could always build your own middleware for this.
Something like:
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://yourdomain.com");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Within the Express documentation, they provide the following demo code, which you should be able to use as a helper.
Client
Server
You could use a makeshift middleware with special headers.. but then all someone has to do is read your client side source code, or look at the network tab in their browser to figure out which headers you're sending, so then can duplicate them. It would prevent random people from snooping, though..
const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Custom special middleware..
function blockBadHosts({ host, whitelistHeader, whitelistHeaderValue }) {
return (req, res, next) => {
if(req.headers['host'] === host) {
if(whitelistHeader && req.headers[whitelistHeader] === whitelistHeaderValue) {
next();
} else {
res.status(301).send('BAD REQUEST');
}
} else {
res.status(301).send("BAD REQUEST");
}
}
}
// Options for our custom middleware
const badHostOptions = {
host: "localhost:3000",
whitelistHeader: "x-my-special-header", // Request must contain this header..
whitelistHeaderValue: "zoo" // .. with this value
}
// This should succeed
app.get('/success', (req, res) => {
res.status(200).send("from /success");
});
// This should fail even if sent from Postman without correct headers
app.get('/failure', blockBadHosts(badHostOptions), (req, res) => {
res.status(200).send("from /failure");
});
// 404 route
app.use((req, res) => {
res.status(404).send("Uh oh can't find that");
})
app.listen(port, () => {
console.log(`App listening on port: '${port}'`);
});

Why does the console.log appear twice in app.js in nodejs

I have written a code in NodeJS where when i hit the url, i need the server to pass through three middleware functions authentication, cookies, logging. Here it happens , but the console gets printed twice. Can you help me figure out why.
var express = require('express');
var app = express();
var router = require('express').Router();
/* Add the middleware to express app */
app.use (function authentication(req,res,next){
if(req.method === 'GET'){
console.log("Inside Authentication.js")
next(); // If your don't use next(), the mmand won't go to the next function.
}
else{
console.log("Inside else Authentication.js")
}
})
app.use( function cookies(req,res,next){
if (req.method === 'GET'){
console.log("Inside cookies.js")
next();
}
else
{
console.log("Inside else cookies.js")
}
})
app.use( function logging(req,res,next){
if(req.method === 'GET'){
console.log("Inside Logging.js");
next();
}
else{
console.log("Inside else of Logging.js");
}
})
app.use(function(req,res) {
res.send('Hello there !');
});
app.listen(8080);
o/ p -
E:\NodeJSProject\middleware>node app.js
Inside Authentication.js
Inside cookies.js
Inside Logging.js
Inside Authentication.js
Inside cookies.js
Inside Logging.js
Your browser will perform a pre-flight CORS request (i.e. an OPTION request) to see whether you're allow to perform the request.
From your perspective its just one request, but the browser is performing two requests and your express server is executing both requests in full.
Given you're using express there is middleware available to handle those requests specifically.
See here for documentation.
If you want to avoid the CORS request altogether, your website and API need to be served from the same host, port, and protocol.
You can read more about CORS here, or you can search Stack -- theres extensive posts.

NodeJS Express Root Path

In NodeJS Express module, specifying path "/" will catch multiple HTTP requests like "/", "lib/main.js", "images/icon.gif", etc
var app = require('express')
app.use('/', authenticate);
In above example, if authenticate is defined as followed
var authenticate = function(request, response, next) {
console.log("=> path = " + request.path);
next()
}
Then you would see
=> path = /
=> path = /lib/main.js
=> path = /images/icon.gif
Could anyone advise how to define path in Express "app.use" that only catch "/"?
If you are trying to expose static files, people usually place those in a folder called public/. express has built-in middleware called static to handle all requests to this folder.
var express = require('express')
var app = express();
app.use(express.static('./public'));
app.use('/', authenticate);
app.get('/home', function(req, res) {
res.send('Hello World');
});
Now if you place images/css/javascript files in public you can access them as if public/ is the root directory
<script src="http://localhost/lib/main.js"></script>
As far as I understand, what you need to do is if you have '/' & '/abc' you need to catch it separately.
This will do the trick:
app.use('/abc', abc);
app.use('/', authenticate);
Means, register the /abc middleware first, then do the / middleware.
There is an issue with this solution also. Here we declared /abc only. So when user calls an unregistered path, then it will hit here.
You can make use of originalUrl property in request object to determine its / only or there is something else. Here is the documentation for this : http://expressjs.com/en/api.html#req.originalUrl
if(req.originalUrl !== '/'){
res.status(404).send('Sorry, we cannot find that!');
}
else{
/*Do your stuff*/
}

Expressjs router based on Content-Type header

For routing, I'd like my middleware to pass the request the routes defined in a /html folder to server HTML(ejs), and if header Content-Type is application/json, use the routes defined in the /api folder.
But I don't want to have to define that in every route.
So I'm not looking for middleware that defines some req.api property that I can check on in every route
app.get('/', function(req, res) {
if(req.api_call) {
// serve api
} else {
// serve html
}
});
But I'd like something like this:
// HTML folder
app.get('/', function(req, res) {
res.send('hi');
});
// API folder
app.get('/', function(req, res) {
res.json({message: 'hi'});
});
Is this possible and if so, how can I do this?
I'd like it to work something like this:
app.use(checkApiCall, apiRouter);
app.use(checkHTMLCall, htmlRouter);
You can insert as the first middleware in the Express chain, a middleware handler that checks the request type and then modifies the req.url into a pseudo URL by adding a prefix path to it. This modification will then force that request to go to only a specific router (a router set up to handle that specific URL prefix). I've verified this works in Express with the following code:
var express = require('express');
var app = express();
app.listen(80);
var routerAPI = express.Router();
var routerHTML = express.Router();
app.use(function(req, res, next) {
// check for some condition related to incoming request type and
// decide how to modify the URL into a pseudo-URL that your routers
// will handle
if (checkAPICall(req)) {
req.url = "/api" + req.url;
} else if (checkHTMLCall(req)) {
req.url = "/html" + req.url;
}
next();
});
app.use("/api", routerAPI);
app.use("/html", routerHTML);
// this router gets hit if checkAPICall() added `/api` to the front
// of the path
routerAPI.get("/", function(req, res) {
res.json({status: "ok"});
});
// this router gets hit if checkHTMLCall() added `/api` to the front
// of the path
routerHTML.get("/", function(req, res) {
res.end("status ok");
});
Note: I did not fill in the code for checkAPICall() or checkHTMLCall() because you were not completely specific about how you wanted those to work. I mocked them up in my own test server to see that the concept works. I assume you can provide the appropriate code for those functions or substitute your own if statement.
Prior Answer
I just verified that you can change req.url in Express middleware so if you have some middleware that modifies the req.url, it will then affect the routing of that request.
// middleware that modifies req.url into a pseudo-URL based on
// the incoming request type so express routing for the pseudo-URLs
// can be used to distinguish requests made to the same path
// but with a different request type
app.use(function(req, res, next) {
// check for some condition related to incoming request type and
// decide how to modify the URL into a pseudo-URL that your routers
// will handle
if (checkAPICall(req)) {
req.url = "/api" + req.url;
} else if (checkHTMLCall(req)) {
req.url = "/html" + req.url;
}
next();
});
// this will get requests sent to "/" with our request type that checkAPICall() looks for
app.get("/api/", function(req, res) {
res.json({status: "ok"});
});
// this will get requests sent to "/" with our request type that checkHTMLCall() looks for
app.get("/html/", function(req, res) {
res.json({status: "ok"});
});
Older Answer
I was able to successfully put a request callback in front of express like this and see that it was succesfully modifying the incoming URL to then affect express routing like this:
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(function(req, res) {
// test modifying the URL before Express sees it
// this could be extended to examine the request type and modify the URL accordingly
req.url = "/api" + req.url;
return app.apply(this, arguments);
});
server.listen(80);
app.get("/api/", function(req, res) {
res.json({status: "ok"});
});
app.get("/html/", function(req, res) {
res.end("status ok");
});
This example (which I tested) just hardwires adding "/api" onto the front of the URL, but you could check the incoming request type yourself and then make the URL modification as appropriate. I have not yet explored whether this could be done entirely within Express.
In this example, when I requested "/", I was given the JSON.
To throw my hat in the ring, I wanted easily readable routes without having .json suffixes everywhere.
router.get("/foo", HTML_ACCEPTED, (req, res) => res.send("<html><h1>baz</h1><p>qux</p></html>"))
router.get("/foo", JSON_ACCEPTED, (req, res) => res.json({foo: "bar"}))
Here's how those middlewares work.
function HTML_ACCEPTED (req, res, next) { return req.accepts("html") ? next() : next("route") }
function JSON_ACCEPTED (req, res, next) { return req.accepts("json") ? next() : next("route") }
Personally I think this is quite readable (and therefore maintainable).
$ curl localhost:5000/foo --header "Accept: text/html"
<html><h1>baz</h1><p>qux</p></html>
$ curl localhost:5000/foo --header "Accept: application/json"
{"foo":"bar"}
Notes:
I recommend putting the HTML routes before the JSON routes because some browsers will accept HTML or JSON, so they'll get whichever route is listed first. I'd expect API users to be capable of understanding and setting the Accept header, but I wouldn't expect that of browser users, so browsers get preference.
The last paragraph in ExpressJS Guide talks about next('route'). In short, next() skips to the next middleware in the same route while next('route') bails out of this route and tries the next one.
Here's the reference on req.accepts.

Resources