aws-amplify / amplify-ui

Amplify UI is a collection of accessible, themeable, performant React (and more!) components that can connect directly to the cloud.
https://ui.docs.amplify.aws
Apache License 2.0
781 stars 257 forks source link

FR (Authenticator): Graceful handling of error message triggered by PreSignup lambda in both react-native and react app #4960

Open ericowhadi opened 5 months ago

ericowhadi commented 5 months ago

On which framework/platform would you like to see this feature implemented?

React, React Native

Which UI component is this feature-request for?

Authenticator

Please describe your feature-request in detail.

Currently, I have a PreSignup lambda trigger that is used to verify that an existing account in cognito with the same email address does not already exist from a user that has been signing using different federated signin method. THis is to avoid creation of multiple account for same user, one per signin method. So the in PreSignup is just throwing an error with a message like "An account with this email already exists. Please use "Sign In with ${JSON.parse(providerName[0].providerName}" or Please "Sign In" using your email and password" if we detect that the existing account is a native cognito account.

However, and error thrown from the PreSignup lambda is displayed to the user in a very ugly, programmer style way. And depending on the various use case (federated signin, user/password), react-native federated, vs user password), the display of this error message is different and ugly in almost all cases. I did not find a way to hook it and make the UI user friendly. Therefore this feature request. Ether handle the error in the Hosted UI cleanly, or/and pass it somehow so we can drive our own UI when receiving it

Please describe a solution you'd like.

Ether handle the error in the Hosted UI cleanly, or/and pass it somehow so we can drive our own UI when receiving it

We love contributors! Is this something you'd be interested in working on?

esauerbo commented 5 months ago

Hi @ericowhadi thanks for creating this issue. We are looking into this

reesscot commented 5 months ago

@ericowhadi Where are you seeing these error messages, in the Hosted UI or in the Authenticator?

ericowhadi commented 5 months ago

it depends on many things. on webapp, if I try using social identity on an existing email/password account, vs use email/password on existing social identity, one will display as error message in Hosted UI, while the other will be passed as redirect URL error message (that I was able to use to display graceful error message). On React Native, it displays in the Authenticator, and I found no way to intercept the error message to gracefully handle it.

ericowhadi commented 5 months ago

By the way, if preSignUp Lambda is baked into the overall logic to intercept the flow, there must be a way to implement gracefully what I try to do. Am I missing something that is not well documented? Throwing an error in this lambda is what I found googling around, but may be there is something else that I should have done instead of having this lambda throw?

calebpollman commented 5 months ago

@ericowhadi Can you provide a code snippet of how you are integrating the PreSignup Lambda with the Authenticator?

ericowhadi commented 5 months ago

Sure, I am using the PreSignup lambda template generated by amplify when setting up auth with social signin. And in the custom.js file I have:

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */
const AWS = require('aws-sdk')
const cognito = new AWS.CognitoIdentityServiceProvider()
exports.handler = async (event, context) => {
  // insert code to be executed by your lambda trigger
  const userPoolId = event.userPoolId
  const email = event.request.userAttributes.email
  try{
      const params = {
        UserPoolId: userPoolId,
        Filter: `email = "${email}"`
      }
        const users = await cognito.listUsers(params).promise()
        if (users.Users && users.Users.length > 0){
          const user = users.Users[0]
          console.log("user",user)
          const providerName = user.Attributes.find(attr=> attr.Name === 'identities')?.Value
          let signInMethod
          if (providerName){
            signInMethod = ` Please use "Sign In With ${JSON.parse(providerName)[0].providerName}"`
          }else{
            signInMethod = ' Please "Sign In" using your email and password'
          }
          //existing user found with the same email
          const errorMessage = `. An account with this email already exists. ${signInMethod}`
          throw new Error(errorMessage)

        }

      return event;
  } catch(error){
    console.error(error)
    throw error
  } 
};

hope this helps. BTW, on the webapp, I do have some client side code to deal with one of the behavior when the hosted ui is not displaying the error but instead passing it as URL param to the redirect: something like:

  const [errorMessage, setErrorMessage] = useState(null)
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const errorDescription = params.get('error_description');
    if (errorDescription) {
      setErrorMessage(errorDescription.startsWith("PreSignUp failed with error . ")?errorDescription.substring(30):errorDescription);
    }
  }, []);

and the render method displaying errorMessage when it is not null. I don't have any client side changes on react native. Error messages display somewhat, ugly and developer style on react native experience.