From 0b843648b75e712f297e4b69db17081f9a6b834a Mon Sep 17 00:00:00 2001 From: tjroach Date: Fri, 23 Aug 2024 13:40:27 -0400 Subject: [PATCH 01/18] initial email mfa work --- .../auth/cognito/AWSCognitoAuthPlugin.kt | 22 ++++++++++++++++++- .../auth/cognito/KotlinAuthFacadeInternal.kt | 4 +++- .../auth/cognito/MFATypeUtil.kt | 1 + .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 16 +++++++++++++- .../actions/SignInChallengeCognitoActions.kt | 1 + .../auth/cognito/helpers/MFAHelper.kt | 1 + .../cognito/helpers/SignInChallengeHelper.kt | 16 ++++++++++++++ .../codegen/events/SignInEvent.kt | 3 +++ .../java/com/amplifyframework/auth/MFAType.kt | 7 +++++- .../auth/result/step/AuthSignInStep.java | 21 ++++++++++++++++++ 10 files changed, 88 insertions(+), 4 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt index b7f23f436b..c3e89fd95a 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt @@ -926,6 +926,7 @@ class AWSCognitoAuthPlugin : AuthPlugin() { ) } + @Deprecated("Use updateMFAPreference(sms, totp, email, onSuccess, onError) instead") fun updateMFAPreference( sms: MFAPreference?, totp: MFAPreference?, @@ -935,7 +936,26 @@ class AWSCognitoAuthPlugin : AuthPlugin() { queueChannel.trySend( pluginScope.launch(start = CoroutineStart.LAZY) { try { - queueFacade.updateMFAPreference(sms, totp) + queueFacade.updateMFAPreference(sms, totp, null) + pluginScope.launch { onSuccess.call() } + } catch (e: Exception) { + pluginScope.launch { onError.accept(e.toAuthException()) } + } + } + ) + } + + fun updateMFAPreference( + sms: MFAPreference? = null, + totp: MFAPreference? = null, + email: MFAPreference? = null, + onSuccess: Action, + onError: Consumer + ) { + queueChannel.trySend( + pluginScope.launch(start = CoroutineStart.LAZY) { + try { + queueFacade.updateMFAPreference(sms, totp, email) pluginScope.launch { onSuccess.call() } } catch (e: Exception) { pluginScope.launch { onError.accept(e.toAuthException()) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt index f64b110fcb..ae448ac3aa 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt @@ -553,12 +553,14 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth suspend fun updateMFAPreference( sms: MFAPreference?, - totp: MFAPreference? + totp: MFAPreference?, + email: MFAPreference? ) { return suspendCoroutine { continuation -> delegate.updateMFAPreference( sms, totp, + email, { continuation.resume(Unit) }, { continuation.resumeWithException(it) } ) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/MFATypeUtil.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/MFATypeUtil.kt index ca4bc57435..dccc880d3e 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/MFATypeUtil.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/MFATypeUtil.kt @@ -27,4 +27,5 @@ val MFAType.challengeResponse: String get() = when (this) { MFAType.SMS -> "SMS_MFA" MFAType.TOTP -> "SOFTWARE_TOKEN_MFA" + MFAType.EMAIL -> "EMAIL_OTP" } 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 bb23eaa7c6..667d43b04b 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 @@ -2270,6 +2270,7 @@ internal class RealAWSCognitoAuthPlugin( fun updateMFAPreference( sms: MFAPreference?, totp: MFAPreference?, + email: MFAPreference?, onSuccess: Action, onError: Consumer ) { @@ -2278,7 +2279,8 @@ internal class RealAWSCognitoAuthPlugin( return } // If either of the params have preferred setting set then ignore fetched preference preferred property - val overridePreferredSetting: Boolean = !(sms?.mfaPreferred == true || totp?.mfaPreferred == true) + val overridePreferredSetting = + !(sms?.mfaPreferred == true || totp?.mfaPreferred == true || email?.mfaPreferred == true) fetchMFAPreference({ userPreference -> authStateMachine.getCurrentState { authState -> when (authState.authNState) { @@ -2316,6 +2318,18 @@ internal class RealAWSCognitoAuthPlugin( preferredMfa = preferredMFASetting } } + this.emailMfaSettings = email?.let { it -> + val preferredMFASetting = it.mfaPreferred + ?: ( + overridePreferredSetting && + userPreference.preferred == MFAType.EMAIL && + it.mfaEnabled + ) + EmailMfaSettingsType.invoke { + enabled = it.mfaEnabled + preferredMfa = preferredMFASetting + } + } }?.also { onSuccess.call() } 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 6c8436476c..5aa4b9fe4a 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 @@ -117,6 +117,7 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { is ChallengeNameType.NewPasswordRequired -> "NEW_PASSWORD" is ChallengeNameType.CustomChallenge, ChallengeNameType.SelectMfaType -> "ANSWER" is ChallengeNameType.SoftwareTokenMfa -> "SOFTWARE_TOKEN_MFA_CODE" + is ChallengeNameType.EmailMfa -> "EMAIL_OTP_CODE" else -> null } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt index c3bf3b8a60..57c7f1a7a2 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt @@ -34,4 +34,5 @@ internal val MFAType.value: String get() = when (this) { MFAType.SMS -> "SMS_MFA" MFAType.TOTP -> "SOFTWARE_TOKEN_MFA" + MFAType.EMAIL -> "EMAIL_OTP" } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index 61e3b47345..d1d5023f33 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -83,6 +83,7 @@ internal object SignInChallengeHelper { challengeNameType is ChallengeNameType.CustomChallenge || challengeNameType is ChallengeNameType.NewPasswordRequired || challengeNameType is ChallengeNameType.SoftwareTokenMfa || + challengeNameType is ChallengeNameType.EmailMfa || challengeNameType is ChallengeNameType.SelectMfaType -> { val challenge = AuthChallenge(challengeNameType.value, username, session, challengeParameters) @@ -205,6 +206,20 @@ internal object SignInChallengeHelper { ) onSuccess.accept(authSignInResult) } + // Change to ChallengeNameType.EMAIL_MFA when available + is ChallengeNameType.EmailMfa -> { + val authSignInResult = AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, + mapOf(), + null, + null, + null + ) + ) + onSuccess.accept(authSignInResult) + } else -> onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) } } @@ -215,6 +230,7 @@ internal object SignInChallengeHelper { when (it) { "SMS_MFA" -> result.add(MFAType.SMS) "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) + "EMAIL_MFA" -> result.add(MFAType.TOTP) else -> throw UnknownException(cause = Exception("MFA type not supported.")) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt index 7f0c367af3..0c7530c0b6 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt @@ -15,6 +15,7 @@ package com.amplifyframework.statemachine.codegen.events +import com.amplifyframework.auth.MFAType import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.DeviceMetadata @@ -62,6 +63,8 @@ internal class SignInEvent(val eventType: EventType, override val time: Date? = data class FinalizeSignIn(val id: String = "") : EventType() data class ReceivedChallenge(val challenge: AuthChallenge) : EventType() data class ThrowError(val exception: Exception) : EventType() + data class InitiateMfaSetup(val allowedMfaTypes: List) : EventType() + object InitiateEmailMfaSetup : EventType() data class InitiateTOTPSetup(val signInTOTPSetupData: SignInTOTPSetupData) : EventType() } diff --git a/core/src/main/java/com/amplifyframework/auth/MFAType.kt b/core/src/main/java/com/amplifyframework/auth/MFAType.kt index c57472a3c8..8e60d2440c 100644 --- a/core/src/main/java/com/amplifyframework/auth/MFAType.kt +++ b/core/src/main/java/com/amplifyframework/auth/MFAType.kt @@ -27,5 +27,10 @@ enum class MFAType { /** * Time-based One Time Password linked with an authenticator app */ - TOTP; + TOTP, + + /** + * Receives MFA codes with an email + */ + EMAIL; } diff --git a/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java b/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java index 3373104db3..b5273b361c 100644 --- a/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java +++ b/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java @@ -58,6 +58,13 @@ public enum AuthSignInStep { */ CONFIRM_SIGN_UP, + /** + * User selects MFA type to use. + * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} + * with MFAType to select. + */ + CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION, + /** * Admin requires user to setup TOTP. * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} @@ -65,6 +72,13 @@ public enum AuthSignInStep { */ CONTINUE_SIGN_IN_WITH_TOTP_SETUP, + /** + * Admin requires user to setup email MFA. + * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} + * with email code to verify. + */ + CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP, + /** * The user account is required to set MFA selection. * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} @@ -79,6 +93,13 @@ public enum AuthSignInStep { */ CONFIRM_SIGN_IN_WITH_TOTP_CODE, + /** + * MFA is enabled on this account and requires the user to confirm with the code received by email. + * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} + * with email code. + */ + CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, + /** * No further steps are needed in the sign in flow. */ From 196bf6735e578bb4425ae0a5404bb0ccfb638501 Mon Sep 17 00:00:00 2001 From: tjroach Date: Thu, 5 Sep 2024 10:03:19 -0400 Subject: [PATCH 02/18] remove unnecessary changes --- .../statemachine/codegen/events/SignInEvent.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt index 0c7530c0b6..7f0c367af3 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt @@ -15,7 +15,6 @@ package com.amplifyframework.statemachine.codegen.events -import com.amplifyframework.auth.MFAType import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.DeviceMetadata @@ -63,8 +62,6 @@ internal class SignInEvent(val eventType: EventType, override val time: Date? = data class FinalizeSignIn(val id: String = "") : EventType() data class ReceivedChallenge(val challenge: AuthChallenge) : EventType() data class ThrowError(val exception: Exception) : EventType() - data class InitiateMfaSetup(val allowedMfaTypes: List) : EventType() - object InitiateEmailMfaSetup : EventType() data class InitiateTOTPSetup(val signInTOTPSetupData: SignInTOTPSetupData) : EventType() } From d6c24ffba5ddecc5bbb1a3a3d3044b502466b48c Mon Sep 17 00:00:00 2001 From: tjroach Date: Tue, 10 Sep 2024 10:31:15 -0400 Subject: [PATCH 03/18] email mfa --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 35 ++++++-- .../actions/SignInChallengeCognitoActions.kt | 35 +++++++- .../auth/cognito/helpers/MFAHelper.kt | 47 +++++++++++ .../cognito/helpers/SignInChallengeHelper.kt | 80 +++++++++++++------ 4 files changed, 163 insertions(+), 34 deletions(-) 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 667d43b04b..91363a2be4 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 @@ -27,6 +27,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChangePasswordRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceRememberedStatusType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgetDeviceRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserRequest @@ -71,9 +72,12 @@ import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.HostedUIHelper import com.amplifyframework.auth.cognito.helpers.SessionHelper import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper +import com.amplifyframework.auth.cognito.helpers.getAllowedMFATypesFromChallengeParameters +import com.amplifyframework.auth.cognito.helpers.getMFASetupTypeOrNull import com.amplifyframework.auth.cognito.helpers.getMFAType import com.amplifyframework.auth.cognito.helpers.getMFATypeOrNull import com.amplifyframework.auth.cognito.helpers.identityProviderName +import com.amplifyframework.auth.cognito.helpers.isMfaSetupSelectionChallenge import com.amplifyframework.auth.cognito.helpers.value import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmResetPasswordOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmSignInOptions @@ -594,7 +598,7 @@ internal class RealAWSCognitoAuthPlugin( onError, totpSetupState.signInTOTPSetupData ) - totpSetupState?.hasNewResponse = false + totpSetupState.hasNewResponse = false } } } @@ -706,20 +710,32 @@ internal class RealAWSCognitoAuthPlugin( ) ) } + signInState is SignInState.ResolvingChallenge && signInState.challengeState is SignInChallengeState.WaitingForAnswer && (signInState.challengeState as SignInChallengeState.WaitingForAnswer).hasNewResponse -> { authStateMachine.cancel(token) val signInChallengeState = signInState.challengeState as SignInChallengeState.WaitingForAnswer var allowedMFATypes: Set? = null + + if (signInChallengeState.challenge.challengeName == ChallengeNameType.MfaSetup.value || + signInChallengeState.challenge.challengeName == ChallengeNameType.EmailOtp.value) { + SignInChallengeHelper.getNextStep( + signInChallengeState.challenge, + onSuccess, + onError + ) + (signInState.challengeState as SignInChallengeState.WaitingForAnswer).hasNewResponse = false + return@listen + } + val signInStep = when (signInChallengeState.challenge.challengeName) { "SMS_MFA" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE "NEW_PASSWORD_REQUIRED" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD "SOFTWARE_TOKEN_MFA" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE "SELECT_MFA_TYPE" -> { - signInChallengeState.challenge.parameters?.get("MFAS_CAN_CHOOSE")?.let { - allowedMFATypes = SignInChallengeHelper.getAllowedMFATypes(it) - } + allowedMFATypes = + getAllowedMFATypesFromChallengeParameters(signInChallengeState.challenge.parameters) AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION } else -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE @@ -797,9 +813,18 @@ internal class RealAWSCognitoAuthPlugin( getMFATypeOrNull(challengeResponse) == null ) { val error = InvalidParameterException( - message = "Value for challengeResponse must be one of SMS_MFA or SOFTWARE_TOKEN_MFA" + message = "Value for challengeResponse must be one of SMS_MFA, EMAIL_OTP or SOFTWARE_TOKEN_MFA" ) onError.accept(error) + } else if (challengeState is SignInChallengeState.WaitingForAnswer && + isMfaSetupSelectionChallenge(challengeState.challenge) && + getMFASetupTypeOrNull(challengeResponse) == null + ) { + val error = InvalidParameterException( + message = "Value for challengeResponse must be one of EMAIL_OTP or SOFTWARE_TOKEN_MFA" + ) + onError.accept(error) + authStateMachine.cancel(token) } else { val event = SignInChallengeEvent( SignInChallengeEvent.EventType.VerifyChallengeAnswer( 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 5aa4b9fe4a..3b1e953ddb 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 @@ -22,6 +22,8 @@ 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 +import com.amplifyframework.auth.cognito.helpers.isEmailMfaSetupChallenge +import com.amplifyframework.auth.cognito.helpers.isMfaSetupSelectionChallenge import com.amplifyframework.auth.exceptions.UnknownException import com.amplifyframework.statemachine.Action import com.amplifyframework.statemachine.codegen.actions.SignInChallengeActions @@ -43,13 +45,29 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { logger.verbose("$id Starting execution") val evt = try { val username = challenge.username + + // If we are selecting an MFA Setup Type, Cognito doesn't want a response. + // We handle the next step locally + if(isMfaSetupSelectionChallenge(challenge)) { + val event = SignInChallengeHelper.evaluateNextStep( + username = username ?: "", + challengeNameType = ChallengeNameType.MfaSetup, + session = challenge.session, + challengeParameters = mapOf("MFAS_CAN_SETUP" to answer), + authenticationResult = null + ) + logger.verbose("$id Sending event ${event.type}") + dispatcher.send(event) + return@Action + } + val challengeResponses = mutableMapOf() if (!username.isNullOrEmpty()) { challengeResponses[KEY_USERNAME] = username } - getChallengeResponseKey(challenge.challengeName)?.also { responseKey -> + getChallengeResponseKey(challenge)?.also { responseKey -> challengeResponses[responseKey] = answer } @@ -111,13 +129,24 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { dispatcher.send(evt) } - private fun getChallengeResponseKey(challengeName: String): String? { + private fun getChallengeResponseKey(challenge: AuthChallenge): String? { + val challengeName = challenge.challengeName return when (ChallengeNameType.fromValue(challengeName)) { is ChallengeNameType.SmsMfa -> "SMS_MFA_CODE" is ChallengeNameType.NewPasswordRequired -> "NEW_PASSWORD" is ChallengeNameType.CustomChallenge, ChallengeNameType.SelectMfaType -> "ANSWER" is ChallengeNameType.SoftwareTokenMfa -> "SOFTWARE_TOKEN_MFA_CODE" - is ChallengeNameType.EmailMfa -> "EMAIL_OTP_CODE" + is ChallengeNameType.EmailOtp -> "EMAIL_OTP_CODE" + // TOTP is not part of this because, it follows a completely different setup path + is ChallengeNameType.MfaSetup -> { + if (isMfaSetupSelectionChallenge(challenge)) { + "MFA_SETUP" + } else if (isEmailMfaSetupChallenge(challenge)) { + "EMAIL" + } else { + null + } + } else -> null } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt index 57c7f1a7a2..bd7bc973ea 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt @@ -16,17 +16,27 @@ package com.amplifyframework.auth.cognito.helpers import com.amplifyframework.auth.MFAType +import com.amplifyframework.auth.exceptions.UnknownException +import com.amplifyframework.statemachine.codegen.data.AuthChallenge @Throws(IllegalArgumentException::class) internal fun getMFAType(value: String) = when (value) { "SMS_MFA" -> MFAType.SMS "SOFTWARE_TOKEN_MFA" -> MFAType.TOTP + "EMAIL_OTP" -> MFAType.EMAIL else -> throw IllegalArgumentException("Unsupported MFA type") } internal fun getMFATypeOrNull(value: String) = when (value) { "SMS_MFA" -> MFAType.SMS "SOFTWARE_TOKEN_MFA" -> MFAType.TOTP + "EMAIL_OTP" -> MFAType.EMAIL + else -> null +} + +internal fun getMFASetupTypeOrNull(value: String) = when (value) { + "SOFTWARE_TOKEN_MFA" -> MFAType.TOTP + "EMAIL_OTP" -> MFAType.EMAIL else -> null } @@ -36,3 +46,40 @@ internal val MFAType.value: String MFAType.TOTP -> "SOFTWARE_TOKEN_MFA" MFAType.EMAIL -> "EMAIL_OTP" } + +internal fun isMfaSetupSelectionChallenge(challenge: AuthChallenge) = + challenge.challengeName == "MFA_SETUP" && + getAllowedMFASetupTypesFromChallengeParameters(challenge.parameters).size > 1 + +internal fun isEmailMfaSetupChallenge(challenge: AuthChallenge) = + challenge.challengeName == "MFA_SETUP" && + getAllowedMFASetupTypesFromChallengeParameters(challenge.parameters) == setOf(MFAType.EMAIL) + + +internal fun getAllowedMFATypesFromChallengeParameters(challengeParameters: Map?): Set { + val mfasCanChoose = challengeParameters?.get("MFAS_CAN_CHOOSE") ?: return emptySet() + val result = mutableSetOf() + mfasCanChoose.replace(Regex("\\[|\\]|\""), "").split(",").forEach { + when (it) { + "SMS_MFA" -> result.add(MFAType.SMS) + "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) + "EMAIL_MFA" -> result.add(MFAType.EMAIL) + else -> throw UnknownException(cause = Exception("MFA type not supported.")) + } + } + return result +} + +// We exclude SMS as a setup type +internal fun getAllowedMFASetupTypesFromChallengeParameters(challengeParameters: Map?): Set { + val mfasCanSetup = challengeParameters?.get("MFAS_CAN_SETUP") ?: return emptySet() + + val result = mutableSetOf() + mfasCanSetup.replace(Regex("\\[|\\]|\""), "").split(",").forEach { + when (it) { + "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) + "EMAIL_OTP" -> result.add(MFAType.EMAIL) + } + } + return result +} \ No newline at end of file diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index d1d5023f33..cd09b3fb39 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -19,6 +19,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.AuthenticationResul import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.smithy.kotlin.runtime.time.Instant import com.amplifyframework.auth.AuthCodeDeliveryDetails +import com.amplifyframework.auth.AuthCodeDeliveryDetails.DeliveryMedium import com.amplifyframework.auth.AuthException import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.TOTPSetupDetails @@ -83,16 +84,19 @@ internal object SignInChallengeHelper { challengeNameType is ChallengeNameType.CustomChallenge || challengeNameType is ChallengeNameType.NewPasswordRequired || challengeNameType is ChallengeNameType.SoftwareTokenMfa || - challengeNameType is ChallengeNameType.EmailMfa || + challengeNameType is ChallengeNameType.EmailOtp || challengeNameType is ChallengeNameType.SelectMfaType -> { val challenge = AuthChallenge(challengeNameType.value, username, session, challengeParameters) SignInEvent(SignInEvent.EventType.ReceivedChallenge(challenge)) } challengeNameType is ChallengeNameType.MfaSetup -> { - val allowedMFASetupTypes = challengeParameters?.get("MFAS_CAN_SETUP") - ?.let { getAllowedMFATypes(it) } ?: emptySet() - if (allowedMFASetupTypes.contains(MFAType.TOTP)) { + val allowedMFASetupTypes = getAllowedMFASetupTypesFromChallengeParameters(challengeParameters) + val challenge = AuthChallenge(challengeNameType.value, username, session, challengeParameters) + + if (allowedMFASetupTypes.contains(MFAType.EMAIL)) { + SignInEvent(SignInEvent.EventType.ReceivedChallenge(challenge)) + } else if (allowedMFASetupTypes.contains(MFAType.TOTP)) { val setupTOTPData = SignInTOTPSetupData("", session, username) SignInEvent(SignInEvent.EventType.InitiateTOTPSetup(setupTOTPData)) } else { @@ -123,7 +127,7 @@ internal object SignInChallengeHelper { is ChallengeNameType.SmsMfa -> { val deliveryDetails = AuthCodeDeliveryDetails( challengeParams.getValue("CODE_DELIVERY_DESTINATION"), - AuthCodeDeliveryDetails.DeliveryMedium.fromString( + DeliveryMedium.fromString( challengeParams.getValue("CODE_DELIVERY_DELIVERY_MEDIUM") ) ) @@ -170,7 +174,7 @@ internal object SignInChallengeHelper { false, AuthNextSignInStep( AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, - mapOf(), + emptyMap(), null, null, null @@ -179,19 +183,47 @@ internal object SignInChallengeHelper { onSuccess.accept(authSignInResult) } is ChallengeNameType.MfaSetup -> { - signInTOTPSetupData?.let { + val allowedMFASetupTypes = getAllowedMFASetupTypesFromChallengeParameters(challengeParams) + + if (allowedMFASetupTypes.contains(MFAType.TOTP) && allowedMFASetupTypes.contains(MFAType.EMAIL)) { + val authSignInResult = AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION, + emptyMap(), + null, + null, + allowedMFASetupTypes + ) + ) + onSuccess.accept(authSignInResult) + } else if (allowedMFASetupTypes.contains(MFAType.TOTP) && signInTOTPSetupData != null) { val authSignInResult = AuthSignInResult( false, AuthNextSignInStep( AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, challengeParams, null, - TOTPSetupDetails(it.secretCode, it.username), + TOTPSetupDetails(signInTOTPSetupData.secretCode, signInTOTPSetupData.username), + allowedMFAType + ) + ) + onSuccess.accept(authSignInResult) + } else if (allowedMFASetupTypes.contains(MFAType.EMAIL)) { + val authSignInResult = AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP, + emptyMap(), + null, + null, allowedMFAType ) ) onSuccess.accept(authSignInResult) - } ?: onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) + } else { + onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) + } } is ChallengeNameType.SelectMfaType -> { val authSignInResult = AuthSignInResult( @@ -201,19 +233,28 @@ internal object SignInChallengeHelper { mapOf(), null, null, - challengeParams["MFAS_CAN_CHOOSE"]?.let { getAllowedMFATypes(it) } + getAllowedMFATypesFromChallengeParameters(challengeParams) ) ) onSuccess.accept(authSignInResult) } - // Change to ChallengeNameType.EMAIL_MFA when available - is ChallengeNameType.EmailMfa -> { + is ChallengeNameType.EmailOtp -> { + val codeDeliveryMedium = DeliveryMedium.fromString( + challengeParams["CODE_DELIVERY_DELIVERY_MEDIUM"] ?: DeliveryMedium.UNKNOWN.value + ) + val codeDeliveryDestination = challengeParams["CODE_DELIVERY_DESTINATION"] + val deliveryDetails = if (codeDeliveryDestination != null) { + AuthCodeDeliveryDetails(codeDeliveryDestination, codeDeliveryMedium) + } else { + null + } + val authSignInResult = AuthSignInResult( false, AuthNextSignInStep( AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, mapOf(), - null, + deliveryDetails, null, null ) @@ -223,17 +264,4 @@ internal object SignInChallengeHelper { else -> onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) } } - - fun getAllowedMFATypes(allowedMFAType: String): Set { - val result = mutableSetOf() - allowedMFAType.replace(Regex("\\[|\\]|\""), "").split(",").forEach { - when (it) { - "SMS_MFA" -> result.add(MFAType.SMS) - "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) - "EMAIL_MFA" -> result.add(MFAType.TOTP) - else -> throw UnknownException(cause = Exception("MFA type not supported.")) - } - } - return result - } } From 5b060ad59a79ae6eae1bf6c00261e87df0cadf64 Mon Sep 17 00:00:00 2001 From: tjroach Date: Fri, 13 Sep 2024 11:57:52 -0400 Subject: [PATCH 04/18] update to official kotlin sdk email release --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 6 +++--- gradle/libs.versions.toml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 decbe85e35..bbda890407 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 @@ -2327,7 +2327,7 @@ internal class RealAWSCognitoAuthPlugin( .cognitoIdentityProviderClient ?.setUserMfaPreference { this.accessToken = token - this.smsMfaSettings = sms?.let { it -> + this.smsMfaSettings = sms?.let { val preferredMFASetting = it.mfaPreferred ?: ( overridePreferredSetting && @@ -2339,7 +2339,7 @@ internal class RealAWSCognitoAuthPlugin( preferredMfa = preferredMFASetting } } - this.softwareTokenMfaSettings = totp?.let { it -> + this.softwareTokenMfaSettings = totp?.let { val preferredMFASetting = it.mfaPreferred ?: ( overridePreferredSetting && @@ -2351,7 +2351,7 @@ internal class RealAWSCognitoAuthPlugin( preferredMfa = preferredMFASetting } } - this.emailMfaSettings = email?.let { it -> + this.emailMfaSettings = email?.let { val preferredMFASetting = it.mfaPreferred ?: ( overridePreferredSetting && diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd160acae3..c99c98f4b3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,9 +17,9 @@ androidx-test-orchestrator = "1.4.2" androidx-test-runner = "1.3.0" androidx-workmanager = "2.7.1" apollo = "4.0.0" -aws-kotlin = "1.2.8" # ensure proper aws-smithy version also set +aws-kotlin = "1.3.31" # ensure proper aws-smithy version also set aws-sdk = "2.62.2" -aws-smithy = "1.2.2" # ensure proper aws-kotlin version also set +aws-smithy = "1.3.8" # ensure proper aws-kotlin version also set binary-compatibility-validator = "0.14.0" coroutines = "1.7.3" desugar = "1.2.0" From 573370ab6f5ae06a0790fec5002bcb77ecd15f43 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 26 Sep 2024 13:09:25 -0700 Subject: [PATCH 05/18] Update unit tests --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 2 +- .../auth/cognito/AWSCognitoAuthPluginTest.kt | 5 +- .../cognito/RealAWSCognitoAuthPluginTest.kt | 450 ++++++++++++++++-- .../SignInChallengeCognitoActionsTest.kt | 29 ++ 4 files changed, 454 insertions(+), 32 deletions(-) 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 bbda890407..f720aaacff 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 @@ -2307,7 +2307,7 @@ internal class RealAWSCognitoAuthPlugin( onSuccess: Action, onError: Consumer ) { - if (sms == null && totp == null) { + if (sms == null && totp == null && email == null) { onError.accept(InvalidParameterException("No mfa settings given")) return } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt index 8afb3f53c5..3dd650b065 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt @@ -722,11 +722,12 @@ class AWSCognitoAuthPluginTest { fun updateMFAPreferences() { val smsPreference = MFAPreference.ENABLED val totpPreference = MFAPreference.PREFERRED + val emailPreference = MFAPreference.NOT_PREFERRED val onSuccess = Action { } val onError = Consumer { } - authPlugin.updateMFAPreference(smsPreference, totpPreference, onSuccess, onError) + authPlugin.updateMFAPreference(smsPreference, totpPreference, emailPreference, onSuccess, onError) verify(timeout = CHANNEL_TIMEOUT) { - realPlugin.updateMFAPreference(smsPreference, totpPreference, any(), any()) + realPlugin.updateMFAPreference(smsPreference, totpPreference, emailPreference, any(), any()) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt index 3099bca7f9..7b1ec6b217 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt @@ -30,6 +30,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmSignUpReques import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmSignUpResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgetDeviceResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserResponse @@ -1603,7 +1604,7 @@ class RealAWSCognitoAuthPluginTest { username = "" } } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, null, onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1644,7 +1645,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, null, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1685,7 +1687,7 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, null, onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1706,10 +1708,59 @@ class RealAWSCognitoAuthPluginTest { } @Test - fun `updateMFAPreferences when both provided sms and totp preference are null and cognito throws an exception`() { + fun `updateMFAPreferences when current preference is email with additional sms and totp preferences are enabled`() { + val onSuccess = ActionWithLatch() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA", "SOFTWARE_TOKEN_MFA", "EMAIL_OTP") + preferredMfaSetting = "EMAIL_OTP" + userAttributes = listOf() + username = "" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke {} + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, + onSuccess, mockk()) + + assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } + + @Test + fun `updateMFAPreferences when provided email sms and totp preference are null and cognito throws an exception`() { val onError = ConsumerWithLatch() coEvery { mockCognitoIPClient.setUserMfaPreference(any()) } throws Exception() - plugin.updateMFAPreference(null, null, mockk(), onError) + plugin.updateMFAPreference(null, null, null, mockk(), onError) assertTrue { onError.latch.await(5, TimeUnit.SECONDS) } } @@ -1723,13 +1774,13 @@ class RealAWSCognitoAuthPluginTest { accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken } } throws Exception() - plugin.updateMFAPreference(null, null, mockk(), onError) + plugin.updateMFAPreference(null, null, null, mockk(), onError) assertTrue { onError.latch.await(5, TimeUnit.SECONDS) } } @Test - fun `updatepref when currentpref is null and TOTP is enabled and SMS is enabled`() { + fun `updatepref when currentpref is null and TOTP, SMS, and email are enabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1749,7 +1800,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1767,10 +1819,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is null and TOTP is enabled and SMS is disabled`() { + fun `updatepref when currentpref is null and TOTP is enabled and SMS and email are disabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1790,7 +1849,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, MFAPreference.DISABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1808,10 +1868,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is null and TOTP is disabled and SMS is enabled`() { + fun `updatepref when currentpref is null and TOTP and email are disabled and SMS is enabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1831,7 +1898,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1849,10 +1917,66 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and TOTP and SMS are disabled and email is enabled`() { + val onSuccess = ActionWithLatch() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + userAttributes = listOf() + username = "" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke {} + } + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.DISABLED, MFAPreference.ENABLED, + onSuccess, mockk()) + + assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is null and SMS is preferred and TOTP is enabled`() { + fun `updatepref when currentpref is null and SMS is preferred and TOTP and email are enabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1872,7 +1996,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1890,10 +2015,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) } @Test - fun `updatepref when currentpref is null and SMS is enabled and TOTP is preferred`() { + fun `updatepref when currentpref is null and SMS and email are enabled and TOTP is preferred`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1913,7 +2045,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1931,10 +2064,66 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and SMS and TOTP are enabled and email is preferred`() { + val onSuccess = ActionWithLatch() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + userAttributes = listOf() + username = "" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke {} + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.PREFERRED, + onSuccess, mockk()) + + assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is null and TOTP is preferred and SMS is disabled`() { + fun `updatepref when currentpref is null and TOTP is preferred and SMS and email are disabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1954,7 +2143,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.PREFERRED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.PREFERRED, MFAPreference.DISABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1972,10 +2162,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is null and TOTP is disabled and SMS is preferred`() { + fun `updatepref when currentpref is null and TOTP and email are disabled and SMS is preferred`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -1995,7 +2192,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.DISABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.DISABLED, MFAPreference.DISABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2013,10 +2211,66 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is null and TOTP and sms are disabled and email is preferred`() { + val onSuccess = ActionWithLatch() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = null + preferredMfaSetting = null + userAttributes = listOf() + username = "" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke {} + } + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.DISABLED, MFAPreference.PREFERRED, + onSuccess, mockk()) + + assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is TOTP preferred and TOTP parameter is disabled`() { + fun `updatepref when currentpref is TOTP preferred and TOTP parameter is disabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -2036,7 +2290,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2054,10 +2309,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is SMS preferred and SMS parameter is disabled`() { + fun `updatepref when currentpref is SMS preferred and SMS parameter is disabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -2077,7 +2339,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2095,6 +2358,62 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is email preferred and email parameter is disabled`() { + val onSuccess = ActionWithLatch() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("EMAIL_OTP") + preferredMfaSetting = "EMAIL_OTP" + userAttributes = listOf() + username = "" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke {} + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, + onSuccess, mockk()) + + assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test @@ -2119,7 +2438,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, onError) + plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, + onSuccess, onError) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2137,10 +2457,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = false + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is TOTP preferred and params include SMS preferred and TOTP enabled`() { + fun `updatepref when currentpref is TOTP preferred and params include SMS preferred and TOTP and email enabled`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -2160,7 +2487,8 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2178,10 +2506,17 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test - fun `updatepref when currentpref is SMS preferred and params include SMS enabled and TOTP preferred`() { + fun `updatepref when currentpref is SMS preferred and params include SMS and email enabled and TOTP preferred`() { val onSuccess = ActionWithLatch() val setUserMFAPreferenceRequest = slot() @@ -2201,7 +2536,57 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, onSuccess, mockk()) + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, + onSuccess, mockk()) + + assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) + } + + @Test + fun `updatepref when currentpref is email preferred and params include SMS and email enabled and TOTP preferred`() { + val onSuccess = ActionWithLatch() + val setUserMFAPreferenceRequest = slot() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("EMAIL_OTP") + preferredMfaSetting = "EMAIL_OTP" + userAttributes = listOf() + username = "" + } + } + + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke {} + } + plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, + onSuccess, mockk()) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2219,6 +2604,13 @@ class RealAWSCognitoAuthPluginTest { }, setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings ) + assertEquals( + EmailMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.emailMfaSettings + ) } @Test 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 index 5c74ac0b5d..3e1c1bf952 100644 --- 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 @@ -138,4 +138,33 @@ class SignInChallengeCognitoActionsTest { assertTrue(capturedRequest.isCaptured) assertEquals(expectedChallengeResponses, capturedRequest.captured.challengeResponses) } + + @Test + fun `verify email MFA setup selection challenge is handled`() = runTest { + val expectedChallengeResponses = mapOf( + "USERNAME" to "testUser", + ) + + val capturedRequest = slot() + coEvery { + cognitoIdentityProviderClientMock.respondToAuthChallenge(capture(capturedRequest)) + }.answers { + mockk() + } + + SignInChallengeCognitoActions.verifyChallengeAuthAction( + "EMAIL_OTP", + emptyMap(), + emptyList(), + AuthChallenge( + "MFA_SETUP", + username = "testUser", + session = null, + parameters = null + ) + ).execute(dispatcher, authEnvironment) + + assertTrue(capturedRequest.isCaptured) + assertEquals(expectedChallengeResponses, capturedRequest.captured.challengeResponses) + } } From b07a7daa17656bbfbd3e2167c1d3b487299c3c2d Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 30 Sep 2024 14:07:47 -0700 Subject: [PATCH 06/18] Fix regressions to TOTP Setup --- .../auth/cognito/AWSCognitoAuthPluginTOTPTests.kt | 6 +++--- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 4 ++-- .../cognito/actions/SetupTOTPCognitoActions.kt | 3 ++- .../auth/cognito/actions/SignInCognitoActions.kt | 5 ++++- .../auth/cognito/helpers/SignInChallengeHelper.kt | 2 +- .../statemachine/codegen/events/SetupTOTPEvent.kt | 5 +++-- .../statemachine/codegen/events/SignInEvent.kt | 5 ++++- .../statemachine/codegen/states/SetupTOTPState.kt | 15 ++++++++++++--- .../actions/SetupTOTPCognitoActionsTest.kt | 11 ++++++++--- 9 files changed, 39 insertions(+), 17 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt index f1d4ac409e..c439e34e0e 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt @@ -143,7 +143,7 @@ class AWSCognitoAuthPluginTOTPTests { ) synchronousAuth.confirmSignIn(otp) synchronousAuth.updateUserAttribute(AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+19876543210")) - updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED) + updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED) synchronousAuth.signOut() val signInResult = synchronousAuth.signIn(userName, password) Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION, signInResult.nextStep.signInStep) @@ -168,9 +168,9 @@ class AWSCognitoAuthPluginTOTPTests { synchronousAuth.signUp(userName, password, options) } - private fun updateMFAPreference(sms: MFAPreference, totp: MFAPreference) { + private fun updateMFAPreference(sms: MFAPreference, totp: MFAPreference, email: MFAPreference) { val latch = CountDownLatch(1) - authPlugin.updateMFAPreference(sms, totp, { latch.countDown() }, { latch.countDown() }) + authPlugin.updateMFAPreference(sms, totp, email, { latch.countDown() }, { latch.countDown() }) latch.await(5, TimeUnit.SECONDS) } } 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 f720aaacff..2e46938106 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 @@ -592,7 +592,7 @@ internal class RealAWSCognitoAuthPlugin( ChallengeNameType.MfaSetup.value, null, null, - null + totpSetupState.challengeParams ), onSuccess, onError, @@ -763,7 +763,7 @@ internal class RealAWSCognitoAuthPlugin( ChallengeNameType.MfaSetup.value, null, null, - null + totpSetupState.challengeParams ), onSuccess, onError, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index 7c00b0892d..991f944b09 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -41,7 +41,8 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { response?.secretCode?.let { secret -> SetupTOTPEvent( SetupTOTPEvent.EventType.WaitForAnswer( - SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username) + totpSetupDetails = SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username), + challengeParams = eventType.challengeParams ) ) } ?: SetupTOTPEvent( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index 2dfe25a605..8e1ded6666 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -142,7 +142,10 @@ internal object SignInCognitoActions : SignInActions { override fun initiateTOTPSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup) = Action("initiateTOTPSetup") { id, dispatcher -> logger.verbose("$id Starting execution") - val evt = SetupTOTPEvent(SetupTOTPEvent.EventType.SetupTOTP(event.signInTOTPSetupData)) + val evt = SetupTOTPEvent(SetupTOTPEvent.EventType.SetupTOTP( + totpSetupDetails = event.signInTOTPSetupData, + challengeParams = event.challengeParams + )) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index cd09b3fb39..ff67d97739 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -98,7 +98,7 @@ internal object SignInChallengeHelper { SignInEvent(SignInEvent.EventType.ReceivedChallenge(challenge)) } else if (allowedMFASetupTypes.contains(MFAType.TOTP)) { val setupTOTPData = SignInTOTPSetupData("", session, username) - SignInEvent(SignInEvent.EventType.InitiateTOTPSetup(setupTOTPData)) + SignInEvent(SignInEvent.EventType.InitiateTOTPSetup(setupTOTPData, challenge.parameters)) } else { SignInEvent( SignInEvent.EventType.ThrowError( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt index 9bf7adefae..ea7d724571 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt @@ -15,6 +15,7 @@ package com.amplifyframework.statemachine.codegen.events import com.amplifyframework.statemachine.StateMachineEvent +import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import java.util.Date @@ -22,8 +23,8 @@ internal class SetupTOTPEvent(val eventType: EventType, override val time: Date? StateMachineEvent { sealed class EventType { - data class SetupTOTP(val totpSetupDetails: SignInTOTPSetupData) : EventType() - data class WaitForAnswer(val totpSetupDetails: SignInTOTPSetupData) : EventType() + data class SetupTOTP(val totpSetupDetails: SignInTOTPSetupData, val challengeParams: Map?) : EventType() + data class WaitForAnswer(val totpSetupDetails: SignInTOTPSetupData, val challengeParams: Map?) : EventType() data class ThrowAuthError(val exception: Exception, val username: String, val session: String?) : EventType() data class VerifyChallengeAnswer( val answer: String, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt index 7f0c367af3..14044d1da6 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt @@ -62,7 +62,10 @@ internal class SignInEvent(val eventType: EventType, override val time: Date? = data class FinalizeSignIn(val id: String = "") : EventType() data class ReceivedChallenge(val challenge: AuthChallenge) : EventType() data class ThrowError(val exception: Exception) : EventType() - data class InitiateTOTPSetup(val signInTOTPSetupData: SignInTOTPSetupData) : EventType() + data class InitiateTOTPSetup( + val signInTOTPSetupData: SignInTOTPSetupData, + val challengeParams: Map? + ) : EventType() } override val type: String = eventType.javaClass.simpleName diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index 2d9b03bfef..d2231bde1e 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -27,7 +27,8 @@ internal sealed class SetupTOTPState : State { data class SetupTOTP(val id: String = "") : SetupTOTPState() data class WaitingForAnswer( val signInTOTPSetupData: SignInTOTPSetupData, - var hasNewResponse: Boolean = false + var hasNewResponse: Boolean = false, + val challengeParams: Map? ) : SetupTOTPState() data class Verifying(val id: String = "") : SetupTOTPState() data class RespondingToAuthChallenge(val id: String = "") : SetupTOTPState() @@ -63,7 +64,11 @@ internal sealed class SetupTOTPState : State { is SetupTOTP -> when (challengeEvent) { is SetupTOTPEvent.EventType.WaitForAnswer -> { - StateResolution(WaitingForAnswer(challengeEvent.totpSetupDetails, true)) + StateResolution(WaitingForAnswer( + signInTOTPSetupData = challengeEvent.totpSetupDetails, + hasNewResponse = true, + challengeParams = challengeEvent.challengeParams + )) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( @@ -130,7 +135,11 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.WaitForAnswer -> { - StateResolution(WaitingForAnswer(challengeEvent.totpSetupDetails, true)) + StateResolution(WaitingForAnswer( + signInTOTPSetupData = challengeEvent.totpSetupDetails, + hasNewResponse = true, + challengeParams = challengeEvent.challengeParams + )) } else -> defaultResolution diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt index c8a7403dfd..636831178a 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt @@ -87,13 +87,17 @@ class SetupTOTPCognitoActionsTest { } val initiateAction = SetupTOTPCognitoActions.initiateTOTPSetup( SetupTOTPEvent.EventType.SetupTOTP( - SignInTOTPSetupData("", "SESSION", "USERNAME") + SignInTOTPSetupData("", "SESSION", "USERNAME"), + mapOf("MFAS_CAN_SETUP" to "SOFTWARE_TOKEN_MFA") ) ) initiateAction.execute(dispatcher, authEnvironment) val expectedEvent = SetupTOTPEvent( - SetupTOTPEvent.EventType.WaitForAnswer(SignInTOTPSetupData(secretCode, session, username)) + SetupTOTPEvent.EventType.WaitForAnswer( + SignInTOTPSetupData(secretCode, session, username), + mapOf("MFAS_CAN_SETUP" to "SOFTWARE_TOKEN_MFA") + ) ) assertEquals( expectedEvent.type, @@ -124,7 +128,8 @@ class SetupTOTPCognitoActionsTest { } val initiateAction = SetupTOTPCognitoActions.initiateTOTPSetup( SetupTOTPEvent.EventType.SetupTOTP( - SignInTOTPSetupData("", "SESSION", "USERNAME") + SignInTOTPSetupData("", "SESSION", "USERNAME"), + mapOf("MFAS_CAN_SETUP" to "SOFTWARE_TOKEN_MFA") ) ) initiateAction.execute(dispatcher, authEnvironment) From 7babc43b8960e03608a801fa6fb1b642670ef334 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 30 Sep 2024 14:34:01 -0700 Subject: [PATCH 07/18] Fix ktlint issues --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 14 +-- .../actions/SetupTOTPCognitoActions.kt | 6 +- .../actions/SignInChallengeCognitoActions.kt | 4 +- .../cognito/actions/SignInCognitoActions.kt | 7 +- .../auth/cognito/helpers/MFAHelper.kt | 7 +- .../codegen/events/SetupTOTPEvent.kt | 11 ++- .../cognito/RealAWSCognitoAuthPluginTest.kt | 99 +++++++++++-------- 7 files changed, 90 insertions(+), 58 deletions(-) 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 2e46938106..082eef794e 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 @@ -719,7 +719,8 @@ internal class RealAWSCognitoAuthPlugin( var allowedMFATypes: Set? = null if (signInChallengeState.challenge.challengeName == ChallengeNameType.MfaSetup.value || - signInChallengeState.challenge.challengeName == ChallengeNameType.EmailOtp.value) { + signInChallengeState.challenge.challengeName == ChallengeNameType.EmailOtp.value + ) { SignInChallengeHelper.getNextStep( signInChallengeState.challenge, onSuccess, @@ -813,7 +814,8 @@ internal class RealAWSCognitoAuthPlugin( getMFATypeOrNull(challengeResponse) == null ) { val error = InvalidParameterException( - message = "Value for challengeResponse must be one of SMS_MFA, EMAIL_OTP or SOFTWARE_TOKEN_MFA" + message = "Value for challengeResponse must be one of " + + "SMS_MFA, EMAIL_OTP or SOFTWARE_TOKEN_MFA" ) onError.accept(error) } else if (challengeState is SignInChallengeState.WaitingForAnswer && @@ -2354,10 +2356,10 @@ internal class RealAWSCognitoAuthPlugin( this.emailMfaSettings = email?.let { val preferredMFASetting = it.mfaPreferred ?: ( - overridePreferredSetting && - userPreference.preferred == MFAType.EMAIL && - it.mfaEnabled - ) + overridePreferredSetting && + userPreference.preferred == MFAType.EMAIL && + it.mfaEnabled + ) EmailMfaSettingsType.invoke { enabled = it.mfaEnabled preferredMfa = preferredMFASetting diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index 991f944b09..8ac25e8cbc 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -41,7 +41,11 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { response?.secretCode?.let { secret -> SetupTOTPEvent( SetupTOTPEvent.EventType.WaitForAnswer( - totpSetupDetails = SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username), + totpSetupDetails = SignInTOTPSetupData( + secretCode = secret, + session = response.session, + username = eventType.totpSetupDetails.username + ), challengeParams = eventType.challengeParams ) ) 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 3b1e953ddb..50738d5149 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 @@ -48,7 +48,7 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { // If we are selecting an MFA Setup Type, Cognito doesn't want a response. // We handle the next step locally - if(isMfaSetupSelectionChallenge(challenge)) { + if (isMfaSetupSelectionChallenge(challenge)) { val event = SignInChallengeHelper.evaluateNextStep( username = username ?: "", challengeNameType = ChallengeNameType.MfaSetup, @@ -130,7 +130,7 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { } private fun getChallengeResponseKey(challenge: AuthChallenge): String? { - val challengeName = challenge.challengeName + val challengeName = challenge.challengeName return when (ChallengeNameType.fromValue(challengeName)) { is ChallengeNameType.SmsMfa -> "SMS_MFA_CODE" is ChallengeNameType.NewPasswordRequired -> "NEW_PASSWORD" diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index 8e1ded6666..7a9d131b4e 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -142,9 +142,10 @@ internal object SignInCognitoActions : SignInActions { override fun initiateTOTPSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup) = Action("initiateTOTPSetup") { id, dispatcher -> logger.verbose("$id Starting execution") - val evt = SetupTOTPEvent(SetupTOTPEvent.EventType.SetupTOTP( - totpSetupDetails = event.signInTOTPSetupData, - challengeParams = event.challengeParams + val evt = SetupTOTPEvent( + SetupTOTPEvent.EventType.SetupTOTP( + totpSetupDetails = event.signInTOTPSetupData, + challengeParams = event.challengeParams )) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt index bd7bc973ea..f9146149b1 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt @@ -49,12 +49,11 @@ internal val MFAType.value: String internal fun isMfaSetupSelectionChallenge(challenge: AuthChallenge) = challenge.challengeName == "MFA_SETUP" && - getAllowedMFASetupTypesFromChallengeParameters(challenge.parameters).size > 1 + getAllowedMFASetupTypesFromChallengeParameters(challenge.parameters).size > 1 internal fun isEmailMfaSetupChallenge(challenge: AuthChallenge) = challenge.challengeName == "MFA_SETUP" && - getAllowedMFASetupTypesFromChallengeParameters(challenge.parameters) == setOf(MFAType.EMAIL) - + getAllowedMFASetupTypesFromChallengeParameters(challenge.parameters) == setOf(MFAType.EMAIL) internal fun getAllowedMFATypesFromChallengeParameters(challengeParameters: Map?): Set { val mfasCanChoose = challengeParameters?.get("MFAS_CAN_CHOOSE") ?: return emptySet() @@ -82,4 +81,4 @@ internal fun getAllowedMFASetupTypesFromChallengeParameters(challengeParameters: } } return result -} \ No newline at end of file +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt index ea7d724571..f0abd5017e 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt @@ -15,7 +15,6 @@ package com.amplifyframework.statemachine.codegen.events import com.amplifyframework.statemachine.StateMachineEvent -import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import java.util.Date @@ -23,8 +22,14 @@ internal class SetupTOTPEvent(val eventType: EventType, override val time: Date? StateMachineEvent { sealed class EventType { - data class SetupTOTP(val totpSetupDetails: SignInTOTPSetupData, val challengeParams: Map?) : EventType() - data class WaitForAnswer(val totpSetupDetails: SignInTOTPSetupData, val challengeParams: Map?) : EventType() + data class SetupTOTP( + val totpSetupDetails: SignInTOTPSetupData, + val challengeParams: Map? + ) : EventType() + data class WaitForAnswer( + val totpSetupDetails: SignInTOTPSetupData, + val challengeParams: Map? + ) : EventType() data class ThrowAuthError(val exception: Exception, val username: String, val session: String?) : EventType() data class VerifyChallengeAnswer( val answer: String, diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt index 7b1ec6b217..1ed6204b78 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt @@ -1645,8 +1645,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, null, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.ENABLED, null, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1687,7 +1688,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, null, onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.ENABLED, null, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1728,8 +1731,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1800,8 +1804,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1849,8 +1854,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, MFAPreference.DISABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.DISABLED, MFAPreference.DISABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1898,8 +1904,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1947,8 +1954,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.DISABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.DISABLED, MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -1996,8 +2004,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.PREFERRED, MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2045,8 +2054,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2094,8 +2104,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.PREFERRED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.PREFERRED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2143,8 +2154,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.PREFERRED, MFAPreference.DISABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.DISABLED, MFAPreference.PREFERRED, MFAPreference.DISABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2192,8 +2204,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.DISABLED, MFAPreference.DISABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.PREFERRED, MFAPreference.DISABLED, MFAPreference.DISABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2241,8 +2254,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.DISABLED, MFAPreference.PREFERRED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.DISABLED, MFAPreference.DISABLED, MFAPreference.PREFERRED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2290,8 +2304,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.DISABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.DISABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2339,8 +2354,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2388,8 +2404,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2438,8 +2455,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, - onSuccess, onError) + plugin.updateMFAPreference( + MFAPreference.DISABLED, MFAPreference.ENABLED, MFAPreference.DISABLED, onSuccess, onError + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2487,8 +2505,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.PREFERRED, MFAPreference.ENABLED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.PREFERRED, MFAPreference.ENABLED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2536,8 +2555,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) @@ -2585,8 +2605,9 @@ class RealAWSCognitoAuthPluginTest { coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { SetUserMfaPreferenceResponse.invoke {} } - plugin.updateMFAPreference(MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, - onSuccess, mockk()) + plugin.updateMFAPreference( + MFAPreference.ENABLED, MFAPreference.PREFERRED, MFAPreference.ENABLED, onSuccess, mockk() + ) assertTrue { onSuccess.latch.await(5, TimeUnit.SECONDS) } assertTrue(setUserMFAPreferenceRequest.isCaptured) From 3026c6aafa2917d6d4ad59f6dbb3fd12fb53dbdb Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Fri, 4 Oct 2024 15:22:51 -0700 Subject: [PATCH 08/18] Add Email MFA integration test and add unit tests --- aws-auth-cognito/build.gradle.kts | 2 + .../assets/create-mfa-subscription.graphql | 7 + .../AWSCognitoAuthPluginEmailMFATests.kt | 192 +++++++++ .../testutils/AbortableCountdownLatch.kt | 30 ++ .../datastore/generated/model/MfaInfo.java | 254 ++++++++++++ .../helpers/SignInChallengeHelperTest.kt | 388 ++++++++++++++++++ scripts/pull_backend_config_from_s3 | 1 + 7 files changed, 874 insertions(+) create mode 100644 aws-auth-cognito/src/androidTest/assets/create-mfa-subscription.graphql create mode 100644 aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt create mode 100644 aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt create mode 100644 aws-auth-cognito/src/androidTest/java/com/amplifyframework/datastore/generated/model/MfaInfo.java create mode 100644 aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt diff --git a/aws-auth-cognito/build.gradle.kts b/aws-auth-cognito/build.gradle.kts index 16a744ed92..93585fe2e9 100644 --- a/aws-auth-cognito/build.gradle.kts +++ b/aws-auth-cognito/build.gradle.kts @@ -68,9 +68,11 @@ dependencies { androidTestImplementation(libs.test.androidx.runner) androidTestImplementation(libs.test.androidx.junit) androidTestImplementation(libs.test.kotlin.coroutines) + androidTestImplementation(libs.test.kotlin.kotlinTest) androidTestImplementation(libs.test.totp) androidTestImplementation(project(":aws-api")) + androidTestImplementation(project(":aws-api-appsync")) androidTestImplementation(project(":testutils")) } diff --git a/aws-auth-cognito/src/androidTest/assets/create-mfa-subscription.graphql b/aws-auth-cognito/src/androidTest/assets/create-mfa-subscription.graphql new file mode 100644 index 0000000000..e20f6f7032 --- /dev/null +++ b/aws-auth-cognito/src/androidTest/assets/create-mfa-subscription.graphql @@ -0,0 +1,7 @@ +subscription OnCreateMfaInfo { + onCreateMfaInfo { + username + code + expirationTime + } +} diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt new file mode 100644 index 0000000000..51e3a56f39 --- /dev/null +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt @@ -0,0 +1,192 @@ +/* + * Copyright 2024 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 + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.amplifyframework.api.aws.AWSApiPlugin +import com.amplifyframework.api.graphql.SimpleGraphQLRequest +import com.amplifyframework.auth.AuthUserAttribute +import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.MFAType +import com.amplifyframework.auth.cognito.exceptions.service.CodeMismatchException +import com.amplifyframework.auth.cognito.test.R +import com.amplifyframework.auth.cognito.testutils.AbortableCountDownLatch +import com.amplifyframework.auth.options.AuthSignUpOptions +import com.amplifyframework.auth.result.AuthSignUpResult +import com.amplifyframework.auth.result.step.AuthSignInStep +import com.amplifyframework.core.configuration.AmplifyOutputs +import com.amplifyframework.core.configuration.AmplifyOutputsData +import com.amplifyframework.datastore.generated.model.MfaInfo +import com.amplifyframework.testutils.Assets +import com.amplifyframework.testutils.sync.SynchronousAuth +import java.util.Random +import java.util.UUID +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import org.junit.After +import org.junit.BeforeClass +import org.junit.Test + +class AWSCognitoAuthPluginEmailMFATests { + + private val password = "${UUID.randomUUID()}BleepBloop1234!" + private val userName = "test${Random().nextInt()}" + private val email = "$userName@testdomain.com" + + companion object { + private var authPlugin = AWSCognitoAuthPlugin() + private var apiPlugin = AWSApiPlugin() + lateinit var synchronousAuth: SynchronousAuth + var mfaCode = "" + var abortableLatch: AbortableCountDownLatch? = null + + @JvmStatic + @BeforeClass + fun initializePlugin() { + val context = ApplicationProvider.getApplicationContext() + val config = AmplifyOutputsData + .deserialize(context, AmplifyOutputs.fromResource(R.raw.amplify_outputs_email_or_totp_mfa)) + + authPlugin.configure(config, context) + apiPlugin.configure(config, context) + synchronousAuth = SynchronousAuth.delegatingTo(authPlugin) + + apiPlugin.subscribe( + SimpleGraphQLRequest( + Assets.readAsString("create-mfa-subscription.graphql"), + MfaInfo::class.java, + null + ), + { println("====== Subscription Established ======") }, + { + println("====== Received some MFA Info ======") + mfaCode = it.data.code + abortableLatch?.abort() + }, + { println("====== Subscription Failed $it ======") }, + { } + ) + } + } + + @After + fun tearDown() { + mfaCode = "" + synchronousAuth.deleteUser() + } + + @Test + fun fresh_email_mfa_setup() { + // Step 1: Sign up a new user + signUpNewUser() + + // Step 2: Attempt to sign in with the newly created user + var signInResult = synchronousAuth.signIn(userName, password) + + // Validation 1: Validate that the next step is MFA Setup Selection + assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION, signInResult.nextStep.signInStep) + + // Validation 2: Validate that the available MFA choices are Email and TOTP + assertEquals(setOf(MFAType.EMAIL, MFAType.TOTP), signInResult.nextStep.allowedMFATypes) + + // Step 3: Select "Email" as the MFA to set up + signInResult = synchronousAuth.confirmSignIn("EMAIL_OTP") + + // Validation 2: Validate that the next step is to input the user's email address + assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP, signInResult.nextStep.signInStep) + + // Step 4: Input the email address to send the code to then wait for the MFA code + abortableLatch = AbortableCountDownLatch(1) + signInResult = synchronousAuth.confirmSignIn(email) + + // Validation 3: Validate that the next step is to confirm the emailed MFA code + assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, signInResult.nextStep.signInStep) + + // Wait until the MFA code has been received + abortableLatch?.await(20, TimeUnit.SECONDS) + + // Step 5: Input the emailed MFA code for confirmation + signInResult = synchronousAuth.confirmSignIn(mfaCode) + + // Validation 4: Validate that MFA setup is done + assertEquals(AuthSignInStep.DONE, signInResult.nextStep.signInStep) + } + + @Test + fun sign_in_to_existing_email_mfa() { + // Step 1: Sign up a new user with an existing email address + signUpNewUser(email) + + // Step 2: Attempt to sign in with the newly created user + abortableLatch = AbortableCountDownLatch(1) + var signInResult = synchronousAuth.signIn(userName, password) + + // Validation 1: Validate that the next step is to confirm the emailed MFA code + assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, signInResult.nextStep.signInStep) + + // Wait until the MFA code has been received + abortableLatch?.await(20, TimeUnit.SECONDS) + + // Step 4: Input the emailed MFA code for confirmation + signInResult = synchronousAuth.confirmSignIn(mfaCode) + + // Validation 2: Validate that MFA setup is done + assertEquals(AuthSignInStep.DONE, signInResult.nextStep.signInStep) + } + + @Test + fun use_an_incorrect_MFA_code_then_sign_in_using_the_correct_one() { + // Step 1: Sign up a new user with an existing email address + signUpNewUser(email) + + // Step 2: Attempt to sign in with the newly created user + abortableLatch = AbortableCountDownLatch(1) + var signInResult = synchronousAuth.signIn(userName, password) + + // Validation 1: Validate that the next step is to confirm the emailed MFA code + assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, signInResult.nextStep.signInStep) + + // Wait until the MFA code has been received + abortableLatch?.await(20, TimeUnit.SECONDS) + + // Step 4: Input the an incorrect MFA code + // Validation 2: Validate that an incorrect MFA code throws a CodeMismatchException + assertFailsWith { + signInResult = synchronousAuth.confirmSignIn(mfaCode.reversed()) + } + + // Step 5: Input the correct MFA code for validation + signInResult = synchronousAuth.confirmSignIn(mfaCode) + + // Validation 3: Validate that MFA setup is done + assertEquals(AuthSignInStep.DONE, signInResult.nextStep.signInStep) + } + + private fun signUpNewUser(email: String? = null): AuthSignUpResult { + val attributes = if (email == null) { + emptyList() + } else { + listOf(AuthUserAttribute(AuthUserAttributeKey.email(), email)) + } + val options = AuthSignUpOptions.builder() + .userAttributes( + attributes + ).build() + return synchronousAuth.signUp(userName, password, options) + } +} diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt new file mode 100644 index 0000000000..155e92f968 --- /dev/null +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2022 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.testutils + +import java.util.concurrent.CountDownLatch + +class AbortableCountDownLatch(count: Int) : CountDownLatch(count) { + + fun abort() { + if (count == 0L) { + return + } + + while (count > 0) { + countDown() + } + } +} diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/datastore/generated/model/MfaInfo.java b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/datastore/generated/model/MfaInfo.java new file mode 100644 index 0000000000..de56bc6e18 --- /dev/null +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/datastore/generated/model/MfaInfo.java @@ -0,0 +1,254 @@ +package com.amplifyframework.datastore.generated.model; + +import com.amplifyframework.core.model.temporal.Temporal; +import com.amplifyframework.core.model.ModelIdentifier; + +import java.util.List; +import java.util.UUID; +import java.util.Objects; + +import androidx.core.util.ObjectsCompat; + +import com.amplifyframework.core.model.Model; +import com.amplifyframework.core.model.annotations.Index; +import com.amplifyframework.core.model.annotations.ModelConfig; +import com.amplifyframework.core.model.annotations.ModelField; +import com.amplifyframework.core.model.query.predicate.QueryField; + +import static com.amplifyframework.core.model.query.predicate.QueryField.field; + +/** This is an auto generated class representing the MfaInfo type in your schema. */ +@SuppressWarnings("all") +@ModelConfig(pluralName = "MfaInfos", type = Model.Type.USER, version = 1) +public final class MfaInfo implements Model { + public static final QueryField ID = field("MfaInfo", "id"); + public static final QueryField USERNAME = field("MfaInfo", "username"); + public static final QueryField CODE = field("MfaInfo", "code"); + public static final QueryField EXPIRATION_TIME = field("MfaInfo", "expirationTime"); + private final @ModelField(targetType="ID", isRequired = true) String id; + private final @ModelField(targetType="String", isRequired = true) String username; + private final @ModelField(targetType="String", isRequired = true) String code; + private final @ModelField(targetType="AWSTimestamp", isRequired = true) Temporal.Timestamp expirationTime; + private @ModelField(targetType="AWSDateTime", isReadOnly = true) Temporal.DateTime createdAt; + private @ModelField(targetType="AWSDateTime", isReadOnly = true) Temporal.DateTime updatedAt; + /** @deprecated This API is internal to Amplify and should not be used. */ + @Deprecated + public String resolveIdentifier() { + return id; + } + + public String getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getCode() { + return code; + } + + public Temporal.Timestamp getExpirationTime() { + return expirationTime; + } + + public Temporal.DateTime getCreatedAt() { + return createdAt; + } + + public Temporal.DateTime getUpdatedAt() { + return updatedAt; + } + + private MfaInfo(String id, String username, String code, Temporal.Timestamp expirationTime) { + this.id = id; + this.username = username; + this.code = code; + this.expirationTime = expirationTime; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if(obj == null || getClass() != obj.getClass()) { + return false; + } else { + MfaInfo mfaInfo = (MfaInfo) obj; + return ObjectsCompat.equals(getId(), mfaInfo.getId()) && + ObjectsCompat.equals(getUsername(), mfaInfo.getUsername()) && + ObjectsCompat.equals(getCode(), mfaInfo.getCode()) && + ObjectsCompat.equals(getExpirationTime(), mfaInfo.getExpirationTime()) && + ObjectsCompat.equals(getCreatedAt(), mfaInfo.getCreatedAt()) && + ObjectsCompat.equals(getUpdatedAt(), mfaInfo.getUpdatedAt()); + } + } + + @Override + public int hashCode() { + return new StringBuilder() + .append(getId()) + .append(getUsername()) + .append(getCode()) + .append(getExpirationTime()) + .append(getCreatedAt()) + .append(getUpdatedAt()) + .toString() + .hashCode(); + } + + @Override + public String toString() { + return new StringBuilder() + .append("MfaInfo {") + .append("id=" + String.valueOf(getId()) + ", ") + .append("username=" + String.valueOf(getUsername()) + ", ") + .append("code=" + String.valueOf(getCode()) + ", ") + .append("expirationTime=" + String.valueOf(getExpirationTime()) + ", ") + .append("createdAt=" + String.valueOf(getCreatedAt()) + ", ") + .append("updatedAt=" + String.valueOf(getUpdatedAt())) + .append("}") + .toString(); + } + + public static UsernameStep builder() { + return new Builder(); + } + + /** + * WARNING: This method should not be used to build an instance of this object for a CREATE mutation. + * This is a convenience method to return an instance of the object with only its ID populated + * to be used in the context of a parameter in a delete mutation or referencing a foreign key + * in a relationship. + * @param id the id of the existing item this instance will represent + * @return an instance of this model with only ID populated + */ + public static MfaInfo justId(String id) { + return new MfaInfo( + id, + null, + null, + null + ); + } + + public CopyOfBuilder copyOfBuilder() { + return new CopyOfBuilder(id, + username, + code, + expirationTime); + } + public interface UsernameStep { + CodeStep username(String username); + } + + + public interface CodeStep { + ExpirationTimeStep code(String code); + } + + + public interface ExpirationTimeStep { + BuildStep expirationTime(Temporal.Timestamp expirationTime); + } + + + public interface BuildStep { + MfaInfo build(); + BuildStep id(String id); + } + + + public static class Builder implements UsernameStep, CodeStep, ExpirationTimeStep, BuildStep { + private String id; + private String username; + private String code; + private Temporal.Timestamp expirationTime; + public Builder() { + + } + + private Builder(String id, String username, String code, Temporal.Timestamp expirationTime) { + this.id = id; + this.username = username; + this.code = code; + this.expirationTime = expirationTime; + } + + @Override + public MfaInfo build() { + String id = this.id != null ? this.id : UUID.randomUUID().toString(); + + return new MfaInfo( + id, + username, + code, + expirationTime); + } + + @Override + public CodeStep username(String username) { + Objects.requireNonNull(username); + this.username = username; + return this; + } + + @Override + public ExpirationTimeStep code(String code) { + Objects.requireNonNull(code); + this.code = code; + return this; + } + + @Override + public BuildStep expirationTime(Temporal.Timestamp expirationTime) { + Objects.requireNonNull(expirationTime); + this.expirationTime = expirationTime; + return this; + } + + /** + * @param id id + * @return Current Builder instance, for fluent method chaining + */ + public BuildStep id(String id) { + this.id = id; + return this; + } + } + + + public final class CopyOfBuilder extends Builder { + private CopyOfBuilder(String id, String username, String code, Temporal.Timestamp expirationTime) { + super(id, username, code, expirationTime); + Objects.requireNonNull(username); + Objects.requireNonNull(code); + Objects.requireNonNull(expirationTime); + } + + @Override + public CopyOfBuilder username(String username) { + return (CopyOfBuilder) super.username(username); + } + + @Override + public CopyOfBuilder code(String code) { + return (CopyOfBuilder) super.code(code); + } + + @Override + public CopyOfBuilder expirationTime(Temporal.Timestamp expirationTime) { + return (CopyOfBuilder) super.expirationTime(expirationTime); + } + } + + + public static class MfaInfoIdentifier extends ModelIdentifier { + private static final long serialVersionUID = 1L; + public MfaInfoIdentifier(String id) { + super(id); + } + } + +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt new file mode 100644 index 0000000000..c223d1a6f2 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt @@ -0,0 +1,388 @@ +/* + * Copyright 2024 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.helpers + +import com.amplifyframework.auth.AuthCodeDeliveryDetails +import com.amplifyframework.auth.AuthException +import com.amplifyframework.auth.MFAType +import com.amplifyframework.auth.TOTPSetupDetails +import com.amplifyframework.auth.exceptions.UnknownException +import com.amplifyframework.auth.result.AuthSignInResult +import com.amplifyframework.auth.result.step.AuthNextSignInStep +import com.amplifyframework.auth.result.step.AuthSignInStep +import com.amplifyframework.statemachine.codegen.data.AuthChallenge +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData +import featureTest.utilities.APICaptorFactory.Companion.onError +import featureTest.utilities.APICaptorFactory.Companion.onSuccess +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class SignInChallengeHelperTest { + private val username = "username" + private val email = "test@testdomain.com" + + // MFA Setup + @Test + fun `User needs to select either Email OTP or TOTP to setup`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "MFA_SETUP", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_SETUP" to "\"EMAIL_OTP\",\"SOFTWARE_TOKEN_MFA\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION, + emptyMap(), + null, + null, + setOf(MFAType.EMAIL, MFAType.TOTP) + ) + ), + signInResult + ) + assertNull(errorResult) + } + + @Test + fun `User needs to setup TOTP`() { + val totpSetupData = SignInTOTPSetupData( + secretCode = "secretCode", + session = "session", + username = username + ) + + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "MFA_SETUP", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_SETUP" to "\"SOFTWARE_TOKEN_MFA\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = totpSetupData, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, + mapOf("MFAS_CAN_SETUP" to "\"SOFTWARE_TOKEN_MFA\""), + null, + TOTPSetupDetails( + sharedSecret = totpSetupData.secretCode, + username = totpSetupData.username + ), + null + ) + ), + signInResult + ) + assertNull(errorResult) + } + + @Test + fun `User needs to setup email OTP`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "MFA_SETUP", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_SETUP" to "\"EMAIL_OTP\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP, + emptyMap(), + null, + null, + null + ) + ), + signInResult + ) + assertNull(errorResult) + } + + // MFA Selection + @Test + fun `User needs to select which MFA type to use`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "SELECT_MFA_TYPE", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_CHOOSE" to "\"EMAIL_MFA\",\"SOFTWARE_TOKEN_MFA\",\"SMS_MFA\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION, + emptyMap(), + null, + null, + setOf(MFAType.EMAIL, MFAType.TOTP, MFAType.SMS) + ) + ), + signInResult + ) + assertNull(errorResult) + } + + // Email OTP + @Test + fun `User is asked to confirm an emailed MFA code`() { + val deliveryDetails = AuthCodeDeliveryDetails(email, AuthCodeDeliveryDetails.DeliveryMedium.EMAIL) + + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "EMAIL_OTP", + username = username, + session = "session", + parameters = mapOf( + "CODE_DELIVERY_DELIVERY_MEDIUM" to "email", + "CODE_DELIVERY_DESTINATION" to email + ) + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, + emptyMap(), + deliveryDetails, + null, + null + ) + ), + signInResult + ) + assertNull(errorResult) + } + + // TOTP + @Test + fun `User is asked to input the TOTP code`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "SOFTWARE_TOKEN_MFA", + username = username, + session = "session", + parameters = null + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, + emptyMap(), + null, + null, + null + ) + ), + signInResult + ) + assertNull(errorResult) + } + + // SMS + @Test + fun `User is asked to confirm an SMS MFA code`() { + val phoneNumber = "+15555555555" + val deliveryDetails = AuthCodeDeliveryDetails(phoneNumber, AuthCodeDeliveryDetails.DeliveryMedium.SMS) + + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "SMS_MFA", + username = username, + session = "session", + parameters = mapOf( + "CODE_DELIVERY_DELIVERY_MEDIUM" to "sms", + "CODE_DELIVERY_DESTINATION" to phoneNumber + ) + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertEquals( + AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, + emptyMap(), + deliveryDetails, + null, + null + ) + ), + signInResult + ) + assertNull(errorResult) + } + + // Exceptions + @Test + fun `Exception when setting up unknown challenge type`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "MFA_SETUP", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_SETUP" to "\"UNKNOWN_CHALLENGE\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertIs(errorResult) + assertNull(signInResult) + } + + @Test + fun `Exception when choosing an unknown challenge type`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "MFA_SETUP", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_CHOOSE" to "\"EMAIL_MFA\",\"UNKNOWN\",\"SMS_MFA\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertIs(errorResult) + assertNull(signInResult) + } + + @Test + fun `Exception when receiving an unsupported challenge name`() { + var signInResult: AuthSignInResult? = null + var errorResult: AuthException? = null + SignInChallengeHelper.getNextStep( + challenge = AuthChallenge( + challengeName = "UNKNOWN", + username = username, + session = "session", + parameters = mapOf("MFAS_CAN_CHOOSE" to "\"EMAIL_MFA\",\"UNKNOWN\",\"SMS_MFA\"") + ), + onSuccess = { + signInResult = it + }, + onError = { + errorResult = it + }, + signInTOTPSetupData = null, + allowedMFAType = null + ) + assertIs(errorResult) + assertNull(signInResult) + } +} \ No newline at end of file diff --git a/scripts/pull_backend_config_from_s3 b/scripts/pull_backend_config_from_s3 index b7671765f7..eb4c385369 100755 --- a/scripts/pull_backend_config_from_s3 +++ b/scripts/pull_backend_config_from_s3 @@ -62,6 +62,7 @@ readonly config_files=( "aws-auth-cognito/src/androidTest/res/raw/awsconfiguration.json" "aws-auth-cognito/src/androidTest/res/raw/credentials.json" "aws-auth-cognito/src/androidTest/res/raw/amplify_outputs.json" + "aws-auth-cognito/src/androidTest/res/raw/amplify_outputs_email_or_totp_mfa.json" ) # Set up output path From b05b95728c65aed45514a30ed830fc8a697e556a Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 7 Oct 2024 16:09:35 -0700 Subject: [PATCH 09/18] Fix ktlint issues --- .../auth/cognito/testutils/AbortableCountdownLatch.kt | 2 +- .../amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt | 2 +- .../auth/cognito/helpers/SignInChallengeHelperTest.kt | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt index 155e92f968..6b1d3709f2 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2024 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. diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt index 789ac451c0..4771fa5403 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt @@ -481,7 +481,7 @@ class AWSCognitoAuthPlugin : AuthPlugin() { onSuccess: Action, onError: Consumer ) = enqueue(onSuccess, onError) { queueFacade.updateMFAPreference(sms, totp, null) } - + fun updateMFAPreference( sms: MFAPreference? = null, totp: MFAPreference? = null, diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt index c223d1a6f2..8d29d4569b 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt @@ -24,12 +24,9 @@ import com.amplifyframework.auth.result.step.AuthNextSignInStep import com.amplifyframework.auth.result.step.AuthSignInStep import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData -import featureTest.utilities.APICaptorFactory.Companion.onError -import featureTest.utilities.APICaptorFactory.Companion.onSuccess import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs -import kotlin.test.assertNotNull import kotlin.test.assertNull class SignInChallengeHelperTest { @@ -385,4 +382,4 @@ class SignInChallengeHelperTest { assertIs(errorResult) assertNull(signInResult) } -} \ No newline at end of file +} From 7a5964590e3a17303a37a48303650b2ceee2ad38 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 7 Oct 2024 18:10:01 -0700 Subject: [PATCH 10/18] Fix ktlint issues --- .../auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt | 10 +++++----- .../auth/cognito/testutils/AbortableCountdownLatch.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt index 51e3a56f39..0c7a98cee1 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt @@ -24,7 +24,7 @@ import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.exceptions.service.CodeMismatchException import com.amplifyframework.auth.cognito.test.R -import com.amplifyframework.auth.cognito.testutils.AbortableCountDownLatch +import com.amplifyframework.auth.cognito.testutils.AbortableCountdownLatch import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.result.AuthSignUpResult import com.amplifyframework.auth.result.step.AuthSignInStep @@ -53,7 +53,7 @@ class AWSCognitoAuthPluginEmailMFATests { private var apiPlugin = AWSApiPlugin() lateinit var synchronousAuth: SynchronousAuth var mfaCode = "" - var abortableLatch: AbortableCountDownLatch? = null + var abortableLatch: AbortableCountdownLatch? = null @JvmStatic @BeforeClass @@ -111,7 +111,7 @@ class AWSCognitoAuthPluginEmailMFATests { assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP, signInResult.nextStep.signInStep) // Step 4: Input the email address to send the code to then wait for the MFA code - abortableLatch = AbortableCountDownLatch(1) + abortableLatch = AbortableCountdownLatch(1) signInResult = synchronousAuth.confirmSignIn(email) // Validation 3: Validate that the next step is to confirm the emailed MFA code @@ -133,7 +133,7 @@ class AWSCognitoAuthPluginEmailMFATests { signUpNewUser(email) // Step 2: Attempt to sign in with the newly created user - abortableLatch = AbortableCountDownLatch(1) + abortableLatch = AbortableCountdownLatch(1) var signInResult = synchronousAuth.signIn(userName, password) // Validation 1: Validate that the next step is to confirm the emailed MFA code @@ -155,7 +155,7 @@ class AWSCognitoAuthPluginEmailMFATests { signUpNewUser(email) // Step 2: Attempt to sign in with the newly created user - abortableLatch = AbortableCountDownLatch(1) + abortableLatch = AbortableCountdownLatch(1) var signInResult = synchronousAuth.signIn(userName, password) // Validation 1: Validate that the next step is to confirm the emailed MFA code diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt index 6b1d3709f2..ca7dd6a422 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt @@ -16,7 +16,7 @@ package com.amplifyframework.auth.cognito.testutils import java.util.concurrent.CountDownLatch -class AbortableCountDownLatch(count: Int) : CountDownLatch(count) { +class AbortableCountdownLatch(count: Int) : CountDownLatch(count) { fun abort() { if (count == 0L) { From 1eeeaf73c851727a249aa443cbcbe0f686a7bc23 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 7 Oct 2024 21:49:41 -0700 Subject: [PATCH 11/18] Fix ktlint issues --- .../cognito/actions/SignInCognitoActions.kt | 3 ++- .../codegen/states/SetupTOTPState.kt | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index 7a9d131b4e..2a89b5c1f4 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -146,7 +146,8 @@ internal object SignInCognitoActions : SignInActions { SetupTOTPEvent.EventType.SetupTOTP( totpSetupDetails = event.signInTOTPSetupData, challengeParams = event.challengeParams - )) + ) + ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index d2231bde1e..e3ce55329c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -64,11 +64,13 @@ internal sealed class SetupTOTPState : State { is SetupTOTP -> when (challengeEvent) { is SetupTOTPEvent.EventType.WaitForAnswer -> { - StateResolution(WaitingForAnswer( - signInTOTPSetupData = challengeEvent.totpSetupDetails, - hasNewResponse = true, - challengeParams = challengeEvent.challengeParams - )) + StateResolution( + WaitingForAnswer( + signInTOTPSetupData = challengeEvent.totpSetupDetails, + hasNewResponse = true, + challengeParams = challengeEvent.challengeParams + ) + ) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( @@ -135,11 +137,13 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.WaitForAnswer -> { - StateResolution(WaitingForAnswer( - signInTOTPSetupData = challengeEvent.totpSetupDetails, - hasNewResponse = true, - challengeParams = challengeEvent.challengeParams - )) + StateResolution( + WaitingForAnswer( + signInTOTPSetupData = challengeEvent.totpSetupDetails, + hasNewResponse = true, + challengeParams = challengeEvent.challengeParams + ) + ) } else -> defaultResolution From 68d50d21742a0eadb587eeb2ab68a6b1ae2be681 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 7 Oct 2024 22:02:41 -0700 Subject: [PATCH 12/18] Update API dump --- aws-auth-cognito/api/aws-auth-cognito.api | 2 ++ core/api/core.api | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/aws-auth-cognito/api/aws-auth-cognito.api b/aws-auth-cognito/api/aws-auth-cognito.api index 3b1d9e0a4d..af4b955052 100644 --- a/aws-auth-cognito/api/aws-auth-cognito.api +++ b/aws-auth-cognito/api/aws-auth-cognito.api @@ -53,7 +53,9 @@ public final class com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin : com/ public fun signOut (Lcom/amplifyframework/auth/options/AuthSignOutOptions;Lcom/amplifyframework/core/Consumer;)V public fun signOut (Lcom/amplifyframework/core/Consumer;)V public fun signUp (Ljava/lang/String;Ljava/lang/String;Lcom/amplifyframework/auth/options/AuthSignUpOptions;Lcom/amplifyframework/core/Consumer;Lcom/amplifyframework/core/Consumer;)V + public final fun updateMFAPreference (Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/core/Action;Lcom/amplifyframework/core/Consumer;)V public final fun updateMFAPreference (Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/core/Action;Lcom/amplifyframework/core/Consumer;)V + public static synthetic fun updateMFAPreference$default (Lcom/amplifyframework/auth/cognito/AWSCognitoAuthPlugin;Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/auth/cognito/MFAPreference;Lcom/amplifyframework/core/Action;Lcom/amplifyframework/core/Consumer;ILjava/lang/Object;)V public fun updatePassword (Ljava/lang/String;Ljava/lang/String;Lcom/amplifyframework/core/Action;Lcom/amplifyframework/core/Consumer;)V public fun updateUserAttribute (Lcom/amplifyframework/auth/AuthUserAttribute;Lcom/amplifyframework/auth/options/AuthUpdateUserAttributeOptions;Lcom/amplifyframework/core/Consumer;Lcom/amplifyframework/core/Consumer;)V public fun updateUserAttribute (Lcom/amplifyframework/auth/AuthUserAttribute;Lcom/amplifyframework/core/Consumer;Lcom/amplifyframework/core/Consumer;)V diff --git a/core/api/core.api b/core/api/core.api index 790095cd96..97d5a5ca0d 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -691,6 +691,7 @@ public final class com/amplifyframework/auth/AuthUserAttributeKey { } public final class com/amplifyframework/auth/MFAType : java/lang/Enum { + public static final field EMAIL Lcom/amplifyframework/auth/MFAType; public static final field SMS Lcom/amplifyframework/auth/MFAType; public static final field TOTP Lcom/amplifyframework/auth/MFAType; public static fun getEntries ()Lkotlin/enums/EnumEntries; @@ -1151,11 +1152,14 @@ public final class com/amplifyframework/auth/result/step/AuthResetPasswordStep : public final class com/amplifyframework/auth/result/step/AuthSignInStep : java/lang/Enum { public static final field CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE Lcom/amplifyframework/auth/result/step/AuthSignInStep; + public static final field CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_IN_WITH_NEW_PASSWORD Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_IN_WITH_TOTP_CODE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_UP Lcom/amplifyframework/auth/result/step/AuthSignInStep; + public static final field CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONTINUE_SIGN_IN_WITH_MFA_SELECTION Lcom/amplifyframework/auth/result/step/AuthSignInStep; + public static final field CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONTINUE_SIGN_IN_WITH_TOTP_SETUP Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field DONE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field RESET_PASSWORD Lcom/amplifyframework/auth/result/step/AuthSignInStep; From 2d042ec03317f2f9d4fda93865844bd8aa213e4a Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 23 Oct 2024 14:30:36 -0700 Subject: [PATCH 13/18] Update AuthSignInStep --- .../auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt | 6 +++--- .../auth/cognito/helpers/SignInChallengeHelper.kt | 2 +- .../auth/cognito/helpers/SignInChallengeHelperTest.kt | 2 +- .../amplifyframework/auth/result/step/AuthSignInStep.java | 7 ++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt index 0c7a98cee1..a36f0436bb 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt @@ -115,7 +115,7 @@ class AWSCognitoAuthPluginEmailMFATests { signInResult = synchronousAuth.confirmSignIn(email) // Validation 3: Validate that the next step is to confirm the emailed MFA code - assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, signInResult.nextStep.signInStep) + assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, signInResult.nextStep.signInStep) // Wait until the MFA code has been received abortableLatch?.await(20, TimeUnit.SECONDS) @@ -137,7 +137,7 @@ class AWSCognitoAuthPluginEmailMFATests { var signInResult = synchronousAuth.signIn(userName, password) // Validation 1: Validate that the next step is to confirm the emailed MFA code - assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, signInResult.nextStep.signInStep) + assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, signInResult.nextStep.signInStep) // Wait until the MFA code has been received abortableLatch?.await(20, TimeUnit.SECONDS) @@ -159,7 +159,7 @@ class AWSCognitoAuthPluginEmailMFATests { var signInResult = synchronousAuth.signIn(userName, password) // Validation 1: Validate that the next step is to confirm the emailed MFA code - assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, signInResult.nextStep.signInStep) + assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, signInResult.nextStep.signInStep) // Wait until the MFA code has been received abortableLatch?.await(20, TimeUnit.SECONDS) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index ff67d97739..feb2481419 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -252,7 +252,7 @@ internal object SignInChallengeHelper { val authSignInResult = AuthSignInResult( false, AuthNextSignInStep( - AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, mapOf(), deliveryDetails, null, diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt index 8d29d4569b..361afde3ba 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt @@ -218,7 +218,7 @@ class SignInChallengeHelperTest { AuthSignInResult( false, AuthNextSignInStep( - AuthSignInStep.CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, emptyMap(), deliveryDetails, null, diff --git a/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java b/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java index b5273b361c..2ad183044f 100644 --- a/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java +++ b/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java @@ -94,11 +94,12 @@ public enum AuthSignInStep { CONFIRM_SIGN_IN_WITH_TOTP_CODE, /** - * MFA is enabled on this account and requires the user to confirm with the code received by email. + * MFA is enabled on this account and requires the user to confirm with the code received by + * email, sms, etc. * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} - * with email code. + * with the OTP code. */ - CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE, + CONFIRM_SIGN_IN_WITH_OTP, /** * No further steps are needed in the sign in flow. From df76317c73fb33ef2a8c2be3ba937ad6555244d2 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Thu, 24 Oct 2024 10:03:19 -0700 Subject: [PATCH 14/18] Update core.api --- core/api/core.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/api/core.api b/core/api/core.api index 97d5a5ca0d..c4e4ae8bc7 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -1152,8 +1152,8 @@ public final class com/amplifyframework/auth/result/step/AuthResetPasswordStep : public final class com/amplifyframework/auth/result/step/AuthSignInStep : java/lang/Enum { public static final field CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE Lcom/amplifyframework/auth/result/step/AuthSignInStep; - public static final field CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_IN_WITH_NEW_PASSWORD Lcom/amplifyframework/auth/result/step/AuthSignInStep; + public static final field CONFIRM_SIGN_IN_WITH_OTP Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_IN_WITH_TOTP_CODE Lcom/amplifyframework/auth/result/step/AuthSignInStep; public static final field CONFIRM_SIGN_UP Lcom/amplifyframework/auth/result/step/AuthSignInStep; From 64e200bc0f471117c869c31a43881822c2675138 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 29 Oct 2024 13:21:33 -0700 Subject: [PATCH 15/18] Remove AbortableCountdownLatch and fix a crash --- .../AWSCognitoAuthPluginEmailMFATests.kt | 18 +++++------ .../testutils/AbortableCountdownLatch.kt | 30 ------------------- .../auth/cognito/helpers/MFAHelper.kt | 2 +- .../auth/cognito/AWSCognitoAuthPluginTest.kt | 12 ++++++++ 4 files changed, 22 insertions(+), 40 deletions(-) delete mode 100644 aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt index a36f0436bb..e243b46325 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt @@ -24,7 +24,6 @@ import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.exceptions.service.CodeMismatchException import com.amplifyframework.auth.cognito.test.R -import com.amplifyframework.auth.cognito.testutils.AbortableCountdownLatch import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.result.AuthSignUpResult import com.amplifyframework.auth.result.step.AuthSignInStep @@ -35,6 +34,7 @@ import com.amplifyframework.testutils.Assets import com.amplifyframework.testutils.sync.SynchronousAuth import java.util.Random import java.util.UUID +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -53,7 +53,7 @@ class AWSCognitoAuthPluginEmailMFATests { private var apiPlugin = AWSApiPlugin() lateinit var synchronousAuth: SynchronousAuth var mfaCode = "" - var abortableLatch: AbortableCountdownLatch? = null + var latch: CountDownLatch? = null @JvmStatic @BeforeClass @@ -76,7 +76,7 @@ class AWSCognitoAuthPluginEmailMFATests { { println("====== Received some MFA Info ======") mfaCode = it.data.code - abortableLatch?.abort() + latch?.countDown() }, { println("====== Subscription Failed $it ======") }, { } @@ -111,14 +111,14 @@ class AWSCognitoAuthPluginEmailMFATests { assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP, signInResult.nextStep.signInStep) // Step 4: Input the email address to send the code to then wait for the MFA code - abortableLatch = AbortableCountdownLatch(1) + latch = CountDownLatch(1) signInResult = synchronousAuth.confirmSignIn(email) // Validation 3: Validate that the next step is to confirm the emailed MFA code assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, signInResult.nextStep.signInStep) // Wait until the MFA code has been received - abortableLatch?.await(20, TimeUnit.SECONDS) + latch?.await(20, TimeUnit.SECONDS) // Step 5: Input the emailed MFA code for confirmation signInResult = synchronousAuth.confirmSignIn(mfaCode) @@ -133,14 +133,14 @@ class AWSCognitoAuthPluginEmailMFATests { signUpNewUser(email) // Step 2: Attempt to sign in with the newly created user - abortableLatch = AbortableCountdownLatch(1) + latch = CountDownLatch(1) var signInResult = synchronousAuth.signIn(userName, password) // Validation 1: Validate that the next step is to confirm the emailed MFA code assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, signInResult.nextStep.signInStep) // Wait until the MFA code has been received - abortableLatch?.await(20, TimeUnit.SECONDS) + latch?.await(20, TimeUnit.SECONDS) // Step 4: Input the emailed MFA code for confirmation signInResult = synchronousAuth.confirmSignIn(mfaCode) @@ -155,14 +155,14 @@ class AWSCognitoAuthPluginEmailMFATests { signUpNewUser(email) // Step 2: Attempt to sign in with the newly created user - abortableLatch = AbortableCountdownLatch(1) + latch = CountDownLatch(1) var signInResult = synchronousAuth.signIn(userName, password) // Validation 1: Validate that the next step is to confirm the emailed MFA code assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP, signInResult.nextStep.signInStep) // Wait until the MFA code has been received - abortableLatch?.await(20, TimeUnit.SECONDS) + latch?.await(20, TimeUnit.SECONDS) // Step 4: Input the an incorrect MFA code // Validation 2: Validate that an incorrect MFA code throws a CodeMismatchException diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt deleted file mode 100644 index ca7dd6a422..0000000000 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/testutils/AbortableCountdownLatch.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2024 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.testutils - -import java.util.concurrent.CountDownLatch - -class AbortableCountdownLatch(count: Int) : CountDownLatch(count) { - - fun abort() { - if (count == 0L) { - return - } - - while (count > 0) { - countDown() - } - } -} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt index f9146149b1..96511090b4 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/MFAHelper.kt @@ -62,7 +62,7 @@ internal fun getAllowedMFATypesFromChallengeParameters(challengeParameters: Map< when (it) { "SMS_MFA" -> result.add(MFAType.SMS) "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) - "EMAIL_MFA" -> result.add(MFAType.EMAIL) + "EMAIL_OTP" -> result.add(MFAType.EMAIL) else -> throw UnknownException(cause = Exception("MFA type not supported.")) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt index 3dd650b065..66f0ef0ceb 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt @@ -718,6 +718,18 @@ class AWSCognitoAuthPluginTest { verify(timeout = CHANNEL_TIMEOUT) { realPlugin.fetchMFAPreference(any(), any()) } } + @Test + fun updateMFAPreferencesDeprecatedApi() { + val smsPreference = MFAPreference.ENABLED + val totpPreference = MFAPreference.PREFERRED + val onSuccess = Action { } + val onError = Consumer { } + authPlugin.updateMFAPreference(smsPreference, totpPreference, onSuccess, onError) + verify(timeout = CHANNEL_TIMEOUT) { + realPlugin.updateMFAPreference(smsPreference, totpPreference, null, any(), any()) + } + } + @Test fun updateMFAPreferences() { val smsPreference = MFAPreference.ENABLED From 9dccb0eaaf0ecc7e95542eb4aa3575731ec441f2 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 29 Oct 2024 14:00:29 -0700 Subject: [PATCH 16/18] Update test email domain and removed @BeforeClass --- .../AWSCognitoAuthPluginEmailMFATests.kt | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt index e243b46325..b69f088b06 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt @@ -39,49 +39,46 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertFailsWith import org.junit.After -import org.junit.BeforeClass +import org.junit.Before import org.junit.Test class AWSCognitoAuthPluginEmailMFATests { private val password = "${UUID.randomUUID()}BleepBloop1234!" private val userName = "test${Random().nextInt()}" - private val email = "$userName@testdomain.com" - - companion object { - private var authPlugin = AWSCognitoAuthPlugin() - private var apiPlugin = AWSApiPlugin() - lateinit var synchronousAuth: SynchronousAuth - var mfaCode = "" - var latch: CountDownLatch? = null - - @JvmStatic - @BeforeClass - fun initializePlugin() { - val context = ApplicationProvider.getApplicationContext() - val config = AmplifyOutputsData - .deserialize(context, AmplifyOutputs.fromResource(R.raw.amplify_outputs_email_or_totp_mfa)) - - authPlugin.configure(config, context) - apiPlugin.configure(config, context) - synchronousAuth = SynchronousAuth.delegatingTo(authPlugin) - - apiPlugin.subscribe( - SimpleGraphQLRequest( - Assets.readAsString("create-mfa-subscription.graphql"), - MfaInfo::class.java, - null - ), - { println("====== Subscription Established ======") }, - { - println("====== Received some MFA Info ======") - mfaCode = it.data.code - latch?.countDown() - }, - { println("====== Subscription Failed $it ======") }, - { } - ) - } + private val email = "$userName@amplify-swift-gamma.awsapps.com" + + private var authPlugin = AWSCognitoAuthPlugin() + private var apiPlugin = AWSApiPlugin() + lateinit var synchronousAuth: SynchronousAuth + private var mfaCode = "" + private var latch: CountDownLatch? = null + + @Before + fun initializePlugin() { + val context = ApplicationProvider.getApplicationContext() + val config = AmplifyOutputsData + .deserialize(context, AmplifyOutputs.fromResource(R.raw.amplify_outputs_email_or_totp_mfa)) + + authPlugin.configure(config, context) + apiPlugin.configure(config, context) + synchronousAuth = SynchronousAuth.delegatingTo(authPlugin) + + apiPlugin.subscribe( + SimpleGraphQLRequest( + Assets.readAsString("create-mfa-subscription.graphql"), + MfaInfo::class.java, + null + ), + { println("====== Subscription Established ======") }, + { + println("====== Received some MFA Info ======") + mfaCode = it.data.code + latch?.countDown() + }, + { println("====== Subscription Failed $it ======") }, + { } + ) } @After From fa3c498b9a6dbf1a5a9dc0f2a04b23f8ed11a79d Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 29 Oct 2024 14:02:05 -0700 Subject: [PATCH 17/18] Made a var private --- .../auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt index b69f088b06..026d45ffd7 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginEmailMFATests.kt @@ -50,7 +50,7 @@ class AWSCognitoAuthPluginEmailMFATests { private var authPlugin = AWSCognitoAuthPlugin() private var apiPlugin = AWSApiPlugin() - lateinit var synchronousAuth: SynchronousAuth + private lateinit var synchronousAuth: SynchronousAuth private var mfaCode = "" private var latch: CountDownLatch? = null From 3d082c4e3eb8c6ac25ca4c013eb1daf65603ae4f Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Tue, 29 Oct 2024 14:34:00 -0700 Subject: [PATCH 18/18] Fix a unit test --- .../auth/cognito/helpers/SignInChallengeHelperTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt index 361afde3ba..a55db8319f 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelperTest.kt @@ -161,7 +161,7 @@ class SignInChallengeHelperTest { challengeName = "SELECT_MFA_TYPE", username = username, session = "session", - parameters = mapOf("MFAS_CAN_CHOOSE" to "\"EMAIL_MFA\",\"SOFTWARE_TOKEN_MFA\",\"SMS_MFA\"") + parameters = mapOf("MFAS_CAN_CHOOSE" to "\"EMAIL_OTP\",\"SOFTWARE_TOKEN_MFA\",\"SMS_MFA\"") ), onSuccess = { signInResult = it