cansecurity
Advanced tools
Comparing version 3.0.0 to 3.0.1
/*jslint node:true, nomen:false, unused:vars */ | ||
var errors = require('./errors'), rparams = require('./param'), sender = require('./sender'), | ||
const errors = require('./errors'), rparams = require('./param'), sender = require('./sender'), | ||
constants = require('./constants').get(), | ||
csauth = constants.header.AUTH, | ||
fields = {}, params = {}, | ||
checkLoggedIn, checkSelf, checkUserRoles, checkParam, checkField, fns, i, j; | ||
fields = {}, params = {}; | ||
checkLoggedIn = function (req, res, next) { | ||
// If our user is authenticated | ||
// then everything is fine :) | ||
var logged = true; | ||
rparams(req); | ||
if (!req[csauth]) { | ||
sender(res,401,errors.unauthenticated()); | ||
logged = false; | ||
} | ||
return (logged); | ||
}; | ||
checkSelf = function (req, res, next) { | ||
var isSelf = false, userid = (req.params[params.id] || req.body[params.id] || "").toString(); | ||
rparams(req); | ||
if (req[csauth][fields.id].toString() === userid) { | ||
isSelf = true; | ||
next(); | ||
} | ||
return (isSelf); | ||
}; | ||
checkUserRoles = function (req, res, next, roles) { | ||
var userRoles = req[csauth][fields.roles], | ||
isRole = false, | ||
i, targetRoles = {}; | ||
rparams(req); | ||
roles = roles || []; | ||
for (i = 0; i < roles.length; i++) { | ||
targetRoles[roles[i]] = true; | ||
} | ||
if (userRoles && userRoles.length && userRoles.length > 0) { | ||
for (i = 0; i < userRoles.length; i++) { | ||
if (targetRoles[userRoles[i]]) { | ||
const checkLoggedIn = (req, res, next) => { | ||
// If our user is authenticated | ||
// then everything is fine :) | ||
let logged = true; | ||
rparams(req); | ||
if (!req[csauth]) { | ||
sender(res,401,errors.unauthenticated()); | ||
logged = false; | ||
} | ||
return (logged); | ||
}, | ||
checkSelf = function (req, res, next) { | ||
const userid = (req.params[params.id] || req.body[params.id] || "").toString(); | ||
let isSelf = false; | ||
rparams(req); | ||
if (req[csauth][fields.id].toString() === userid) { | ||
isSelf = true; | ||
next(); | ||
} | ||
return (isSelf); | ||
}, | ||
checkUserRoles = (req, res, next, roles) => { | ||
const userRoles = req[csauth][fields.roles], targetRoles = {}; | ||
rparams(req); | ||
roles = roles || []; | ||
for (let i of roles) { | ||
targetRoles[i] = true; | ||
} | ||
let isRole = false; | ||
for (let i of userRoles || []) { | ||
if (targetRoles[i]) { | ||
isRole = true; | ||
break; | ||
} | ||
@@ -46,189 +45,156 @@ } | ||
} | ||
} | ||
return (isRole); | ||
}; | ||
checkParam = function (req, res, next, param) { | ||
var isParam = false, | ||
id = req[csauth][fields.id], | ||
i; | ||
rparams(req); | ||
param = [].concat(param); | ||
// check the ID of the user against each field in each result for which it is allowed | ||
for (i = 0; i < param.length; i++) { | ||
if (id === req.param(param[i])) { | ||
isParam = true; | ||
break; | ||
return (isRole); | ||
}, | ||
checkParam = (req, res, next, param) => { | ||
const id = req[csauth][fields.id]; | ||
let isParam = false; | ||
rparams(req); | ||
param = [].concat(param); | ||
// check the ID of the user against each field in each result for which it is allowed | ||
for (let i of param) { | ||
if (id === req.param(i)) { | ||
isParam = true; | ||
break; | ||
} | ||
} | ||
} | ||
if (isParam) { | ||
next(); | ||
} | ||
return (isParam); | ||
}; | ||
checkField = function (req, res, next, field, getObject) { | ||
var id = req[csauth][fields.id], | ||
i, j, valid, list = [].concat(getObject(req, res) || {}); | ||
rparams(req); | ||
// check the ID of the user against each field in each result for which it is allowed, for each object | ||
// *all* must pass to be allowed | ||
for (j = 0; j < list.length; j++) { | ||
valid = false; | ||
for (i = 0; i < field.length; i++) { | ||
if (id === list[j][field[i]]) { | ||
valid = true; | ||
if (isParam) { | ||
next(); | ||
} | ||
return (isParam); | ||
}, | ||
checkField = (req, res, next, field, getObject) => { | ||
const id = req[csauth][fields.id], list = [].concat(getObject(req, res) || {}); | ||
let valid; | ||
rparams(req); | ||
// check the ID of the user against each field in each result for which it is allowed, for each object | ||
// *all* must pass to be allowed | ||
for (let item of list) { | ||
valid = false; | ||
for (let name of field) { | ||
if (id === item[name]) { | ||
valid = true; | ||
break; | ||
} | ||
} | ||
if (!valid) { | ||
break; | ||
} | ||
} | ||
if (!valid) { | ||
break; | ||
if (valid) { | ||
next(); | ||
} | ||
} | ||
if (valid) { | ||
next(); | ||
} | ||
return (valid); | ||
}; | ||
return (valid); | ||
}, | ||
fns = { | ||
direct: { | ||
// valid if the user is logged in | ||
restrictToLoggedIn: function (req, res, next) { | ||
// If our user is authenticated | ||
// then everything is fine :) | ||
if (checkLoggedIn(req, res, next)) { | ||
next(); | ||
fns = { | ||
direct: { | ||
// valid if the user is logged in | ||
restrictToLoggedIn: (req, res, next) => { | ||
// If our user is authenticated | ||
// then everything is fine :) | ||
checkLoggedIn(req, res, next) && next(); | ||
}, | ||
// valid if user is logged in *and* the ID if the logged-in user is equal to the ID param | ||
restrictToSelf: (req, res, next) => { | ||
// If our authenticated user is the user we are viewing | ||
// then everything is fine :) | ||
checkLoggedIn(req, res, next) && !checkSelf(req, res, next) && sender(res,403,errors.unauthorized()); | ||
} | ||
}, | ||
// valid if user is logged in *and* the ID if the logged-in user is equal to the ID param | ||
restrictToSelf: function (req, res, next) { | ||
// If our authenticated user is the user we are viewing | ||
// then everything is fine :) | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkSelf(req, res, next)) { | ||
sender(res,403,errors.unauthorized()); | ||
indirect: { | ||
// valid if user is logged in *and* the logged-in user has at least one of the given roles | ||
restrictToRoles: (roles) => { | ||
roles = roles ? [].concat(roles) : []; | ||
return (req, res, next) => { | ||
checkLoggedIn(req, res, next) && !checkUserRoles(req, res, next, roles) && sender(res,403,errors.unauthorized()); | ||
}; | ||
}, | ||
// valid if user is logged in *and* the logged-in user is equal to the ID param *or* user has at least one of the given roles | ||
restrictToSelfOrRoles: (roles) => { | ||
roles = roles ? [].concat(roles) : []; | ||
return (req, res, next) => { | ||
checkLoggedIn(req, res, next) && | ||
!checkSelf(req, res, next) && | ||
!checkUserRoles(req, res, next, roles) && | ||
sender(res,403,errors.unauthorized()); | ||
}; | ||
}, | ||
// valid if user is logged in *and* some given field is the same as a given param | ||
restrictToParam: (param) => | ||
(req, res, next) => { | ||
checkLoggedIn(req, res, next) && | ||
!checkParam(req, res, next, param) && | ||
sender(res,403,errors.unauthorized()); | ||
} | ||
, | ||
// valid if user is logged in *and* some given field is the same as a given param *or* user has one of the given roles | ||
restrictToParamOrRoles: (param, roles) => { | ||
roles = roles ? [].concat(roles) : []; | ||
param = param ? [].concat(param) : []; | ||
return (req, res, next) => { | ||
// valid if the user name is the same as the param name, or the logged in user is an admin | ||
checkLoggedIn(req, res, next) && | ||
!checkUserRoles(req, res, next, roles) && | ||
!checkParam(req, res, next, param) && | ||
sender(res,403,errors.unauthorized()); | ||
}; | ||
}, | ||
// valid if user is logged in *and* the ID of the logged-in user is equivalent to some field on an arbitrary object | ||
restrictToField: (field, getField) => { | ||
field = field ? [].concat(field) : []; | ||
return (req, res, next) => { | ||
// valid if the user name is the same as the param name, or the logged in user is an admin | ||
checkLoggedIn(req, res, next) && | ||
!checkField(req, res, next, field, getField) && | ||
sender(res,403,errors.unauthorized()); | ||
}; | ||
}, | ||
// valid if user is logged in *and* the ID of the logged-in user is equivalent to some field on an arbitrary object *or* user has one of the given roles | ||
restrictToFieldOrRoles: (field, roles, getField) => { | ||
roles = roles ? [].concat(roles) : []; | ||
field = field ? [].concat(field) : []; | ||
return (req, res, next) => { | ||
// valid if the user name is the same as the param name, or the logged in user is an admin | ||
checkLoggedIn(req, res, next) && | ||
!checkUserRoles(req, res, next, roles) && | ||
!checkField(req, res, next, field, getField) && | ||
sender(res,403,errors.unauthorized()); | ||
}; | ||
} | ||
} | ||
}, | ||
indirect: { | ||
// valid if user is logged in *and* the logged-in user has at least one of the given roles | ||
restrictToRoles: function (roles) { | ||
roles = roles ? [].concat(roles) : []; | ||
return function (req, res, next) { | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkUserRoles(req, res, next, roles)) { | ||
sender(res,403,errors.unauthorized()); | ||
} | ||
} | ||
}; | ||
}, | ||
// valid if user is logged in *and* the logged-in user is equal to the ID param *or* user has at least one of the given roles | ||
restrictToSelfOrRoles: function (roles) { | ||
roles = roles ? [].concat(roles) : []; | ||
return function (req, res, next) { | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkSelf(req, res, next)) { | ||
if (!checkUserRoles(req, res, next, roles)) { | ||
sender(res,403,errors.unauthorized()); | ||
ifs: { | ||
// limit our restriction to if a certain param has a certain value | ||
ifParam: (param, val) => { | ||
let that = {}; | ||
const | ||
makeMiddleware = (fn) => | ||
(req, res, next) => { | ||
if ((req.query && req.query[param] === val) || (req.body && req.body[param] === val)) { | ||
fn(req, res, next); | ||
} else { | ||
next(); | ||
} | ||
}, | ||
makeIndirectMiddleware = (middleware) => | ||
// this cannot be an arrow function because we use arguments | ||
function () { | ||
return makeMiddleware(middleware.apply(that, arguments)); | ||
} | ||
} | ||
} | ||
}; | ||
}, | ||
// valid if user is logged in *and* some given field is the same as a given param | ||
restrictToParam: function (param) { | ||
return function (req, res, next) { | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkParam(req, res, next, param)) { | ||
sender(res,403,errors.unauthorized()); | ||
} | ||
} | ||
}; | ||
}, | ||
// valid if user is logged in *and* some given field is the same as a given param *or* user has one of the given roles | ||
restrictToParamOrRoles: function (param, roles) { | ||
roles = roles ? [].concat(roles) : []; | ||
param = param ? [].concat(param) : []; | ||
return function (req, res, next) { | ||
// valid if the user name is the same as the param name, or the logged in user is an admin | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkUserRoles(req, res, next, roles)) { | ||
if (!checkParam(req, res, next, param)) { | ||
sender(res,403,errors.unauthorized()); | ||
} | ||
} | ||
} | ||
}; | ||
}, | ||
// valid if user is logged in *and* the ID of the logged-in user is equivalent to some field on an arbitrary object | ||
restrictToField: function (field, getField) { | ||
field = field ? [].concat(field) : []; | ||
return function (req, res, next) { | ||
// valid if the user name is the same as the param name, or the logged in user is an admin | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkField(req, res, next, field, getField)) { | ||
sender(res,403,errors.unauthorized()); | ||
} | ||
} | ||
}; | ||
}, | ||
// valid if user is logged in *and* the ID of the logged-in user is equivalent to some field on an arbitrary object *or* user has one of the given roles | ||
restrictToFieldOrRoles: function (field, roles, getField) { | ||
roles = roles ? [].concat(roles) : []; | ||
field = field ? [].concat(field) : []; | ||
return function (req, res, next) { | ||
// valid if the user name is the same as the param name, or the logged in user is an admin | ||
if (checkLoggedIn(req, res, next)) { | ||
if (!checkUserRoles(req, res, next, roles)) { | ||
if (!checkField(req, res, next, field, getField)) { | ||
sender(res,403,errors.unauthorized()); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}, | ||
ifs: { | ||
// limit our restriction to if a certain param has a certain value | ||
ifParam: function (param, val) { | ||
var that = {}, i, j, makeMiddleware, makeIndirectMiddleware; | ||
makeMiddleware = function (fn) { | ||
return function (req, res, next) { | ||
if ((req.query && req.query[param] === val) || (req.body && req.body[param] === val)) { | ||
fn(req, res, next); | ||
} else { | ||
next(); | ||
} | ||
}; | ||
}; | ||
makeIndirectMiddleware = function (middleware) { | ||
return function () { | ||
return makeMiddleware(middleware.apply(that, arguments)); | ||
}; | ||
}; | ||
// wrap the possible return functions | ||
for (i in fns.direct) { | ||
if (fns.direct.hasOwnProperty(i)) { | ||
//makeIndirectMiddleware = (middleware) => () => makeMiddleware(middleware.apply(that, arguments)) | ||
; | ||
// wrap the possible return functions | ||
for (let i of Object.keys(fns.direct)) { | ||
that[i] = makeMiddleware(fns.direct[i]); | ||
} | ||
} | ||
for (j in fns.indirect) { | ||
if (fns.indirect.hasOwnProperty(j)) { | ||
that[j] = makeIndirectMiddleware(fns.indirect[j]); | ||
for (let i of Object.keys(fns.indirect)) { | ||
that[i] = makeIndirectMiddleware(fns.indirect[i]); | ||
} | ||
return that; | ||
} | ||
return that; | ||
} | ||
} | ||
}; | ||
}; | ||
var that = {}; | ||
for (i in fns) { | ||
if (fns.hasOwnProperty(i)) { | ||
for (j in fns[i]) { | ||
if (fns[i].hasOwnProperty(j)) { | ||
that[j] = fns[i][j]; | ||
} | ||
} | ||
} | ||
const that = {}; | ||
for (let i of Object.keys(fns)) { | ||
Object.assign(that,fns[i]); | ||
} | ||
@@ -249,2 +215,2 @@ // pass required configs: | ||
} | ||
}; | ||
}; |
@@ -1,25 +0,23 @@ | ||
var constants = { | ||
header : { | ||
USER : 'X-CS-User', | ||
AUTH: 'X-CS-Auth', | ||
AUTHMETHOD : 'X-CS-Auth.method', | ||
AUTHSESSION: 'X-CS-Auth', | ||
CORS: 'Access-Control-Expose-Headers' | ||
}, | ||
method: { | ||
CREDENTIALS :"credentials", | ||
TOKEN: "token" | ||
} | ||
const constants = { | ||
header : { | ||
USER : 'X-CS-User', | ||
AUTH: 'X-CS-Auth', | ||
AUTHMETHOD : 'X-CS-Auth.method', | ||
AUTHSESSION: 'X-CS-Auth', | ||
CORS: 'Access-Control-Expose-Headers' | ||
}, | ||
method: { | ||
CREDENTIALS :"credentials", | ||
TOKEN: "token" | ||
} | ||
}; | ||
module.exports = { | ||
init : function (config) { | ||
if(!config) return; | ||
init : (config) => { | ||
if (!config) {return;} | ||
constants.header.AUTH = config.authHeader || constants.header.AUTH; | ||
constants.header.USER = config.userHeader || constants.header.USER; | ||
}, | ||
get: function() { | ||
return constants; | ||
} | ||
}; | ||
constants.header.AUTH = config.authHeader || constants.header.AUTH; | ||
constants.header.USER = config.userHeader || constants.header.USER; | ||
}, | ||
get: () => constants | ||
}; |
/*jslint node:true, nomen:true */ | ||
var fs = require('fs'), | ||
vm = require('vm'), | ||
_ = require('lodash'), | ||
async = require('async'), | ||
errors = require('./errors'), | ||
sender = require('./sender'), | ||
constants = require('./constants').get(), | ||
csauth = constants.header.AUTH, | ||
/* | ||
* pathRegexp from expressjs https://github.com/visionmedia/express/blob/master/lib/utils.js and modified per our needs | ||
* expressjs was released under MIT license as of this writing | ||
* https://github.com/visionmedia/express/blob/9914a1eb3f7bbe01e3783fa70cb78e02570d7336/LICENSE | ||
*/ | ||
pathRegexp = function (path, keys, sensitive, strict) { | ||
if (path && path.toString() === '[object RegExp]') { | ||
return path; | ||
} | ||
if (Array.isArray(path)) { | ||
path = '(' + path.join('|') + ')'; | ||
} | ||
path = path.concat(strict ? '' : '/?').replace(/\/\(/g, '(?:/').replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function (_, slash, format, key, capture, optional, star) { | ||
keys.push({ | ||
name: key, | ||
optional: !! optional | ||
}); | ||
slash = slash || ''; | ||
return String( | ||
(optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || ((format && '([^/.]+?)') || '([^/]+?)')) + ')' + (optional || '') + (star ? '(/*)?' : '')); | ||
}).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.*)'); | ||
return new RegExp('^' + path + '$', sensitive ? '' : 'i'); | ||
}, | ||
pathToFormat = function (path,format) { | ||
var ret = (!format || path.match(/(\/|\.\:\w+\?)$/)) ? path : path + ".:format?"; | ||
return(ret); | ||
}, | ||
globalLoader; | ||
const fs = require('fs'), | ||
vm = require('vm'), | ||
_ = require('lodash'), | ||
async = require('async'), | ||
errors = require('./errors'), | ||
sender = require('./sender'), | ||
constants = require('./constants').get(), | ||
paramProc = require('./param'), | ||
csauth = constants.header.AUTH, | ||
/* | ||
* pathRegexp from expressjs https://github.com/visionmedia/express/blob/master/lib/utils.js and modified per our needs | ||
* expressjs was released under MIT license as of this writing | ||
* https://github.com/visionmedia/express/blob/9914a1eb3f7bbe01e3783fa70cb78e02570d7336/LICENSE | ||
*/ | ||
pathRegexp = (path, keys, sensitive, strict) => { | ||
if (path && path.toString() === '[object RegExp]') { | ||
return path; | ||
} | ||
if (Array.isArray(path)) { | ||
path = '(' + path.join('|') + ')'; | ||
} | ||
path = path.concat(strict ? '' : '/?').replace(/\/\(/g, '(?:/').replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function (_, slash, format, key, capture, optional, star) { | ||
keys.push({ | ||
name: key, | ||
optional: !! optional | ||
}); | ||
slash = slash || ''; | ||
return String( | ||
(optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || ((format && '([^/.]+?)') || '([^/]+?)')) + ')' + (optional || '') + (star ? '(/*)?' : '')); | ||
}).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.*)'); | ||
return new RegExp('^' + path + '$', sensitive ? '' : 'i'); | ||
}, | ||
pathToFormat = (path,format) => (!format || path.match(/(\/|\.\:\w+\?)$/)) ? path : path + ".:format?"; | ||
let globalLoader; | ||
module.exports = { | ||
init: function (config) { | ||
globalLoader = (config || {}).loader; | ||
}, | ||
loadFile: function (cfile,options) { | ||
var data, routes = {}, fpath, loader, localLoader; | ||
options = options || {}; | ||
localLoader = options.loader || {}; | ||
// source the config file | ||
/*jslint stupid:true */ | ||
data = fs.readFileSync(cfile, "utf8"); | ||
/*jslint stupid:false */ | ||
data = JSON.parse(data) || {}; | ||
// do we have rules? | ||
/* each rule is | ||
* [verb,path,[param,][loggedIn,][loader,]condition] | ||
* [string,string,[object,][boolean,][string,]string] | ||
*/ | ||
_.each(data.routes || [], function (rule) { | ||
var entry, verb, keys = [], | ||
re; | ||
rule = rule || []; | ||
if (typeof (rule[2]) !== "object") { | ||
rule.splice(2, 0, null); | ||
} | ||
if (typeof (rule[3]) !== "boolean") { | ||
rule.splice(3, 0, false); | ||
} | ||
if (rule.length < 6) { | ||
rule.splice(4, 0, null); | ||
} | ||
verb = rule[0].toLowerCase(); | ||
fpath = pathToFormat(rule[1],options.format); | ||
re = pathRegexp(fpath,keys); | ||
entry = { | ||
verb: verb, | ||
url: rule[1], | ||
param: rule[2], | ||
loggedIn: rule[3], | ||
loader: rule[4], | ||
condition: rule[5], | ||
re: re, | ||
keys: keys | ||
}; | ||
routes[verb] = routes[verb] || []; | ||
routes[verb].push(entry); | ||
}); | ||
return function (req, res, next) { | ||
// authenticated: false = was not logged in and needed to be, send a 401, else check authorized | ||
// authorized: false = send a 403, else next() | ||
var user = req[csauth], | ||
keys, match, oldParams = req.params; | ||
// make sure there is req.param() | ||
require('./param')(req); | ||
// first check verb, then check route regexp match, then check params | ||
async.each(routes[req.method.toLowerCase()] || [], function (entry, callback) { | ||
var useRule = false, path = typeof(req.path) === "function" ? req.path() : req.path, | ||
checkCondition = function (condition, req, user, item) { | ||
var authorized; | ||
try { | ||
authorized = vm.runInNewContext(condition, { | ||
req: req, | ||
request: req, | ||
user: user, | ||
_: _, | ||
item: item | ||
}); | ||
} catch (e) { | ||
authorized = false; | ||
} | ||
return (authorized); | ||
}; | ||
keys = {}; | ||
// path match check | ||
match = (path || "").match(entry.re); | ||
if (match) { | ||
useRule = true; | ||
// create the important parameters | ||
_.each(entry.keys || [], function (p, i) { | ||
keys[p.name] = match[i + 1]; | ||
}); | ||
// this is so that req.param() or req.params will work | ||
req.params = keys; | ||
// next check if we use param - will be false unless no param, or param is match | ||
if (entry.param) { | ||
useRule = false; | ||
_.each(entry.param, function (val, key) { | ||
if (val !== null && val !== undefined && req.param(key) === val) { | ||
useRule = true; | ||
} | ||
}); | ||
} | ||
if (useRule) { | ||
// did we match the verb+path+param? | ||
// first check the authentication | ||
// authenticated = !entry.loggedIn || !!req[csauth]; | ||
if (entry.loggedIn && !req[csauth]) { | ||
callback(401, errors.unauthenticated()); | ||
} else { | ||
// next check for the loader | ||
if (entry.loader) { | ||
try { | ||
req.cansecurity = req.cansecurity || {}; | ||
loader = localLoader[entry.loader] || globalLoader[entry.loader]; | ||
loader(req, res, function (err) { | ||
if (err) { | ||
next(err); | ||
} else if (checkCondition(entry.condition, req, user, (req.cansecurity || {}).item)){ | ||
callback(); | ||
} else { | ||
callback(403, errors.unauthorized()); | ||
} | ||
}); | ||
} catch (err) { | ||
callback(500, errors.uninitialized()); | ||
} | ||
} else if (checkCondition(entry.condition, req, user)) { | ||
callback(); | ||
} else { | ||
callback(403, errors.unauthorized()); | ||
} | ||
} | ||
} else { | ||
callback(); | ||
} | ||
} else { | ||
callback(); | ||
} | ||
}, function (err,msg) { | ||
// now reset req.params | ||
req.params = oldParams; | ||
if (err) { | ||
sender(res,err,msg); | ||
} else { | ||
next(); | ||
} | ||
}); | ||
}; | ||
} | ||
init: (config) => { | ||
globalLoader = (config || {}).loader; | ||
}, | ||
loadFile: (cfile,options) => { | ||
options = options || {}; | ||
const routes = {}, localLoader = options.loader || {}, | ||
// source the config file | ||
/*jslint stupid:true */ | ||
data = JSON.parse( fs.readFileSync(cfile, "utf8") ) || {}; | ||
/*jslint stupid:false */ | ||
// do we have rules? | ||
/* each rule is | ||
* [verb,path,[param,][loggedIn,][loader,]condition] | ||
* [string,string,[object,][boolean,][string,]string] | ||
*/ | ||
for (let rule of data.routes || []) { | ||
rule = rule || []; | ||
if (typeof (rule[2]) !== "object") { | ||
rule.splice(2, 0, null); | ||
} | ||
if (typeof (rule[3]) !== "boolean") { | ||
rule.splice(3, 0, false); | ||
} | ||
if (rule.length < 6) { | ||
rule.splice(4, 0, null); | ||
} | ||
const keys = [], | ||
verb = rule[0].toLowerCase(), | ||
fpath = pathToFormat(rule[1],options.format), | ||
re = pathRegexp(fpath,keys), | ||
entry = { | ||
verb: verb, | ||
url: rule[1], | ||
param: rule[2], | ||
loggedIn: rule[3], | ||
loader: rule[4], | ||
condition: rule[5], | ||
re: re, | ||
keys: keys | ||
}; | ||
routes[verb] = routes[verb] || []; | ||
routes[verb].push(entry); | ||
} | ||
return (req, res, next) => { | ||
// authenticated: false = was not logged in and needed to be, send a 401, else check authorized | ||
// authorized: false = send a 403, else next() | ||
const user = req[csauth], oldParams = req.params; | ||
// make sure there is req.param() | ||
paramProc(req); | ||
// first check verb, then check route regexp match, then check params | ||
async.each(routes[req.method.toLowerCase()] || [], (entry, callback) => { | ||
let useRule = false; | ||
const path = typeof(req.path) === "function" ? req.path() : req.path, | ||
checkCondition = (condition, req, user, item) => { | ||
var authorized; | ||
try { | ||
authorized = vm.runInNewContext(condition, { | ||
req: req, | ||
request: req, | ||
user: user, | ||
_: _, | ||
item: item | ||
}); | ||
} catch (e) { | ||
authorized = false; | ||
} | ||
return (authorized); | ||
}, | ||
// path match check | ||
match = (path || "").match(entry.re); | ||
if (match) { | ||
useRule = true; | ||
// create the important parameters | ||
req.params = (entry.keys||[]).reduce((result,val,i) => { | ||
result[val.name] = match[i+1]; | ||
return result; | ||
},{}); | ||
// next check if we use param - will be false unless no param, or param is match | ||
if (entry.param) { | ||
useRule = false; | ||
for (let key of Object.keys(entry.param)) { | ||
const val = entry.param[key]; | ||
if (val !== null && val !== undefined && req.param(key) == val) { | ||
useRule = true; | ||
break; | ||
} | ||
} | ||
} | ||
if (useRule) { | ||
// did we match the verb+path+param? | ||
// first check the authentication | ||
// authenticated = !entry.loggedIn || !!req[csauth]; | ||
if (entry.loggedIn && !req[csauth]) { | ||
callback(401, errors.unauthenticated()); | ||
} else { | ||
// next check for the loader | ||
if (entry.loader) { | ||
try { | ||
req.cansecurity = req.cansecurity || {}; | ||
const loader = localLoader[entry.loader] || globalLoader[entry.loader]; | ||
loader(req, res, function (err) { | ||
if (err) { | ||
next(err); | ||
} else if (checkCondition(entry.condition, req, user, (req.cansecurity || {}).item)){ | ||
callback(); | ||
} else { | ||
callback(403, errors.unauthorized()); | ||
} | ||
}); | ||
} catch (err) { | ||
callback(500, errors.uninitialized()); | ||
} | ||
} else if (checkCondition(entry.condition, req, user)) { | ||
callback(); | ||
} else { | ||
callback(403, errors.unauthorized()); | ||
} | ||
} | ||
} else { | ||
callback(); | ||
} | ||
} else { | ||
callback(); | ||
} | ||
}, (err,msg) => { | ||
// now reset req.params | ||
req.params = oldParams; | ||
if (err) { | ||
sender(res,err,msg); | ||
} else { | ||
next(); | ||
} | ||
}); | ||
}; | ||
} | ||
}; |
/*global module, require, Buffer */ | ||
var errors = { | ||
unauthorized: function(msg){ return msg || "unauthorized";}, | ||
unauthenticated: function(msg){ return msg || "unauthenticated";}, | ||
invalidtoken: function(msg){ return msg || "invalidtoken";}, | ||
module.exports = { | ||
unauthorized: (msg) => msg || "unauthorized", | ||
unauthenticated: (msg) => msg || "unauthenticated", | ||
invalidtoken: (msg) => msg || "invalidtoken", | ||
// 409 is a resource conflict - see RFC2616 | ||
conflict: function(msg){ return msg || "conflict";}, | ||
badRequest: function(msg){ return msg || "badrequest";}, | ||
notFound: function(msg){ return msg || "notfound";}, | ||
uninitialized: function(msg){ return msg || "uninitialized";}, | ||
server: function(msg) {return msg || "";} | ||
conflict: (msg) => msg || "conflict", | ||
badRequest: (msg) => msg || "badrequest", | ||
notFound: (msg) => msg || "notfound", | ||
uninitialized: (msg) => msg || "uninitialized", | ||
server: (msg) => msg || "" | ||
}; | ||
module.exports = errors; | ||
/*jslint node:true */ | ||
// get the authentication/sessionManager library and the authorization library | ||
var cansec = require('./sessionManager'), | ||
auth = require('./authorization'), | ||
declarative = require('./declarative'), | ||
constants = require('./constants'); | ||
const cansec = require('./sessionManager'), | ||
auth = require('./authorization'), | ||
declarative = require('./declarative'), | ||
constants = require('./constants'); | ||
module.exports = { | ||
init: function (config) { | ||
var authentication, authorization, ret = {}, i, that = this; | ||
// initialize each part of the library | ||
constants.init(config); | ||
authentication = cansec.init(config); | ||
authorization = auth.init(config); | ||
declarative.init(config); | ||
// merge the two into ret object | ||
// authentication methods | ||
for (i in authentication) { | ||
if (authentication.hasOwnProperty(i)) { | ||
ret[i] = authentication[i]; | ||
} | ||
} | ||
// authorization methods | ||
for (i in authorization) { | ||
if (authorization.hasOwnProperty(i)) { | ||
ret[i] = authorization[i]; | ||
} | ||
} | ||
// declarative authorization | ||
that.authorizer = ret.authorizer = declarative.loadFile; | ||
that.getAuthMethod = ret.getAuthMethod; | ||
that.getUser = ret.getUser; | ||
return (ret); | ||
} | ||
}; | ||
init: (config) => { | ||
const ret = {}, that = this, authentication = cansec.init(config), authorization = auth.init(config); | ||
// initialize each part of the library | ||
constants.init(config); | ||
declarative.init(config); | ||
// merge the two into ret object | ||
// authentication methods | ||
Object.assign(ret,authentication,authorization); | ||
// declarative authorization | ||
that.authorizer = ret.authorizer = declarative.loadFile; | ||
that.getAuthMethod = ret.getAuthMethod; | ||
that.getUser = ret.getUser; | ||
return (ret); | ||
} | ||
}; |
/*jslint node:true */ | ||
module.exports = function (req) { | ||
req.param = function(name, defaultValue){ | ||
var params = this.params || {}, body = this.body || {}, query = this.query || {}, | ||
ret = defaultValue; | ||
if (params[name] !== null && params[name] !== undefined && params.hasOwnProperty(name)) { | ||
ret = params[name]; | ||
} else if (body[name] !== null && body[name] !== undefined) { | ||
ret = body[name]; | ||
} else if (query[name] !== null && query[name] !== undefined) { | ||
ret = query[name]; | ||
} | ||
return ret; | ||
}; | ||
}; | ||
module.exports = (req) => { | ||
// leave this as old-style function or restify breaks | ||
req.param = function(name, defaultValue) { | ||
const params = this.params || {}, body = this.body || {}, query = this.query || {}; | ||
let ret = defaultValue; | ||
if (params[name] !== null && params[name] !== undefined && params.hasOwnProperty(name)) { | ||
ret = params[name]; | ||
} else if (body[name] !== null && body[name] !== undefined) { | ||
ret = body[name]; | ||
} else if (query[name] !== null && query[name] !== undefined) { | ||
ret = query[name]; | ||
} | ||
return ret; | ||
}; | ||
}; |
/*jslint node:true */ | ||
module.exports = function (res,status,body) { | ||
// are we in express or restify? | ||
// in express, arity of res.send() is 1 (just body) | ||
// in restify, arity of res.send() is 3 (code,body,headers) | ||
var l = res.send.length; | ||
if (l === 1) { | ||
res.status(status); | ||
if (body !== undefined && body !== null) { | ||
res.send(body); | ||
} else { | ||
res.end(); | ||
} | ||
} else { | ||
res.send(status,body); | ||
} | ||
}; | ||
module.exports = (res,status,body) => { | ||
// are we in express or restify? | ||
// in express, arity of res.send() is 1 (just body) | ||
// in restify, arity of res.send() is 3 (code,body,headers) | ||
const l = res.send.length; | ||
if (l === 1) { | ||
res.status(status); | ||
if (body !== undefined && body !== null) { | ||
res.send(body); | ||
} else { | ||
res.end(); | ||
} | ||
} else { | ||
res.send(status,body); | ||
} | ||
}; |
/*global module, require, Buffer, console */ | ||
var _ = require( 'lodash' ), | ||
crypto = require( 'crypto' ), | ||
tokenlib = require( './token' ), | ||
util = require('./util'), | ||
now = util.now, | ||
publicMethods, validate, invalidTokenMessage, | ||
errors = require( './errors' ), | ||
sender = require('./sender'), | ||
constants = require( './constants' ).get(), | ||
debug = false, | ||
warn = function (msg) { | ||
if (debug) { | ||
console.error(msg); | ||
} | ||
}; | ||
const _ = require( 'lodash' ), | ||
crypto = require( 'crypto' ), | ||
tokenlib = require( './token' ), | ||
util = require('./util'), | ||
errors = require( './errors' ), | ||
sender = require('./sender'), | ||
constants = require( './constants' ).get(), | ||
now = util.now, | ||
warn = function (msg) { | ||
if (debug) { | ||
console.error(msg); | ||
} | ||
}; | ||
const AUTHHEADER = constants.header.AUTH, | ||
AUTHMETHODHEADER = constants.header.AUTHMETHOD, | ||
AUTHSESSION = AUTHHEADER, | ||
CORSHEADER = constants.header.CORS, | ||
SESSIONEXPIRY = 15, // minutes | ||
RANDOM_STRING_LENGTH = 60, | ||
/*jslint regexp:true */ | ||
MSGRE = /^error (.+)$/; | ||
/*jslint regexp:false */ | ||
let validate, invalidTokenMessage, debug = false, sessionExpiry, | ||
encryptHeader = false; | ||
// set up warn on tokenlib | ||
tokenlib.setWarn(warn); | ||
var AUTHHEADER = constants.header.AUTH, | ||
AUTHMETHODHEADER = constants.header.AUTHMETHOD, | ||
AUTHSESSION = AUTHHEADER, | ||
CORSHEADER = constants.header.CORS, | ||
SESSIONEXPIRY = 15, // minutes | ||
sessionExpiry, hasInit = false, | ||
encryptHeader = false, | ||
RANDOM_STRING_LENGTH = 60; | ||
/*jslint regexp:true */ | ||
var MSGRE = /^error (.+)$/; | ||
/*jslint regexp:false */ | ||
/////////////// | ||
@@ -38,9 +40,5 @@ //// UTILS //// | ||
var fnOrNull = function ( f ) { | ||
return ( f && typeof ( f ) === "function" ? f : null ); | ||
}, | ||
genRandomString = function ( length ) { | ||
return crypto.randomBytes( length ) | ||
.toString( 'hex' ); | ||
}; | ||
const fnOrNull = ( f ) => ( f && typeof ( f ) === "function" ? f : null ), | ||
genRandomString = ( length ) => crypto.randomBytes( length ).toString( 'hex' ) | ||
; | ||
@@ -52,52 +50,30 @@ | ||
var requestHasBasicAuthCredentials = function ( request ) { | ||
return request.headers.authorization && request.headers.authorization.indexOf( "Basic " ) === 0; | ||
}, getBasicAuthCredentials = function ( request ) { | ||
var header = new Buffer( request.headers.authorization.split( ' ' )[ 1 ], 'base64' ) | ||
.toString() | ||
.split( ":" ); | ||
if ( header && header.length === 2 ) { | ||
return { | ||
user: header[ 0 ], | ||
password: header[ 1 ] | ||
}; | ||
} | ||
return null; | ||
}, getAuthCredentials = function ( req ) { | ||
if ( requestHasBasicAuthCredentials( req ) ) { | ||
return getBasicAuthCredentials( req ); | ||
} | ||
return null; | ||
}, appendCORSHeader = function ( response ) { | ||
var existing = response.get( CORSHEADER ) || ""; | ||
response.set( CORSHEADER, _.compact( existing.split( /,/ ) ) | ||
.concat( [ AUTHHEADER ] ) | ||
.join( "," ) ); | ||
}, | ||
getAuthTokenFromHeaders = function ( req ) { | ||
var header = req.headers.authorization, | ||
authToken = header && header.indexOf("Bearer ") === 0 ? header.split(' ')[1] : null; | ||
const requestHasBasicAuthCredentials = ( request ) => request.headers.authorization && request.headers.authorization.indexOf( "Basic " ) === 0, | ||
getBasicAuthCredentials = ( request ) => { | ||
const header = new Buffer( request.headers.authorization.split( ' ' )[ 1 ], 'base64' ) | ||
.toString() | ||
.split( ":" ); | ||
if ( header && header.length === 2 ) { | ||
return { | ||
user: header[ 0 ], | ||
password: header[ 1 ] | ||
}; | ||
} | ||
return null; | ||
}, | ||
getAuthCredentials = ( req ) => requestHasBasicAuthCredentials( req ) ? getBasicAuthCredentials( req ) : null, | ||
appendCORSHeader = ( response ) => { | ||
const existing = response.get( CORSHEADER ) || ""; | ||
response.set( CORSHEADER, _.compact( existing.split( /,/ ) ) | ||
.concat( [ AUTHHEADER ] ) | ||
.join( "," ) ); | ||
}, | ||
getAuthTokenFromHeaders = ( req ) => { | ||
const header = req.headers.authorization, | ||
authToken = header && header.indexOf("Bearer ") === 0 ? header.split(' ')[1] : null; | ||
return authToken; | ||
/* | ||
if ( !authToken ) {return null;} | ||
return authToken; | ||
}; | ||
if ( encryptHeader ) { | ||
authToken = tokenlib.decipher( authToken ); | ||
} | ||
var authTokenParts = authToken.split( ':' ); | ||
var auth = { | ||
valid: authTokenParts.length >= 2, | ||
token: authToken, | ||
hash: authTokenParts[ 0 ], | ||
user: authTokenParts[ 1 ], | ||
expiry: authTokenParts[ 2 ] | ||
}; | ||
return auth; | ||
*/ | ||
}; | ||
///////////////// | ||
@@ -107,66 +83,66 @@ //// SESSION //// | ||
var setupSessionData = function ( request, user, login, expiry ) { | ||
if ( request.session ) { | ||
request.session[ AUTHSESSION ] = { | ||
user: user, | ||
login: login, | ||
expiry: expiry | ||
}; | ||
request.session.touch(); | ||
} | ||
}, | ||
setupSessionHeaders = function ( request, response, user, login, method, expiry ) { | ||
var userAsJson, header; | ||
request[ AUTHHEADER ] = user || {}; | ||
request[ AUTHMETHODHEADER ] = method; | ||
userAsJson = JSON.stringify( request[ AUTHHEADER ] ); | ||
header = [ | ||
"success", | ||
tokenlib.generate( login, userAsJson, expiry ), | ||
login, | ||
expiry | ||
]; response.header( AUTHHEADER, header.join(" ")); | ||
}, | ||
removeSessionData = function ( request ) { | ||
if ( request.session ) { | ||
delete request.session[ AUTHSESSION ]; | ||
} | ||
}, | ||
clearHeaders = function ( response ) { | ||
response.removeHeader( AUTHHEADER ); | ||
}, | ||
endSession = function ( request, response ) { | ||
removeSessionData( request ); | ||
clearHeaders( response ); | ||
}, | ||
cantStablishSession = function ( req, res, next ) { | ||
endSession( req, res ); | ||
next(); | ||
}, | ||
prepareErrorHeaders = function ( response, message ) { | ||
response.header( AUTHHEADER, "error " + message ); | ||
}, | ||
endSessionWithErrorMessage = function ( request, response, message ) { | ||
removeSessionData( request ); | ||
prepareErrorHeaders( response, message ); | ||
}, | ||
getSessionDataFromRequest = function ( request ) { | ||
var session = {}; | ||
if ( request.session ) { | ||
session.auth = request.session[ AUTHSESSION ] || null; | ||
if ( session.auth ) { | ||
session.user = session.auth.user; | ||
} | ||
} | ||
return session; | ||
}, | ||
startSession = function ( config ) { | ||
var req = config.req, | ||
res = config.res, | ||
expiry = now() + sessionExpiry; | ||
const setupSessionData = ( request, user, login, expiry ) => { | ||
if ( request.session ) { | ||
request.session[ AUTHSESSION ] = { | ||
user: user, | ||
login: login, | ||
expiry: expiry | ||
}; | ||
request.session.touch(); | ||
} | ||
}, | ||
setupSessionHeaders = ( request, response, user, login, method, expiry ) => { | ||
const u = user || {}, userAsJson = JSON.stringify(u), | ||
header = [ | ||
"success", | ||
tokenlib.generate( login, userAsJson, expiry ), | ||
login, | ||
expiry | ||
]; | ||
request[ AUTHHEADER ] = u; | ||
request[ AUTHMETHODHEADER ] = method; | ||
response.header( AUTHHEADER, header.join(" ")); | ||
}, | ||
removeSessionData = ( request ) => { | ||
if ( request.session ) { | ||
delete request.session[ AUTHSESSION ]; | ||
} | ||
}, | ||
clearHeaders = ( response ) => { | ||
response.removeHeader( AUTHHEADER ); | ||
}, | ||
endSession = ( request, response ) => { | ||
removeSessionData( request ); | ||
clearHeaders( response ); | ||
}, | ||
cantStablishSession = ( req, res, next ) => { | ||
endSession( req, res ); | ||
next(); | ||
}, | ||
prepareErrorHeaders = ( response, message ) => { | ||
response.header( AUTHHEADER, "error " + message ); | ||
}, | ||
endSessionWithErrorMessage = ( request, response, message ) => { | ||
removeSessionData( request ); | ||
prepareErrorHeaders( response, message ); | ||
}, | ||
getSessionDataFromRequest = ( request ) => { | ||
const session = {}; | ||
if ( request.session ) { | ||
session.auth = request.session[ AUTHSESSION ] || null; | ||
if ( session.auth ) { | ||
session.user = session.auth.user; | ||
} | ||
} | ||
return session; | ||
}, | ||
startSession = ( config ) => { | ||
const req = config.req, | ||
res = config.res, | ||
expiry = now() + sessionExpiry; | ||
appendCORSHeader( res ); | ||
setupSessionData( req, config.user, config.login, expiry ); | ||
setupSessionHeaders( req, res, config.user, config.login, config.method, expiry ); | ||
}; | ||
appendCORSHeader( res ); | ||
setupSessionData( req, config.user, config.login, expiry ); | ||
setupSessionHeaders( req, res, config.user, config.login, config.method, expiry ); | ||
}; | ||
@@ -180,19 +156,19 @@ | ||
var validateCallback = function ( success, user, message, pass ) { | ||
if ( success && user ) { | ||
warn("Successfully validated "+user+" via basic auth"); | ||
startSession( { | ||
req: this.req, | ||
res: this.res, | ||
user: user, | ||
login: this.creds.user, | ||
password: pass, | ||
method: constants.method.CREDENTIALS | ||
} ); | ||
this.next(); | ||
} else { | ||
warn("Failed to validate user via basic auth"); | ||
endSessionWithErrorMessage( this.req, this.res, message ); | ||
sender(this.res, 401, errors.unauthenticated( message ) ); | ||
} | ||
const validateCallback = function( success, user, message, pass ) { | ||
if ( success && user ) { | ||
warn(`Successfully validated ${user} via basic auth`); | ||
startSession( { | ||
req: this.req, | ||
res: this.res, | ||
user: user, | ||
login: this.creds.user, | ||
password: pass, | ||
method: constants.method.CREDENTIALS | ||
} ); | ||
this.next(); | ||
} else { | ||
warn(`Failed to validate user via basic auth`); | ||
endSessionWithErrorMessage( this.req, this.res, message ); | ||
sender(this.res, 401, errors.unauthenticated( message ) ); | ||
} | ||
}; | ||
@@ -204,89 +180,91 @@ | ||
var tryStartSessionWithBasicAuthCredentials = function ( req, res, next ) { | ||
var creds = getAuthCredentials( req ); | ||
warn("Try Session with Basic Auth Creds for "+req.url); | ||
if ( creds ) { | ||
warn("Basic Auth Creds found for "+creds.user); | ||
const tryStartSessionWithBasicAuthCredentials = ( req, res, next ) => { | ||
const creds = getAuthCredentials( req ); | ||
warn(`Try Session with Basic Auth Creds for ${req.url}`); | ||
if ( creds ) { | ||
warn(`Basic Auth Creds found for ${creds.user}`); | ||
var context = { | ||
req: req, | ||
res: res, | ||
next: next, | ||
creds: creds | ||
}; | ||
validate( creds.user, creds.password, validateCallback.bind( context ) ); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
tryStartWithPreviousSessionData = function ( req, res, next ) { | ||
var session = getSessionDataFromRequest( req ), ret; | ||
warn("Try Session with Previous express session data for "+req.url); | ||
const context = { | ||
req: req, | ||
res: res, | ||
next: next, | ||
creds: creds | ||
}; | ||
validate( creds.user, creds.password, validateCallback.bind( context ) ); | ||
return true; | ||
} | ||
return false; | ||
}, | ||
tryStartWithPreviousSessionData = ( req, res, next ) => { | ||
const session = getSessionDataFromRequest( req ); | ||
let ret; | ||
warn(`Try Session with Previous express session data for ${req.url}`); | ||
if ( session.user ) { | ||
warn("Session user found"); | ||
if ( session.auth.expiry > now() ) { | ||
warn("Session still valid"); | ||
startSession( { | ||
req: req, | ||
res: res, | ||
user: session.user, | ||
login: session.auth.login, | ||
password: session.user.pass | ||
} ); | ||
} else { | ||
warn("Session expired"); | ||
endSession( req, res ); | ||
} | ||
next(); | ||
ret = true; | ||
} else { | ||
warn("No previous session user found"); | ||
ret = false; | ||
} | ||
if ( session.user ) { | ||
warn(`Session user found`); | ||
if ( session.auth.expiry > now() ) { | ||
warn(`Session still valid`); | ||
startSession( { | ||
req: req, | ||
res: res, | ||
user: session.user, | ||
login: session.auth.login, | ||
password: session.user.pass | ||
} ); | ||
} else { | ||
warn(`Session expired`); | ||
endSession( req, res ); | ||
} | ||
next(); | ||
ret = true; | ||
} else { | ||
warn(`No previous session user found`); | ||
ret = false; | ||
} | ||
return ret; | ||
}, | ||
tryStartSessionWithAuthToken = function ( req, res, next ) { | ||
var auth = getAuthTokenFromHeaders( req ), ret, token, login; | ||
warn("Try Session with Auth Token for "+req.url); | ||
if ( auth ) { | ||
warn("Auth Token found"); | ||
return ret; | ||
}, | ||
tryStartSessionWithAuthToken = ( req, res, next ) => { | ||
const auth = getAuthTokenFromHeaders( req ); | ||
let ret, token, login; | ||
warn("Try Session with Auth Token for "+req.url); | ||
if ( auth ) { | ||
warn(`Auth Token found`); | ||
// first validate the token | ||
token = tokenlib.validate( auth ); | ||
// first validate the token | ||
token = tokenlib.validate( auth ); | ||
// if succeeded, then we need to validate the user from the DB | ||
if ( token ) { | ||
login = token.sub; | ||
warn("Successfully validated "+login+" via auth token"); | ||
// if succeeded, then we need to validate the user from the DB | ||
if ( token ) { | ||
login = token.sub; | ||
warn(`Successfully validated ${login} via auth token`); | ||
if ( validate ) { | ||
warn("Trying to check user ..."); | ||
validate( token.sub, undefined, function (success, user ) { | ||
warn("Tried validation, success? "+success); | ||
if (success) { | ||
startSession( { | ||
req: req, | ||
res: res, | ||
user: user, | ||
login: login, | ||
method: constants.method.TOKEN | ||
} ); | ||
next(); | ||
} | ||
} ); | ||
} | ||
} else { | ||
warn("Failed to validate "+login+" via auth token"); | ||
endSessionWithErrorMessage( req, res, errors.invalidtoken( invalidTokenMessage ) ); | ||
sender(res, 401, errors.invalidtoken( invalidTokenMessage ) ); | ||
} | ||
ret = true; | ||
} else { | ||
warn("No auth token found"); | ||
ret = false; | ||
} | ||
return ret; | ||
}; | ||
if ( validate ) { | ||
warn(`Trying to check user ...`); | ||
validate( token.sub, undefined, function (success, user ) { | ||
warn(`Tried validation, success? ${success}`); | ||
if (success) { | ||
startSession( { | ||
req: req, | ||
res: res, | ||
user: user, | ||
login: login, | ||
method: constants.method.TOKEN | ||
} ); | ||
next(); | ||
} | ||
} ); | ||
} | ||
} else { | ||
warn(`Failed to validate ${login} via auth token`); | ||
endSessionWithErrorMessage( req, res, errors.invalidtoken( invalidTokenMessage ) ); | ||
sender(res, 401, errors.invalidtoken( invalidTokenMessage ) ); | ||
} | ||
ret = true; | ||
} else { | ||
warn(`No auth token found`); | ||
ret = false; | ||
} | ||
return ret; | ||
}; | ||
@@ -299,41 +277,35 @@ | ||
publicMethods = { | ||
// if there is a login session token, refresh its timeout on each request, and validate it | ||
validate: function ( req, res, next ) { | ||
"use strict"; | ||
return tryStartSessionWithBasicAuthCredentials( req, res, next ) || | ||
tryStartWithPreviousSessionData( req, res, next ) || | ||
tryStartSessionWithAuthToken( req, res, next ) || | ||
cantStablishSession( req, res, next ); | ||
}, | ||
clear: endSession, | ||
message: function ( res ) { | ||
var msg, p = res.headers ? ( res.headers[ AUTHHEADER ] || res.headers[ AUTHHEADER.toLowerCase() ] || "" ) : "", | ||
match = MSGRE.exec( p ); | ||
const publicMethods = { | ||
// if there is a login session token, refresh its timeout on each request, and validate it | ||
validate: ( req, res, next ) => { | ||
"use strict"; | ||
return tryStartSessionWithBasicAuthCredentials( req, res, next ) || | ||
tryStartWithPreviousSessionData( req, res, next ) || | ||
tryStartSessionWithAuthToken( req, res, next ) || | ||
cantStablishSession( req, res, next ); | ||
}, | ||
clear: endSession, | ||
message: ( res ) => { | ||
const p = res.headers ? ( res.headers[ AUTHHEADER ] || res.headers[ AUTHHEADER.toLowerCase() ] || "" ) : "", | ||
match = MSGRE.exec( p ), | ||
msg = match && match.length > 1 && match[ 1 ].length > 0 ? match[ 1 ] : ""; | ||
msg = match && match.length > 1 && match[ 1 ].length > 0 ? match[ 1 ] : ""; | ||
return ( msg ); | ||
}, | ||
getAuthMethod: function ( req ) { | ||
return ( req ? req[ AUTHMETHODHEADER ] : null ); | ||
}, | ||
getUser: function ( req ) { | ||
if ( req ) { | ||
return req[ AUTHHEADER ]; | ||
} | ||
return null; | ||
} | ||
return ( msg ); | ||
}, | ||
getAuthMethod: ( req ) => ( req ? req[ AUTHMETHODHEADER ] : null ), | ||
getUser: ( req ) => req ? req[ AUTHHEADER ] : null | ||
}; | ||
module.exports = { | ||
init: function ( config ) { | ||
validate = fnOrNull( config.validate ); | ||
invalidTokenMessage = config.invalidTokenMessage || null; | ||
sessionExpiry = ( config.expiry || SESSIONEXPIRY ) * 60 * 1000; | ||
encryptHeader = ( config.encryptHeader || false ); | ||
tokenlib.init( config.sessionKey || genRandomString( RANDOM_STRING_LENGTH ), encryptHeader ); | ||
debug = config.debug || false; | ||
hasInit = true; | ||
return publicMethods; | ||
} | ||
init: ( config ) => { | ||
validate = fnOrNull( config.validate ); | ||
invalidTokenMessage = config.invalidTokenMessage || null; | ||
sessionExpiry = ( config.expiry || SESSIONEXPIRY ) * 60 * 1000; | ||
encryptHeader = ( config.encryptHeader || false ); | ||
debug = config.debug || false; | ||
tokenlib.init( config.sessionKey || genRandomString( RANDOM_STRING_LENGTH ), encryptHeader ); | ||
return publicMethods; | ||
} | ||
}; |
102
lib/token.js
@@ -1,66 +0,56 @@ | ||
var crypto = require('crypto'), exports = module.exports, jwt = require('jsonwebtoken'); | ||
const crypto = require('crypto'), jwt = require('jsonwebtoken'), now = require('./util').now; | ||
var sessionKey = null; | ||
var encryptHeader = false; | ||
// for safety | ||
var warn = function () { | ||
}; | ||
var now = require('./util').now; | ||
let sessionKey = null, encryptHeader = false, warn = () => {}; | ||
exports.init = function(key, encrypt) { | ||
sessionKey = key; | ||
encryptHeader = encrypt || false; | ||
}; | ||
exports.setWarn = function (fn) { | ||
warn = fn; | ||
}; | ||
exports.generate = function(name, user, expiry) { | ||
var token = jwt.sign({sub:name,exp:expiry,"cs-user":user},sessionKey,{algorithm:"HS256"}); | ||
module.exports = { | ||
init : (key, encrypt) => { | ||
sessionKey = key; | ||
encryptHeader = encrypt || false; | ||
}, | ||
setWarn : (fn) => { | ||
warn = fn; | ||
}, | ||
if(encryptHeader) { | ||
token = exports.cipher(token); | ||
} | ||
generate : (name, user, expiry) => { | ||
const token = jwt.sign({sub:name,exp:expiry,"cs-user":user},sessionKey,{algorithm:"HS256"}); | ||
return(token); | ||
}; | ||
return(encryptHeader ? module.exports.cipher(token) : token); | ||
}, | ||
exports.validate = function(token) { | ||
warn("validating auth token "+token); | ||
var valid = false, expiry, decoded, t = now(); | ||
token = token || ""; | ||
if (encryptHeader) { | ||
token = exports.decipher(token); | ||
} | ||
try { | ||
decoded = jwt.verify(token,sessionKey,{algorithms:"HS256"}); | ||
expiry = decoded.exp; | ||
warn("token expiry "+expiry+" now "+t); | ||
warn("token name "+decoded.sub); | ||
if (!isNaN(expiry) && parseInt(expiry,10) > t) { | ||
valid = decoded; | ||
} else { | ||
valid = false; | ||
} | ||
} catch (e) { | ||
valid = false; | ||
} | ||
validate : (token) => { | ||
warn(`validating auth token ${token}`); | ||
const t = now(); | ||
let valid = false, expiry, decoded; | ||
token = token || ""; | ||
if (encryptHeader) { | ||
token = module.exports.decipher(token); | ||
} | ||
try { | ||
decoded = jwt.verify(token,sessionKey,{algorithms:"HS256"}); | ||
expiry = decoded.exp; | ||
warn(`token expiry ${expiry} now ${t}`); | ||
warn(`token name ${decoded.sub}`); | ||
if (!isNaN(expiry) && parseInt(expiry,10) > t) { | ||
valid = decoded; | ||
} else { | ||
valid = false; | ||
} | ||
} catch (e) { | ||
valid = false; | ||
} | ||
warn("token valid? "+valid); | ||
return(decoded); | ||
}; | ||
warn(`token valid? ${valid}`); | ||
return(decoded); | ||
}, | ||
exports.cipher = function(token) { | ||
var cipher = crypto.createCipher('rc4-hmac-md5', sessionKey); | ||
token = cipher.update(token, 'utf8','base64'); | ||
token += cipher.final('base64'); | ||
return token; | ||
}; | ||
cipher : (token) => { | ||
const cipher = crypto.createCipher('rc4-hmac-md5', sessionKey); | ||
return cipher.update(token, 'utf8','base64') + cipher.final('base64'); | ||
}, | ||
exports.decipher = function(token){ | ||
var decipher = crypto.createDecipher('rc4-hmac-md5', sessionKey); | ||
token = decipher.update(token, 'base64','utf8'); | ||
token += decipher.final('utf8'); | ||
return token; | ||
decipher : (token) => { | ||
const decipher = crypto.createDecipher('rc4-hmac-md5', sessionKey); | ||
return decipher.update(token, 'base64','utf8') + decipher.final('utf8'); | ||
} | ||
}; | ||
module.exports = { | ||
now: function () { | ||
return Math.floor(Date.now()/1000); | ||
} | ||
}; | ||
now: () => Math.floor(Date.now()/1000) | ||
}; |
{ | ||
"name": "cansecurity", | ||
"description": "cansecurity is your all-in-one security library for user authentication, authorization and management in node expressjs apps", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "url": "http://github.com/deitch/cansecurity", |
216425
2122