deep-package-manager
Version:
DEEP Package Manager
553 lines (447 loc) • 16.8 kB
JavaScript
/**
* 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;