Preventing posts from origin other than domain with axios - node.js

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}'`);
});

Related

Is there a mechanism to force express redirect to use host paths?

I have the following code snippet made with express js
import serverless from 'serverless-http'
import express from 'express';
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.get('/api/info', (req, res) => {
res.send({ application: 'sample-app', version: '1.0' });
});
app.get('/jump', (req, res) => {
res.redirect('/api/info');
});
app.get('/explicit-jump', (req, res) => {
res.redirect('/dev/api/info');
});
app.post('/api/v1/getback', (req, res) => {
res.send({ ...req.body });
});
export default serverless(app)
If I deploy that code with serverless probably I will get an endpoint like https://my-api.some-region.amazonaws.com/dev/
Now if I try to reach the endpoint that redirect without the '/dev' path (/jump), I will get forbidden because is trying to reach https://my-api.some-region.amazonaws.com/api/info.
The one that set the path explicitly (/explicit-jump) works fine.
Fixing this single case is easy but I'm in the context of using an external app boilerplate (shopify express app) that has an incredible amount of redirects, really a high number.
I tried using a middleware that rewrites the urls when redirects:
app.use((req, res, next) => {
const redirector = res.redirect
res.redirect = function (url) {
console.log('CHANGING: ' + url);
url = url.replace('/api', '/dev/api')
console.log('TO: ' + url);
redirector.call(this, url)
}
next()
})
But I have the feeling that this is a brute force idea and actually it worked only in some occasions, somehow there are still redirects that goes to the base url ignoring the '/dev' path.
Is there a way I could fix this in a reasonable way so all redirects use the host in where the function is running?

Is CORS error specific to the nginx server or should I add something to my code

I recently deployed a website for the first time ever. We have 2 servers, so to say: 'https://baseUrl.com' and 'https://api.baseUrl.com' to make requests.
When trying to submit a contact form the data uploads to mongoDB but it isn't sent to node.js or from node.js it isn't sent to our e-mail address via nodemailer (I don't know exactly) and I get this error: Access to XMLHttpRequest at 'https://api.baseUrl.com' from origin 'https://baseUrl.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I do not have access to the nginx server but I was told that the specific CORS header/s have been set.
What should I do? Is there anything I could write in my code to fix this or it's strictly a server issue?
For example, I tried adding this code on node.js but it didn't help
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "YOUR-DOMAIN.TLD"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Example of code that is not executed because of the CORS error:
app.post('/api/*endpoint*',(req,res) => {
upload(req,res,function(err){
if(err){
return res.end("Something went wrong!");
}else{
let mailOptions = {
from: req.body.email,
to: '*email*',
};
transporter.sendMail(mailOptions, function(err) {
if (err) {
return res(err);
}
})
}
})
});
you can use cors express middleware to avoid cors troubles. Sample usage is below. Before using, you have to install npm package by typing "npm i cors"
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})

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

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.

Real-time listener doesn't work in PWA with Pusher

I'm trying to build my first PWA. I managed to do it, however, pushers functionality for some reason doesn't work.
I was debugging this for a few hours now and I can't make it work. I already tried rebuilding the app several times.
So, I'm subscribing to this
this.prices = this.pusher.subscribe('coin-prices');
then I have this function
sendPricePusher(data) {
console.log('Sending data from React')
console.log(data)
axios.post('/prices/new', {
prices: data
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error)
})
}
I'm calling the function above every 10 seconds from my
componentDidMount()
setInterval(() => {
axios.get('https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH,LTC&tsyms=USD')
.then(response => {
this.sendPricePusher(response.data)
})
.catch(error => {
console.log(error)
})
}, 10000)
NodeJs handles it perfectly. I see 200 in dev console.
app.post('/prices/new', (req, res) => {
// Trigger the 'prices' event to the 'coin-prices' channel
pusher.trigger( 'coin-prices', 'prices', {
prices: req.body.prices
});
res.sendStatus(200);
})
For some reason this magic piece of code doesn't work.
this.prices.bind('prices', price => {
this.setState({ btcprice: price.prices.BTC.USD });
this.setState({ ethprice: price.prices.ETH.USD });
this.setState({ ltcprice: price.prices.LTC.USD });
}, this);
It should recreate the state and the values will be updated.
So, I came to the conclusion that something is wrong with my server code. I want to host the app on heroku. I tried to write different variations of servers but none of them seem to work. However, I'm not 100% sure that my server is the problem. Can you please have a look at my server code? Here's my server.js file and a link to the project on github in case the problem is not so obvious. Pusher seems like a cool tech. I want to keep using it in my future projects just need to understand how.
// server.js
const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')
const app = express()
const Pusher = require('pusher')
const HTTP_PORT = process.env.PORT || 5000;
//initialize Pusher with your appId, key, secret and cluster
const pusher = new Pusher({
appId: '593364',
key: '8d30ce41f530c3ebe6b0',
secret: '8598161f533c653455be',
cluster: 'eu',
encrypted: true
})
// Body parser middleware
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(express.static("build"));
// CORS middleware
app.use((req, res, next) => {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', '*')
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true)
// Pass to next layer of middleware
next()
})
// API route in which the price information will be sent to from the clientside
app.post('/prices/new', (req, res) => {
// Trigger the 'prices' event to the 'coin-prices' channel
pusher.trigger( 'coin-prices', 'prices', {
prices: req.body.prices
});
res.sendStatus(200);
})
app.use((req, res) => {
res.sendFile(path.join(__dirname + "/build/index.html"));
});
app.listen(HTTP_PORT, err => {
if (err) {
console.error(err)
} else {
console.log('Server runs on ' + HTTP_PORT)
}
})
A good way to distinguish if the problem is in your server or Pusher's functionality is to test the Pusher part of the code separately with dummy data to ensure that at least the Publish/Subscribe functionality is working fine, i.e you have set it up as expected.
So you could just try the following in separate files:
//publisher
pusher.trigger( 'coin-prices', 'prices', {
prices: dummyprices
});
//subscriber
var prices = pusher.subscribe('coin-prices');
prices.bind('prices', ({ price }) => {
console.log(price);
})
Assuming you have initialized the Pusher SDKs correctly, this should work, if so, then the Pusher side of things is just fine and you can concentrate on finding out what in your server is causing the app to not work.
btw, in your existing code, you might wanna change:
this.prices.bind('prices', price => {})
to
this.prices.bind('prices', ({ price }) => {})
Hope this helps.

Socket.io - Origin is not allowed access

I'm having this weird problem with socket.io. I have an express app which I run on port 5000. I have configured socket.io like this:
const app = require('../index');
const http = require('http');
const server = http.Server(app);
const io = require('socket.io')(server);
io.on('connection', function (socket) {
console.log('User has connected');
socket.emit('connect', {
message: 'Hello World'
});
});
Then I import this piece of code into my index.js file like this:
const express = require('express');
const app = module.exports = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const request = require('request');
const boxRoutes = require('./routes/v1/boxRoutes');
const bidRoutes = require('./routes/v1/bidRoutes');
// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
app.use(bodyParser.json({ limit: '10mb' }));
require('./services/usersClass');
// cors set up
app.use(cors());
app.use(function (req, res, next) {
console.log('Headers Middleware Called');
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'origin, x-requested-with, content-type, accept, x-xsrf-token', 'token');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Request headers you wish to expose
res.setHeader('Access-Control-Expose-Headers', false);
next();
});
// Middleware to authenticate the requests to this service
app.use(function(req, res, next) {
console.log('Auth Middleware Called');
if(!req || !req.headers['authorization']) return res.sendStatus(401);
const token = req.headers['authorization'].split(' ')[1];
request.post(
'http://localhost:4000/api/v1/users/auth',
{
headers: {
'Authorization': `Bearer ${token}`
}
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
const data = JSON.parse(body);
res.locals.user = data.user;
next();
} else {
console.log('Request has failed. Please make sure you are logged in');
res.sendStatus(401);
}
}
);
});
app.use('/api/v1/boxes/', boxRoutes);
app.use('/api/v1/bids/', bidRoutes);
// disable 'powered by'
app.disable('x-powered-by');
app.listen(5000, () => {
console.log('Trading service is running on port 5000');
});
Now, in my client code, I try to establish socket.io connection when the user logs in. Everytime I try to connect to the server, I get the following error:
Failed to load
http://localhost:5000/socket.io/?EIO=3&transport=polling&t=MA_9wXE:
Response to preflight request doesn't pass access control check: The
value of the 'Access-Control-Allow-Origin' header in the response must
not be the wildcard '*' when the request's credentials mode is
'include'. Origin 'http://localhost:3000' is therefore not allowed
access. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.
I don't understand why the connection fails. I have configured Access-Control-Allow-Origin to my client domain but it still fails.
You can use cors npm module. It will fix your problem.
var cors = require('cors')
var app = express()
app.use(cors({origin: '*'}))
start '*' means allow every origins. You can type spesific origin too.
I've seen this problem before, but never seen it manifested as a cross origin issue. You are creating two separate http servers. One you are making your express server and the other you are making your socket.io server. The code you show only actually starts the express server and you show no code that actually starts your socket.io server.
Here's where you create these two separate servers:
const server = http.Server(app); // creates the http server you use for socket.io
app.listen(5000, () => {...}); // creates the http server you use with Express
Inside of app.listen(), it creates it's own new server and starts it. Your other server is never started (at least per the code you show here).
When you probably want to do is to make your socket.io server use the same server as your express server and then you should be able to connect just fine without any CORs issues.
If you want to use app.listen(), it will return the server object that it created and you need to use that to initialize socket.io.
If you want to use the other server, then you need to share that with your express initialization code so it can use that one.

Resources