aws-amplify / amplify-android

The fastest and easiest way to use AWS from your Android app.
https://docs.amplify.aws/lib/q/platform/android/
Apache License 2.0
245 stars 115 forks source link

errorMessage field availability on AuthException objects via Amplify calls/AuthException child types as error cause #1102

Closed vanceanderson41 closed 3 years ago

vanceanderson41 commented 3 years ago
  1. Description: I use some APIs associated with the AWSCognitoAuthPlugin and am unable to easily get useful error messages from the root error objects returned by Amplify with which to to surface to my users.
  2. The version of Amplify Android you're using: 1.6.8
  3. The code you're using to call Amplify Android:
// Dependencies: Amplify/Cognito
def amplifyVersion = '1.6.8'
implementation "com.amplifyframework:core:$amplifyVersion"
implementation "com.amplifyframework:aws-auth-cognito:$amplifyVersion"
implementation "com.amplifyframework:rxbindings:$amplifyVersion"

// Configuration in Application class:
Amplify.addPlugin(AWSCognitoAuthPlugin())
Amplify.configure(this

// For resetting a user pool user's password:
RxAmplify.Auth.resetPassword(username)
    .doOnSubscribe { _resetPassword.postValue(Resource.Loading()) }
    .subscribeBy(
        onSuccess = {
            userRepository.saveUsername(username)
            _resetPassword.postValue(Resource.Success(username))
        },
        onError = {
            (it as AmplifyException).let {
                _resetPassword.postValue(Resource.Error(it.message))
            }
        }
    )

// For confirming a user pool user's password reset
RxAmplify.Auth.confirmResetPassword(newPassword, code)
    .doOnSubscribe { _confirmResetPassword.postValue(Resource.Loading()) }
    .subscribeBy(
        onComplete = {
            _confirmResetPassword.postValue(Resource.Success(newPassword))
        },
        onError = {
            (it as? AmplifyException)?.let {
                Timber.e(it)
                _confirmResetPassword.postValue(Resource.Error(it.message))
            }
        }
    )

// For updating a user pool user's password who already has an active session
RxAmplify.Auth.updatePassword(oldPassword, newPassword)
    .doOnSubscribe { _updatePassword.postValue(Resource.Loading()) }
    .subscribeBy(
        onComplete = {
            _updatePassword.postValue(Resource.Success(Any()))
        },
        onError = {
            (it as AmplifyException).let {
                _updatePassword.postValue(Resource.Error(it.message))
            }
        }
    )
  1. Any relevant logs:
    E/ResetPasswordRepository$confirmResetPassword: AmplifyException {message=An error occurred confirming password recovery code, cause=com.amazonaws.services.cognitoidentityprovider.model.CodeMismatchException: Invalid verification code provided, please try again. (Service: AmazonCognitoIdentityProvider; Status Code: 400; Error Code: CodeMismatchException; Request ID: 9e7d9688-2ee5-41b1-8c47-d69ff123e14a), recoverySuggestion=See attached exception for more details}
        at com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin$13.onError(AWSCognitoAuthPlugin.java:684)
        at com.amazonaws.mobile.client.internal.InternalCallback.call(InternalCallback.java:77)
        at com.amazonaws.mobile.client.internal.InternalCallback.onError(InternalCallback.java:67)
        at com.amazonaws.mobile.client.internal.InternalCallback.call(InternalCallback.java:77)
        at com.amazonaws.mobile.client.internal.InternalCallback.onError(InternalCallback.java:67)
        at com.amazonaws.mobile.client.AWSMobileClient$16$1.onFailure(AWSMobileClient.java:2324)
        at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser$4$2.run(CognitoUser.java:757)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: com.amazonaws.services.cognitoidentityprovider.model.CodeMismatchException: Invalid verification code provided, please try again. (Service: AmazonCognitoIdentityProvider; Status Code: 400; Error Code: CodeMismatchException; Request ID: 9e7d9688-2ee5-41b1-8c47-d69ff123e14a)
        at com.amazonaws.http.AmazonHttpClient.handleErrorResponse(AmazonHttpClient.java:731)
        at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:405)
        at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:212)
        at com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient.invoke(AmazonCognitoIdentityProviderClient.java:6329)
        at com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient.confirmForgotPassword(AmazonCognitoIdentityProviderClient.java:2337)
        at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.confirmPasswordInternal(CognitoUser.java:847)
        at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.access$400(CognitoUser.java:133)
        at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser$4.run(CognitoUser.java:746)
        at java.lang.Thread.run(Thread.java:923)

In the case of 4), 1) The type supplied to onError is explicitly a Throwable but seemingly reliably cast as an AmplifyException.AuthException (As clearly seen in the logs as the throwable type) e.g. in Kotlin, this works in every test I have performed:

 (it as AmplifyException).let {
    _confirmResetPassword.postValue(Resource.Error(it.message))
 }

2) it as an AmplifyException has public String messages associated with it which are the following: message, localizedMessage, recoverySuggestion (and no others) 2a) In the case of RxAmplify.Auth.confirmResetPassword(...), that errors for something like a CodeMismatchException, those values are:

message: An error occurred confirming password recovery code
localizedMessage: An error occurred confirming password recovery code
recoverySuggestion: See attached exception for more details

1) The above are the error strings supplied with the provided AuthException AND they are generic/less than explicit and user friendly than anything we might surface to users interacting with this API. 2) The underlying cause (Throwable) field exposed by the AuthException object when returned from all above cases outlined in the above code snippets, DO have the types of strings I would want to supply to my users via errorMessage field.

e.g. Invalid verification code provided, please try again.

The cause field's values also match the supplied values for the same scenarios on the ios-amplify-sdk root error objects produced here: https://github.com/aws-amplify/aws-sdk-ios However: In all the above API usage scenarios, the cause fields are of types that ARE NOT those that extend AuthException. For example, one might expect, in the supplied scenario, that the cause field supplied with an AuthException is of the type AuthException.CodeMismatchException but it is instead AmazonServiceException.CodeMismatchException. (The AuthException derived classes defined in the AuthException class are not used as the cause)

Further, those cause fields are of type Throwable who use the constructor with only the cause supplied and as such whose message fields contain the whole toString() of the Throwable passed to the respective constructor resulting in something like:

Invalid verification code provided, please try again. (Service: AmazonCognitoIdentityProvider; Status Code: 400; Error Code: CodeMismatchException; Request ID: ae617a3b-cf3b-4568-ad23-79359efac707) instead of Invalid verification code provided, please try again. as the message field content

To clarify further: 1) AuthException fields do not supply representative strings beyond message, localizedMessage when maybe they should supply something like an errorMessage explicitly that is more user friendly like iOS SDKs do 2) Their cause fields in the outlined scenarios provide better error messages but are not obviously generically typed (AmplifyException children vs AmazonServiceException children as a parent)

TL;DR: What is the best way to surface intended/user friendly Amplify error messages to my users?

raphkim commented 3 years ago

Hi @vanceanderson41 ,

AmplifyException (and category exceptions that extend it such as AuthException on Android platform currently has the following properties:

In all the above API usage scenarios, the cause fields are of types that ARE NOT those that extend AuthException. For example, one might expect, in the supplied scenario, that the cause field supplied with an AuthException is of the type AuthException.CodeMismatchException but it is instead AmazonServiceException.CodeMismatchException.

This was by design. We wanted the subclasses of AuthException (e.g. AuthException#CodeMismatchException) to be the directly surfaced by the error callbacks to immediately make it obvious what type of error is being encountered rather than hide it under the cause field.

However, I do agree that "An error occurred confirming password recovery code" isn't exactly a very helpful error message. I will bring this issue up with the team to see how we can improve the quality of existing error messages to be more informative.

changxu0306 commented 3 years ago

Hello @vanceanderson41 , Thanks for reporting the issue. Our team is working on this feature right now. Will get back to you once the feature is released.

div5yesh commented 3 years ago

This issue is now resolved with v1.19.0.

vancefunraise commented 5 months ago

I still see this issue. Which error message is intended here: image

mattcreaser commented 5 months ago

Hi @vancefunraise - closed issues have limited visibility for the team, can you please open a new issue?