UNPKG

deep-security

Version:
753 lines (597 loc) 19.8 kB
/** * 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; }();