File: //var/dev/farhangmoaser/web/helpers/auth.js
/**
* Authentication Helper
* Version: 0.1
* Author: Babak Vandad
*
* Contains common functionalities for authentication.
* It also delegates with session data stored in Redis.
*/
"use strict";
var path = require('path');
var md5 = require('md5');
var ip = require('ip');
var db = require(path.join(BASEDIR, 'connectors/mysql'));
var consts = require(path.join(BASEDIR, 'consts.js'));
var redis = require(path.join(BASEDIR, 'connectors/redis.js'));
var UserModel = require(path.join(BASEDIR, 'models/user.js'));
var UserAccessModel = require(path.join(BASEDIR, 'models/userAccess.js'));
var redclient;
redis.connect(function(client) {
redclient = client;
});
var fail = function(response, conn, error, status){
return function(){
if(conn && conn.release)
conn.release();
response.status(status ? status : error.status).json({error: error.code, message: error.message});
};
};
class AuthHelper {
/**
* Serializes user data (id and type) and stores them in Redis.
* @param {Object} user User object loaded from database
* @param {Function} done Callback
*/
static serialize(user, done) {
var d = new Date();
var token = (user.field('userType')==consts.v.USER_TYPE_PEOPLE ? 'usr-' : 'app-') + md5(d.getTime()+':'+user.field('key'));
user.token = token;
redclient.set(token, JSON.stringify({type: user.field('userType'), id: user.field('id')}), function(){
done(null, token);
});
}
/**
* Reads user/app id from Redis based on the given token,
* then loads the user object from db.
* @param {stringify} token Key for Redis
* @param {Function} done Callback
*/
static deserialize(token, done) {
redclient.get(token, function(err, reply) {
if (err) return done(err, null);
try {
reply = JSON.parse(reply);
}catch(e) {
reply = null;
}
if(!reply || !reply.hasOwnProperty('id'))
return done('invalid stored string.');
db.getConnection(function(err, conn){
if(err) return done(err);
var user = new UserModel(conn, {id: reply.id});
user.load().then(
function(){
user.token = token;
conn.release();
done(null, user);
}, function(errmsg){
conn.release();
done(errmsg);
});
});
});
}
/**
* checks if the given IP address is in the given range or not.
* @param {string} reqip Request IP
* @param {string} range IP range separated by ; and -
* @return {boolean} true if is in the range, otherwise false.
*/
static ipcheck(reqip, range){
if(!range) return true;
var ipRange = range.split(';');
for(let i in ipRange) {
var res = false;
var el = ipRange[i].split('-');
if (el.length>1){
if ((ip.toLong(el[0]) <= ip.toLong(reqip)) && (ip.toLong(el[1]) >= ip.toLong(reqip)))
return true;
}else
if(ip.isEqual(el[0], reqip))
return true;
}
return false;
}
/**
* Express middleware to validate token and corresponding user.
* Authorizes api key based on hit count, ip range, ...
* Adds a Passport styled user object to the request.
* @param {request} req Express request
* @param {response} res Express response
* @param {middleware} next next middleware in the list
*/
static authenticate(req, res, next){
// if(req.isAuthenticated()) return next(); //TODO: this is true for app model. check this for users too.
let token = req.headers['x-token'] || req.query.token;
// req.user = null;
if(!token) return next();
AuthHelper.deserialize(token, function(err, user){
if(err)
return fail(res, null, consts.e.ERR_SERIALIZATION_ERROR)();
if(!AuthHelper.ipcheck(req.ip, user.field('iprange')))
return fail(res, null, consts.e.ERR_AUTH_IP)();
if(user.field('hitLimit')) {
let diff = Math.floor(((new Date()).getTime() - user.hitData.lastReset)/1000);
if(!user.field('hitPeriod') || diff<user.field('hitPeriod')){
if(user.hitData.hitCount<user.field('hitLimit'))
return user.touch().then(
function(){
user.token = token;
req.user = user;
next();
}, fail(res, null, consts.e.ERR_DB_ERROR));
else
return fail(res, null, consts.e.ERR_AUTH_QUOTA)();
} else
return user.resetHitCounter().then(
function(){
user.token = token;
req.user = user;
next();
}, fail(res, null, consts.e.ERR_DB_ERROR));
}
user.token = token;
req.user = user;
next();
});
}
static access(action, desc){
UserAccessModel.register(action, desc);
return function(req, res, next){
var role;
if(!req.isAuthenticated()) role = consts.v.ROLE_UNAUTHENTICATED;
else role = req.user.field('role');
if(role == consts.v.ROLE_ADMIN) return next();
UserAccessModel.check(action, role).then(
function(permission){
if(permission) return next();
fail(res, null, consts.e.ERR_ACCESS_PERMISSION)();
});
};
}
}
module.exports = AuthHelper;