Skip to content

Commit f420d15

Browse files
authored
fix(auth): Fix confirm signin when incorrect MFA code is entered (#2286)
1 parent 290526c commit f420d15

18 files changed

+325
-43
lines changed

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -588,11 +588,17 @@ internal class RealAWSCognitoAuthPlugin(
588588
authStateMachine.getCurrentState { authState ->
589589
val authNState = authState.authNState
590590
val signInState = (authNState as? AuthenticationState.SigningIn)?.signInState
591-
when ((signInState as? SignInState.ResolvingChallenge)?.challengeState) {
592-
is SignInChallengeState.WaitingForAnswer -> {
593-
_confirmSignIn(challengeResponse, options, onSuccess, onError)
591+
if (signInState is SignInState.ResolvingChallenge) {
592+
when (signInState.challengeState) {
593+
is SignInChallengeState.WaitingForAnswer -> {
594+
_confirmSignIn(challengeResponse, options, onSuccess, onError)
595+
}
596+
else -> {
597+
onError.accept(InvalidStateException())
598+
}
594599
}
595-
else -> onError.accept(InvalidStateException())
600+
} else {
601+
onError.accept(InvalidStateException())
596602
}
597603
}
598604
}
@@ -610,7 +616,6 @@ internal class RealAWSCognitoAuthPlugin(
610616
val authNState = authState.authNState
611617
val authZState = authState.authZState
612618
val signInState = (authNState as? AuthenticationState.SigningIn)?.signInState
613-
val challengeState = (signInState as? SignInState.ResolvingChallenge)?.challengeState
614619
when {
615620
authNState is AuthenticationState.SignedIn &&
616621
authZState is AuthorizationState.SessionEstablished -> {
@@ -625,21 +630,34 @@ internal class RealAWSCognitoAuthPlugin(
625630
signInState is SignInState.Error -> {
626631
authStateMachine.cancel(token)
627632
onError.accept(
628-
CognitoAuthExceptionConverter.lookup(signInState.exception, "Confirm Sign in failed.")
633+
CognitoAuthExceptionConverter.lookup(
634+
signInState.exception, "Confirm Sign in failed."
635+
)
636+
)
637+
}
638+
signInState is SignInState.ResolvingChallenge &&
639+
signInState.challengeState is SignInChallengeState.Error -> {
640+
authStateMachine.cancel(token)
641+
onError.accept(
642+
CognitoAuthExceptionConverter.lookup(
643+
(
644+
signInState.challengeState as SignInChallengeState.Error
645+
).exception,
646+
"Confirm Sign in failed."
647+
)
629648
)
630649
}
631650
}
632-
},
633-
{
634-
val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions
635-
val event = SignInChallengeEvent(
636-
SignInChallengeEvent.EventType.VerifyChallengeAnswer(
637-
challengeResponse,
638-
awsCognitoConfirmSignInOptions?.metadata ?: mapOf()
639-
)
651+
}, {
652+
val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions
653+
val event = SignInChallengeEvent(
654+
SignInChallengeEvent.EventType.VerifyChallengeAnswer(
655+
challengeResponse,
656+
awsCognitoConfirmSignInOptions?.metadata ?: mapOf()
640657
)
641-
authStateMachine.send(event)
642-
}
658+
)
659+
authStateMachine.send(event)
660+
}
643661
)
644662
}
645663

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License").
55
* You may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import com.amplifyframework.statemachine.codegen.actions.SignInChallengeActions
2626
import com.amplifyframework.statemachine.codegen.data.AuthChallenge
2727
import com.amplifyframework.statemachine.codegen.events.CustomSignInEvent
2828
import com.amplifyframework.statemachine.codegen.events.SignInChallengeEvent
29-
import com.amplifyframework.statemachine.codegen.events.SignInEvent
3029

3130
internal object SignInChallengeCognitoActions : SignInChallengeActions {
3231
private const val KEY_SECRET_HASH = "SECRET_HASH"
@@ -81,20 +80,25 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions {
8180
)
8281
)
8382
} catch (e: Exception) {
84-
SignInEvent(SignInEvent.EventType.ThrowError(e))
83+
SignInChallengeEvent(SignInChallengeEvent.EventType.ThrowError(e, challenge))
8584
}
8685
logger.verbose("$id Sending event ${evt.type}")
8786
dispatcher.send(evt)
8887
}
8988

89+
override fun resetToWaitingForAnswer(
90+
event: SignInChallengeEvent.EventType.ThrowError,
91+
challenge: AuthChallenge
92+
): Action = Action<AuthEnvironment>("ResetToWaitingForAnswer") { id, dispatcher ->
93+
logger.verbose("$id Starting execution")
94+
dispatcher.send(SignInChallengeEvent(SignInChallengeEvent.EventType.WaitForAnswer(challenge)))
95+
}
96+
9097
private fun getChallengeResponseKey(challengeName: String): String? {
91-
val VALUE_ANSWER = "ANSWER"
92-
val VALUE_SMS_MFA = "SMS_MFA_CODE"
93-
val VALUE_NEW_PASSWORD = "NEW_PASSWORD"
9498
return when (ChallengeNameType.fromValue(challengeName)) {
95-
is ChallengeNameType.SmsMfa -> VALUE_SMS_MFA
96-
is ChallengeNameType.NewPasswordRequired -> VALUE_NEW_PASSWORD
97-
is ChallengeNameType.CustomChallenge -> VALUE_ANSWER
99+
is ChallengeNameType.SmsMfa -> "SMS_MFA_CODE"
100+
is ChallengeNameType.NewPasswordRequired -> "NEW_PASSWORD"
101+
is ChallengeNameType.CustomChallenge -> "ANSWER"
98102
else -> null
99103
}
100104
}

aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,8 @@ internal interface SignInChallengeActions {
2424
event: SignInChallengeEvent.EventType.VerifyChallengeAnswer,
2525
challenge: AuthChallenge
2626
): Action
27+
fun resetToWaitingForAnswer(
28+
event: SignInChallengeEvent.EventType.ThrowError,
29+
challenge: AuthChallenge
30+
): Action
2731
}

aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal class SignInChallengeEvent(val eventType: EventType, override val time:
2525
data class VerifyChallengeAnswer(val answer: String, val metadata: Map<String, String>) : EventType()
2626
data class FinalizeSignIn(val accessToken: String) : EventType()
2727
data class Verified(val id: String = "") : EventType()
28+
data class ThrowError(val exception: Exception, val challenge: AuthChallenge) : EventType()
2829
}
2930

3031
override val type: String = eventType.javaClass.simpleName

aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal sealed class SignInChallengeState : State {
2828
data class WaitingForAnswer(val challenge: AuthChallenge) : SignInChallengeState()
2929
data class Verifying(val id: String = "") : SignInChallengeState()
3030
data class Verified(val id: String = "") : SignInChallengeState()
31+
data class Error(val exception: Exception, val challenge: AuthChallenge) : SignInChallengeState()
3132

3233
class Resolver(private val challengeActions: SignInChallengeActions) : StateMachineResolver<SignInChallengeState> {
3334
override val defaultState: SignInChallengeState = NotStarted()
@@ -58,8 +59,25 @@ internal sealed class SignInChallengeState : State {
5859
}
5960
is Verifying -> when (challengeEvent) {
6061
is SignInChallengeEvent.EventType.Verified -> StateResolution(Verified())
62+
is SignInChallengeEvent.EventType.ThrowError -> {
63+
val action = challengeActions.resetToWaitingForAnswer(challengeEvent, challengeEvent.challenge)
64+
StateResolution(Error(challengeEvent.exception, challengeEvent.challenge), listOf(action))
65+
}
66+
6167
else -> defaultResolution
6268
}
69+
is Error -> {
70+
when (challengeEvent) {
71+
is SignInChallengeEvent.EventType.VerifyChallengeAnswer -> {
72+
val action = challengeActions.verifyChallengeAuthAction(challengeEvent, oldState.challenge)
73+
StateResolution(Verifying(oldState.challenge.challengeName), listOf(action))
74+
}
75+
is SignInChallengeEvent.EventType.WaitForAnswer -> {
76+
StateResolution(WaitingForAnswer(challengeEvent.challenge))
77+
}
78+
else -> defaultResolution
79+
}
80+
}
6381
else -> defaultResolution
6482
}
6583
}

aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License").
55
* You may not use this file except in compliance with the License.
@@ -149,6 +149,10 @@ internal sealed class SignInState : State {
149149
val action = signInActions.confirmDevice(signInEvent)
150150
StateResolution(ConfirmingDevice(), listOf(action))
151151
}
152+
is SignInEvent.EventType.ReceivedChallenge -> {
153+
val action = signInActions.initResolveChallenge(signInEvent)
154+
StateResolution(ResolvingChallenge(oldState.challengeState), listOf(action))
155+
}
152156
is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception))
153157
else -> defaultResolution
154158
}

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/generators/testcasegenerators/ConfirmSignInTestCaseGenerator.kt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package com.amplifyframework.auth.cognito.featuretest.generators.testcasegenerators
1717

18+
import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException
19+
import com.amplifyframework.auth.cognito.CognitoAuthExceptionConverter
1820
import com.amplifyframework.auth.cognito.featuretest.API
1921
import com.amplifyframework.auth.cognito.featuretest.AuthAPI
2022
import com.amplifyframework.auth.cognito.featuretest.CognitoType
@@ -102,5 +104,37 @@ object ConfirmSignInTestCaseGenerator : SerializableProvider {
102104
)
103105
)
104106

105-
override val serializables: List<Any> = listOf(baseCase)
107+
private val errorCase: FeatureTestCase
108+
get() {
109+
val exception = CodeMismatchException.invoke {
110+
message = "Confirmation code entered is not correct."
111+
}
112+
return baseCase.copy(
113+
description = "Test that invalid code on confirm SignIn with SMS challenge errors out",
114+
preConditions = PreConditions(
115+
"authconfiguration.json",
116+
"SigningIn_SigningIn.json",
117+
mockedResponses = listOf(
118+
MockResponse(
119+
CognitoType.CognitoIdentityProvider,
120+
"respondToAuthChallenge",
121+
ResponseType.Failure,
122+
exception.toJsonElement()
123+
)
124+
)
125+
),
126+
validations = listOf(
127+
ExpectationShapes.Amplify(
128+
AuthAPI.confirmSignIn,
129+
ResponseType.Failure,
130+
CognitoAuthExceptionConverter.lookup(
131+
exception,
132+
"Confirm Sign in failed."
133+
).toJsonElement()
134+
)
135+
)
136+
)
137+
}
138+
139+
override val serializables: List<Any> = listOf(baseCase, errorCase)
106140
}

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ internal data class AuthStatesProxy(
177177
type = "SignInChallengeState.WaitingForAnswer",
178178
authChallenge = authState.challenge
179179
)
180+
is SignInChallengeState.Error -> TODO()
180181
}
181182
}
182183
else -> {

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/CognitoExceptionSerializers.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
package com.amplifyframework.auth.cognito.featuretest.serializers
1919

2020
import aws.sdk.kotlin.services.cognitoidentity.model.CognitoIdentityException
21+
import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException
2122
import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException
2223
import aws.sdk.kotlin.services.cognitoidentityprovider.model.NotAuthorizedException
24+
import com.amplifyframework.auth.exceptions.UnknownException
2325
import kotlinx.serialization.KSerializer
2426
import kotlinx.serialization.Serializable
2527
import kotlinx.serialization.descriptors.SerialDescriptor
@@ -32,14 +34,19 @@ private data class CognitoExceptionSurrogate(
3234
val errorMessage: String?
3335
) {
3436
fun <T> toRealException(): T {
35-
return when (errorType) {
37+
val exception = when (errorType) {
3638
NotAuthorizedException::class.java.simpleName -> NotAuthorizedException.invoke {
3739
message = errorMessage
3840
} as T
41+
UnknownException::class.java.simpleName -> UnknownException(message = errorMessage ?: "") as T
42+
CodeMismatchException::class.java.simpleName -> CodeMismatchException.invoke {
43+
message = errorMessage
44+
} as T
3945
else -> {
4046
error("Exception for $errorType not defined")
4147
}
4248
}
49+
return exception
4350
}
4451

4552
companion object {

aws-auth-cognito/src/test/java/featureTest/utilities/APIExecutor.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import com.amplifyframework.core.Consumer
2222
import com.google.gson.Gson
2323
import java.util.concurrent.CountDownLatch
2424
import java.util.concurrent.TimeUnit
25-
import kotlin.Exception
2625
import kotlin.reflect.KClass
2726
import kotlin.reflect.KFunction
2827
import kotlin.reflect.KParameter

aws-auth-cognito/src/test/java/featureTest/utilities/CognitoMockFactory.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import aws.sdk.kotlin.services.cognitoidentity.model.Credentials
2020
import aws.sdk.kotlin.services.cognitoidentity.model.GetCredentialsForIdentityResponse
2121
import aws.sdk.kotlin.services.cognitoidentity.model.GetIdResponse
2222
import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient
23-
import aws.sdk.kotlin.services.cognitoidentityprovider.forgetDevice
2423
import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType
2524
import aws.sdk.kotlin.services.cognitoidentityprovider.model.AuthenticationResultType
2625
import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType
@@ -211,13 +210,14 @@ class CognitoMockFactory(
211210
responseObject: JsonObject
212211
) {
213212
if (mockResponse.responseType == ResponseType.Failure) {
214-
throw Json.decodeFromString(
213+
val response = Json.decodeFromString(
215214
when (mockResponse.type) {
216215
CognitoType.CognitoIdentity -> CognitoIdentityExceptionSerializer
217216
CognitoType.CognitoIdentityProvider -> CognitoIdentityProviderExceptionSerializer
218217
},
219218
responseObject.toString()
220219
)
220+
throw response
221221
}
222222
}
223223

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"description": "Test that invalid code on confirm SignIn with SMS challenge errors out",
3+
"preConditions": {
4+
"amplify-configuration": "authconfiguration.json",
5+
"state": "SigningIn_SigningIn.json",
6+
"mockedResponses": [
7+
{
8+
"type": "cognitoIdentityProvider",
9+
"apiName": "respondToAuthChallenge",
10+
"responseType": "failure",
11+
"response": {
12+
"errorType": "CodeMismatchException",
13+
"errorMessage": "Confirmation code entered is not correct."
14+
}
15+
}
16+
]
17+
},
18+
"api": {
19+
"name": "confirmSignIn",
20+
"params": {
21+
"challengeResponse": "000000"
22+
},
23+
"options": {
24+
}
25+
},
26+
"validations": [
27+
{
28+
"type": "amplify",
29+
"apiName": "confirmSignIn",
30+
"responseType": "failure",
31+
"response": {
32+
"errorType": "CodeMismatchException",
33+
"errorMessage": "Confirmation code entered is not correct.",
34+
"recoverySuggestion": "Enter correct confirmation code.",
35+
"cause": {
36+
"errorType": "CodeMismatchException",
37+
"errorMessage": "Confirmation code entered is not correct."
38+
}
39+
}
40+
}
41+
]
42+
}

aws-auth-cognito/src/test/resources/feature-test/testsuites/fetchDevices/List_of_devices_returned_when_fetch_devices_API_succeeds.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@
1919
],
2020
"deviceCreateDate": {
2121
"value": {
22-
"seconds": 1.671733506E9,
23-
"nanos": 9.95178E8
22+
"seconds": 1.676485556E9,
23+
"nanos": 7.65001E8
2424
}
2525
},
2626
"deviceKey": "deviceKey",
2727
"deviceLastAuthenticatedDate": {
2828
"value": {
29-
"seconds": 1.671733506E9,
30-
"nanos": 9.95186E8
29+
"seconds": 1.676485556E9,
30+
"nanos": 7.65007E8
3131
}
3232
},
3333
"deviceLastModifiedDate": {
3434
"value": {
35-
"seconds": 1.671733506E9,
36-
"nanos": 9.95188E8
35+
"seconds": 1.676485556E9,
36+
"nanos": 7.65008E8
3737
}
3838
}
3939
}

0 commit comments

Comments
 (0)