UNPKG

deep-package-manager

Version:
553 lines (447 loc) 16.8 kB
/** * Created by CCristi on 6/27/16. */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.CognitoIdentityProviderService = undefined; var _AbstractService = require('./AbstractService'); var _LambdaService = require('./LambdaService'); var _FailedToCreateCognitoUserPoolException = require('./Exception/FailedToCreateCognitoUserPoolException'); var _FailedToUpdateUserPoolException = require('./Exception/FailedToUpdateUserPoolException'); var _FailedToCreateAdminUserException = require('./Exception/FailedToCreateAdminUserException'); var _CognitoIdentityService = require('./CognitoIdentityService'); var _deepCore = require('deep-core'); var _deepCore2 = _interopRequireDefault(_deepCore); var _passwordGenerator = require('password-generator'); var _passwordGenerator2 = _interopRequireDefault(_passwordGenerator); var _awsSdk = require('aws-sdk'); var _awsSdk2 = _interopRequireDefault(_awsSdk); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } global.AWS = _awsSdk2.default; // amazon cognito js need AWS object to be set globally require('amazon-cognito-js'); class CognitoIdentityProviderService extends _AbstractService.AbstractService { /** * @param {Object[]} args */ constructor(...args) { super(...args); this._userPoolMetadata = null; } /** * @returns {String} */ name() { return _deepCore2.default.AWS.Service.COGNITO_IDENTITY_PROVIDER; } /** * @returns {CognitoIdentityProviderService} * @private */ _setup() { let oldPool = this._config.userPool; if (this.isCognitoPoolEnabled && !oldPool) { this._createUserPool().then(userPool => { this._config.userPool = userPool; this._config.providerName = this._generateCognitoProviderName(userPool); return this._createUserPoolClients(userPool); }).then(userPoolClients => { this._config.userPoolClients = userPoolClients; this._ready = true; }); return this; } this._ready = true; return this; } /** * @returns {CognitoIdentityProviderService} * @private */ _postProvision() { // @todo: implement! if (this._isUpdate) { this._readyTeardown = true; return this; } this._readyTeardown = true; return this; } /** * @returns {CognitoIdentityProviderService} * @private */ _postDeployProvision() /* services */{ if (!this.isCognitoPoolEnabled) { this._ready = true; return this; } this._registerUserPoolTriggers().then(() => this._config.adminUser || this._createAdminUser()).then(adminUser => { this._config.adminUser = adminUser; this._ready = true; }); return this; } /** * @returns {Promise} * @private */ _createAdminUser() { let globals = this.property.config.globals; let adminMetadata = (globals.security || {}).admin; if (!adminMetadata) { return Promise.resolve(null); } let cognitoIdentityServiceProvider = this.provisioning.cognitoIdentityServiceProvider; let systemClientApp = this._config.userPoolClients[CognitoIdentityProviderService.SYSTEM_CLIENT_APP]; let adminUserPayload = { ClientId: systemClientApp.ClientId, Password: this._generatePseudoRandomPassword(), Username: adminMetadata.email, UserAttributes: [{ Name: 'email', Value: adminMetadata.email }] }; return cognitoIdentityServiceProvider.signUp(adminUserPayload).promise().then(() => { let payload = { UserPoolId: this._config.userPool.Id, Username: adminUserPayload.Username }; return cognitoIdentityServiceProvider.adminGetUser(payload).promise().then(response => { return response.UserStatus === CognitoIdentityProviderService.CONFIRMED_STATUS ? Promise.resolve(null) : cognitoIdentityServiceProvider.adminConfirmSignUp(payload).promise(); }); }).then(() => this._authenticateAdminUser(adminUserPayload)).then(identityId => { adminUserPayload.identityId = identityId; return adminUserPayload; }).catch(e => { // @todo: Sorry guys :/, Promise suppresses any kind of synchronous errors. setImmediate(() => { throw new _FailedToCreateAdminUserException.FailedToCreateAdminUserException(e); }); }); } /** * @param {Object} adminUser * @returns {Promise} * @private */ _authenticateAdminUser(adminUser) { let cognitoIdentityServiceProvider = this.provisioning.cognitoIdentityServiceProvider; let systemClientApp = this._config.userPoolClients[CognitoIdentityProviderService.SYSTEM_CLIENT_APP]; let userPool = this._config.userPool; let authPayload = { AuthFlow: 'ADMIN_NO_SRP_AUTH', UserPoolId: userPool.Id, ClientId: systemClientApp.ClientId, AuthParameters: { USERNAME: adminUser.Username, PASSWORD: adminUser.Password } }; return cognitoIdentityServiceProvider.adminInitiateAuth(authPayload).promise().then(authResponse => { let cognitoConfig = this.provisioning.services.find(_CognitoIdentityService.CognitoIdentityService).config(); let cognitoParams = { IdentityPoolId: cognitoConfig.identityPool.IdentityPoolId, Logins: {} }; cognitoParams.Logins[this._config.providerName] = authResponse.AuthenticationResult.IdToken; let credentials = new _awsSdk2.default.CognitoIdentityCredentials(cognitoParams); return new Promise((resolve, reject) => { credentials.refresh(error => { if (error) { return reject(error); } resolve(credentials.identityId); }); }); }); } /** * @returns {Promise} * @private */ _createUserPool() { let cognitoIdentityServiceProvider = this.provisioning.cognitoIdentityServiceProvider; let userPoolMetadata = this.userPoolMetadata; let payload = { PoolName: userPoolMetadata.poolName, Policies: { PasswordPolicy: userPoolMetadata.passwordPolicy }, UserPoolAddOns: { AdvancedSecurityMode: 'OFF' } }; let emailVerifications = userPoolMetadata.verifications.email; if (emailVerifications && emailVerifications.enabled) { payload.AutoVerifiedAttributes = ['email']; } return cognitoIdentityServiceProvider.createUserPool(payload).promise().then(data => data.UserPool).catch(e => { setImmediate(() => { throw new _FailedToCreateCognitoUserPoolException.FailedToCreateCognitoUserPoolException(userPoolMetadata.poolName, e); }); }); } /** * @param {Object} userPool * @returns {Promise} * @private */ _createUserPoolClients(userPool) { let cognitoIdentityServiceProvider = this.provisioning.cognitoIdentityServiceProvider; let promises = []; this.userPoolMetadata.clients.forEach(clientObj => { let payload = { UserPoolId: userPool.Id, // JavaScript SDK doesn't support apps that have a client secret, // http://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html GenerateSecret: false, ClientName: clientObj.Name, RefreshTokenValidity: clientObj.RefreshTokenValidity, ExplicitAuthFlows: ['ADMIN_NO_SRP_AUTH'] }; promises.push(cognitoIdentityServiceProvider.createUserPoolClient(payload).promise()); }); return Promise.all(promises).then(responses => { let clientsMap = {}; responses.forEach(response => { let client = response.UserPoolClient; clientsMap[client.ClientName] = client; }); return clientsMap; }).catch(e => { setImmediate(() => { throw new _FailedToCreateCognitoUserPoolException.FailedToCreateCognitoUserPoolException(this.userPoolMetadata.poolName, e); }); }); } /** * @todo: add user schema specific attributes * @returns {{enabled: Boolean, name: String}} */ get userPoolMetadata() { if (this._userPoolMetadata === null) { let globalConfig = this.property.config.globals; let poolConfig = globalConfig.security && globalConfig.security.userPool ? globalConfig.security.userPool : {}; // @todo - remove fallback to globalConfig.userPool && globalConfig.userPool.enabled // when "enabled" parameter will be consolidated into deep-account under security key let enabled = poolConfig.enabled || globalConfig.userPool && globalConfig.userPool.enabled; // @link http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-user-pools-using-the-access-token let refreshTokenValidity = poolConfig.refreshTokenValidity || 3560; let clients = []; let clientsNames = (poolConfig.clients || '').trim(); clientsNames = clientsNames ? clientsNames.split(/[\s,]+/) : []; if (clientsNames.indexOf(CognitoIdentityProviderService.SYSTEM_CLIENT_APP) === -1) { clientsNames.push(CognitoIdentityProviderService.SYSTEM_CLIENT_APP); } clientsNames.forEach(clientName => { clients.push({ Name: clientName, RefreshTokenValidity: refreshTokenValidity }); }); this._userPoolMetadata = { enabled: enabled, clients: clients, verifications: poolConfig.verifications || {}, poolName: this.generateAwsResourceName(CognitoIdentityProviderService.USER_POOL_NAME, this.name()), passwordPolicy: CognitoIdentityProviderService.DEFAULT_PASSWORD_POLICY }; } return this._userPoolMetadata; } /** * @private * @returns {Promise} */ _registerUserPoolTriggers() { let userPool = this._config.userPool; let cognitoIdentityServiceProvider = this.provisioning.cognitoIdentityServiceProvider; let triggers = this._extractUserPoolTriggers(); if (Object.keys(triggers).length === 0) { return Promise.resolve(userPool); } userPool.LambdaConfig = triggers; let payload = Object.assign({ UserPoolId: userPool.Id }, userPool); // cleanup payload from `UnexpectedParameters`, to avoid UnexpectedParameterException ['Id', 'Name', 'LastModifiedDate', 'CreationDate', 'SchemaAttributes', 'EstimatedNumberOfUsers'].forEach(key => { delete payload[key]; }); return cognitoIdentityServiceProvider.updateUserPool(payload).promise().then(() => { //@todo: move permissions provision into LambdaService? let lambda = this.provisioning.lambda; let userPoolId = this._config.userPool.Id; let cognitoIdpArn = this._generateCognitoIdpArn(); let promises = Object.keys(triggers).map(triggerName => { let lambdaArn = triggers[triggerName]; let payload = { Action: 'lambda:InvokeFunction', Principal: _deepCore2.default.AWS.Service.identifier(this.name()), FunctionName: lambdaArn, StatementId: `${triggerName}_${userPoolId}`, SourceArn: cognitoIdpArn }; return lambda.addPermission(payload).promise(); }); return Promise.all(promises); }).catch(e => { if (e.code === 'ResourceConflictException') { return Promise.resolve(userPool); } setImmediate(() => { throw new _FailedToUpdateUserPoolException.FailedToUpdateUserPoolException(userPool.Name, e); }); }); } /** * @see: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#createUserPool-property, LambdaConfig * @returns {Object} * @private */ _extractUserPoolTriggers() { let globalConfig = this.property.config.globals; let lambdaService = this.provisioning.services.find(_LambdaService.LambdaService); let triggers = ((globalConfig.security || {}).userPool || {}).triggers || {}; let normalizedTriggers = {}; for (let triggerName in triggers) { if (!triggers.hasOwnProperty(triggerName)) { continue; } let deepResourceName = triggers[triggerName]; let lambdaArn = lambdaService.resolveDeepResourceName(deepResourceName); if (lambdaArn) { normalizedTriggers[triggerName] = lambdaArn; } else { //@todo: Throw error here? console.warn(`Unknown deep resource name: "${deepResourceName}". ` + `Skipping setting cognito user pool "${triggerName}" trigger...`); } } return normalizedTriggers; } /** * @param {String[]} actions * @returns {Object} */ generateAllowActionsStatement(actions) { let policy = new _deepCore2.default.AWS.IAM.Policy(); let statement = policy.statement.add(); actions.forEach(actionName => { statement.action.add(_deepCore2.default.AWS.Service.COGNITO_IDENTITY_PROVIDER, actionName); }); statement.resource.add(_deepCore2.default.AWS.Service.COGNITO_IDENTITY_PROVIDER, this.provisioning.cognitoIdentityServiceProvider.config.region, this.awsAccountId, `userpool/${this._config.userPool.Id}`); return statement; } /** * @returns {String} * @private */ _generateCognitoIdpArn() { let userPool = this._config.userPool; let region = this.provisioning.cognitoIdentityServiceProvider.config.region; return `arn:aws:${this.name()}:${region}:${this.awsAccountId}:userpool/${userPool.Id}`; } /** * @returns {String} * @private */ _generatePseudoRandomPassword() { const policy = CognitoIdentityProviderService.DEFAULT_PASSWORD_POLICY; let minLength = policy.MinimumLength; let maxLength = minLength + 8; let uppercaseMinCount = 2; let lowercaseMinCount = 2; let numberMinCount = 2; let specialMinCount = 2; let UPPERCASE_RE = /([A-Z])/g; let LOWERCASE_RE = /([a-z])/g; let NUMBER_RE = /([\d])/g; let SPECIAL_CHAR_RE = /([\?\-])/g; let isStrongEnough = password => { let uc = password.match(UPPERCASE_RE); let lc = password.match(LOWERCASE_RE); let n = password.match(NUMBER_RE); let sc = password.match(SPECIAL_CHAR_RE); if (password.length < minLength) { return false; } if (policy.RequireUppercase && !uc || uc && uc.length < uppercaseMinCount) { return false; } if (policy.RequireLowercase && !lc || lc && lc.length < lowercaseMinCount) { return false; } if (policy.RequireNumbers && !n || n && n.length < numberMinCount) { return false; } if (policy.RequireSymbols && !sc || sc && sc.length < specialMinCount) { return false; } return true; }; let customPassword = () => { let password = ''; let randomLength = Math.floor(Math.random() * (maxLength - minLength)) + minLength; while (!isStrongEnough(password)) { password = (0, _passwordGenerator2.default)(randomLength, false, /[\w\d\?\-]/); } return password; }; return customPassword(); } /** * @param {Object} userPool * @returns {String} * @private */ _generateCognitoProviderName(userPool) { let region = this.provisioning.cognitoIdentityServiceProvider.config.region; return `${this.name()}.${region}.amazonaws.com/${userPool.Id}`; } /** * @returns {Boolean} */ get isCognitoPoolEnabled() { return this.userPoolMetadata.enabled; } /** * @returns {String} */ static get CONFIRMED_STATUS() { return 'CONFIRMED'; } /** * @returns {String} */ static get USER_POOL_NAME() { return 'UserPool'; } /** * @returns {String} */ static get SYSTEM_CLIENT_APP() { return 'system'; } /** * @returns {{MinimumLength: Number, RequireUppercase: Boolean, RequireLowercase: Boolean, RequireNumbers: Boolean, RequireSymbols: Boolean}} */ static get DEFAULT_PASSWORD_POLICY() { return { MinimumLength: 8, RequireUppercase: true, RequireLowercase: true, RequireNumbers: true, RequireSymbols: false }; } /** * @returns {String[]} */ static get AVAILABLE_REGIONS() { return [_deepCore2.default.AWS.Region.AP_NORTHEAST_TOKYO, _deepCore2.default.AWS.Region.AP_NORTHEAST_SEOUL, _deepCore2.default.AWS.Region.AP_SOUTHEAST_SYDNEY, _deepCore2.default.AWS.Region.AP_SOUTH_MUMBAI, _deepCore2.default.AWS.Region.EU_CENTRAL_FRANKFURT, _deepCore2.default.AWS.Region.EU_WEST_IRELAND, _deepCore2.default.AWS.Region.EU_WEST_LONDON, _deepCore2.default.AWS.Region.US_EAST_VIRGINIA, _deepCore2.default.AWS.Region.US_EAST_OHIO, _deepCore2.default.AWS.Region.US_WEST_OREGON]; } } exports.CognitoIdentityProviderService = CognitoIdentityProviderService;