diff --git a/docs/providers/aws/README.md b/docs/providers/aws/README.md index d464d02fc..f9dc3ced4 100644 --- a/docs/providers/aws/README.md +++ b/docs/providers/aws/README.md @@ -90,6 +90,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
  • IoT
  • CloudWatch Event
  • CloudWatch Log
  • +
  • Cognito User Pool
  • diff --git a/docs/providers/aws/events/cognito-user-pool.md b/docs/providers/aws/events/cognito-user-pool.md new file mode 100644 index 000000000..0c2b65207 --- /dev/null +++ b/docs/providers/aws/events/cognito-user-pool.md @@ -0,0 +1,116 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/cognito-user-pool) + + +# Cognito User Pool + +## Valid Triggers + +Serverless supports all Cognito User Pool Triggers as specified [here][aws-triggers-list]. + +## Simple event definition + +This will create a Cognito User Pool with the specified name. You can reference the same pool multiple times. + +```yml +functions: + preSignUp: + handler: preSignUp.handler + events: + - cognitoUserPool: + pool: MyUserPool + trigger: PreSignUp + customMessage: + handler: customMessage.handler + events: + - cognitoUserPool: + pool: MyUserPool + trigger: CustomMessage +``` + +## Multiple pools event definitions + +This will create multiple Cognito User Pools with their specified names: + +```yml +functions: + preSignUpForPool1: + handler: preSignUp.handler + events: + - cognitoUserPool: + pool: MyUserPool1 + trigger: PreSignUp + preSignUpForPool2: + handler: preSignUp.handler + events: + - cognitoUserPool: + pool: MyUserPool2 + trigger: PreSignUp +``` + +You can also deploy the same function for different user pools: + +```yml +functions: + preSignUp: + handler: preSignUp.handler + events: + - cognitoUserPool: + pool: MyUserPool1 + trigger: PreSignUp + - cognitoUserPool: + pool: MyUserPool2 + trigger: PreSignUp +``` + +## Custom message trigger handlers + +For custom messages, you will need to check `event.triggerSource` type inside your handler function: + +```js +// customMessage.js +function handler(event, context, callback) { + if (event.triggerSource === 'CustomMessage_AdminCreateUser') { + // ... + } + if (event.triggerSource === 'CustomMessage_ResendCode') { + // ... + } +} +``` + +## Overriding a generated User Pool + +A Cognito User Pool created by an event can be overridden by using the [logical resource name][logical-resource-names] in `Resources`: + +```yml +functions: + preSignUp: + handler: preSignUpForPool1.handler + events: + - cognitoUserPool: + pool: MyUserPool + trigger: PreSignUp + postConfirmation: + handler: postConfirmation.handler + events: + - cognitoUserPool: + pool: MyUserPool + trigger: PostConfirmation + +resources: + Resources: + CognitoUserPoolMyUserPool: + Type: AWS::Cognito::UserPool +``` + +[aws-triggers-list]: http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html#cognito-user-pools-lambda-trigger-syntax-shared +[logical-resource-names]: ../guide/resources#aws-cloudformation-resource-reference diff --git a/docs/providers/aws/guide/resources.md b/docs/providers/aws/guide/resources.md index aecb4c462..6007cbbc1 100644 --- a/docs/providers/aws/guide/resources.md +++ b/docs/providers/aws/guide/resources.md @@ -70,7 +70,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This |Lambda::Function | {normalizedFunctionName}LambdaFunction | HelloLambdaFunction | |Lambda::Version | {normalizedFunctionName}LambdaVersion{sha256} | HelloLambdaVersionr3pgoTvv1xT4E4NiCL6JG02fl6vIyi7OS1aW0FwAI | |Logs::LogGroup | {normalizedFunctionName}LogGroup | HelloLogGroup | -|Lambda::Permission | | | +|Lambda::Permission | | | |Events::Rule | | | |AWS::Logs::SubscriptionFilter | {normalizedFuntionName}LogsSubscriptionFilterCloudWatchLog{SequentialID} | HelloLogsSubscriptionFilterCloudWatchLog1 | |AWS::IoT::TopicRule | {normalizedFuntionName}IotTopicRule{SequentialID} | HelloIotTopicRule1 | @@ -83,6 +83,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This |SNS::Topic | SNSTopic{normalizedTopicName} | SNSTopicSometopic | |SNS::Subscription | {normalizedFunctionName}SnsSubscription{normalizedTopicName} | HelloSnsSubscriptionSomeTopic | |AWS::Lambda::EventSourceMapping | | | +|Cognito::UserPool | CognitoUserPool{normalizedPoolId} | CognitoUserPoolPoolId | ## Override AWS CloudFormation Resource diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 05c1bf301..2c81c81a4 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -137,6 +137,9 @@ functions: - cloudwatchLog: logGroup: '/aws/lambda/hello' filter: '{$.userIdentity.type = Root}' + - cognitoUserPool: + pool: MyUserPool + trigger: PreSignUp # The "Resources" your "Functions" use. Raw AWS CloudFormation goes in here. resources: diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 48b655570..8149aa451 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -36,6 +36,7 @@ "./aws/package/compile/events/iot/index.js", "./aws/package/compile/events/cloudWatchEvent/index.js", "./aws/package/compile/events/cloudWatchLog/index.js", + "./aws/package/compile/events/cognitoUserPool/index.js", "./aws/deployFunction/index.js", "./aws/deployList/index.js", "./aws/invokeLocal/index.js" diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index e81d281fe..82fe10abe 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -246,6 +246,11 @@ module.exports = { .getNormalizedFunctionName(functionName)}LogsSubscriptionFilterCloudWatchLog${logsIndex}`; }, + // Cognito User Pool + getCognitoUserPoolLogicalId(poolId) { + return `CognitoUserPool${this.normalizeNameToAlphaNumericOnly(poolId)}`; + }, + // Permissions getLambdaS3PermissionLogicalId(functionName, bucketName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${this @@ -278,4 +283,9 @@ module.exports = { return `${this.getNormalizedFunctionName(functionName) }LambdaPermissionLogsSubscriptionFilterCloudWatchLog${logsIndex}`; }, + getLambdaCognitoUserPoolPermissionLogicalId(functionName, poolId, triggerSource) { + return `${this + .getNormalizedFunctionName(functionName)}LambdaPermissionCognitoUserPool${ + this.normalizeNameToAlphaNumericOnly(poolId)}TriggerSource${triggerSource}`; + }, }; diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 950e92ff0..ae1f77de0 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -388,6 +388,13 @@ describe('#naming()', () => { }); }); + describe('#getCognitoUserPoolLogicalId()', () => { + it('should normalize the user pool name and add the standard prefix', () => { + expect(sdk.naming.getCognitoUserPoolLogicalId('us-east-1_v123sDAS1')) + .to.equal('CognitoUserPoolUseast1v123sDAS1'); + }); + }); + describe('#getLambdaS3PermissionLogicalId()', () => { it('should normalize the function name and add the standard suffix', () => { expect(sdk.naming.getLambdaS3PermissionLogicalId('functionName', 'bucket')) @@ -463,4 +470,14 @@ describe('#naming()', () => { .to.equal('FunctionNameLambdaPermissionLogsSubscriptionFilterCloudWatchLog0'); }); }); + + describe('#getLambdaCognitoUserPoolPermissionLogicalId()', () => { + it('should normalize the function name and add the standard suffix', () => { + expect(sdk.naming.getLambdaCognitoUserPoolPermissionLogicalId( + 'functionName', + 'Pool1', + 'CustomMessage' + )).to.equal('FunctionNameLambdaPermissionCognitoUserPoolPool1TriggerSourceCustomMessage'); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js new file mode 100644 index 000000000..d94063701 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -0,0 +1,164 @@ +'use strict'; + +const _ = require('lodash'); + +const validTriggerSources = [ + 'PreSignUp', + 'PostConfirmation', + 'PreAuthentication', + 'PostAuthentication', + 'CustomMessage', + 'DefineAuthChallenge', + 'CreateAuthChallenge', + 'VerifyAuthChallengeResponse', +]; + +class AwsCompileCognitoUserPoolEvents { + constructor(serverless) { + this.serverless = serverless; + this.provider = this.serverless.getProvider('aws'); + + this.hooks = { + 'package:compileEvents': this.compileCognitoUserPoolEvents.bind(this), + }; + } + + compileCognitoUserPoolEvents() { + const userPools = []; + const cognitoUserPoolTriggerFunctions = []; + + // Iterate through all functions declared in `serverless.yml` + this.serverless.service.getAllFunctions().forEach((functionName) => { + const functionObj = this.serverless.service.getFunction(functionName); + + if (functionObj.events) { + functionObj.events.forEach(event => { + if (event.cognitoUserPool) { + // Check event definition for `cognitoUserPool` object + if (typeof event.cognitoUserPool === 'object') { + // Check `cognitoUserPool` object has required properties + if (!event.cognitoUserPool.pool || !event.cognitoUserPool.trigger) { + throw new this.serverless.classes + .Error([ + `Cognito User Pool event of function "${functionName}" is not an object.`, + 'The correct syntax is an object with the "pool" and "trigger" properties.', + 'Please check the docs for more info.', + ].join(' ')); + } + + // Check `cognitoUserPool` trigger is valid + if (!_.includes(validTriggerSources, event.cognitoUserPool.trigger)) { + throw new this.serverless.classes + .Error([ + 'Cognito User Pool trigger source is invalid, must be one of:', + `${validTriggerSources.join(', ')}.`, + 'Please check the docs for more info.', + ].join(' ')); + } + + // Save trigger functions so we can use them to generate + // IAM permissions later + cognitoUserPoolTriggerFunctions.push({ + functionName, + poolName: event.cognitoUserPool.pool, + triggerSource: event.cognitoUserPool.trigger, + }); + + // Save user pools so we can use them to generate + // CloudFormation resources later + userPools.push(event.cognitoUserPool.pool); + } else { + throw new this.serverless.classes + .Error([ + `Cognito User Pool event of function "${functionName}" is not an object.`, + 'The correct syntax is an object with the "pool" and "trigger" properties.', + 'Please check the docs for more info.', + ].join(' ')); + } + } + }); + } + }); + + // Generate CloudFormation templates for Cognito User Pool changes + _.forEach(userPools, (poolName) => { + // Create a `LambdaConfig` object for the CloudFormation template + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { + poolName, + }); + + const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + + // Return a new object to avoid lint errors + return Object.assign({}, result, { + [value.triggerSource]: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + }); + }, {}); + + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + + const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this + .provider.naming.getLambdaLogicalId(value.functionName)); + + const userPoolTemplate = { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: poolName, + LambdaConfig: lambdaConfig, + }, + DependsOn, + }; + + const userPoolCFResource = { + [userPoolLogicalId]: userPoolTemplate, + }; + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + userPoolCFResource); + }); + + // Generate CloudFormation templates for IAM permissions to allow Cognito to trigger Lambda + cognitoUserPoolTriggerFunctions.forEach((cognitoUserPoolTriggerFunction) => { + const userPoolLogicalId = this.provider.naming + .getCognitoUserPoolLogicalId(cognitoUserPoolTriggerFunction.poolName); + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(cognitoUserPoolTriggerFunction.functionName); + + const permissionTemplate = { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: 'cognito-idp.amazonaws.com', + SourceArn: { + 'Fn::GetAtt': [ + userPoolLogicalId, + 'Arn', + ], + }, + }, + }; + const lambdaPermissionLogicalId = this.provider.naming + .getLambdaCognitoUserPoolPermissionLogicalId(cognitoUserPoolTriggerFunction.functionName, + cognitoUserPoolTriggerFunction.poolName, cognitoUserPoolTriggerFunction.triggerSource); + const permissionCFResource = { + [lambdaPermissionLogicalId]: permissionTemplate, + }; + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCFResource); + }); + } +} + +module.exports = AwsCompileCognitoUserPoolEvents; diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js new file mode 100644 index 000000000..6ec9a6c28 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js @@ -0,0 +1,282 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsProvider = require('../../../../provider/awsProvider'); +const AwsCompileCognitoUserPoolEvents = require('./index'); +const Serverless = require('../../../../../../Serverless'); + +describe('AwsCompileCognitoUserPoolEvents', () => { + let serverless; + let awsCompileCognitoUserPoolEvents; + + beforeEach(() => { + serverless = new Serverless(); + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + serverless.setProvider('aws', new AwsProvider(serverless)); + awsCompileCognitoUserPoolEvents = new AwsCompileCognitoUserPoolEvents(serverless); + awsCompileCognitoUserPoolEvents.serverless.service.service = 'new-service'; + }); + + describe('#constructor()', () => { + it('should set the provider variable to an instance of AwsProvider', () => + expect(awsCompileCognitoUserPoolEvents.provider).to.be.instanceof(AwsProvider)); + }); + + describe('#compileCognitoUserPoolEvents()', () => { + it('should throw an error if cognitoUserPool event type is not an object', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: 42, + }, + ], + }, + }; + + expect(() => awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents()).to.throw(Error); + }); + + it('should throw an error if the "pool" property is not given', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: null, + }, + }, + ], + }, + }; + + expect(() => awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents()).to.throw(Error); + }); + + it('should throw an error if the "trigger" property is not given', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + trigger: null, + }, + }, + ], + }, + }; + + expect(() => awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents()).to.throw(Error); + }); + + it('should throw an error if the "trigger" property is invalid', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'invalidTrigger', + }, + }, + ], + }, + }; + + expect(() => awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents()).to.throw(Error); + }); + + it('should create resources when CUP events are given as separate functions', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool1', + trigger: 'PreSignUp', + }, + }, + ], + }, + second: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool2', + trigger: 'PostConfirmation', + }, + }, + ], + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .SecondLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePostConfirmation.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should create resources when CUP events are given with the same function', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool1', + trigger: 'PreSignUp', + }, + }, + { + cognitoUserPool: { + pool: 'MyUserPool2', + trigger: 'PostConfirmation', + }, + }, + ], + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePostConfirmation.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should create resources when CUP events are given with diff funcs and single event', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool1', + trigger: 'PreSignUp', + }, + }, + ], + }, + second: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool2', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1 + .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] + ).to.equal(serverless.service.serverless.getProvider('aws') + .naming.getLambdaLogicalId('first')); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2 + .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] + ).to.equal(serverless.service.serverless.getProvider('aws') + .naming.getLambdaLogicalId('second')); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .SecondLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should create single user pool resource when the same pool referenced repeatedly', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + second: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PostConfirmation', + }, + }, + ], + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig).length + ).to.equal(2); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .SecondLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePostConfirmation.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should not create resources when CUP events are not given', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [], + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + ).to.deep.equal({}); + }); + }); +}); diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index 57e708604..e1f60bedb 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -79,6 +79,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index ca9c60b82..9ef210f6b 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -77,6 +77,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index 53a22f47c..d421ab1d5 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -77,6 +77,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index a677a2212..d6fe45620 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -77,6 +77,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 6cdb54b2c..059b386d4 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -82,6 +82,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index d3d0b6285..110ae584f 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -82,6 +82,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index 156d8608a..73cce39e6 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -82,6 +82,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 309390a97..c7ce81f69 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -79,6 +79,9 @@ functions: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp # Define function environment variables here # environment: diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js new file mode 100644 index 000000000..a1e2afe62 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js @@ -0,0 +1,27 @@ +'use strict'; + +const preSignUp = (event, context, callback) => { + const nextEvent = Object.assign({}, event); + nextEvent.response.autoConfirmUser = true; + + process.stdout.write(JSON.stringify(nextEvent)); + callback(null, nextEvent); +}; + +const customMessage = (event, context, callback) => { + const nextEvent = Object.assign({}, event); + if (event.triggerSource === 'CustomMessage_SignUp') { + nextEvent.response.smsMessage = `Welcome to the service. Your confirmation code is ${ + event.request.codeParameter}`; + nextEvent.response.emailSubject = 'Welcome to the service'; + nextEvent.response.emailMessage = `Thank you for signing up. ${ + event.request.codeParameter} is your verification code`; + } + process.stdout.write(JSON.stringify(nextEvent)); + callback(null, nextEvent); +}; + +module.exports.preSignUp1 = preSignUp; +module.exports.preSignUp2 = preSignUp; +module.exports.customMessage1 = customMessage; +module.exports.customMessage2 = customMessage; diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml new file mode 100644 index 000000000..7dc6f310a --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml @@ -0,0 +1,31 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs6.10 + +functions: + preSignUp1: + handler: handler.preSignUp1 + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_1} + trigger: PreSignUp + customMessage1: + handler: handler.customMessage1 + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_1} + trigger: CustomMessage + preSignUp2: + handler: handler.preSignUp2 + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_2} + trigger: PreSignUp + customMessage2: + handler: handler.customMessage2 + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_2} + trigger: CustomMessage diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js new file mode 100644 index 000000000..df2ba5d40 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js @@ -0,0 +1,77 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const Utils = require('../../../../utils/index'); + +describe('AWS - Cognito User Pool: Multiple User Pools with multiple ' + + 'events with multiple functions', () => { + beforeAll(() => { + Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + Utils.deployService(); + }); + + it('should call the specified function on the first User Pool when PreSignUp ' + + 'event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) + .then((poolId) => + Promise.all([ + poolId, + Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), + ]) + ) + .delay(60000) + .then((promiseResponse) => { + const poolId = promiseResponse[0]; + const logs = Utils.getFunctionLogs('preSignUp1'); + + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); + }) + ); + + it('should call the specified function on the first User Pool when CustomMessage ' + + 'event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) + .then((poolId) => { + const logs = Utils.getFunctionLogs('customMessage1'); + + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"CustomMessage_AdminCreateUser"/g.test(logs)).to.equal(true); + }) + ); + + it('should call the specified function on the second User Pool when PreSignUp ' + + 'event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_2) + .then((poolId) => + Promise.all([ + poolId, + Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), + ]) + ) + .delay(60000) + .then((promiseResponse) => { + const poolId = promiseResponse[0]; + const logs = Utils.getFunctionLogs('preSignUp2'); + + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); + }) + ); + + it('should call the specified function on the second User Pool when CustomMessage ' + + 'event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_2) + .then((poolId) => { + const logs = Utils.getFunctionLogs('customMessage2'); + + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"CustomMessage_AdminCreateUser"/g.test(logs)).to.equal(true); + }) + ); + + afterAll(() => { + Utils.removeService(); + }); +}); diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js new file mode 100644 index 000000000..b7f044321 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports.preSignUp = (event, context, callback) => { + const nextEvent = Object.assign({}, event); + nextEvent.response.autoConfirmUser = true; + + process.stdout.write(JSON.stringify(nextEvent)); + callback(null, nextEvent); +}; diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml new file mode 100644 index 000000000..121de6025 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml @@ -0,0 +1,16 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs6.10 + +functions: + preSignUp: + handler: handler.preSignUp + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_1} + trigger: PreSignUp + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_2} + trigger: PreSignUp diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js new file mode 100644 index 000000000..9a9477545 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js @@ -0,0 +1,53 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const Utils = require('../../../../utils/index'); + +describe('AWS - Cognito User Pool: Multiple User Pools with single ' + + 'event with single function', () => { + beforeAll(() => { + Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + Utils.deployService(); + }); + + it('should call the specified function on the first User Pool when PreSignUp ' + + 'event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) + .then((poolId) => + Promise.all([ + poolId, + Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), + ]) + ) + .delay(60000) + .then((promiseResponse) => { + const poolId = promiseResponse[0]; + const logs = Utils.getFunctionLogs('preSignUp'); + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); + }) + ); + + it('should call the specified function on the second User Pool when PreSignUp ' + + 'event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_2) + .then((poolId) => + Promise.all([ + poolId, + Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), + ]) + ) + .delay(60000) + .then((promiseResponse) => { + const poolId = promiseResponse[0]; + const logs = Utils.getFunctionLogs('preSignUp'); + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); + }) + ); + + afterAll(() => { + Utils.removeService(); + }); +}); diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js new file mode 100644 index 000000000..2801cf8c5 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js @@ -0,0 +1,22 @@ +'use strict'; + +module.exports.preSignUp = (event, context, callback) => { + const nextEvent = Object.assign({}, event); + nextEvent.response.autoConfirmUser = true; + + process.stdout.write(JSON.stringify(nextEvent)); + callback(null, nextEvent); +}; + +module.exports.customMessage = (event, context, callback) => { + const nextEvent = Object.assign({}, event); + if (event.triggerSource === 'CustomMessage_SignUp') { + nextEvent.response.smsMessage = `Welcome to the service. Your confirmation code is ${ + event.request.codeParameter}`; + nextEvent.response.emailSubject = 'Welcome to the service'; + nextEvent.response.emailMessage = `Thank you for signing up. ${ + event.request.codeParameter} is your verification code`; + } + process.stdout.write(JSON.stringify(nextEvent)); + callback(null, nextEvent); +}; diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml new file mode 100644 index 000000000..9ecaf3f5f --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml @@ -0,0 +1,19 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs6.10 + +functions: + preSignUp: + handler: handler.preSignUp + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_1} + trigger: PreSignUp + customMessage: + handler: handler.customMessage + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_1} + trigger: CustomMessage diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js new file mode 100644 index 000000000..96d25c39b --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js @@ -0,0 +1,39 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const Utils = require('../../../../utils/index'); + +describe('AWS - Cognito User Pool: Single User Pool with multiple ' + + 'events with multiple functions', () => { + beforeAll(() => { + Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + Utils.deployService(); + }); + + it('should call the specified function when PreSignUp event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) + .then((poolId) => + Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!') + ) + .delay(60000) + .then(() => { + const logs = Utils.getFunctionLogs('preSignUp'); + expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); + }) + ); + + it('should call the specified function when CustomMessage event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) + .then((poolId) => { + const logs = Utils.getFunctionLogs('customMessage'); + + expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); + expect(/"triggerSource":"CustomMessage_AdminCreateUser"/g.test(logs)).to.equal(true); + }) + ); + + afterAll(() => { + Utils.removeService(); + }); +}); diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js new file mode 100644 index 000000000..b7f044321 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports.preSignUp = (event, context, callback) => { + const nextEvent = Object.assign({}, event); + nextEvent.response.autoConfirmUser = true; + + process.stdout.write(JSON.stringify(nextEvent)); + callback(null, nextEvent); +}; diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml new file mode 100644 index 000000000..97167e310 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml @@ -0,0 +1,13 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs6.10 + +functions: + preSignUp: + handler: handler.preSignUp + events: + - cognitoUserPool: + pool: ${env:COGNITO_USER_POOL_1} + trigger: PreSignUp diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js new file mode 100644 index 000000000..b77837821 --- /dev/null +++ b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js @@ -0,0 +1,29 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const Utils = require('../../../../utils/index'); + +describe('AWS - Cognito User Pool: Single User Pool with single ' + + 'event with single function', () => { + beforeAll(() => { + Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + Utils.deployService(); + }); + + it('should call the specified function when PreSignUp event is triggered', () => Utils + .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) + .then((poolId) => + Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!') + ) + .delay(60000) + .then(() => { + const logs = Utils.getFunctionLogs('preSignUp'); + expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); + }) + ); + + afterAll(() => { + Utils.removeService(); + }); +}); diff --git a/tests/utils/index.js b/tests/utils/index.js index a6d6cd5a3..358f6a1bd 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -51,6 +51,8 @@ module.exports = { process.env.TOPIC_2 = `${serviceName}-1`; process.env.BUCKET_1 = `${serviceName}-1`; process.env.BUCKET_2 = `${serviceName}-2`; + process.env.COGNITO_USER_POOL_1 = `${serviceName}-1`; + process.env.COGNITO_USER_POOL_2 = `${serviceName}-2`; // return the name of the CloudFormation stack return `${serviceName}-dev`; @@ -163,6 +165,32 @@ module.exports = { return cwe.putEventsPromised(params); }, + getCognitoUserPoolId(userPoolName) { + const cisp = new AWS.CognitoIdentityServiceProvider({ region: 'us-east-1' }); + BbPromise.promisifyAll(cisp, { suffix: 'Promised' }); + + const params = { + MaxResults: 50, + }; + + return cisp.listUserPoolsPromised(params) + .then((data) => data.UserPools.find((userPool) => + RegExp(userPoolName, 'g').test(userPool.Name)).Id + ); + }, + + createCognitoUser(userPoolId, username, password) { + const cisp = new AWS.CognitoIdentityServiceProvider({ region: 'us-east-1' }); + BbPromise.promisifyAll(cisp, { suffix: 'Promised' }); + + const params = { + UserPoolId: userPoolId, + Username: username, + TemporaryPassword: password, + }; + return cisp.adminCreateUserPromised(params); + }, + getFunctionLogs(functionName) { const logs = execSync(`${serverlessExec} logs --function ${functionName} --noGreeting true`); const logsString = new Buffer(logs, 'base64').toString();