I've got a working node app where I need to connect to different DBs based on what user is connecting to the app via basicAuth.
Here's a sample:
// Authenticating function
var sgAuth = express.basicAuth(function(user, pass, callback){
if(config.credentials.clients[user] === undefined) {
callback(null, false);
} else {
callback(null, config.credentials.clients[user].password == pass);
}
});
// This function needs to know what user has authenticated
function putEvents(req, res) {
//How do I know what user authenticated in this request?
var authUser = ???;
var table = getUserTable(authUser);
...
}
app.post('/put', sgAuth, putEvents);
Storing username in sgAuth to some var surely won't work, because there can be many incoming connections from different users, so you can't guarantee that its the same user, right? Can this info be retrieved from the request header somehow?
The basicAuth() middleware will set req.user and req.remoteUser once authorized.
Though, note that the 2nd argument to the callback is expected to be the user, not simply an authorized boolean. But, it can be any truthy value you desire, including the user name.
callback(null, config.credentials.clients[user].password == pass ? user : null);
After that, you should be able to retrieve it with:
var authUser = req.user;
Note that: basicAuth is deprecated
Here the code:
app.use(express.basicAuth(function(user, pass, callback){
if(config.credentials.clients[user] === undefined) {
callback('user not found!!!');
} else {
if(config.credentials.clients[user].password === pass) {
callback(null, config.credentials.clients[user]);
} else {
callback('wrong pass!!!');
}
}
});
app.post('/put', function putEvents(req, res) {
console.log(req.user.name)
res.end();
});
Related
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.
My NodeJS server, using express, has a bunch of entries to specify various routes:
app.post('list_streams.json', auth, stream_handler.list_streams);
app.post('add_stream.json', auth, stream_handler.add_stream);
app.post('delete_stream.json', auth, stream_handler.delete_stream);
etc...
The auth middleware is written like this:
var auth = express.basicAuth(function(user, pass, callback) {
user_handler.authenticate_user(user, pass, callback);
});
Inside the user_handler.authenticate_user() function, an access to the database is performed to validate the user. I'd like to add some statistics and keep track of every access that a particular user performs. I'd like to do this inside the authenticate_user() function, as this is where a database is accessed for the user record and I can use the same access to update the statistics info in the user record, but I need to somehow pass an extra argument to the authenticate_user() specifying the type of access that was performed; either the route itself or some token that identifies the route being accessed. And I can't figure out how to do this. The 'req' is not available inside the authenticate_user() function.
Thank You,
Gary
I'm not sure what you need can be easily done from your authenticate_user function since it's only called once per session at the first access from any user.
The best approach to log ALL access per user would be to create a new middleware function as described at the end of this post.
But assuming you only wish to log user authentications, one way to solve your problem would be to replace express.basicAuth with your own version that binds the callback function to the express req object, like this:
var util=require('util'),
express=require('express'),
app=express(),
auth=basicAuth(function(username,password,next){
console.log('auth has access to req as "this": %s',util.inspect(this));
});
app.get('/',auth,function(req,res){
console.log('in request for "/", req is: %s',util.inspect(req));
res.send('SUCCESS');
});
app.listen(4000,function(){
console.log('running');
});
// Replacement for connect.basicAuth (as used by express)
// lifted from https://github.com/expressjs/basic-auth-connect
function unauthorized(res, realm) { // required by basicAuth
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
res.end('Unauthorized');
}
function error(code, msg){ // required by basicAuth
var err = new Error(msg || http.STATUS_CODES[code]);
err.status = code;
return err;
}
// replacement basic auth which binds the callback to the "req" object
function basicAuth(callback, realm) {
var username, password;
// user / pass strings
if ('string' == typeof callback) {
username = callback;
password = realm;
if ('string' != typeof password) throw new Error('password argument required');
realm = arguments[2];
callback = function(user, pass){
return user == username && pass == password;
}
}
realm = realm || 'Authorization Required';
return function(req, res, next) {
var authorization = req.headers.authorization;
// 20140601 RR - !!NOTE!! bind callback to req
callback=callback.bind(req);
if (req.user) return next();
if (!authorization) return unauthorized(res, realm);
var parts = authorization.split(' ');
if (parts.length !== 2) return next(error(400));
var scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString()
, index = credentials.indexOf(':');
if ('Basic' != scheme || index < 0) return next(error(400));
var user = credentials.slice(0, index)
, pass = credentials.slice(index + 1);
// async
if (callback.length >= 3) {
callback(user, pass, function(err, user){
if (err || !user) return unauthorized(res, realm);
req.user = req.remoteUser = user;
next();
});
// sync
} else {
if (callback(user, pass)) {
req.user = req.remoteUser = user;
next();
} else {
unauthorized(res, realm);
}
}
}
}
If you look at the line marked with "!!NOTE!!" above, you'll see that the callback you pass to the new basicAuth function has been bound to express' req request object, which makes its idea of this a reference to the request.
Now all you need to do is reference this.url to get the original request URL and log it.
As mentioned above, one thing to note is that the callback to auth is only called to authenticate the user once.
Subsequent requests already have the req.user HTTP header variable set, so the request is allowed to pass without calling the authentication callback.
This is why the best way to log all interactions for a particular user would be to add your own middleware after the call to auth, such as:
function logUser(req,res,next){
// since this middleware is called AFTER auth, the user is already authorized.
log.info('user "'+req.user+'" called url:'+req.url);
next(); // pass control to the next stage in fulfilling the request
}
app.get('/',auth,logUser,function(req,res){
...
});
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.
Can you please help me make a connection persistent script. I used jsftp node module to connect to ftp server. What I need to do is to check if the user is already authenticated every time he send a request. Thanks in advance! here's my code:
var Ftp = require('jsftp');
var dumpLog = function (event){
console.log('Code: '+ event.code);
console.log('Message: '+ event.text);
}
var FtpController = {
index : function (req , res) {
res.view('ftp/login');
},
auth : function (req , res){
// Initialize some common variables
var user = req.param('user');
var pass = req.param('pass');
var ftp = new Ftp({
host: req.param('host'),
port: req.param('port') // Defaults to 21
});
ftp.auth( user, pass, function (err , auth_res){
if (err) throw err;
dumpLog(auth_res);
});
res.view('ftp/folder');
},
serve_folder : function(req,res){
res.view('ftp/folder');
},
};
module.exports = FtpController;
Best way to do stuff like this is probably a policy, since you'll want to be able to apply the check to various controllers as you build out your app. Here's what your policy might look like:
// policies/ftpAuthenticated.js
module.exports = function loginToFTP (req, res, next) {
if (req.session.ftpAuthenticated) {
// Onward!
next();
}
else {
// authenticate here (we assume it works in this example)
var success = true;
if (success) {
// Track that the user is connected via ftp for next time
req.session.ftpAuthenticated = true;
// save the connection object
req.session.ftp = theFTPConnectionThing;
next();
}
// if an error occurs, use the default error handler
else {
next( new Error('Sorry, an error occurred authenticating with FTP') );
}
}
}
Guys I am trying to get myself authenticated and for this I am using node.js and mongo DB.But the thing is that after registarion the user is not able to authenticate himself.Here is my snippet
app.post('/login',function(req,res){
ContactProvider.findAll(function(error, posts) {
var aut = req.body;
if (aut.user == posts.user && aut.pass == posts.pass) {
req.session.name = {name:aut.user};
res.redirect('/home');
} else {
res.send('Bad user/pass');
}
});
});
Below is my snippet for registering the user
app.post('/register',function(req, res) {
var post=req.body;
if(post.pass!=post.cpass) {
res.send("Error:Password doesnt match");
} else {
ContactProvider.save({
user: req.param('user'),
pass: req.param('pass'),
cpass: req.param('cpass'),
email: req.param('email')
}, function(error, docs) {
res.redirect('/');
});
}
});
The ContactProvider is the one below where post provider is a different file where all the mongoose things happen
var ContactProvider = require('./PostProvider').ContactProvider;
var ContactProvider= new ContactProvider();
This is the finone query in the postprovider file
ContactProvider.prototype.findone = function(name,pass, callback) {
Post.findOne({name:name},{pass:pass}, function (err, post) {
callback(null, post);
});
};
Something's seriously wrong with your code ( why you use name posts for an array of ContactProvider? ). You have to search for ContactProvider based on username and password. Something like this:
app.post('/login',function(req,res){
var aut = req.body;
ContactProvider.findOne(
{
user: aut.user,
pass: aut.pass
},
function(err, usr) {
if (error || !usr) {
res.send('Bad user/pass');
} else {
// we have a user, authenticate!
req.session.name = {name:aut.user};
res.redirect('/home');
}
}
);
});
SIDE NOTE: This is a very simple way of authenticating users, but it is not secure at all. You should read more about authentication and security in the internet. Very useful knowledge indeed.
EDIT: There's also an issue with your registration. Your data is stored in post variable, so use it on ContactProvider as well:
// some other code
ContactProvider.save({
user: post.user,
pass: post.pass,
cpass: post.cpass, // no need to store the same thing twice
email: post.email