aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.41k stars 2.11k forks source link

amplify/auth resetPassword succeeds but customSmsSender trigger lambda never called #13403

Closed timheilman closed 3 months ago

timheilman commented 3 months ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

CDK

Environment information

``` # Put output below this line System: OS: macOS 13.5.1 CPU: (10) arm64 Apple M2 Pro Memory: 61.17 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 18.19.0 - ~/.nvm/versions/node/v18.19.0/bin/node Yarn: 1.22.21 - ~/.nvm/versions/node/v18.19.0/bin/yarn npm: 10.2.3 - ~/.nvm/versions/node/v18.19.0/bin/npm Watchman: 2024.01.22.00 - /opt/homebrew/bin/watchman Browsers: Brave Browser: 124.1.65.132 Chrome: 124.0.6367.208 Safari: 16.6 npmPackages: @aws-amplify/react-native: ^1.0.22 => 1.0.22 @babel/core: ^7.20.0 => 7.23.9 @babel/preset-env: ^7.24.3 => 7.24.3 @babel/runtime: ^7.23.8 => 7.23.9 @expo/server: ^0.1.0 => 0.1.0 (0.3.0) @expo/webpack-config: ^19.0.0 => 19.0.1 @react-native-async-storage/async-storage: 1.21.0 => 1.21.0 @react-native-community/datetimepicker: ^7.6.3 => 7.6.3 @react-native-community/netinfo: ^11.1.0 => 11.1.0 @react-navigation/bottom-tabs: ^6.5.12 => 6.5.12 @react-navigation/elements: ^1.3.21 => 1.3.22 @react-navigation/material-bottom-tabs: ^6.2.20 => 6.2.20 @react-navigation/native-stack: ^6.9.17 => 6.9.17 @reduxjs/toolkit: ^2.2.3 => 2.2.3 @reduxjs/toolkit-query: 1.0.0 @reduxjs/toolkit-query-react: 1.0.0 @reduxjs/toolkit-react: 1.0.0 @testing-library/react-native: ^12.4.3 => 12.4.3 @types/jest: ^29.5.11 => 29.5.11 @types/lodash: ^4.14.202 => 4.14.202 @types/react: ~18.2.14 => 18.2.48 @types/react-test-renderer: ^18.0.7 => 18.0.7 @typescript-eslint/eslint-plugin: ^7.3.1 => 7.3.1 @typescript-eslint/parser: ^7.3.1 => 7.3.1 HelloWorld: 0.0.1 aws-amplify: ^6.0.21 => 6.0.21 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/data: undefined () aws-amplify/data/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () axios: ^1.6.5 => 1.6.7 dompurify: ^3.0.8 => 3.0.8 eslint: ^8.57.0 => 8.57.0 eslint-plugin-react: ^7.34.1 => 7.34.1 eslint-plugin-react-hooks: ^4.6.0 => 4.6.0 eslint-plugin-react-native: ^4.1.0 => 4.1.0 eslint-plugin-simple-import-sort: ^12.0.0 => 12.0.0 expo: ~50.0.17 => 50.0.18 expo-av: ~13.10.6 => 13.10.6 expo-camera: ~14.1.3 => 14.1.3 expo-constants: ~15.4.6 => 15.4.6 expo-crypto: ~12.8.1 => 12.8.1 expo-dev-client: ~3.3.11 => 3.3.11 expo-device: ~5.9.4 => 5.9.4 expo-file-system: ~16.0.5 => 16.0.9 expo-image-manipulator: ~11.8.0 => 11.8.0 expo-image-picker: ~14.7.1 => 14.7.1 expo-linear-gradient: ~12.7.2 => 12.7.2 expo-linking: ~6.2.2 => 6.2.2 expo-media-library: ~15.9.2 => 15.9.2 expo-notifications: ~0.27.7 => 0.27.7 expo-print: ~12.8.1 => 12.8.1 expo-router: ~3.4.10 => 3.4.10 expo-secure-store: ~12.8.1 => 12.8.1 expo-sharing: ~11.10.0 => 11.10.0 expo-splash-screen: ~0.26.5 => 0.26.5 expo-status-bar: ~1.11.1 => 1.11.1 expo-store-review: ~6.8.3 => 6.8.3 expo-system-ui: ~2.9.4 => 2.9.4 expo-updates: ~0.24.12 => 0.24.12 jest: ^29.2.1 => 29.7.0 jest-expo: ~50.0.4 => 50.0.4 jest-fetch-mock: ^3.0.3 => 3.0.3 jetifier: ^2.0.0 => 2.0.0 lodash: ^4.17.21 => 4.17.21 mock-xmlhttprequest: ^8.3.0 => 8.3.0 prettier: ^3.2.5 => 3.2.5 react: 18.2.0 => 18.2.0 react-dom: 18.2.0 => 18.2.0 react-native: 0.73.6 => 0.73.6 react-native-gesture-handler: ~2.14.0 => 2.14.1 react-native-paper: ^4.12.8 => 4.12.8 react-native-reanimated: ~3.6.0 => 3.6.2 react-native-safe-area-context: 4.8.2 => 4.8.2 react-native-screens: ~3.29.0 => 3.29.0 react-native-svg: 14.1.0 => 14.1.0 react-native-url-polyfill: ^2.0.0 => 2.0.0 (1.3.0) react-native-vector-icons: ^10.0.3 => 10.0.3 react-native-web: ~0.19.6 => 0.19.10 react-native-webview: ^13.6.4 => 13.6.4 react-redux: ^9.1.1 => 9.1.1 react-test-renderer: ^18.2.0 => 18.2.0 redux-thunk: ^3.1.0 => 3.1.0 stream-chat: ^8.14.4 => 8.17.0 stream-chat-expo: ^5.22.1 => 5.27.0 ts-node: ^10.9.2 => 10.9.2 typescript: ^5.1.3 => 5.3.3 uuid: ^9.0.1 => 9.0.1 (8.3.2, 7.0.3) npmGlobalPackages: corepack: 0.22.0 eas-cli: 5.9.3 npm: 10.2.3 yarn: 1.22.21 ```

Describe the bug

I have a customSmsSender trigger configured like so (CDK):

In the lambda construct:

      this.cognitoCdkNonprodCustomSmsSender = new NodejsFunction(
        this,
        cognitoCdkNonprodCustomSmsSenderId,
        {
          runtime: Runtime.NODEJS_20_X,
          environment: {
            STAGE: stage(),
            SNS_CDK_NONPROD_CUSTOM_SNS_TOPIC_ARN: snsTopic.topicArn,
            COGNITO_CDK_NONPROD_CUSTOM_SMS_SENDER_KEY_ARN: smsSenderKey.keyArn,
          },
        },
      );
      this.cognitoCdkNonprodCustomSmsSender.role?.attachInlinePolicy(
        new Policy(this, `cognitoCdkNonprodCustomSmsSenderAccessPolicy`, {
          statements: [
            new PolicyStatement({
              actions: ["sns:Publish"],
              resources: [snsTopic.topicArn],
            }),
            new PolicyStatement({
              actions: ["kms:Decrypt"],
              resources: [smsSenderKey.keyArn],
            }),
          ],
        }),
      );

In the cognito construct (including it all, although it is the lambdaTriggers section that is relevant:

    this.userPool = new UserPool(parent, `MainUserPool-${stage()}`, {
      autoVerify: { email: true, phone: true },
      keepOriginal: { email: true },
      passwordPolicy: {
        minLength: 8,
        requireLowercase: true,
        requireUppercase: true,
        requireDigits: true,
        requireSymbols: true,
      },
      signInAliases: {
        email: true,
        phone: true,
      },
      standardAttributes: {
        phoneNumber: { mutable: true, required: false },
        email: { mutable: true, required: false },
      },
      mfa: Mfa.OPTIONAL,
      mfaSecondFactor: { sms: true, otp: true },
      email: UserPoolEmail.withCognito(
        `user-${stage()}@domain.com`,
      ),
      userPoolName: `YellowBrik-Jobs-${stage()}`,
      selfSignUpEnabled: true,
      userVerification: {
        emailSubject: verificationEmailSubject,
        emailBody: verificationEmailMessage,
        smsMessage: verificationSmsMessage,
      },
      userInvitation: {
        emailSubject: invitationEmailSubject,
        emailBody: invitationEmailMessage,
        smsMessage: invitationSmsMessage,
      },
      deletionProtection: process.env.PREP_FOR_DELETE === "true" ? false : true,
      customSenderKmsKey: smsSenderKey,
      lambdaTriggers: {
        postConfirmation: postConfirmationLambda,
        ...(stage().toLowerCase() !== "prod"
          ? { customSmsSender: customSmsSenderLambda }
          : {}),
      },
      removalPolicy:
        stage().toLowerCase() === "prod"
          ? RemovalPolicy.RETAIN
          : RemovalPolicy.DESTROY,
    });

When using the functions from aws-amplify/auth v6:

This is despite that a successful response is returned from resetPassword:

 LOG  2024-05-20T19:25:16.697Z DEBUG (@DEBUG)app.screens.SignUpScreens.resetPassword.ResetPasswordScreen.resetPasswordSuccess {"nextStep": {"codeDeliveryDetails": {"attributeName": "phone_number", "deliveryMedium": "SMS", "destination": "+*******5167"}, "resetPasswordStep": "CONFIRM_RESET_PASSWORD_WITH_CODE"}, "resetPasswordResponse": {"isPasswordReset": false, "nextStep": {"codeDeliveryDetails": [Object], "resetPasswordStep": "CONFIRM_RESET_PASSWORD_WITH_CODE"}}}

That successful response indicates that an SMS was "sent" with the code to reset the user's password. These are the custom SMS trigger events:

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html#cognito-user-pools-lambda-trigger-syntax-custom-message-trigger-source

Expected behavior

Given that the successful response from aws-amplify/auth's resetPassword has resetPasswordStep set to CONFIRM_RESET_PASSWORD_WITH_CODE, the custom SMS Sender trigger lambda should get invoked with event.triggerSource === "CustomSMSSender_ForgotPassword".

Reproduction steps

Set up a cognito user pool, with the settings as shown in the CDK code above. Create and verify a user using a phone number as username alias and using SMS verification. (You can retrieve the SMS verification code via the custom trigger lambda.) Log out as that user. Use aws-amplify/auth's call, resetPassword, sending the same phone number. Receive the success payload:

{"isPasswordReset": false, "nextStep": {"codeDeliveryDetails": {"codeDeliveryDetails": {"attributeName": "phone_number", "deliveryMedium": "SMS", "destination": "+*******5167"}, "resetPasswordStep": "CONFIRM_RESET_PASSWORD_WITH_CODE"}, "resetPasswordStep": "CONFIRM_RESET_PASSWORD_WITH_CODE"}}

Note that the custom SMS send trigger lambda is never invoked.

Code Snippet

        const response = await resetPassword({ username: "+1******5167" });
        console.log(response);

Log output

``` // Put your logs below this line LOG 2024-05-20T20:24:43.590Z INFO (@DEBUG)app.screens.SignUpScreens.MFA.resetPasswordResponse {"nextStep": {"codeDeliveryDetails": {"attributeName": "phone_number", "deliveryMedium": "SMS", "destination": "+*******5167"}, "resetPasswordStep": "CONFIRM_RESET_PASSWORD_WITH_CODE"}, "response": {"isPasswordReset": false, "nextStep": {"codeDeliveryDetails": [Object], "resetPasswordStep": "CONFIRM_RESET_PASSWORD_WITH_CODE"}}} ```

aws-exports.js

No response

Manual configuration

{
  Auth: {
    Cognito: {
      userPoolClientId: requiredExpoPublicEnvVar(
        "USER_POOL_APP_CLIENT_YELLOW_BRIK_MOBILE",
      ),
      userPoolId: requiredExpoPublicEnvVar("USER_POOL_ID"),
    },
  },
...
}

Additional configuration

{
    "UserPool": {
        "Id": "us-east-2_Nx79RfcrD",
        "Name": "YellowBrik-Jobs-Dev",
        "Policies": {
            "PasswordPolicy": {
                "MinimumLength": 8,
                "RequireUppercase": true,
                "RequireLowercase": true,
                "RequireNumbers": true,
                "RequireSymbols": true,
                "TemporaryPasswordValidityDays": 7
            }
        },
        "DeletionProtection": "ACTIVE",
        "LambdaConfig": {
            "PostConfirmation": "arn:aws:lambda:us-east-2:058264413221:function:YbjStack-Dev-LambdacognitoPostConfirmationE1811AF6-emNlsJXhq98r",
            "CustomSMSSender": {
                "LambdaVersion": "V1_0",
                "LambdaArn": "arn:aws:lambda:us-east-2:058264413221:function:YbjStack-Dev-LambdacognitoCdkNonprodCustomSmsSende-TmoCrYrg1IfY"
            },
            "KMSKeyID": "arn:aws:kms:us-east-2:058264413221:key/4a688f19-48b3-459a-a70d-02769181972d"
        },
        "LastModifiedDate": "2024-05-20T13:23:16.624000-07:00",
        "CreationDate": "2024-03-19T11:04:24.428000-07:00",
        "SchemaAttributes": [
            {
                "Name": "sub",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": false,
                "Required": true,
                "StringAttributeConstraints": {
                    "MinLength": "1",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "given_name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "family_name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "middle_name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "nickname",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "preferred_username",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "profile",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "picture",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "website",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "email",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "email_verified",
                "AttributeDataType": "Boolean",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false
            },
            {
                "Name": "gender",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "birthdate",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "10",
                    "MaxLength": "10"
                }
            },
            {
                "Name": "zoneinfo",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "locale",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "phone_number",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "phone_number_verified",
                "AttributeDataType": "Boolean",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false
            },
            {
                "Name": "address",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "updated_at",
                "AttributeDataType": "Number",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "NumberAttributeConstraints": {
                    "MinValue": "0"
                }
            }
        ],
        "AutoVerifiedAttributes": [
            "email",
            "phone_number"
        ],
        "UsernameAttributes": [
            "email",
            "phone_number"
        ],
        "SmsVerificationMessage": "The verification code to your new Yellowbrik account is {####}",
        "EmailVerificationMessage": "The verification code to your new account is {####}",
        "EmailVerificationSubject": "Verify your new account",
        "VerificationMessageTemplate": {
            "SmsMessage": "The verification code to your new Yellowbrik account is {####}",
            "EmailMessage": "The verification code to your new account is {####}",
            "EmailSubject": "Verify your new account",
            "DefaultEmailOption": "CONFIRM_WITH_CODE"
        },
        "UserAttributeUpdateSettings": {
            "AttributesRequireVerificationBeforeUpdate": [
                "phone_number",
                "email"
            ]
        },
        "MfaConfiguration": "OPTIONAL",
        "EstimatedNumberOfUsers": 28,
        "EmailConfiguration": {
            "ReplyToEmailAddress": "blakel+cognitoReplyTo-Dev@yellowbrikjobs.com",
            "EmailSendingAccount": "COGNITO_DEFAULT"
        },
        "SmsConfiguration": {
            "SnsCallerArn": "arn:aws:iam::058264413221:role/YbjStack-Dev-MainUserPoolDevsmsRoleD5461332-pGSgIPStBaar",
            "ExternalId": "YbjStackDevMainUserPoolDevF27E9CA9",
            "SnsRegion": "us-east-2"
        },
        "UserPoolTags": {
            "Creator": "jiim-at-CLC ported to CDK by Tim Heilman",
            "Name": "Gognito-UserPool-Dev"
        },
        "SmsConfigurationFailure": "SNSSandbox",
        "Domain": "yellowbrik-jobs-dev",
        "AdminCreateUserConfig": {
            "AllowAdminCreateUserOnly": false,
            "UnusedAccountValidityDays": 7,
            "InviteMessageTemplate": {
                "SMSMessage": "Your username is {username} and temporary password is {####}.",
                "EmailMessage": "Your username is {username} and temporary password is {####}.",
                "EmailSubject": "Your temporary password"
            }
        },
        "Arn": "arn:aws:cognito-idp:us-east-2:058264413221:userpool/us-east-2_Nx79RfcrD",
        "AccountRecoverySetting": {
            "RecoveryMechanisms": [
                {
                    "Priority": 1,
                    "Name": "verified_phone_number"
                },
                {
                    "Priority": 2,
                    "Name": "verified_email"
                }
            ]
        }
    }
}

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

timheilman commented 3 months ago

I've now managed to test this using a genuine SMS Pinpoint Origination identity and no custom sms sender cognito trigger lambda. SignUp and SignIn SMS messages are sent, but reset password messages are not. The response from amplify/auth's resetPassword call is successful, the same as above.

timheilman commented 3 months ago

Oops. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cognito.AccountRecovery.html

I had been on the default, PHONE_WITHOUT_MFA_AND_EMAIL .

To get the behavior I was hoping for, need: PHONE_AND_EMAIL

It's discouraged, but that was the setting I was looking for. Never mind.

cwomack commented 3 months ago

Hey, @timheilman 👋. Glad you were able to unblock yourself! If you feel this is something that could be improved on in the documentation, feel free to create an issue here on the amplify-docs repo.