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.42k stars 2.12k forks source link

verifiedContact not returning email as verified despite AWS Console #10967

Closed 5t33 closed 1 year ago

5t33 commented 1 year ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

Authentication

Amplify Categories

auth

Environment information

``` # Put output below this line System: OS: macOS 10.15.7 CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz Memory: 251.48 MB / 32.00 GB Shell: 5.7.1 - /bin/zsh Binaries: Node: 18.7.0 - ~/.nvm/versions/node/v18.7.0/bin/node Yarn: 1.22.19 - ~/.nvm/versions/node/v18.7.0/bin/yarn npm: 8.15.0 - ~/.nvm/versions/node/v18.7.0/bin/npm Browsers: Chrome: 109.0.5414.119 Safari: 15.4 npmPackages: @emotion/react: ^11.10.0 => 11.10.0 @emotion/styled: ^11.10.0 => 11.10.0 @midival/core: ^0.0.17 => 0.0.17 @midival/core-examples: 1.0.0 @mui/icons-material: ^5.10.9 => 5.10.9 @mui/material: ^5.10.2 => 5.10.2 @mui/types: ^7.1.5 => 7.1.5 @shopify/react-web-worker: ^5.0.6 => 5.0.6 @testing-library/jest-dom: ^5.16.5 => 5.16.5 @testing-library/react: ^13.3.0 => 13.3.0 @testing-library/user-event: ^13.5.0 => 13.5.0 @tonejs/midi: ^2.0.28 => 2.0.28 @types/crypto-js: ^4.1.1 => 4.1.1 @types/jest: ^29.1.2 => 29.2.1 @types/keymirror: ^0.1.1 => 0.1.1 @types/lodash: ^4.14.187 => 4.14.187 @types/lodash.isnil: ^4.0.7 => 4.0.7 @types/node: ^16.11.49 => 16.11.51 @types/react: ^18.0.17 => 18.0.17 @types/react-dom: ^18.0.6 => 18.0.6 @types/react-test-renderer: ^18.0.0 => 18.0.0 @types/yup: ^0.32.0 => 0.32.0 aws-amplify: ^5.0.14 => 5.0.14 axios: ^0.27.2 => 0.27.2 (0.26.0) connected-react-router: ^6.9.3 => 6.9.3 cors: ^2.8.5 => 2.8.5 crypto-js: ^4.1.1 => 4.1.1 envinfo: ^7.8.1 => 7.8.1 fast-xml-parser: ^4.0.10 => 4.0.11 (3.21.1, 3.19.0) formik: ^2.2.9 => 2.2.9 history: ^5.3.0 => 5.3.0 immutable: ^4.1.0 => 4.1.0 jest-canvas-mock: ^2.4.0 => 2.4.0 lodash: ^4.17.21 => 4.17.21 lodash.isnil: ^4.0.0 => 4.0.0 opensheetmusicdisplay: ^1.5.1 => 1.5.5 postcss-normalize: ^10.0.1 => 10.0.1 postcss-selector-parser: 6.0.11 => 6.0.11 react: ^18.2.0 => 18.2.0 react-dom: ^18.2.0 => 18.2.0 react-piano: ^3.1.3 => 3.1.3 react-redux: ^8.0.5 => 8.0.5 react-router-dom: ^6.4.1 => 6.4.1 react-scripts: 5.0.1 => 5.0.1 react-transition-group: ^4.4.5 => 4.4.5 react-transition-group/CSSTransition: undefined () react-transition-group/ReplaceTransition: undefined () react-transition-group/SwitchTransition: undefined () react-transition-group/Transition: undefined () react-transition-group/TransitionGroup: undefined () react-transition-group/TransitionGroupContext: undefined () react-transition-group/config: undefined () redux: ^4.2.0 => 4.2.0 redux-devtools-extension: ^2.13.9 => 2.13.9 redux-saga: ^1.2.1 => 1.2.2 redux-saga/effects: undefined () sanitize.css: 12.0.1 => 12.0.1 standardized-audio-context-mock: ^9.6.8 => 9.6.8 tone: ^14.7.77 => 14.7.77 typescript: ^4.7.4 => 4.7.4 util: ^0.12.5 => 0.12.5 vexflow-musicxml: ^0.3.1 => 0.3.1 web-vitals: ^2.1.4 => 2.1.4 yup: ^0.32.11 => 0.32.11 npmGlobalPackages: corepack: 0.12.1 create-react-app: 5.0.1 npm: 8.15.0 typescript: 4.7.4 yarn: 1.22.19 ```

Describe the bug

I have a form to allow users to update their account information in a settings page. It's a bit circuitous but it uses this Amplify auth function:

import { Auth } from "aws-amplify";
...
export const updateUserAttributes = async (attributes: any) => {
  const user = await getCurrentUser();
  return Auth.updateUserAttributes(user, attributes);
}

Once this function is called, my code shows a view that requests the verification code which is sent to the users email when an email is updated.

That calls this function with the code and the new email:

export const verifyCurrentUserAttributeSubmit = async (attr: string, code: string) => {
  return Auth.verifyCurrentUserAttributeSubmit(attr, code)
}

Now, there's a chance that the user doesn't complete this step. So, I have a use effect hook which calls the following function:

export const verifiedContact = async (): Promise<{verified: {}, unverified:{}}> => {
  const user = await getCurrentUser();
  return Auth.verifiedContact(user)
} 

And if the user's email isn't verified it asks the user to verify (by re-sending the code and opening the "input code" form).

However, this function always returns unverified for the email, even after the verifyCurrentUserAttributeSubmit returns success. The email is verified in the AWS console (where is wasn't immediately after being changed).

Screen Shot 2023-02-11 at 8 02 05 PM Screen Shot 2023-02-11 at 8 02 23 PM Screen Shot 2023-02-11 at 8 00 57 PM Screen Shot 2023-02-11 at 8 00 34 PM

Expected behavior

I expect the "unverified" block of the VerifiedContact function to no longer contain the email address after returning success after being called with "email" and the provided code.

Reproduction steps

I hopefully explained this in the steps above, but more explicitly:

  1. call Auth.updateUserAttributes(user, attributes); with a new email address string 2.call Auth.verifyCurrentUserAttributeSubmit(attr, code) with "email" as the attribute and code as the emailed verification code
  2. Check that the AWS console shows "verified"
  3. call Auth.verifiedContacts(user)

Code Snippet

// Put your code below this line.

Log output

``` // Put your logs below this line ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

tannerabread commented 1 year ago

Hi @5t33

Is the email address the login method for your user pool or do you have another method and the email is just an attribute? I tried this out and it seems to be working as expected with a user pool set up to login by username with email address as an attribute.

I noticed that it never filled in the unverified key, rather it kept the old email as verified until I successfully completed the verifyCurrentUserAttributeSubmit step, then showed the new email as the verified one.

tannerabread commented 1 year ago

Hi 👋 Closing this as we have not heard back from you. If you are still experiencing this issue and are in need of assistance, please feel free to comment and provide us with any information previously requested by our team members so we can re-open this issue and be better able to assist you.

Thank you!

5t33 commented 1 year ago

@tannerabread sorry, we moved on to other things and I haven't checked back in.

Here's the Terraform configuration for my Cognito:

resource "aws_cognito_user_pool" "users" {
    provider = aws.terraform_role
    username_attributes           = [
        "email",
    ]
    auto_verified_attributes   = [
        "email",
    ]
    username_configuration {
        case_sensitive = false
    }
    mfa_configuration          = "OFF"
    name                       = "users_${var.environment}"
    tags                       = {
      Environment = var.environment
    }

    account_recovery_setting {
        recovery_mechanism {
            priority = 1
            name     = "verified_email"
        }
    }

    admin_create_user_config {
        allow_admin_create_user_only = false

        invite_message_template {
            email_message = "Your username is {username} and temporary password is {####}. "
            email_subject = "Your temporary password"
            sms_message   = "Your username is {username} and temporary password is {####}. "
        }
    }

    device_configuration {
        challenge_required_on_new_device      = true
        device_only_remembered_on_user_prompt = false
    }

    email_configuration {
        email_sending_account  = "DEVELOPER"
        from_email_address     = local.no_reply_email
        reply_to_email_address = local.no_reply_email
        source_arn             = local.sending_email_arn
    }

    password_policy {
        minimum_length                   = 8
        require_lowercase                = false
        require_numbers                  = false
        require_symbols                  = false
        require_uppercase                = false
        temporary_password_validity_days = 3
    }

    schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "email"
        required                 = true

        string_attribute_constraints {
            max_length = "2048"
            min_length = "0"
        }
    }

    schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "name"
        required                 = false

        string_attribute_constraints {
            max_length = "2048"
            min_length = "0"
        }
    }

     schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "user_type"
        required                 = false

        string_attribute_constraints {
            max_length = "2048"
            min_length = "0"
        }
    }

    schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "plan"
        required                 = false

        string_attribute_constraints {
            max_length = "256"
            min_length = "1"
        }
    }

    schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "is_test_user"
        required                 = false

        string_attribute_constraints {
            max_length = "256"
            min_length = "1"
        }
    }

    verification_message_template {
        default_email_option  = "CONFIRM_WITH_LINK"
        email_message_by_link = "Please click the link below to verify your email address. {##Verify Email##} "
        email_subject         = "Your verification code"
        email_subject_by_link = "Your ${var.org_name} verification link"
    }

}

resource "aws_cognito_user_pool_client" "users_web" {
    provider = aws.terraform_role
    allowed_oauth_flows                  = []
    allowed_oauth_flows_user_pool_client = false
    allowed_oauth_scopes                 = []
    callback_urls                        = []
    explicit_auth_flows                  = [
        "ALLOW_REFRESH_TOKEN_AUTH",
        "ALLOW_USER_SRP_AUTH",
        // "ALLOW_USER_PASSWORD_AUTH",
        // "USER_PASSWORD_AUTH"
    ]
    logout_urls                          = []
    name                                 = "${var.org_name}-web-${var.environment}"
    prevent_user_existence_errors        = "ENABLED"
    read_attributes                      = [
        "email",
        "name",
        "custom:user_type",
        "custom:is_test_user",
        "updated_at",
    ]
    refresh_token_validity               = 30
    supported_identity_providers         = []
    user_pool_id                         = aws_cognito_user_pool.users.id
    write_attributes                     = [
        "email",
        "name",
        "custom:user_type",
        "custom:is_test_user",
        "updated_at",
    ]
}
5t33 commented 1 year ago

email is the username

tannerabread commented 1 year ago

I do not have experience with terraform, but tried again with a new app with only this functionality and am seeing the same results as I did previously.

I only get the verified object with information, and the unverified remains empty. Then when the new email is verified it switches the email that is shown in the verified object.

As you are using terraform and I used the CLI, can you confirm if these settings are the same in your Cognito user-pool? I tried to read through your terraform config following the docs and I do not see anything wrong, but maybe I'm missing something. image

It's also worth asking since you are creating the resources by terraform how the config looks for Amplify? Since I created mine with the CLI, it ends up looking like this:

Amplify.configure({
    "aws_project_region": "us-east-1",
    "aws_cognito_identity_pool_id": "us-east-1:xxxxxx",
    "aws_cognito_region": "us-east-1",
    "aws_user_pools_id": "us-east-1_xxxxxx",
    "aws_user_pools_web_client_id": "xxxxxx",
    "oauth": {},
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "EMAIL"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ]
})

After configuring, I created an App.js like:

import logo from "./logo.svg";
import "./App.css";
import { Auth } from "aws-amplify";
import { verifiedContact } from "./test.ts";

function App() {
  async function signUp() {
    try {
      const { user } = await Auth.signUp({
        username: "test1@gmail.com",
        password: "testtest",
      });
      console.log(user);
    } catch (error) {
      console.log("error signing up:", error);
    }
  }

  async function confirmSignUp() {
    try {
      await Auth.confirmSignUp("test1@gmail.com", "123456");
    } catch (error) {
      console.log("error confirming sign up", error);
    }
  }

  async function signIn() {
    try {
      const user = await Auth.signIn("test1@gmail.com", "testtest");
    } catch (error) {
      console.log("error signing in", error);
    }
  }

  async function signOut() {
    try {
      await Auth.signOut();
    } catch (error) {
      console.log("error signing out: ", error);
    }
  }

  async function updateUserAttributes() {
    const user = await Auth.currentAuthenticatedUser();
    console.log(user.attributes);
    const newUser = await Auth.updateUserAttributes(user, {
      email: "test2@gmail.com",
    });
    console.log({newUser})
  }

  async function verifyCurrentUserAttributeSubmit() {
    const response = await Auth.verifyCurrentUserAttributeSubmit('email', '654321')
    console.log({response})
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        <button onClick={signUp}>Sign Up</button>
        <button onClick={confirmSignUp}>Confirm Sign Up</button>
        <button onClick={signIn}>Sign In</button>
        <button onClick={signOut}>Sign Out</button>
        <button onClick={updateUserAttributes}>Update User Attributes</button>
        <button onClick={verifyCurrentUserAttributeSubmit}>Verify Current User Attribute Submit</button>
        <button onClick={verifiedContact}>Verified Contact</button>
      </header>
    </div>
  );
}

export default App;

With the buttons I set up, I did the following process:

  1. Sign Up
  2. Confirm Sign Up (with code from email)
  3. Sign In
  4. Update User Attributes (changing to a new email)
  5. Verified Contact (shows the original email in the verified object and a blank unverified object)
  6. Verify Current User Attribute Submit (with code from new email)
  7. Verified Contact (shows new email in verified and a blank unverified object) All of this I never saw an unverified object
5t33 commented 1 year ago

Hey @tannerabread

First I'd like to thank you for your help and apologize for my inconsistent communication. This is a side project for me and there's a lot to do so I have limited time. I took a look at the sign up experience page and did notice some inconsistencies with what yours showed. It looks like I need to enable "Verifying attribute changes".

Screen Shot 2023-03-12 at 2 52 11 PM
tannerabread commented 1 year ago

You're welcome! No worries about the communication I'm here to help when you have time.

Does that solve your issue or were you just pointing out the inconsistency? If you were trying to keep that as disabled I will try my user pool with disabled as well and see if I get the same results

tannerabread commented 1 year ago

So I was just curious to see if I would experience the same bug, but when I try it with that option disabled, I get what would be expected.

Steps/Flow I see:

  1. Sign In (test1 email)
  2. Update User Attributes (test2 email)
  3. Call verifiedContact function (shows new email as unverified, verified is blank)
  4. Call verifyCurrentUserAttributeSubmit
  5. Call verifiedContact again (shows new email as verified, unverified is blank)
tannerabread commented 1 year ago

Hi @5t33 were you ever able to solve this? I wasn't positive if the tweak in the Cognito console solved your issue or not.

Someone suggested to me that if the above doesn't solve your issue, try to get the user by bypassing the cache as well, e.g.

Auth.currentAuthenticatedUser({ 'bypassCache': true });
cwomack commented 1 year ago

@5t33, we'll close this issue out for now since we have not heard back from you. If you are still experiencing this, please feel free to reply back and provide any information previously requested and we'd be happy to re-open the issue.

Thank you!

5t33 commented 1 year ago

Hey there,

Unfortunately I had to get into the weeds on another task again so I left this issue for now, but I'm back for a bit of troubleshooting.

I did try to update the field shown above and it did not help. Here's the screen shot:

Screenshot 2023-05-15 at 11 37 02 PM

While I could give Auth.currentAuthenticatedUser({ 'bypassCache': true }); a shot... I don't have a lot of hope because it happens even with users who have logged in for the first time (and just verified their email).

Here's a user I just created (and verified):

Screenshot 2023-05-15 at 11 40 49 PM

and the response from verifiedContact:

Screenshot 2023-05-15 at 11 45 30 PM

I'm going to poke around at that config @tannerabread mentioned above and see if there are any discrepancies with my set up. Barring that, we're still in dev and haven't shipped yet so I might delete and re-create with the new config to see if it's an "on-create" type thing.

I'm also wondering if it has something to do with the discrepancy between "username" and "email". Here's my sign up user function:

export const signUpUser = (signUpData: SignUpData): Promise<any> => {
  userSignUpParams.forEach((field: string) => {
    if (isNil(signUpData[field as keyof SignUpData])) {
      throw new MissingSignUpFormDataError(field);
    }
  });
  const fields = {
    username: signUpData.email,
    password: signUpData.password,
    attributes: {
      name: signUpData.name,
      email: signUpData.email,
      'custom:is_test_user': 'false',
      'custom:user_type': 'user'
    }
  };

As you can see, the "username" field is being set as the email, because that's what they use to log in. Is it possible I need to send "username" back to verifyCurrentUserAttributeSubmit for the verification step?

5t33 commented 1 year ago

Wow I figured it out!

After poking around the UI a bit I noticed the "Attribute read and write permissions" section of the client UI. After inspecting the permissions of the client we use for our web app, I noticed that it did not have permissions set for the "email_verified" field.

Screenshot 2023-05-15 at 11 44 53 PM

In the UI I wasn't able to add the write permission for the field to the client, but after adding the read perm, the verification output returned correctly - at least for the newly created user. I have yet to try changing a user's email. Anyway, for completeness, here's the terraform config I changed:

    read_attributes                      = [
        "email",
        "email_verified",
        ...
    ]
5t33 commented 1 year ago

Well, ironically, this is only an issue in the event that a user starts the process (hits submit) but never actually validates the new email. That was kind of the whole point of the check. But, the change that @tannerabread suggested (enabled verify attribute change) made that completely unnecessary because the attribute remains the same until verification is complete.

Well I guess I learned something.

Thanks for your help @tannerabread and @cwomack