nitrogen-core
Version:
Core services used across ingestion, registry, and consumption servers.
166 lines (130 loc) • 5.43 kB
JavaScript
var async = require('async')
, core = require('../../lib')
, crypto = require('crypto')
, jwt = require('jsonwebtoken')
, moment = require('moment')
, mongoose = require('mongoose');
var cacheKeyToken = function(token) {
return "token." + token;
};
var clearTokenCacheEntry = function(token, callback) {
var cacheKey = cacheKeyToken(token);
core.log.debug('accessTokens: clearing cache entry ' + cacheKey);
core.config.cache_provider.del('accessTokens', cacheKey, callback);
};
var create = function(principal, options, callback) {
core.log.debug('accesstokens: creating accesstoken for principal: ' + principal.id);
if (typeof(options) === "function") {
callback = options;
options = {};
}
var expiration = moment().add(core.config.access_token_lifetime, 'days');
if (options.expires) {
expiration = moment(new Date(options.expires));
}
var accessToken = new core.models.AccessToken({
expires_at: expiration,
principal: principal
});
var expirationMins = (expiration - Date.now()) / (1000 * 60);
accessToken.token = jwt.sign({
iss: principal.id
}, core.config.access_token_signing_key, { expiresInMinutes: expirationMins });
accessToken.save(callback);
};
var find = function(query, options, callback) {
core.models.AccessToken.find(query, null, options, callback);
};
var findByPrincipal = function(principal, callback) {
find({ principal: principal.id }, { sort: { expires_at: -1 } }, callback);
};
var findByTokenCached = function(token, callback) {
var cacheKey = cacheKeyToken(token);
core.config.cache_provider.get('accessTokens', cacheKey, function(err, accessTokenObj) {
if (err) return callback(err);
if (accessTokenObj) {
core.log.debug("accessTokens: " + cacheKey + ": cache hit");
var accessToken = new core.models.AccessToken(accessTokenObj);
// Mongoose by default will override the passed id with a new unique one. Set it back.
accessToken._id = mongoose.Types.ObjectId(accessTokenObj.id);
return callback(null, accessToken);
}
core.log.debug("accessTokens: " + cacheKey + ": cache miss.");
// find and cache result
return findByToken(token, callback);
});
};
var findByToken = function(token, callback) {
core.models.AccessToken.findOne({
token: token
}, function(err, accessToken) {
if (err) return callback(err);
if (!accessToken) return callback(null, undefined);
var cacheKey = cacheKeyToken(token);
core.log.debug("accessTokens: setting cache entry for " + cacheKey);
core.config.cache_provider.set('accessTokens', cacheKey, accessToken, accessToken.expires_at, function(err) {
return callback(err, accessToken);
});
});
};
var findOrCreateToken = function(principal, callback) {
findByPrincipal(principal, function(err, tokens) {
if (err) return callback(err);
if (tokens && tokens.length > 0) {
core.log.debug('accesstokens: found existing accesstoken for principal: ' + JSON.stringify(tokens[0]));
}
if (tokens && tokens.length > 0 && !isCloseToExpiration(tokens[0])) {
return callback(null, tokens[0]);
} else {
create(principal, function(err, accessToken) {
if (err) return callback(err);
callback(null, accessToken);
});
}
});
};
// an access token is close to expiration if less than 10% of its original life exists.
var isCloseToExpiration = function(accessToken) {
return accessToken.secondsToExpiration() < core.config.refresh_token_threshold * core.config.access_token_lifetime * 24 * 60 * 60;
};
var remove = function(query, callback) {
find(query, {}, function(err, accessTokens) {
if (err) return callback(err);
// remove all matches from cache before removal
async.eachLimit(accessTokens, 20, function(accessToken, cb) {
clearTokenCacheEntry(accessToken.token, cb);
}, function(err) {
if (err) return callback(err);
core.models.AccessToken.remove(query, callback);
});
});
};
var removeByPrincipal = function(principal, callback) {
remove({ principal: principal._id }, callback);
};
var verify = function(token, done) {
jwt.verify(token, core.config.access_token_signing_key, function(err, jwtToken) {
if (err) return done(err);
core.services.principals.findByIdCached(core.services.principals.servicePrincipal, jwtToken.iss, function(err, principal) {
if (err) return done(err);
if (!principal) {
var msg = "AccessToken service.verify: principal for accessToken " + token + " with id " + jwtToken.iss + " not found.";
core.log.error(msg);
return done(new Error(msg));
}
principal.jwtToken = jwtToken;
done(null, principal);
});
});
};
module.exports = {
create: create,
findByPrincipal: findByPrincipal,
findByToken: findByToken,
findByTokenCached: findByTokenCached,
findOrCreateToken: findOrCreateToken,
isCloseToExpiration: isCloseToExpiration,
remove: remove,
removeByPrincipal: removeByPrincipal,
verify: verify
};