Repository: serverless/serverless (JavaScript)
Date: 2018-01-31 15:44:10+00:00
Task type: Feature

Issue:
Feature Proposal: Restricting alexaSkill function invocations by skill id
<!--
1. If you have a question and not a bug/feature request please ask it at http://forum.serverless.com
2. Please check if an issue already exists so there are no duplicates
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Feature Proposal

## Description

Alexa skills can run any lambda ARNs, given default execution permissions. The [official documentation](https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html#configuring-the-alexa-skills-kit-trigger) encourages setting an "EventSourceToken" (Alexa Skill ID) for each Alexa lambda function, to avoid verifying that the request was intended for your function.

As of today, the following configuration is present:
```yaml
functions:
  alexa:
    handler: handler.alexa
    events:
      - alexaSkill
```

I suggest adding optional parameters for the Alexa skill ID and function status (just like the [alexaSmartHome](https://www.serverless.com/framework/docs/providers/aws/events/alexa-smart-home) event):
```yaml
functions:
  alexa:
    handler: handler.alexa
    events:
      - alexaSkill: amzn1.ask.skill.5ac5b15a-ee34-44d8-97d1-d1c1c658102a
  anotherAlexaSkill:
    handler: handler.anotherAlexaSkill
    events:
      - alexaSkill:
          appId: amzn1.ask.skill.5ac5b15a-ee34-44d8-97d1-d1c1c658102a
          enabled: false
```

## Additional Data

* Maybe add some warning during deployment if this parameter is omitted, in order to encourage people to use it.


Issue discussion:
@kobim Nice catch 👍 
any comments on my thoughts? I was going to create a pull request with that implementation, just wanted to verify there are no modifications required.
Great to hear that you'll engage with this issue 🙌 

The only caveat is, that the implementation must make sure that the old syntax continues to work for backwards compatibility. Additionally, you should update the documentation properly in the PR.
From the behavior description you provided everything feels sound. We'll comment on the details in the PR review then.


Solution patch:
diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md
index 915464bcf1b..3ade42de377 100644
--- a/docs/providers/aws/events/alexa-skill.md
+++ b/docs/providers/aws/events/alexa-skill.md
@@ -14,14 +14,41 @@ layout: Doc
 
 ## Event definition
 
-This will enable your Lambda function to be called by an Alexa skill kit.
+This will enable your Lambda function to be called by an Alexa Skill kit.
+`amzn1.ask.skill.xx-xx-xx-xx-xx` is a skill ID for Alexa Skills kit. You receive a skill ID once you register and create a skill in [Amazon Developer Console](https://developer.amazon.com/).
+After deploying, add your deployed Lambda function ARN to which this event is attached to the Service Endpoint under Configuration on Amazon Developer Console.
 
 ```yml
 functions:
   mySkill:
     handler: mySkill.handler
     events:
-      - alexaSkill
+      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx-xx
 ```
 
 You can find detailed guides on how to create an Alexa Skill with Serverless using NodeJS [here](https://github.com/serverless/examples/tree/master/aws-node-alexa-skill) as well as in combination with Python [here](https://github.com/serverless/examples/tree/master/aws-python-alexa-skill).
+
+## Enabling / Disabling
+
+**Note:** `alexaSkill` events are enabled by default.
+
+This will create and attach a alexaSkill event for the `mySkill` function which is disabled. If enabled it will call
+the `mySkill` function by an Alexa Skill.
+
+```yaml
+functions:
+  mySkill:
+    handler: mySkill.handler
+    events:
+      - alexaSkill:
+          appId: amzn1.ask.skill.xx-xx-xx-xx
+          enabled: false
+```
+
+## Backwards compatability
+
+Previous syntax of this event didn't require a skill ID as parameter, but according to [Amazon's documentation](https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html#configuring-the-alexa-skills-kit-trigger) you should restrict your lambda function to be executed only by your skill.
+
+Omitting the skill id will make your Lambda function available for the public, allowing any other skill developer to invoke it.
+
+(This is important, as [opposing to custom HTTPS endpoints](https://developer.amazon.com/docs/custom-skills/handle-requests-sent-by-alexa.html#request-verify), there's no way to validate the request was sent by your skill.)
\ No newline at end of file
diff --git a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml
index c90e1089a71..6ef081debe8 100644
--- a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml
+++ b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml
@@ -66,7 +66,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
 
diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md
index c9f1c35a771..16608c5b70a 100644
--- a/docs/providers/aws/guide/serverless.yml.md
+++ b/docs/providers/aws/guide/serverless.yml.md
@@ -159,7 +159,9 @@ functions:
           batchSize: 100
           startingPosition: LATEST
           enabled: false
-      - alexaSkill
+      - alexaSkill:
+          appId: amzn1.ask.skill.xx-xx-xx-xx
+          enabled: true
       - alexaSmartHome:
           appId: amzn1.ask.skill.xx-xx-xx-xx
           enabled: true
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index f2c81c776e4..efb408408ce 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -282,8 +282,9 @@ module.exports = {
   getLambdaApiGatewayPermissionLogicalId(functionName) {
     return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionApiGateway`;
   },
-  getLambdaAlexaSkillPermissionLogicalId(functionName) {
-    return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill`;
+  getLambdaAlexaSkillPermissionLogicalId(functionName, alexaSkillIndex) {
+    return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill${
+      alexaSkillIndex || '0'}`;
   },
   getLambdaAlexaSmartHomePermissionLogicalId(functionName, alexaSmartHomeIndex) {
     return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSmartHome${
diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.js
index ecc32cb4451..5baa07ca2d7 100644
--- a/lib/plugins/aws/package/compile/events/alexaSkill/index.js
+++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.js
@@ -15,37 +15,35 @@ class AwsCompileAlexaSkillEvents {
   compileAlexaSkillEvents() {
     this.serverless.service.getAllFunctions().forEach((functionName) => {
       const functionObj = this.serverless.service.getFunction(functionName);
+      let alexaSkillNumberInFunction = 0;
 
       if (functionObj.events) {
         functionObj.events.forEach(event => {
+          let enabled = true;
+          let appId;
           if (event === 'alexaSkill') {
-            const lambdaLogicalId = this.provider.naming
-              .getLambdaLogicalId(functionName);
-
-            const permissionTemplate = {
-              Type: 'AWS::Lambda::Permission',
-              Properties: {
-                FunctionName: {
-                  'Fn::GetAtt': [
-                    lambdaLogicalId,
-                    'Arn',
-                  ],
-                },
-                Action: 'lambda:InvokeFunction',
-                Principal: 'alexa-appkit.amazon.com',
-              },
-            };
-
-            const lambdaPermissionLogicalId = this.provider.naming
-              .getLambdaAlexaSkillPermissionLogicalId(functionName);
-
-            const permissionCloudForamtionResource = {
-              [lambdaPermissionLogicalId]: permissionTemplate,
-            };
-
-            _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
-              permissionCloudForamtionResource);
-          } else if (event.alexaSkill) {
+            const warningMessage = [
+              'Warning! You are using an old syntax for alexaSkill which doesn\'t',
+              ' restrict the invocation solely to your skill.',
+              ' Please refer to the documentation for additional information.',
+            ].join('');
+            this.serverless.cli.log(warningMessage);
+          } else if (_.isString(event.alexaSkill)) {
+            appId = event.alexaSkill;
+          } else if (_.isPlainObject(event.alexaSkill)) {
+            if (!_.isString(event.alexaSkill.appId)) {
+              const errorMessage = [
+                `Missing "appId" property for alexaSkill event in function ${functionName}`,
+                ' The correct syntax is: appId: amzn1.ask.skill.xx-xx-xx-xx-xx',
+                ' OR an object with "appId" property.',
+                ' Please check the docs for more info.',
+              ].join('');
+              throw new this.serverless.classes.Error(errorMessage);
+            }
+            appId = event.alexaSkill.appId;
+            // Parameter `enabled` is optional, hence the explicit non-equal check for false.
+            enabled = event.alexaSkill.enabled !== false;
+          } else {
             const errorMessage = [
               `Alexa Skill event of function "${functionName}" is not an object or string.`,
               ' The correct syntax is: alexaSkill.',
@@ -53,6 +51,39 @@ class AwsCompileAlexaSkillEvents {
             ].join('');
             throw new this.serverless.classes.Error(errorMessage);
           }
+          alexaSkillNumberInFunction++;
+
+          const lambdaLogicalId = this.provider.naming
+            .getLambdaLogicalId(functionName);
+
+          const permissionTemplate = {
+            Type: 'AWS::Lambda::Permission',
+            Properties: {
+              FunctionName: {
+                'Fn::GetAtt': [
+                  lambdaLogicalId,
+                  'Arn',
+                ],
+              },
+              Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction',
+              Principal: 'alexa-appkit.amazon.com',
+            },
+          };
+
+          if (appId) {
+            permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, '');
+          }
+
+          const lambdaPermissionLogicalId = this.provider.naming
+            .getLambdaAlexaSkillPermissionLogicalId(functionName,
+              alexaSkillNumberInFunction);
+
+          const permissionCloudForamtionResource = {
+            [lambdaPermissionLogicalId]: permissionTemplate,
+          };
+
+          _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
+            permissionCloudForamtionResource);
         });
       }
     });
diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml
index fb55c7aacee..12e2196fe43 100644
--- a/lib/plugins/create/templates/aws-csharp/serverless.yml
+++ b/lib/plugins/create/templates/aws-csharp/serverless.yml
@@ -67,7 +67,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml
index 21dd15dd37d..0f8ca52b410 100644
--- a/lib/plugins/create/templates/aws-fsharp/serverless.yml
+++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml
@@ -67,7 +67,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml
index 67f92c77f2e..76819414366 100644
--- a/lib/plugins/create/templates/aws-go-dep/serverless.yml
+++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml
@@ -69,7 +69,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml
index c3a1e2c95d4..47fd7378729 100644
--- a/lib/plugins/create/templates/aws-go/serverless.yml
+++ b/lib/plugins/create/templates/aws-go/serverless.yml
@@ -69,7 +69,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml
index a1adaaf4d92..e7956282923 100644
--- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml
+++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml
@@ -64,7 +64,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml
index e8811732e9e..c1079296fda 100644
--- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml
+++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml
@@ -64,7 +64,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml
index 106bab8aaca..7f94d0131c5 100644
--- a/lib/plugins/create/templates/aws-java-maven/serverless.yml
+++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml
@@ -64,7 +64,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml
index fff1b55109b..434eb3d8155 100644
--- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml
+++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml
@@ -64,7 +64,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml
index cb892140f01..1387dfc8a84 100644
--- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml
+++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml
@@ -64,7 +64,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml
index 5fd69aab122..bbe5cbed097 100644
--- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml
+++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml
@@ -60,7 +60,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml
index 1cce386a131..471855a41df 100644
--- a/lib/plugins/create/templates/aws-nodejs/serverless.yml
+++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml
@@ -69,7 +69,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml
index f651608e047..2486ba648b2 100644
--- a/lib/plugins/create/templates/aws-python/serverless.yml
+++ b/lib/plugins/create/templates/aws-python/serverless.yml
@@ -69,7 +69,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml
index 1d69fcb9da4..3d77f19acd3 100644
--- a/lib/plugins/create/templates/aws-python3/serverless.yml
+++ b/lib/plugins/create/templates/aws-python3/serverless.yml
@@ -69,7 +69,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"
diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml
index 7eb89a39da7..ee0e94d284d 100644
--- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml
+++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml
@@ -66,7 +66,7 @@ functions:
 #      - schedule: rate(10 minutes)
 #      - sns: greeter-topic
 #      - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
-#      - alexaSkill
+#      - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
 #      - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
 #      - iot:
 #          sql: "SELECT * FROM 'some_topic'"


Test patch:
diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js
index 28d5820e99b..370422d73d3 100644
--- a/lib/plugins/aws/deploy/lib/createStack.test.js
+++ b/lib/plugins/aws/deploy/lib/createStack.test.js
@@ -6,7 +6,6 @@ const path = require('path');
 const AwsProvider = require('../../provider/awsProvider');
 const AwsDeploy = require('../index');
 const Serverless = require('../../../../Serverless');
-const BbPromise = require('bluebird');
 const testUtils = require('../../../../../tests/utils');
 
 describe('createStack', () => {
@@ -98,8 +97,7 @@ describe('createStack', () => {
 
     it('should set the createLater flag and resolve if deployment bucket is provided', () => {
       awsDeploy.serverless.service.provider.deploymentBucket = 'serverless';
-      sandbox.stub(awsDeploy.provider, 'request')
-        .returns(BbPromise.reject({ message: 'does not exist' }));
+      sandbox.stub(awsDeploy.provider, 'request').rejects(new Error('does not exist'));
 
       return awsDeploy.createStack().then(() => {
         expect(awsDeploy.createLater).to.equal(true);
diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/lib/plugins/aws/deploy/lib/extendedValidate.test.js
index 7b1d3d9adcf..7cbabdc5fb9 100644
--- a/lib/plugins/aws/deploy/lib/extendedValidate.test.js
+++ b/lib/plugins/aws/deploy/lib/extendedValidate.test.js
@@ -1,6 +1,6 @@
 'use strict';
 
-const expect = require('chai').expect;
+const chai = require('chai');
 const sinon = require('sinon');
 const path = require('path');
 const AwsProvider = require('../../provider/awsProvider');
@@ -8,6 +8,10 @@ const AwsDeploy = require('../index');
 const Serverless = require('../../../../Serverless');
 const testUtils = require('../../../../../tests/utils');
 
+chai.use(require('sinon-chai'));
+
+const expect = chai.expect;
+
 describe('extendedValidate', () => {
   let awsDeploy;
   const tmpDirPath = testUtils.getTmpDirPath();
@@ -60,8 +64,8 @@ describe('extendedValidate', () => {
     });
 
     afterEach(() => {
-      awsDeploy.serverless.utils.fileExistsSync.restore();
-      awsDeploy.serverless.utils.readFileSync.restore();
+      fileExistsSyncStub.restore();
+      readFileSyncStub.restore();
     });
 
     it('should throw error if state file does not exist', () => {
diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js
index df96e23ff68..7c907b34587 100644
--- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js
+++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js
@@ -90,8 +90,8 @@ describe('uploadArtifacts', () => {
     });
 
     afterEach(() => {
-      normalizeFiles.normalizeCloudFormationTemplate.restore();
-      awsDeploy.provider.request.restore();
+      normalizeCloudFormationTemplateStub.restore();
+      uploadStub.restore();
     });
 
     it('should upload the CloudFormation file to the S3 bucket', () => {
@@ -159,8 +159,8 @@ describe('uploadArtifacts', () => {
     });
 
     afterEach(() => {
-      fs.readFileSync.restore();
-      awsDeploy.provider.request.restore();
+      readFileSyncStub.restore();
+      uploadStub.restore();
     });
 
     it('should throw for null artifact paths', () => {
@@ -265,7 +265,7 @@ describe('uploadArtifacts', () => {
           .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact);
         expect(uploadZipFileStub.args[1][0])
           .to.be.equal(awsDeploy.serverless.service.functions.second.package.artifact);
-        awsDeploy.uploadZipFile.restore();
+        uploadZipFileStub.restore();
       });
     });
 
@@ -286,7 +286,7 @@ describe('uploadArtifacts', () => {
 
       const uploadZipFileStub = sinon
         .stub(awsDeploy, 'uploadZipFile').resolves();
-      sinon.stub(fs, 'statSync').returns({ size: 1024 });
+      const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 });
 
       return awsDeploy.uploadFunctions().then(() => {
         expect(uploadZipFileStub.calledTwice).to.be.equal(true);
@@ -294,8 +294,9 @@ describe('uploadArtifacts', () => {
           .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact);
         expect(uploadZipFileStub.args[1][0])
           .to.be.equal(awsDeploy.serverless.service.package.artifact);
-        awsDeploy.uploadZipFile.restore();
-        fs.statSync.restore();
+      }).finally(() => {
+        uploadZipFileStub.restore();
+        statSyncStub.restore();
       });
     });
 
@@ -303,16 +304,16 @@ describe('uploadArtifacts', () => {
       awsDeploy.serverless.config.servicePath = 'some/path';
       awsDeploy.serverless.service.service = 'new-service';
 
-      sinon.stub(fs, 'statSync').returns({ size: 1024 });
-      sinon.stub(awsDeploy, 'uploadZipFile').resolves();
+      const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 });
+      const uploadZipFileStub = sinon.stub(awsDeploy, 'uploadZipFile').resolves();
       sinon.spy(awsDeploy.serverless.cli, 'log');
 
       return awsDeploy.uploadFunctions().then(() => {
         const expected = 'Uploading service .zip file to S3 (1 KB)...';
         expect(awsDeploy.serverless.cli.log.calledWithExactly(expected)).to.be.equal(true);
-
-        fs.statSync.restore();
-        awsDeploy.uploadZipFile.restore();
+      }).finally(() => {
+        statSyncStub.restore();
+        uploadZipFileStub.restore();
       });
     });
   });
diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js
index 7becb143864..530d0621e56 100644
--- a/lib/plugins/aws/lib/naming.test.js
+++ b/lib/plugins/aws/lib/naming.test.js
@@ -464,9 +464,15 @@ describe('#naming()', () => {
 
   describe('#getLambdaAlexaSkillPermissionLogicalId()', () => {
     it('should normalize the function name and append the standard suffix',
+      () => {
+        expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName', 2))
+          .to.equal('FunctionNameLambdaPermissionAlexaSkill2');
+      });
+
+    it('should normalize the function name and append a default suffix if not defined',
       () => {
         expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName'))
-          .to.equal('FunctionNameLambdaPermissionAlexaSkill');
+          .to.equal('FunctionNameLambdaPermissionAlexaSkill0');
       });
   });
 
diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js
index 338a9fd0838..6b94fd4a7f5 100644
--- a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js
+++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js
@@ -1,5 +1,7 @@
 'use strict';
 
+/* eslint-disable no-unused-expressions */
+
 const expect = require('chai').expect;
 const AwsProvider = require('../../../../provider/awsProvider');
 const AwsCompileAlexaSkillEvents = require('./index');
@@ -8,11 +10,19 @@ const Serverless = require('../../../../../../Serverless');
 describe('AwsCompileAlexaSkillEvents', () => {
   let serverless;
   let awsCompileAlexaSkillEvents;
+  let consolePrinted;
 
   beforeEach(() => {
     serverless = new Serverless();
     serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
     serverless.setProvider('aws', new AwsProvider(serverless));
+    consolePrinted = '';
+    serverless.cli = {
+      // serverless.cli isn't available in tests, so we will mimic it.
+      log: txt => {
+        consolePrinted += `${txt}\r\n`;
+      },
+    };
     awsCompileAlexaSkillEvents = new AwsCompileAlexaSkillEvents(serverless);
   });
 
@@ -25,7 +35,42 @@ describe('AwsCompileAlexaSkillEvents', () => {
   });
 
   describe('#compileAlexaSkillEvents()', () => {
-    it('should throw an error if alexaSkill event is not an string', () => {
+    it('should show a warning if alexaSkill appId is not specified', () => {
+      awsCompileAlexaSkillEvents.serverless.service.functions = {
+        first: {
+          events: [
+            'alexaSkill',
+          ],
+        },
+      };
+
+      awsCompileAlexaSkillEvents.compileAlexaSkillEvents();
+
+      expect(consolePrinted).to.contain.string('old syntax for alexaSkill');
+
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Type
+      ).to.equal('AWS::Lambda::Permission');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName
+      ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.Action
+      ).to.equal('lambda:InvokeFunction');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.Principal
+      ).to.equal('alexa-appkit.amazon.com');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken
+      ).to.be.undefined;
+    });
+
+    it('should throw an error if alexaSkill event is not a string or an object', () => {
       awsCompileAlexaSkillEvents.serverless.service.functions = {
         first: {
           events: [
@@ -39,11 +84,36 @@ describe('AwsCompileAlexaSkillEvents', () => {
       expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error);
     });
 
-    it('should create corresponding resources when a alexaSkill event is provided', () => {
+    it('should throw an error if alexaSkill event appId is not a string', () => {
       awsCompileAlexaSkillEvents.serverless.service.functions = {
         first: {
           events: [
-            'alexaSkill',
+            {
+              alexaSkill: {
+                appId: 42,
+              },
+            },
+          ],
+        },
+      };
+
+      expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error);
+    });
+
+    it('should create corresponding resources when multiple alexaSkill events are provided', () => {
+      const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx';
+      const skillId2 = 'amzn1.ask.skill.yy-yy-yy-yy';
+      awsCompileAlexaSkillEvents.serverless.service.functions = {
+        first: {
+          events: [
+            {
+              alexaSkill: skillId1,
+            },
+            {
+              alexaSkill: {
+                appId: skillId2,
+              },
+            },
           ],
         },
       };
@@ -52,20 +122,84 @@ describe('AwsCompileAlexaSkillEvents', () => {
 
       expect(awsCompileAlexaSkillEvents.serverless.service
         .provider.compiledCloudFormationTemplate.Resources
-        .FirstLambdaPermissionAlexaSkill.Type
+        .FirstLambdaPermissionAlexaSkill1.Type
+      ).to.equal('AWS::Lambda::Permission');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName
+      ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.Action
+      ).to.equal('lambda:InvokeFunction');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.Principal
+      ).to.equal('alexa-appkit.amazon.com');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken
+      ).to.equal(skillId1);
+
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill2.Type
       ).to.equal('AWS::Lambda::Permission');
       expect(awsCompileAlexaSkillEvents.serverless.service
         .provider.compiledCloudFormationTemplate.Resources
-        .FirstLambdaPermissionAlexaSkill.Properties.FunctionName
+        .FirstLambdaPermissionAlexaSkill2.Properties.FunctionName
       ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
       expect(awsCompileAlexaSkillEvents.serverless.service
         .provider.compiledCloudFormationTemplate.Resources
-        .FirstLambdaPermissionAlexaSkill.Properties.Action
+        .FirstLambdaPermissionAlexaSkill2.Properties.Action
       ).to.equal('lambda:InvokeFunction');
       expect(awsCompileAlexaSkillEvents.serverless.service
         .provider.compiledCloudFormationTemplate.Resources
-        .FirstLambdaPermissionAlexaSkill.Properties.Principal
+        .FirstLambdaPermissionAlexaSkill2.Properties.Principal
+      ).to.equal('alexa-appkit.amazon.com');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill2.Properties.EventSourceToken
+      ).to.equal(skillId2);
+    });
+
+    it('should create corresponding resources when a disabled alexaSkill event is provided', () => {
+      const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx';
+      awsCompileAlexaSkillEvents.serverless.service.functions = {
+        first: {
+          events: [
+            {
+              alexaSkill: {
+                appId: skillId1,
+                enabled: false,
+              },
+            },
+          ],
+        },
+      };
+
+      awsCompileAlexaSkillEvents.compileAlexaSkillEvents();
+
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Type
+      ).to.equal('AWS::Lambda::Permission');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName
+      ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.Action
+      ).to.equal('lambda:DisableInvokeFunction');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.Principal
       ).to.equal('alexa-appkit.amazon.com');
+      expect(awsCompileAlexaSkillEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources
+        .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken
+      ).to.equal(skillId1);
     });
 
     it('should not create corresponding resources when alexaSkill event is not given', () => {