subscriber
Version:
Subscriber Module
183 lines (169 loc) • 6.06 kB
JavaScript
/*jslint node:true, nomen:true */
var _ = require('lodash'), async = require('async'),
actionMap = {
create:"post",
index:"get",
show:"get",
update:"put",
"delete":"delete",
patch:"patch"
},
msInDay = 24*60*60*1000,
/*
* 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');
}, loader;
module.exports = {
init: function (config) {
config = config || {};
var db = config.db, plans = {reset: function () {
this.plan = {}; this.path = {}; this.trial = {};
}}, base = ((config.base || '')+'/').replace(/\/+$/,'/'), expiry, timeout = (config.timeout || 60)*60*1000;
return function (req,res,next) {
var now = new Date().getTime(), utype = typeof(req.user), user = req.user && utype === "object" ? req.user.id : req.user;
async.series([
// first load the plans, if we do not have them or they are expired
function (cb) {
if (!plans || !expiry || expiry < now) {
db.plans(function (err,p) {
var resources = [];
expiry = now + timeout;
plans.reset();
// save the plans by key
_.each(p.plans||p,function (plan) {
var limits;
plans.plan[plan.name] = plan;
if (!plan.limits) {
limits = _.extend({},plan);
delete limits.name;
}
_.each(plan.limits,function (actions,resource) {
var acts = typeof(actions) === "number" ? ["create"] : _.keys(actions);
_.each(acts,function (a) {
resources.push({verb:a,resource:resource});
});
});
});
// was there a trial setting?
if (p.trial) {
plans.trial = p.trial.duration ? p.trial : {duration: p.trial};
}
// save the paths for each item
_.each(_.unique(resources,function (item) {
return(item.verb+" "+item.resource);
}),function (entry) {
var re, verb = entry.verb, resource = entry.resource, resourcePath = (config.paths && config.paths[resource]) ? config.paths[resource] : resource;
if (resourcePath.charAt(0) !== '/') {
resourcePath = (base||'/')+resourcePath;
}
// and now need to convert that to the regexp
re = pathRegexp( verb === "create" || verb === "index" ? resourcePath : resourcePath+'/:id',[]);
verb = actionMap[verb] || verb;
plans.path[verb] = plans.path[verb] || [];
plans.path[verb].push({re:re,name:resource});
});
cb();
});
} else {
cb();
}
},function (cb) {
var resource;
// before we even get the user, is this a request we care about? match req.method and req.url to the list
// 1) What are we doing? Is it something restricted by the plan?
_.each(plans.path[req.method.toLowerCase()],function (path) {
var ret = true;
if (req.url.match(path.re)) {
// this is the one we use
resource = path.name;
ret = false;
}
return (ret);
});
// if we matched a path, and thus have a resource, check this user plan
if (resource) {
if (user) {
db.user(user,function (err,u) {
var limit, usage, plan, name, now = new Date().getTime(), expire;
if (err) {
res.send(400,err);
} else {
if (u) {
if (u.plan) {
plan = u.plan.name ? u.plan : {name:u.plan};
name = plan.name;
}
if (plan.expire) {
expire = plan.expire;
} else if (plan.trial) {
expire = plan.join+plans.trial.duration*msInDay;
}
// is there a fallback plan?
if (expire<now && plan.trial && plans.trial && plans.trial.fallback) {
name = plans.trial.fallback;
}
if (plans.plan[name] && plans.plan[name].limits && plans.plan[name].limits[resource] !== undefined) {
limit = plans.plan[name].limits[resource];
}
if (limit !== undefined && typeof(limit) === "object") {
limit = limit[req.method.toLowerCase()];
}
// did the user have usage?
if (u && u.usage && u.usage[resource]) {
usage = u.usage[resource];
} else if (u && u[resource]) {
usage = u[resource];
}
// so now, were there limits? and did the user exceed them?
if (limit === undefined || limit === null) {
cb();
} else if (usage !== undefined && usage !== null && usage >= limit){
res.send(403,{reason:"subscription",plan:name,limit:resource,maximum:limit});
} else if (expire<now) {
// is the user on a trial plan, and it is expired?
res.send(403,{reason:"subscription",plan:name,expired:true,date:expire});
} else {
cb();
}
} else {
res.send(401);
}
}
});
} else {
res.send(401);
}
} else {
cb();
}
}
],next);
};
}
};