为什么我的AWS Lambda函数返回“无效的JSON”错误?

时间:2019-08-21 01:19:05

标签: node.js json aws-lambda amazon-cognito

我有一个几天前写的lambda函数,在测试时表现很好。今天要对其进行测试(不更改任何代码)之后,我收到以下错误:"Invalid lambda function output : Invalid JSON"

这是功能代码(Node.js 10.x):

const AWS = require("aws-sdk");
const joi = require("@hapi/joi");

const Cognito = new AWS.CognitoIdentityServiceProvider();

exports.handler = async (event) => {
    // NOTE: Cognito expects Username to be the user's email

    // Vars
    const userPoolId = process.env.COGNITO_USER_POOL_ID;
    const {email : UNSAFE_EMAIL, language : UNSAFE_LANGUAGE = "en-US"} = event;

    // Normalize email and language
    const UNSAFE_TRIMMED_EMAIL = UNSAFE_EMAIL.trim();
    const UNSAFE_TRIMMED_LANGUAGE = UNSAFE_LANGUAGE.trim();

    // Validate UNSAFE_INPUTS
    const languageRegex = /^[a-z]{2}-[A-Z]{2}$/;

    const schema = joi.object().keys({
        email: joi.string().trim().email({minDomainSegments: 2}).required(),
        language: joi.string().trim().min(2).max(5).regex(languageRegex).required()
    });

    const validationResult = joi.validate({
        email: UNSAFE_TRIMMED_EMAIL,
        language: UNSAFE_TRIMMED_LANGUAGE
    }, schema);

    if(validationResult.error) {
        console.log(JSON.stringify(validationResult.error, null, 2));
        return {
            statusCode: 400,
            body: JSON.stringify({
                error: true,
                error_message: "Invalid"
            })
        }
    }

    // Validation successful, change variable names to reflect
    const VALIDATED_EMAIL = UNSAFE_TRIMMED_EMAIL;
    const VALIDATED_LANGUAGE = UNSAFE_TRIMMED_LANGUAGE;

    // Cognito params
    // Username is the user's email
    // email is also required in UserAttributes in order to send confirmation
    // DesiredDeliveryMediums is required to send confirmation
    const params = {
        UserPoolId: userPoolId,
        Username: VALIDATED_EMAIL,
        UserAttributes: [
            {
                Name: "email",
                Value: VALIDATED_EMAIL
            },
            {
                Name: "custom:language",
                Value: VALIDATED_LANGUAGE
            } 
        ],
        DesiredDeliveryMediums: ["EMAIL"]
    }

    // Attempt to create user in Cognito
    try {
        const authRes = await Cognito.adminCreateUser(params).promise();
        console.log("Success: ", JSON.stringify(authRes, null, 2));
        return {
            statusCode: 200,
            body: JSON.stringify({
                success: true
            })
        }
    } catch(err) {
        console.log("Error: ", JSON.stringify(err, null, 2));
        return {
            statusCode: 400,
            body: JSON.stringify({
                error: true,
                error_message: err.message
            })
        }
    }
};

运行测试,在传递格式错误的事件数据时收到预期的错误消息,并且尝试两次创建具有相同电子邮件的用户时出现Cognito错误。同样,这是预期的。但是,当在用户池中传递没有用户的有效电子邮件时,我得到以下响应(为便于阅读而设置):

Response:
{
  "statusCode": 400,
  "body": {
    "error": true,
    "error_message": "Invalid lambda function output : Invalid JSON"
  }
}

在此功能连接到的Cognito用户池中,我发现已经成功创建了一个用户。但是,几天前还没有电子邮件发送到该电子邮件地址。

所有记录的信息是说我有一个无效的JSON错误,根本没有记录authRes。当删除对Cognito的调用和相应的console.log调用时,try块将成功运行。因此,问题在于致电Cognito。

但是,为什么几天前代码运行良好,今天的代码为什么会失败?那就是让我非常沮丧的部分。

1 个答案:

答案 0 :(得分:0)

这个问题根本与lambda函数无关。 AWS和我用作Cognito用户池的自定义消息触发器的lambda函数是一个问题。这是哪里出了问题:

根据AWS文档,提供给adminCreateUser函数调用的自定义消息触发器lambda的事件数据具有以下形式:

{
  "version": 1,
  "triggerSource": "CustomMessage_AdminCreateUser",
  "region": "<region>",
  "userPoolId": "<userPoolId>",
  "userName": "<userName>",
  "callerContext": {
      "awsSdk": "<calling aws sdk with version>",
      "clientId": "<apps client id>",
      ...
  },
  "request": {
      "userAttributes": {
          "phone_number_verified": false,
          "email_verified": true,
           ...
      },
      "codeParameter": "####",
      "usernameParameter": "username"
  },
  "response": {
      "smsMessage": "<custom message to be sent in the message with code parameter and username parameter>"
      "emailMessage": "<custom message to be sent in the message with code parameter and username parameter>"
      "emailSubject": "<custom email subject>"
  }
}

并且希望从“自定义消息触发器” lambda返回的数据具有与事件相同的形式-仅更改了response对象。

这就是我为lambda编写的内容:

const email_message = require("./email_message");

exports.handler = async (event) => {
    // Vars
    const {codeParameter, usernameParameter} = event.request;
    console.log("Cognito Event: ", event);

    // Check that codeParameter equals "####" and usernameParameter equals "username"
    // This is to ensure that no compromised values are entered into the html
    if(!(codeParameter === "####" && usernameParameter === "username")) {
        return null;
    }


    const newRes = {
        smsMessage: `Welcome: confirmation code is ${codeParameter} and username is ${usernameParameter}`,
        emailMessage: email_message({codeParameter, usernameParameter}),
        emailSubject: "Welcome To Our Site"
    }

    return {...event, response: newRes};
};

这在几天前进行测试时有效,因为事件对象具有上面的形式。发生的是,AWS偷偷地将codeParameterusernameParameter字段的内容更改为以下内容:

{
    ...
    "codeParameter": "{####}",
    "usernameParameter": "{username}",
    ...
}

由于这些字符串未通过验证,因此lambda函数返回了null-并且null不是有效的JSON。

因此,临时解决方法是改为验证这些新字符串。但是,这引起了一些担忧。为什么AWS突然更改事件对象而没有对文档进行任何更新?其次,如何验证这些字符串可以安全地注入客户的电子邮件地址?我知道我可以清理usernameParameter,但是codeParameter可能会包含危险的字符,例如< > & ' ",因为它是由随机符号生成的密码,因此FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this)); Job networkJob = dispatcher.newJobBuilder() .setService(NetworkJobService.class) .setTag(Const.NETWORK_JOB_TAG) .setReplaceCurrent(true) .setLifetime(Lifetime.FOREVER) .setRetryStrategy(RetryStrategy.DEFAULT_LINEAR) .setRecurring(true) .setTrigger(Trigger.executionWindow(0, 0)) .setConstraints(Constraint.ON_ANY_NETWORK) .build(); dispatcher.schedule(networkJob); 怎么样?如果我自己生成密码,则可以确保它不会包含来自恶意行为者的数据,因此无需清理。但是,如果它来自AWS,那么谁会说这些价值没有受到损害呢?因此,为什么我首先添加了验证步骤,如果这些值已更改,该步骤应该会失败。到底发生了什么。

因此,简而言之,我所有的代码都表现出预期的效果。 AWS突然更改了事件对象,恕不另行通知。