From 3f2ea63725f00117d2bfe06f1b3b76bb19a19482 Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Tue, 21 Nov 2023 16:25:28 -0500 Subject: [PATCH] fix(auth): Add userAttributes to confirmSignIn call (#2640) --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 5 +- .../actions/SignInChallengeCognitoActions.kt | 10 ++ .../codegen/actions/SignInChallengeActions.kt | 2 + .../codegen/events/SignInChallengeEvent.kt | 8 +- .../codegen/states/SignInChallengeState.kt | 15 +- .../auth/cognito/StateTransitionTestBase.kt | 1 + .../auth/cognito/StateTransitionTests.kt | 10 +- .../SignInChallengeCognitoActionsTest.kt | 141 ++++++++++++++++++ 8 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActionsTest.kt diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 55f1adfdba..5f938178ae 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -785,12 +785,15 @@ internal class RealAWSCognitoAuthPlugin( }, { val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions + val metadata = awsCognitoConfirmSignInOptions?.metadata ?: emptyMap() + val userAttributes = awsCognitoConfirmSignInOptions?.userAttributes ?: emptyList() when (signInState) { is SignInState.ResolvingChallenge -> { val event = SignInChallengeEvent( SignInChallengeEvent.EventType.VerifyChallengeAnswer( challengeResponse, - awsCognitoConfirmSignInOptions?.metadata ?: mapOf() + metadata, + userAttributes ) ) authStateMachine.send(event) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt index 7046d5f142..6c8436476c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt @@ -18,6 +18,7 @@ package com.amplifyframework.auth.cognito.actions import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ResourceNotFoundException import aws.sdk.kotlin.services.cognitoidentityprovider.respondToAuthChallenge +import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper @@ -32,9 +33,11 @@ import com.amplifyframework.statemachine.codegen.events.SignInChallengeEvent internal object SignInChallengeCognitoActions : SignInChallengeActions { private const val KEY_SECRET_HASH = "SECRET_HASH" private const val KEY_USERNAME = "USERNAME" + private const val KEY_PREFIX_USER_ATTRIBUTE = "userAttributes." override fun verifyChallengeAuthAction( answer: String, metadata: Map, + attributes: List, challenge: AuthChallenge ): Action = Action("VerifySignInChallenge") { id, dispatcher -> logger.verbose("$id Starting execution") @@ -50,6 +53,12 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { challengeResponses[responseKey] = answer } + challengeResponses.putAll( + attributes.map { + Pair("${KEY_PREFIX_USER_ATTRIBUTE}${it.key.keyString}", it.value) + } + ) + val secretHash = AuthHelper.getSecretHash( username, configuration.userPool?.appClient, @@ -90,6 +99,7 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { SignInChallengeEvent.EventType.RetryVerifyChallengeAnswer( answer, metadata, + attributes, challenge ) ) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt index 8d8dc496a3..8793d217d6 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInChallengeActions.kt @@ -15,6 +15,7 @@ package com.amplifyframework.statemachine.codegen.actions +import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.statemachine.Action import com.amplifyframework.statemachine.codegen.data.AuthChallenge @@ -22,6 +23,7 @@ internal interface SignInChallengeActions { fun verifyChallengeAuthAction( answer: String, metadata: Map, + userAttributes: List, challenge: AuthChallenge ): Action } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt index c8173f941c..6c1a27b574 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInChallengeEvent.kt @@ -15,6 +15,7 @@ package com.amplifyframework.statemachine.codegen.events +import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.codegen.data.AuthChallenge import java.util.Date @@ -22,11 +23,16 @@ import java.util.Date internal class SignInChallengeEvent(val eventType: EventType, override val time: Date? = null) : StateMachineEvent { sealed class EventType { data class WaitForAnswer(val challenge: AuthChallenge, val hasNewResponse: Boolean = false) : EventType() - data class VerifyChallengeAnswer(val answer: String, val metadata: Map) : EventType() + data class VerifyChallengeAnswer( + val answer: String, + val metadata: Map, + val userAttributes: List + ) : EventType() data class RetryVerifyChallengeAnswer( val answer: String, val metadata: Map, + val userAttributes: List, val authChallenge: AuthChallenge ) : EventType() data class FinalizeSignIn(val accessToken: String) : EventType() diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt index bc9db8adc6..3dad9cda40 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt @@ -60,7 +60,10 @@ internal sealed class SignInChallengeState : State { is WaitingForAnswer -> when (challengeEvent) { is SignInChallengeEvent.EventType.VerifyChallengeAnswer -> { val action = challengeActions.verifyChallengeAuthAction( - challengeEvent.answer, challengeEvent.metadata, oldState.challenge + challengeEvent.answer, + challengeEvent.metadata, + challengeEvent.userAttributes, + oldState.challenge ) StateResolution(Verifying(oldState.challenge.challengeName), listOf(action)) } @@ -78,7 +81,10 @@ internal sealed class SignInChallengeState : State { } is SignInChallengeEvent.EventType.RetryVerifyChallengeAnswer -> { val action = challengeActions.verifyChallengeAuthAction( - challengeEvent.answer, challengeEvent.metadata, challengeEvent.authChallenge + challengeEvent.answer, + challengeEvent.metadata, + challengeEvent.userAttributes, + challengeEvent.authChallenge, ) StateResolution(Verifying(challengeEvent.authChallenge.challengeName), listOf(action)) } @@ -92,7 +98,10 @@ internal sealed class SignInChallengeState : State { when (challengeEvent) { is SignInChallengeEvent.EventType.VerifyChallengeAnswer -> { val action = challengeActions.verifyChallengeAuthAction( - challengeEvent.answer, challengeEvent.metadata, oldState.challenge + challengeEvent.answer, + challengeEvent.metadata, + challengeEvent.userAttributes, + oldState.challenge, ) StateResolution(Verifying(oldState.challenge.challengeName), listOf(action)) } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt index 35a1e0d460..9c3ee743b7 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt @@ -327,6 +327,7 @@ open class StateTransitionTestBase { Mockito.`when`( mockSignInChallengeActions.verifyChallengeAuthAction( + MockitoHelper.anyObject(), MockitoHelper.anyObject(), MockitoHelper.anyObject(), MockitoHelper.anyObject() diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt index f9313f2719..7bca155442 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt @@ -387,12 +387,13 @@ class StateTransitionTests : StateTransitionTestBase() { SignInChallengeEvent( SignInChallengeEvent.EventType.RetryVerifyChallengeAnswer( "test", - mapOf(), + emptyMap(), + emptyList(), AuthChallenge( ChallengeNameType.CustomChallenge.toString(), "Test", "session_mock_value", - mapOf() + emptyMap(), ) ) ) @@ -401,7 +402,8 @@ class StateTransitionTests : StateTransitionTestBase() { SignInChallengeEvent( SignInChallengeEvent.EventType.VerifyChallengeAnswer( "test", - mapOf() + emptyMap(), + emptyList() ) ) ) @@ -481,7 +483,7 @@ class StateTransitionTests : StateTransitionTestBase() { challengeState?.apply { stateMachine.send( SignInChallengeEvent( - SignInChallengeEvent.EventType.VerifyChallengeAnswer("test", mapOf()) + SignInChallengeEvent.EventType.VerifyChallengeAnswer("test", emptyMap(), emptyList()) ) ) } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActionsTest.kt new file mode 100644 index 0000000000..8c3b5bba7b --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActionsTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth.cognito.actions + +import androidx.test.core.app.ApplicationProvider +import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest +import com.amplifyframework.auth.AuthUserAttribute +import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.cognito.AWSCognitoAuthService +import com.amplifyframework.auth.cognito.AuthEnvironment +import com.amplifyframework.auth.cognito.StoreClientBehavior +import com.amplifyframework.logging.Logger +import com.amplifyframework.statemachine.EventDispatcher +import com.amplifyframework.statemachine.StateMachineEvent +import com.amplifyframework.statemachine.codegen.data.AmplifyCredential +import com.amplifyframework.statemachine.codegen.data.AuthChallenge +import com.amplifyframework.statemachine.codegen.data.AuthConfiguration +import com.amplifyframework.statemachine.codegen.data.CredentialType +import com.amplifyframework.statemachine.codegen.data.UserPoolConfiguration +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import junit.framework.TestCase.assertTrue +import kotlin.test.assertEquals +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SignInChallengeCognitoActionsTest { + + private val pool = mockk { + every { appClient } returns "client" + every { appClientSecret } returns null + every { pinpointAppId } returns null + } + private val configuration = mockk { + every { userPool } returns pool + } + private val cognitoAuthService = mockk() + private val credentialStoreClient = mockk { + coEvery { loadCredentials(CredentialType.ASF) } returns AmplifyCredential.ASFDevice("asf_id") + } + private val logger = mockk(relaxed = true) + private val cognitoIdentityProviderClientMock = mockk() + + private val capturedEvent = slot() + private val dispatcher = mockk { + every { send(capture(capturedEvent)) }.answers { } + } + + private lateinit var authEnvironment: AuthEnvironment + + @Before + fun setup() { + every { cognitoAuthService.cognitoIdentityProviderClient }.answers { cognitoIdentityProviderClientMock } + authEnvironment = AuthEnvironment( + ApplicationProvider.getApplicationContext(), + configuration, + cognitoAuthService, + credentialStoreClient, + null, + null, + logger + ) + } + + @Test + fun `very auth challenge without user attributes`() = runTest { + val expectedChallengeResponses = mapOf( + "USERNAME" to "testUser" + ) + val capturedRequest = slot() + coEvery { + cognitoIdentityProviderClientMock.respondToAuthChallenge(capture(capturedRequest)) + }.answers { + mockk() + } + + SignInChallengeCognitoActions.verifyChallengeAuthAction( + "myAnswer", + emptyMap(), + emptyList(), + AuthChallenge( + "CONFIRM_SIGN_IN_WITH_NEW_PASSWORD", + username = "testUser", + session = null, + parameters = null + ) + ).execute(dispatcher, authEnvironment) + + assertTrue(capturedRequest.isCaptured) + assertEquals(expectedChallengeResponses, capturedRequest.captured.challengeResponses) + } + + @Test + fun `user attributes are added to auth challenge`() = runTest { + val providedUserAttributes = listOf(AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555555555")) + val expectedChallengeResponses = mapOf( + "USERNAME" to "testUser", + "userAttributes.phone_number" to "+15555555555" + ) + val capturedRequest = slot() + coEvery { + cognitoIdentityProviderClientMock.respondToAuthChallenge(capture(capturedRequest)) + }.answers { + mockk() + } + + SignInChallengeCognitoActions.verifyChallengeAuthAction( + "myAnswer", + emptyMap(), + providedUserAttributes, + AuthChallenge( + "CONFIRM_SIGN_IN_WITH_NEW_PASSWORD", + username = "testUser", + session = null, + parameters = null + ) + ).execute(dispatcher, authEnvironment) + + assertTrue(capturedRequest.isCaptured) + assertEquals(expectedChallengeResponses, capturedRequest.captured.challengeResponses) + } +}