deep-security
Version:
DEEP Security Library
753 lines (597 loc) • 19.8 kB
JavaScript
/**
* Created by mgoria on 6/23/15.
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Token = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _awsSdk = require('aws-sdk');
var _awsSdk2 = _interopRequireDefault(_awsSdk);
var _deepCore = require('deep-core');
var _deepCore2 = _interopRequireDefault(_deepCore);
var _util = require('util');
var _util2 = _interopRequireDefault(_util);
var _IdentityProviderTokenExpiredException = require('./Exception/IdentityProviderTokenExpiredException');
var _DescribeIdentityException = require('./Exception/DescribeIdentityException');
var _Security = require('./Security');
var _TokenManager = require('./TokenManager');
var _CredentialsManager = require('./CredentialsManager');
var _IdentityProvider = require('./IdentityProvider');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Security token holds details about logged user
*/
let Token = exports.Token = function () {
_createClass(Token, null, [{
key: 'MAX_RETRIES',
/**
* @returns {number}
*/
get: function get() {
return 3;
}
/**
* @returns {number}
*/
}, {
key: 'RETRIES_INTERVAL_MS',
get: function get() {
return 200;
}
/**
* @param {String} identityPoolId
*/
}]);
function Token(identityPoolId) {
_classCallCheck(this, Token);
this._identityPoolId = identityPoolId;
this._lambdaContext = null;
this._user = null;
this._identityMetadata = null;
this._tokenExpiredCallback = null;
this._identityProvider = null;
this._userProvider = null;
this._roleResolver = null;
this._logService = null;
this._cacheService = null;
this._credsPromises = {};
this._tokenManager = new _TokenManager.TokenManager(identityPoolId);
this._credentialsManager = new _CredentialsManager.CredentialsManager(this);
this._sts = new _awsSdk2.default.STS();
this._setupAwsCognitoConfig();
}
/**
* Setup region for CognitoIdentity and CognitoSync services
*
* @private
*/
_createClass(Token, [{
key: '_setupAwsCognitoConfig',
value: function _setupAwsCognitoConfig() {
// @todo: set retries in a smarter way...
_awsSdk2.default.config.maxRetries = 3;
let cognitoRegion = Token.getRegionFromIdentityPoolId(this._identityPoolId);
_awsSdk2.default.config.update({
cognitoidentity: { region: cognitoRegion },
cognitosync: { region: cognitoRegion }
});
}
/**
* @returns {CredentialsManager}
*/
}, {
key: '_tryLoadIdentityProvider',
/**
* @returns {Promise<IdentityProvider>}
* @private
*/
value: function _tryLoadIdentityProvider() {
if (this._identityProvider) {
return Promise.resolve(this._identityProvider);
}
return new Promise(resolve => {
this._cacheService.get(Token.IDENTITY_PROVIDER_CACHE_KEY, (error, rawProvider) => {
if (error || !rawProvider) {
return resolve(null);
}
let providerSnapshot = JSON.parse(rawProvider);
let providerInstance = _IdentityProvider.IdentityProvider.createFromSnapshot(providerSnapshot);
resolve(providerInstance.isTokenValid() ? providerInstance : null);
});
});
}
/**
* Example: token.isAllowed('deep-security:role:create').then(boolean => {});
*
* @param {String} authScope
* @returns {Promise}
*/
}, {
key: 'isAllowed',
value: function isAllowed(authScope) {
return this._roleResolver.resolve(authScope).then(role => {
return !!role;
});
}
/**
* @returns {Promise}
*/
}, {
key: 'loadLambdaCredentials',
value: function loadLambdaCredentials() {
return new Promise((resolve, reject) => {
this._cacheService.get('credentialsCache', (error, credentialsCache) => {
if (error && error.name !== 'MissingCacheException') {
return reject(error);
}
credentialsCache = credentialsCache || {};
// overwrite env credentials each time to avoid their expiration
credentialsCache.default = _deepCore2.default.AWS.ENV_CREDENTIALS;
this._sts.config.credentials = credentialsCache.default;
this.getUser((error, user) => {
if (error) {
return reject(error);
}
if (!user || !user.ActiveAccount || !user.ActiveAccount.BackendRole) {
return resolve(credentialsCache.default);
}
let awsRole = user.ActiveAccount.BackendRole;
if (credentialsCache.hasOwnProperty(awsRole.Arn) && this._credentialsManager.validCredentials(credentialsCache[awsRole.Arn])) {
return resolve(credentialsCache[awsRole.Arn]);
}
let stsParams = {
RoleArn: awsRole.Arn,
RoleSessionName: `backend-role-${awsRole.Name}`
};
this._stsAssumeRole(stsParams).then(response => {
let credentialsObj = response.Credentials;
let credentials = new _awsSdk2.default.Credentials({
accessKeyId: credentialsObj.AccessKeyId,
secretAccessKey: credentialsObj.SecretAccessKey,
sessionToken: credentialsObj.SessionToken
});
credentials.expireTime = credentialsObj.Expiration;
credentialsCache[awsRole.Arn] = credentials;
// save backend credentials asynchronously
this._cacheService.set('credentialsCache', credentialsCache);
return resolve(credentialsCache[awsRole.Arn]);
}).catch(reject);
});
});
});
}
/**
* @param {Object} stsParams
* @param {Number} _retryCount
* @returns {Promise}
* @private
*/
}, {
key: '_stsAssumeRole',
value: function _stsAssumeRole(stsParams) {
let _retryCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
return this._sts.assumeRole(stsParams).promise().catch(e => {
if (_retryCount++ < Token.MAX_RETRIES) {
console.warn(`Retrying "sts:assumeRole" with params: ${JSON.stringify(stsParams)}`);
return new Promise((resolve, reject) => {
setTimeout(() => {
this._stsAssumeRole(stsParams, _retryCount).then(resolve).catch(reject);
}, Math.pow(2, _retryCount) * 1000);
});
}
throw e;
});
}
/**
* @param {Function} callback
* @param {String|null} authScope
* @returns {Promise}
*/
}, {
key: 'loadCredentials',
value: function loadCredentials() {
let callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : () => {};
let authScope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
let scopeKey = authScope ? authScope.toString() : 'default';
let event = {
service: 'deep-security',
resourceType: 'Cognito',
resourceId: this._identityPoolId,
eventName: 'loadCredentials',
eventId: _Security.Security.customEventId(this._identityPoolId),
time: new Date().getTime()
};
let proxyCallback = (error, credentials) => {
if (error instanceof _IdentityProviderTokenExpiredException.IdentityProviderTokenExpiredException && typeof this._tokenExpiredCallback === 'function') {
this._tokenExpiredCallback(this.identityProvider);
this._identityProvider = null;
}
// log event only after credentials are loaded to get identityId
this._logService.rumLog(event);
event = _util2.default._extend({}, event);
event.payload = { error: error, credentials: {} }; // avoid logging user credentials
this._logService.rumLog(event);
delete this._credsPromises[scopeKey];
// run callback async, to avoid catching sync errors
setTimeout(() => {
callback(error, credentials);
}, 0);
};
if (!this._credsPromises.hasOwnProperty(scopeKey)) {
this._credsPromises[scopeKey] = this._tryLoadIdentityProvider().then(identityProvider => {
this._identityProvider = identityProvider;
return this._loadTokenSnapshot();
}).then(tokenSnapshot => {
if (tokenSnapshot) {
this._fillFromTokenSnapshot(tokenSnapshot);
}
return this._credentialsManager.getCredentials();
}).then(defaultCredentials => {
if (!authScope) {
return defaultCredentials;
}
// roleResolver needs system credentials to be loaded
return this._roleResolver.resolve(authScope).then(role => {
return this._credentialsManager.getCredentials(role);
});
}).then(credentials => {
if (!this.lambdaContext) {
return this._saveToken().then(() => credentials).catch(() => Promise.resolve(credentials));
}
return credentials;
});
}
return this._credsPromises[scopeKey].then(credentials => proxyCallback(null, credentials)).catch(error => proxyCallback(error, null));
}
/**
* @returns {*}
* @private
*/
}, {
key: '_loadTokenSnapshot',
value: function _loadTokenSnapshot() {
return this._credentialsManager.getCredentials(null, false).then(credentials => {
if (this._credentialsManager.validCredentials(credentials)) {
return null; // do not load token snapshot if credentials are already valid
} else if (this.lambdaContext) {
return this._tokenManager.loadBackendToken(this.identityId);
} else {
_awsSdk2.default.config.credentials = credentials; // CognitoSyncClient requires credentials to be set
return this._tokenManager.loadFrontendToken();
}
});
}
/**
* @param {Object} tokenSnapshot
* @returns {Token}
* @private
*/
}, {
key: '_fillFromTokenSnapshot',
value: function _fillFromTokenSnapshot(tokenSnapshot) {
let providerSnapshot = tokenSnapshot.identityProvider;
if (providerSnapshot) {
if (!this._identityProvider && this._lambdaContext) {
this._identityProvider = _IdentityProvider.IdentityProvider.createFromSnapshot(providerSnapshot);
}
}
if (this._credentialsManager.validCredentials(tokenSnapshot.credentials)) {
this._credentialsManager.systemCredentials = tokenSnapshot.credentials;
this._credentialsManager.rolesCredentials = tokenSnapshot.rolesCredentials;
}
return this;
}
/**
* @returns {Promise}
* @private
*/
}, {
key: '_saveToken',
value: function _saveToken() {
if (this.identityProvider) {
this._cacheService.set(Token.IDENTITY_PROVIDER_CACHE_KEY, JSON.stringify(this._identityProvider.toJSON()), parseInt((this.identityProvider.tokenExpirationTime.getTime() - Date.now()) / 1000));
}
return this._tokenManager.saveToken(this);
}
/**
* @returns {String}
*/
}, {
key: 'getUser',
/**
* @param {Function} callback
*/
value: function getUser(callback) {
// @todo: backward compatibility hook, remove on next major release
let argsHandler = (error, user) => {
if (callback.length === 1) {
if (error) {
throw error;
}
return callback(user);
}
callback(error, user);
};
if (this.lambdaContext) {
this._loadUser(argsHandler);
// this._describeIdentity(this.identityId).then(() => {
// this._loadUser(argsHandler);
// }).catch(argsHandler);
} else {
this._loadUser(argsHandler);
}
}
/**
* @param {Function} callback
* @private
*/
}, {
key: '_loadUser',
value: function _loadUser(callback) {
if (this.isAnonymous) {
callback(null);
return;
}
if (!this._user) {
this._userProvider.loadUserByIdentityId(this.identityId, (error, user) => {
if (error) {
callback(error, null);
return;
}
this._user = user;
callback(null, this._user);
});
return;
}
callback(null, this._user);
}
/**
* @param {String} identityId
* @returns {Promise}
* @private
*/
}, {
key: '_describeIdentity',
value: function _describeIdentity(identityId) {
if (this._identityMetadata) {
return Promise.resolve(this._identityMetadata);
}
let cognitoIdentity = new _awsSdk2.default.CognitoIdentity({
credentials: _deepCore2.default.AWS.ENV_CREDENTIALS
});
return cognitoIdentity.describeIdentity({ IdentityId: identityId }).promise().then(data => {
this._identityMetadata = data;
return data;
}).catch(error => {
throw new _DescribeIdentityException.DescribeIdentityException(identityId, error);
});
}
/**
* @returns {Array}
* @private
*/
}, {
key: 'registerTokenExpiredCallback',
/**
* @param {Function} callback
* @returns {Token}
*/
value: function registerTokenExpiredCallback(callback) {
if (typeof callback !== 'function') {
throw new _deepCore.Exception.InvalidArgumentException(callback, 'function');
}
this._tokenExpiredCallback = callback;
return this;
}
/**
* Removes identity credentials related cached stuff
* @returns {Promise}
*/
}, {
key: 'destroy',
value: function destroy() {
return Promise.all(Object.keys(this._credsPromises).map(k => this._credsPromises[k])).catch(e => Promise.resolve(null)).then(() => {
// clear cache, even on credentials load error
this._credentialsManager.clearCache();
this._tokenManager.deleteToken();
this._cacheService.invalidate(Token.IDENTITY_PROVIDER_CACHE_KEY);
this._credsPromises = {};
this._identityProvider = null;
});
}
/**
* @returns {Object}
*/
}, {
key: 'toJSON',
value: function toJSON() {
return {
credentials: this._credentialsManager.systemCredentials,
rolesCredentials: this._credentialsManager.rolesCredentials,
identityId: this.identityId,
identityProvider: this._identityProvider.toJSON()
};
}
/**
* @param {String} identityPoolId
* @returns {String}
*/
}, {
key: 'credentialsManager',
get: function get() {
return this._credentialsManager;
}
/**
* @returns {IdentityProvider}
*/
}, {
key: 'identityProvider',
get: function get() {
return this._identityProvider;
}
/**
* @returns {String}
*/
,
/**
* @param {IdentityProvider} provider
*/
set: function set(provider) {
this._identityProvider = provider;
}
/**
* @param {Cache|LocalStorageDriver} cacheService
*/
}, {
key: 'identityPoolId',
get: function get() {
return this._identityPoolId;
}
}, {
key: 'cacheService',
set: function set(cacheService) {
this._cacheService = cacheService;
}
/**
* @returns {Object}
*/
}, {
key: 'lambdaContext',
get: function get() {
return this._lambdaContext;
}
/**
* @param {Object} lambdaContext
*/
,
set: function set(lambdaContext) {
this._lambdaContext = lambdaContext;
}
/**
* @param {Object} logService
*/
}, {
key: 'logService',
set: function set(logService) {
this._logService = logService;
}
/**
* @param {RoleResolver} roleResolver
*/
}, {
key: 'roleResolver',
set: function set(roleResolver) {
this._roleResolver = roleResolver;
}
/**
* @param {Object} user
*/
}, {
key: 'user',
set: function set(user) {
this._user = user;
}
/**
* @returns {Object}
*/
,
get: function get() {
return this._user;
}
}, {
key: 'identityId',
get: function get() {
let identityId = null;
let credentials = this.credentialsManager.systemCredentials;
if (this.lambdaContext) {
identityId = this.lambdaContext.identity.cognitoIdentityId;
} else if (credentials) {
if (credentials.identityId) {
identityId = credentials.identityId;
} else if (credentials.params && credentials.params.IdentityId) {
// load IdentityId from localStorage cache
identityId = credentials.params.IdentityId;
} else if (this._tokenManager.identityId) {
identityId = this._tokenManager.identityId;
}
}
return identityId;
}
/**
* @returns {Boolean}
*/
}, {
key: 'isAnonymous',
get: function get() {
if (this.lambdaContext) {
// @todo: find a better way instead of describe identity
return false; //this._identityLogins.length <= 0;
} else {
return !this.identityProvider;
}
}
/**
* @param {UserProvider} userProvider
*/
}, {
key: 'userProvider',
set: function set(userProvider) {
this._userProvider = userProvider;
}
}, {
key: '_identityLogins',
get: function get() {
return this._identityMetadata && this._identityMetadata.hasOwnProperty('Logins') ? this._identityMetadata.Logins : [];
}
}], [{
key: 'getRegionFromIdentityPoolId',
value: function getRegionFromIdentityPoolId(identityPoolId) {
return identityPoolId.split(':')[0];
}
/**
* @param {String} identityPoolId
* @returns {Token}
*/
}, {
key: 'create',
value: function create(identityPoolId) {
return new this(identityPoolId);
}
/**
* @param {String} identityPoolId
* @param {IdentityProvider} identityProvider
* @returns {Token}
*/
}, {
key: 'createFromIdentityProvider',
value: function createFromIdentityProvider(identityPoolId, identityProvider) {
let token = new this(identityPoolId);
token.identityProvider = identityProvider;
return token;
}
/**
* @param {String} identityPoolId
* @param {Object} lambdaContext
* @returns {Token}
*/
}, {
key: 'createFromLambdaContext',
value: function createFromLambdaContext(identityPoolId, lambdaContext) {
let token = new this(identityPoolId);
token.lambdaContext = lambdaContext;
return token;
}
/**
* @returns {String}
*/
}, {
key: 'IDENTITY_PROVIDER_CACHE_KEY',
get: function get() {
return '__deep_framework|security|token|identity-provider';
}
}]);
return Token;
}();