Passing req.session to route - node.js

I'm trying to make login session in nodejs,
My app.js
app.post('/login', async(req, res) => {
const { login, password } = req.body
const admin = await register.findOne({ where: { login } })
if (await bcrypt.compare(password, admin.password)) {
req.session.admin = true
res.render('adminPage', { session:req.session.admin }) <---- this not working
} else {
res.send("Wrong password...")
}
})
I'm trying to pass " req.session.admin" to my route to make some kind of authentication
My routes/adminPage.js
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
//request.session.admin always giving me "undefined"
try { if (request.session.admin == true) res.render('adminPage') } catch {
res.send("You have to log in...")
}
})
module.exports = router
Is it possible to do or am I stupid?

If you're just initializing a key called session in you request object then indeed your data is being lost after sending the response. I'd go for a third-party solution which persists and rehydrates the data, manages expiration date, so on... Eg: Express-session
Also, I would move my logic to check "whether the user is logged in" to a middleware so I could reuse that code and have it separeted from the main concern of the endpoint.

Related

I'm trying to create a global variable in express (also using mongoose), but the app keeps deleting the variable when the page is refreshed

I'm working on an e-commerce project. I'm trying to create a shopping cart within the app so that people don't accidentally access another user's data in the Mongo database. To do this, I tried setting up a variable as res.locals.cart. This didn't work because I found out from the docs that res.locals expires in each new page.
My next idea was to create an anonymous shopping cart each time app.js started and store it in the global app.locals object. This does work, and in the following code, you can see it returns the model of the shopping cart. But after that, it's undefined as soon as I refresh or go to a new page as seen by console.log. Why is it doing that? How can I make it so that my data stays across the whole app? And I need it to be a variable, so that it changes for each new user. If there are also any NPM packages that solve this problem, that would be helpful to know.
app.locals.cart = Cart.create({}, function (err, newCart) {
if (!err) {
console.log(newCart);
return newCart
}
});
app.get('/cart', function (req, res) {
console.log(app.locals.cart);
res.render('cart')
});
💡 This is not the best practive, but, if you still want to do it, than this is an example code you can see:
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.locals.cart = [];
const item = {
id: 1,
name: 'Testing'
}
const addToCart = function(req, res, next) {
const { username } = req.body;
// req.body.username just for identifier
// you can change it with user id from token or token or whatever you want
if(typeof app.locals.cart[username] === 'undefined') {
app.locals.cart[username] = [];
}
// add item to cart by username / identifier
app.locals.cart[username].push(item);
next();
}
// or if you want to use this add to global, than you can use this middleware
// and now, you can see
// app.use(addToCart);
app.post('/', addToCart, (req, res, next) => {
// console.log
const { username } = req.body;
console.log(app.locals.cart[username])
res.send(app.locals.cart[username]);
})
app.get('/:username', (req, res, next) => {
const { username } = req.params;
console.log(app.locals.cart[username]);
res.send(app.locals.cart[username]);
})
app.listen(3000, () => {
console.log('Server is up');
})
I hope it's can help you 🙏.
I think the way you are trying is not a best practice.
Instead of using the locals try a different approach.
Creating a cart for each user in the database will be better.
You can link the card with the user. And whenever a user makes a request you fetch the cart from DB and do whatever you want.
To do that, you can add a user property to the Cart Schema. Whenever a user signs up, create a cart for it in the DB. When the user checkouts the cart, save the products in the card as another Document, let say an Order in the Orders Collection and clear the cart for future usage.
QUICK DIGEST:
Store any data from Mongoose onto a variable on your middleware and then have that variable read by app.locals or res.locals. The reason for this is because app.locals is changing and your middleware variable isn't, which lets it be read the same way every time. Example:
res.locals.data = middleware.var;
//or
app.locals.data = middleware.var;
Model.findById("model-id", function (err, noErr) {
if (!err) {
middleware.var = noErr //data retrieved from callback being passed into middleware.var
}
});
Here's what I discovered. My code didn't work because Express refuses to store data directly from a Mongoose function. I discovered this by console.logging the data inside the function and again outside it. I'm not sure why Express refuses to take data this way, but that's what was happening.
Cart.create({}, function (err, newCart) {
if (!err) {
app.locals.cart = newCart;
console.log(app.locals.cart);
//Returns Mongoose model
}
});
console.log(app.locals.cart);
//Now returns undefined
I solved this by storing the value to a variable in my middleware object called mids.
Here's the code on my app.js:
mids.anonymousCart();
app.use(function (req, res, next) {
res.locals.userTrue = req.user;
res.locals.cart = mids.cart;
next();
});
And here's the code on my middleware file:
var mids = {
anonymous_id: undefined,
cart: []
};
mids.anonymousCart = function () {
if (typeof mids.anonymous_id === 'undefined') {
Cart.create({}, function (err, newCart) {
if (!err) {
mids.anonymous_id = newCart._id.toString();
Cart.findById(mids.anonymous_id).populate('items').exec(function (err, cartReady) {
if (!err) {
mids.cart = cartReady;
}
});
}
});
}
}
What's happening here is that when app.js first starts, my middleware mids.anonymousCart is run. In my middleware, it checks that the id of Cart model stored as anonymous_id exists or not. It doesn't, so it creates the cart and stores the id. Since this is stored in the middleware and not Express, it can be accessed by any file linked to it.
I then turned the id I got back into a string, so that it could be read in the next function's findById. And if there's any items in the cart, Mongoose will then fill them in using the populate() method. But THE THING TO PAY ATTENTION TO is the way the data received in cartReady is stored in the middleware variable mids.cart, which is then read by res.locals.cart on app.js.

How to efficiently forward request to multiple endpoints using nodejs?

I built a nodejs server to act as an adapter server, which upon receiving a post request containing some data, extracts the data from the request body and then forwards it to a few other external servers. Finally, my server will send a response consisting of the responses from each of the external server (success/fail).
If there's only 1 endpoint to forward to, it seems fairly straightforward. However, when I have to forward to more than one servers, I have to rely on things like Promise.All(), which has a fail-fast behaviour. That means if one promise is rejected (an external server is down), all other promises will also be rejected immediately and the rest the servers will not receive my data.
May be this ain't be the exact solution. But, what I am posting could be the work around of your problem.
Few days back I had the same problem, as I wanted to implement API versioning. Here is the solution I implemented, please have a look.
Architecture Diagram
Let me explain this diagram
Here in the diagram is the initial configuration for the server as we do. all the api request come here will pass to the "index.js" file inside the release directory.
index.js (in release directory)
const express = require('express');
const fid = require('./core/file.helper');
const router = express.Router();
fid.getFiles(__dirname,'./release').then(releases => {
releases.forEach(release => {
// release = release.replace(/.js/g,'');
router.use(`/${release}`,require(`./release/${release}/index`))
})
})
module.exports = router
code snippet for helper.js
//requiring path and fs modules
const path = require('path');
const fs = require('fs');
module.exports = {
getFiles: (presentDirectory, directoryName) => {
return new Promise((resolve, reject) => {
//joining path of directory
const directoryPath = path.join(presentDirectory, directoryName);
//passsing directoryPath and callback function
fs.readdir(directoryPath, function (err, files) {
// console.log(files);
//handling error
if (err) {
console.log('Unable to scan directory: ' + err);
reject(err)
}
//listing all files using forEach
// files.forEach(function (file) {
// // Do whatever you want to do with the file
// console.log(file);
// });
resolve(files)
});
})
}
}
Now, from this index file all the index.js inside each version folder is mapped
Here is the code bellow for "index.js" inside v1 or v2 ...
const express = require('express');
const mongoose = require('mongoose');
const fid = require('../../core/file.helper');
const dbconf = require('./config/datastore');
const router = express.Router();
// const connection_string = `mongodb+srv://${dbconf.atlas.username}:${dbconf.atlas.password}#${dbconf.atlas.host}/${dbconf.atlas.database}`;
const connection_string = `mongodb://${dbconf.default.username}:${dbconf.default.password}#${dbconf.default.host}:${dbconf.default.port}/${dbconf.default.database}`;
mongoose.connect(connection_string,{
useCreateIndex: true,
useNewUrlParser:true
}).then(status => {
console.log(`Database connected to mongodb://${dbconf.atlas.username}#${dbconf.atlas.host}/${dbconf.atlas.database}`);
fid.getFiles(__dirname,'./endpoints').then(files => {
files.forEach(file => {
file = file.replace(/.js/g,'');
router.use(`/${file}`,require(`./endpoints/${file}`))
});
})
}).catch(err => {
console.log(`Error connecting database ${err}`);
})
module.exports = router
In each of this index.js inside version folder is actually mapped to each endpoints inside endpoints folder.
code for one of the endpoints is given bellow
const express = require('express');
const router = express.Router();
const userCtrl = require('../controllers/users');
router.post('/signup', userCtrl.signup);
router.post('/login', userCtrl.login);
module.exports = router;
Here in this file actually we are connecting the endpoints to its controllers.
var config = {'targets':
[
'https://abc.api.xxx',
'https://xyz.abc',
'https://stackoverflow.net'
]};
relay(req, resp, config);
function relay(req, resp, config) {
doRelay(req, resp, config['targets'], relayOne);
}
function doRelay(req, resp, servers, relayOne) {
var finalresponses = [];
if (servers.length > 0) {
var loop = function(servers, index, relayOne, done) {
relayOne(req, servers[index], function(response) {
finalresponses.push[response];
if (++index < servers.length) {
setTimeout(function(){
loop(servers, index, relayOne, done);
}, 0);
} else {
done(resp, finalresponses);
}
});
};
loop(servers, 0, relayOne, done);
} else {
done(resp, finalresponses);
}
}
function relayOne(req, targetserver, relaydone) {
//call the targetserver and return the response data
/*return relaydone(response data);*/
}
function done(resp, finalresponses){
console.log('ended');
resp.writeHead(200, 'OK', {
'Content-Type' : 'text/plain'
});
resp.end(finalresponses);
return;
}
It sounds like you are trying to design a reverse proxy. If you are struggling to get custom code to work, there is a free npm library which is very robust.
I would recommend node-http-proxy
I have posted link below, which will lead you directly to the "modify response", since you mentioned modification of the API format in your question. Be sure to read the entire page though.
https://github.com/http-party/node-http-proxy#modify-a-response-from-a-proxied-server
Note: this library is also very good because it can support SSL, and proxies to both localhost (servers on the same machine) and servers on other machines (remote).
Promise.all() from MDN
It rejects with the reason of the first promise that rejects.
To overcome the problem, you'll need to catch() each request you've made.
e.g.
Promise.all([
request('<url 1>').catch(err => /* .. error handling */),
request('<url 2>').catch(err => /* .. error handling */),
request('<url 3>').catch(err => /* .. error handling */)
])
.then(([result1, result2, result3]) => {
if(result1.err) { }
if(result2.err) { }
if(result3.err) { }
})

Not able to post data to mongo db using express API and node JS(Angular4)

I have successfully completed fetching data from MongoDB using express API and node js and binding it to angular ng2-smart-table.
On click of add button of the table, I am calling express API but my post method is not called in api.js, I tried printing console.log inside post but it is not called.
For better understanding hereby I post the code:
Component.ts
onCreateConfirm(event,_dataService): void {
this._dataService.postMutualFunds(event.newData);
event.confirm.resolve(event.newData);
}
data.service.ts
postMutualFunds(parameterValue:any ){
return this._http.post('/add_mutual_funds',
{params:parameterValue}).map(result => this.result = result.json().data);
}
api.js
var express = require('express');
var router = express.Router();
var MongoClient = require('mongodb').MongoClient;
var ObjectID = require('mongodb').ObjectID;
var bodyParser = require("body-parser");
const connection = (closure) => {
return
MongoClient.connect('mongodb://xxx.com:63898/xx', (err,db) => {
if(err){
return console.log(err);
}
closure(db);
});
}
//Get call
router.get('/mutual_funds',(req,res) =>{
console.log("test get"); //This is executed
connection((db) => {
var projection = {_id:0};
db.collection('mutual_funds').find().project(projection).
toArray().then((mutual_funds) => {
response.data = mutual_funds;
res.json(response);
})
})
})
//Post call
router.post('/mutual_funds',(req,res) => {
console.log(req.body.params);
console.log("test post");
db.collection('mutual_funds').insertOne(req.body.params)
})
module.exports = router;
Now i am able to call the api js but not able to insert the data to mongodb please find the screenshot below of the api.js exeution output
//Why am I not able to post the data?(Updated my post code above) encountering error 500
You didn't subscribe to the service
onCreateConfirm(event,_dataService): void {
this._dataService.postMutualFunds(event.newData).subscribe(
(data)=>{
console.log(data)
},
(err)=>{
console.log(err)
}
)
event.confirm.resolve(event.newData);
}
The service method returns an Observable of configuration
data, the component subscribes to the method's return value. The
subscription callback copies the data fields into the component's
config object, which is data-bound in the component template for
display.
why are you not subscribing to the service function call
this._dataService.postMutualFunds(event.newData).subscribe((data)=>{ console.log(data)},
(err)=>console.log(err),
()=>console.log('Observable complete')
);

Sanitising data passed from the client to the api backend on the req.body and the headers

I have a form with the onSubmit function collecting input data from the state and sending it to the backend.
I then collect the input from the req.body and the ip from the headers on the backend.
The ip is persisted to redis and the form input is being passed to another daemon process through pm2 and finally mailed with mandrill, rather than being persisted to any db.
Scenario I
The clients ip is collected and persisted to redis:
module.exports = (req, res, next) => {
const client = redis.createClient()
client.select(2, (err) => {
console.log('redisWriteIP selected 2snd redis db')
if (err) {
next(new DbErr(err))
} else {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
client.set(ip, true, 'EX', 120, (err, rep) => {
if (err) {
next(new DbErr(err))
} else {
return next()
}
})
}
})
}
Question 1:
Do I need to sanitise the ip In this scenario? Can a user temper with the request headers and send anything else other than his ip address or numbers?
Scenario 2
Input fields filled in by the user and sent to the api on the req.body
The api server - using body parser:
const api = express()
// Body parser for the post requests
const bodyParser = require('body-parser')
api.use(bodyParser.urlencoded({ extended: false }))
api.use(bodyParser.json())
api.set('trust proxy', 'loopback')
const routes = require('./routes')
api.use('/api', routes)
Validating fields middlware:
module.exports = (req, res, next) => {
let payload = req.body
const err = {}
let isFormValid = true
// Validating a form.
if (payload.question) {
if (typeof payload.email !== 'string' || !validator.isEmail(payload.email)) {
isFormValid = false
err.email = 'Please provide a correct email address.'
}
if (typeof payload.name !== 'string' || payload.name.trim().length === 0) {
isFormValid = false
err.name = 'Please provide your name.'
}
// Validating another form.
} else if (payload.booking) {
if (typeof payload.email !== 'string' || !validator.isEmail(payload.email)) {
isFormValid = false
err.email = 'Please provide a correct email address.'
}
if (typeof payload.dates !== 'string' || payload.dates.trim().length === 0) {
isFormValid = false
err.msg = 'Something went wrong'
}
} else {
// No form type in the payload.
isFormValid = false
err.msg = 'Something went wrong'
}
if (!isFormValid) {
next(new FormFieldErr(JSON.stringify(err)))
} else {
return next()
}
}
Example of how the data is being sent to another process:
...
// Send the payload to the mandrill pid.
pm2.sendDataToProcessId(pid, payload, (err, res) => {
if (err) {
next(new MailerErr(err))
} else {
next()
}
})
Question 2:
Do I need to sanitise the req.body before doing any kind of operations with it's data even when it's not persisted to any db.
For example before I check if (payload.question) {...} in the validation middleware or before I send the payload with the pm2.sendDataToProcessId method?
I worry that a function can be passed from the client and executed on the backend even if no data is persisted.
Question 3
If the above are indeed a security risk, can I simply have a middlware running in the beginning of the chain on the req.body and any other parts of the request I might use, escaping or deleting all dangerous characters and effectively solving the problem?
EDIT
I've seen libs that validate the fields, but I don't need an extensive validation solution, but rather a simple sanitation solution. That's why I thought of making or installing a middleware which will first save the req.body or any other data without dangerous characters and then the other middlwares can deal with the data safely.
Something like:
sanitise middleware:
module.exports = (req, res, next) => {
req.body.replace(/[|&;$%#"<>()+,]/g, "")
return next()
}
some api route:
api.route('/', sanitise, someMiddleware, (req, res, next) => {
// Now we can safely handle req.body in the middlwares.
})
Answer 1: yes user can change any of the headers. But not the req.connection.remoteAddress. So you may want to give priority to that one.
Answer 2: Yes, escaping strings and validating combination of data is generally a good practice. You should do it at the API level.
Answer 3: I like Joi as a nice API validation package. There are other packages which might suit your needs better.

Node JS - Express, Socket.io complete session destruction when user logs out

The session variable is created when user logs in to the system. Then I load session variable in my authorization code. I want to destroy that variable when user logs out. Here is some simplified code
store = new express.session.MemoryStore();
var parseCookie = express.cookieParser('secret');
app.use(parseCookie);
app.use(express.session({store: store, key:'sid'}));
app.post('/login', function(req,res){
var post = req.body;
if (post.user == 'hugo' && post.password == '123')
{
req.session.user_name = post.user;
res.redirect('/mypage');
}
else res.send('wrong user or pass');
});
io.set('authorization', function (data, callback) {
parseCookie(data, {}, function(prserr) {
var sess = (data.secureCookies && data.secureCookies['sid']);
store.load(sess, function(err, session){
if (err || !session ) { callback('not logged in', false); }
else {
data.session = session; // save session
callback(null, true);
}
});
});
});
and finally
app.get('/logout', function (req, res) {
req.session.destroy();
/* Here I want to destroy session variable that is declared in
authorization code (see above: data.session = session )*/
res.redirect('/');
});
while destroying session via req.session.destroy() the variable
socket.handshake.session.user_name still exists. I want to destroy it too. But I have no idea how to access desired variable in above mentioned place (in logout code).
Have you considered using Passport? It might be quicker (and more efficient) than trying to roll your own authentication solution.

Resources