How to access express route inside basicAuth - node.js

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){
...
});

Related

Sails.js Waterlock /auth/register causes error 500

In trying to make Waterlock not to create new user on login.
When I set createOnNotFound to false and try to use http://localhost:1337/auth/register?email=a#s.d&password=12345678 to register a new user. I've got 500 error:
error: Sending 500 ("Server Error") response: TypeError: undefined is not a function
at Object.module.exports (D:\temp\sails-waterlock\node_modules\waterlock\lib\controllers\actions\register.js:25:44)
at bound (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\lodash\dist\lodash.js:729:21)
at routeTargetFnWrapper (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\lib\router\bind.js:179:5)
at callbacks (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:164:37)
at param (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:138:11)
at param (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:135:11)
at pass (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:145:5)
at nextRoute (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:100:7)
at callbacks (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:167:11)
at C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\lib\router\bind.js:187:7
at alwaysAllow (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\lib\hooks\policies\index.js:207:11)
at routeTargetFnWrapper (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\lib\router\bind.js:179:5)
at callbacks (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:164:37)
at param (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:138:11)
at param (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:135:11)
at pass (C:\Users\sandres\AppData\Roaming\npm\node_modules\sails\node_modules\express\lib\router\index.js:145:5) [TypeError: undefined is not a function]
The register module, where the error happens is a part of waterlock library.
Here goes the code of the register module as it is now:
'use strict';
/**
* login action
*
* tries to find if we have an auth method to handle this type of login
* request.
*
* GET /auth/login
*/
module.exports = function(req, res){
var params = waterlock._utils.allParams(req);
// If there is only 1 chosen auth method just assume it
if(waterlock._utils.countTopLevel(waterlock.methods) === 1){
params.type = waterlock._utils.accessObjectLikeArray(0, waterlock.methods).authType;
}
if(typeof params.type === 'undefined'){
return res.badRequest('You must specify a type parameter.');
}
if(waterlock.methods.hasOwnProperty(params.type)){
// call the login function of the correct auth type
waterlock.methods[params.type].actions.register(req, res);
}else{
return res.badRequest('unknown/invalid authentication type');
}
};
Sails v 0.11, Waterlock v 0.1.0
How can I register user now?
UPDATE:
This happens due to register action is not yet implemented in waterlock-local-auth
module I use for authentication. See this PR for details.
So the updated question is: how to work this around until the implementation will be done?
I've solved this with custom registration action in /controllers/AuthController.js:
module.exports = require('waterlock').waterlocked({
register: function(req, res) {
var params = req.params.all(),
def = waterlock.Auth.definition,
criteria = { },
scopeKey = def.email !== undefined ? 'email' : 'username';
var attr = {
password: params.password
}
attr[scopeKey] = params[scopeKey];
criteria[scopeKey] = attr[scopeKey];
waterlock.engine.findAuth(criteria, function(err, user) {
if (user)
return res.badRequest("User already exists");
else
waterlock.engine.findOrCreateAuth(criteria, attr, function(err, user) {
if (err)
return res.badRequest(err);
delete user.password;
return res.ok(user);
});
});
}
});
This is exactely what I need.
Meanwhile Wayne-o, one of Waterlock contributors, said that he will finish his implementation of registration in waterlock-local-auth.

Pass variable using next()

In node.js, i have function that before any action is checking if everything is ok at the start of request (validating JSON etc). Almost everything is working ok, but I have one problem. I don't now how to pass object reference using next();
To call checking function I'm using.
app.all('/:action', frontendProcessor.checkSession());
At the middle of this code, I'm using next()
frontendProcessor.checkSession = function(){
return function(req, res, next) {
var inputJson = req.body.JSONVAR || false,
action = req.params.action;
// validation
frontendProcessor.validateJSON(inputJson, afterValidation);
function afterValidation(err, inputData){
if(err) {
global.consoleLog(err);
res.send(fail);
}else{
if(action == 'login' ){
console.log(inputData);
next(inputData); //<< here, how to pass inputData thru next
}else{
mUsers.checkSessionId(email, sessionId, process);
};
};
};
function process(response) {
if(!response){
global.consoleLog("Security Error: Bad session Id.");
var response = JSON.stringify(badSession);
res.send(response);
}else{
global.consoleLog('Security: session ok! next');
next(inputData);
};
};
};
};
next() shouldn't ever pass data because it's just designed to call the next request handler, not a specific request handler. Nirk's comment is correct, you should attach your data to the req object and read it from there when needed.

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.

NodeJS express basicAuth - how to pass username to the route function?

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();
});

How to keep persistent ftp connection in nodejs

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

Resources