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;