Repository: serverless/serverless (JavaScript)
Date: 2017-02-03 18:33:12+00:00
Task type: Bug Fix

Issue:
Multiple claims for authorizer break body mapping template
<!--
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 Bug Report

## Description
Setting multiple claims for an authorizer breaks the body mapping template. 

For bug reports:
* What went wrong?
Received the following error from API Gateway: `{"message": "Could not parse request body into json: Unexpected character (\',\' (code 44)): was expecting double-quote to start field name\n at...`

* What did you expect should have happened?
Body mapping template to correctly parse claim fields. 
* What was the config you used?
The below:
``` yaml
...
    events:
      - http:
          path: person
          method: post
          cors: true
          integration: lambda
          authorizer:
            arn: ${self:custom.apiGatewayUserPoolAuthorization}
            claims:
              - email
              - name
....
```
* What stacktrace or error message from your provider did you see?
Received the following from API gateway: `{"message": "Could not parse request body into json: Unexpected character (\',\' (code 44)): was expecting double-quote to start field name\n at...`

## Additional Data

With two claims, the body mapping template for `application/x-www-form-urlencoded` gets configured as the below. **Two commas appear between the fields**. 

```json
.....   
 "cognitoPoolClaims" : {
       "email": "$context.authorizer.claims.email",,"name": "$context.authorizer.claims.name",
       "sub": "$context.authorizer.claims.sub"
    },
.....
```


* ***Serverless Framework Version you're using***: 1.5.0
* ***Operating System***: darwin




Issue discussion:
Temporary Fix: when the second claim is removed (```- name```) it works fine.
It's not a fix if you have to remove something you need :wink:. The temporary fix is to update the body mapping template after a deploy (which would then enable access to all claim fields). 
I guess you're right, but since for my solution I didn't really need
anything more than the Email, it worked for me.

On Jan 28, 2017 4:40 PM, "pisaacs" <notifications@github.com> wrote:

It's not a fix if you have to remove something you need 😉. The temporary
fix is to update the body mapping template after a deploy (which would then
enable access to all claim fields).

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<https://github.com/serverless/serverless/issues/3088#issuecomment-275855176>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJdRiyDh9b-9rG-MO4N6kxrf2aP2pkEks5rW2GAgaJpZM4LhVg9>
.


Solution patch:
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.js
index aa5cbe4bd0e..e8e040b2bf0 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.js
@@ -45,14 +45,24 @@ module.exports = {
       let extraCognitoPoolClaims;
       if (event.http.authorizer) {
         const claims = event.http.authorizer.claims || [];
-        extraCognitoPoolClaims = _.map(claims, claim =>
-            `"${claim}": "$context.authorizer.claims.${claim}",`
-        );
+        extraCognitoPoolClaims = _.map(claims, (claim) => {
+          if (typeof claim === 'string') {
+            const colonIndex = claim.indexOf(':');
+            if (colonIndex !== -1) {
+              const subClaim = claim.substring(colonIndex + 1);
+              return `"${subClaim}": "$context.authorizer.claims['${claim}']"`;
+            }
+          }
+          return `"${claim}": "$context.authorizer.claims.${claim}"`;
+        });
       }
       const requestTemplates = template.Properties.Integration.RequestTemplates;
       _.forEach(requestTemplates, (value, key) => {
-        requestTemplates[key] =
-          value.replace('extraCognitoPoolClaims', extraCognitoPoolClaims || '');
+        let claimsString = '';
+        if (extraCognitoPoolClaims && extraCognitoPoolClaims.length > 0) {
+          claimsString = extraCognitoPoolClaims.join(',').concat(',');
+        }
+        requestTemplates[key] = value.replace('extraCognitoPoolClaims', claimsString);
       });
 
       this.apiGatewayMethodLogicalIds.push(methodLogicalId);


Test patch:
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.test.js
index 95e0630871c..5123e7813ed 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.test.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/method/index.test.js
@@ -219,14 +219,75 @@ describe('#compileMethods()', () => {
     ];
 
     return awsCompileApigEvents.compileMethods().then(() => {
-      expect(
-        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
-          .Resources.ApiGatewayMethodUsersCreatePost.Properties
-          .Integration.RequestTemplates['application/json']
-          ).to.match(/email/);
+      const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider
+          .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties
+          .Integration.RequestTemplates['application/json'];
+      const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/;
+      const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1];
+      const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString);
+      expect(cognitoPoolClaims.email).to.equal('$context.authorizer.claims.email');
     });
   });
 
+  it('should set multiple claims for a cognito user pool', () => {
+    awsCompileApigEvents.validated.events = [
+      {
+        functionName: 'First',
+        http: {
+          authorizer: {
+            name: 'authorizer',
+            arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ',
+            claims: ['email', 'gender'],
+          },
+          integration: 'AWS',
+          path: 'users/create',
+          method: 'post',
+        },
+      },
+    ];
+
+    return awsCompileApigEvents.compileMethods().then(() => {
+      const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider
+          .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties
+          .Integration.RequestTemplates['application/json'];
+      const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/;
+      const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1];
+      const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString);
+      expect(cognitoPoolClaims.email).to.equal('$context.authorizer.claims.email');
+      expect(cognitoPoolClaims.gender).to.equal('$context.authorizer.claims.gender');
+    });
+  });
+
+  it('should properly set claims for custom properties inside the cognito user pool', () => {
+    awsCompileApigEvents.validated.events = [
+      {
+        functionName: 'First',
+        http: {
+          authorizer: {
+            name: 'authorizer',
+            arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ',
+            claims: ['email', 'custom:score'],
+          },
+          integration: 'AWS',
+          path: 'users/create',
+          method: 'post',
+        },
+      },
+    ];
+
+    return awsCompileApigEvents.compileMethods().then(() => {
+      const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider
+          .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties
+          .Integration.RequestTemplates['application/json'];
+      const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/;
+      const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1];
+      const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString);
+      expect(cognitoPoolClaims.email).to.equal('$context.authorizer.claims.email');
+      expect(cognitoPoolClaims.score).to.equal('$context.authorizer.claims[\'custom:score\']');
+    });
+  });
+
+
   it('should replace the extra claims in the template if there are none', () => {
     awsCompileApigEvents.validated.events = [
       {