nightscout
Version:
Nightscout acts as a web-based CGM (Continuous Glucose Monitor) to allow multiple caregivers to remotely view a patients glucose data in realtime.
259 lines (206 loc) • 7.17 kB
JavaScript
;
var _ = require('lodash');
var crypto = require('crypto');
var shiroTrie = require('shiro-trie');
var ObjectID = require('mongodb').ObjectID;
var find_options = require('../server/query');
function init (env, ctx) {
var storage = { };
var rolesCollection = ctx.store.collection(env.authentication_collections_prefix + 'roles');
var subjectsCollection = ctx.store.collection(env.authentication_collections_prefix + 'subjects');
storage.queryOpts = {
dateField: 'created_at'
, noDateFilter: true
};
function query_for (opts) {
return find_options(opts, storage.queryOpts);
}
function create (collection) {
function doCreate(obj, fn) {
if (!Object.prototype.hasOwnProperty.call(obj, 'created_at')) {
obj.created_at = (new Date()).toISOString();
}
collection.insert(obj, function (err, doc) {
if (err != null && err.message) {
console.log('Data insertion error', err.message);
fn(err.message, null);
return;
}
storage.reload(function loaded() {
fn(null, doc.ops);
});
});
}
return doCreate;
}
function list (collection) {
function doList(opts, fn) {
// these functions, find, sort, and limit, are used to
// dynamically configure the request, based on the options we've
// been given
// determine sort options
function sort() {
return opts && opts.sort || {date: -1};
}
// configure the limit portion of the current query
function limit() {
if (opts && opts.count) {
return this.limit(parseInt(opts.count));
}
return this;
}
// handle all the results
function toArray(err, entries) {
fn(err, entries);
}
// now just stitch them all together
limit.call(collection
.find(query_for(opts))
.sort(sort())
).toArray(toArray);
}
return doList;
}
function remove (collection) {
function doRemove (_id, callback) {
collection.remove({ '_id': new ObjectID(_id) }, function (err) {
storage.reload(function loaded() {
callback(err, null);
});
});
}
return doRemove;
}
function save (collection) {
function doSave (obj, callback) {
obj._id = new ObjectID(obj._id);
if (!obj.created_at) {
obj.created_at = (new Date()).toISOString();
}
collection.save(obj, function (err) {
//id should be added for new docs
storage.reload(function loaded() {
callback(err, obj);
});
});
}
return doSave;
}
storage.createSubject = create(subjectsCollection);
storage.saveSubject = save(subjectsCollection);
storage.removeSubject = remove(subjectsCollection);
storage.listSubjects = list(subjectsCollection);
storage.createRole = create(rolesCollection);
storage.saveRole = save(rolesCollection);
storage.removeRole = remove(rolesCollection);
storage.listRoles = list(rolesCollection);
storage.defaultRoles = [
{ name: 'admin', permissions: ['*'] }
, { name: 'denied', permissions: [ ] }
, { name: 'status-only', permissions: [ 'api:status:read' ] }
, { name: 'readable', permissions: [ '*:*:read' ] }
, { name: 'careportal', permissions: [ 'api:treatments:create' ] }
, { name: 'devicestatus-upload', permissions: [ 'api:devicestatus:create' ] }
, { name: 'activity', permissions: [ 'api:activity:create' ] }
];
storage.getSHA1 = function getSHA1 (message) {
var shasum = crypto.createHash('sha1');
shasum.update(message);
return shasum.digest('hex');
}
storage.reload = function reload (callback) {
storage.listRoles({sort: {name: 1}}, function listResults (err, results) {
if (err) {
return callback && callback(err);
}
storage.roles = results || [ ];
_.forEach(storage.defaultRoles, function eachRole (role) {
if (_.isEmpty(_.find(storage.roles, {name: role.name}))) {
storage.roles.push(role);
}
});
storage.roles = _.sortBy(storage.roles, 'name');
storage.listSubjects({sort: {name: 1}}, function listResults (err, results) {
if (err) {
return callback && callback(err);
}
storage.subjects = _.map(results, function eachSubject (subject) {
if (env.enclave.isApiKeySet()) {
subject.digest = env.enclave.getSubjectHash(subject._id.toString());
var abbrev = subject.name.toLowerCase().replace(/[\W]/g, '').substring(0, 10);
subject.accessToken = abbrev + '-' + subject.digest.substring(0, 16);
subject.accessTokenDigest = storage.getSHA1(subject.accessToken);
}
return subject;
});
if (callback) {
callback( );
}
});
});
};
storage.findRole = function findRole (roleName) {
return _.find(storage.roles, {name: roleName});
};
storage.roleToShiro = function roleToShiro (roleName) {
var shiro = null;
var role = storage.findRole(roleName);
if (role) {
shiro = shiroTrie.new();
shiro.add(role.permissions);
}
return shiro;
};
storage.rolesToShiros = function roleToShiro (roleNames) {
return _.chain(roleNames)
.map(storage.roleToShiro)
.reject(_.isEmpty)
.value();
};
storage.roleToPermissions = function roleToPermissions (roleName) {
var permissions = [ ];
var role = storage.findRole(roleName);
if (role) {
permissions = role.permissions;
}
return permissions;
};
storage.findSubject = function findSubject (accessToken) {
if (!accessToken) return null;
function checkToken(accessToken) {
var split_token = accessToken.split('-');
var prefix = split_token ? _.last(split_token) : '';
if (prefix.length < 16) {
return null;
}
return _.find(storage.subjects, function matches (subject) {
return subject.accessTokenDigest.indexOf(accessToken) === 0 || subject.digest.indexOf(prefix) === 0;
});
}
if (!Array.isArray(accessToken)) accessToken = [accessToken];
for (let i=0; i < accessToken.length; i++) {
const subject = checkToken(accessToken[i]);
if (subject) return subject;
}
return null;
};
storage.doesAccessTokenExist = function doesAccessTokenExist(accessToken) {
if (storage.findSubject(accessToken)) {
return true;
}
return false;
}
storage.resolveSubjectAndPermissions = function resolveSubjectAndPermissions (accessToken) {
var shiros = [];
var subject = storage.findSubject(accessToken);
if (subject) {
shiros = storage.rolesToShiros(subject.roles);
}
return {
subject: subject
, shiros: shiros
};
};
return storage;
}
module.exports = init;