diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfa.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfa.kt new file mode 100644 index 00000000..6f2b2957 --- /dev/null +++ b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInConfirmMfa.kt @@ -0,0 +1,114 @@ +/* + * 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.ui.authenticator.ui + +import com.amplifyframework.auth.AuthCodeDeliveryDetails +import com.amplifyframework.ui.authenticator.ScreenshotTestBase +import com.amplifyframework.ui.authenticator.SignInConfirmMfaState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldConfig +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.mockFieldData +import com.amplifyframework.ui.authenticator.mockFieldState +import com.amplifyframework.ui.authenticator.mockForm +import org.junit.Test + +class SignInConfirmMfa : ScreenshotTestBase() { + + @Test + fun sign_in_confirm_email_mfa_default() { + screenshot { + SignInConfirmMfa( + mockSignInConfirmMfaState( + AuthCodeDeliveryDetails( + "email@email.com", + AuthCodeDeliveryDetails.DeliveryMedium.EMAIL + ) + ) + ) + } + } + + @Test + fun sign_in_confirm_email_mfa_incorrect_code() { + screenshot { + SignInConfirmMfa( + mockSignInConfirmMfaState( + AuthCodeDeliveryDetails( + "email@email.com", + AuthCodeDeliveryDetails.DeliveryMedium.EMAIL + ), + "123456", + FieldError.ConfirmationCodeIncorrect + ) + ) + } + } + + @Test + fun sign_in_confirm_sms_mfa_default() { + screenshot { + SignInConfirmMfa( + mockSignInConfirmMfaState( + AuthCodeDeliveryDetails( + "123-123-1234", + AuthCodeDeliveryDetails.DeliveryMedium.SMS + ) + ) + ) + } + } + + @Test + fun sign_in_confirm_sms_mfa_incorrect_code() { + screenshot { + SignInConfirmMfa( + mockSignInConfirmMfaState( + AuthCodeDeliveryDetails( + "123-123-1234", + AuthCodeDeliveryDetails.DeliveryMedium.SMS + ), + "123456", + FieldError.ConfirmationCodeIncorrect + ) + ) + } + } + + private fun mockSignInConfirmMfaState( + deliveryDetails: AuthCodeDeliveryDetails, + confirmationCode: String = "", + fieldError: FieldError? = null + ) = object : SignInConfirmMfaState { + override val form = mockForm( + mockFieldData( + config = FieldConfig.Text(FieldKey.ConfirmationCode), + state = mockFieldState(content = confirmationCode, error = fieldError) + ) + ) + override val deliveryDetails: AuthCodeDeliveryDetails + get() = deliveryDetails + + override fun moveTo(step: AuthenticatorInitialStep) {} + override suspend fun confirmSignIn() { + TODO("Not yet implemented") + } + + override val step = AuthenticatorStep.SignInContinueWithEmailSetup + } +} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupScreenshots.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupScreenshots.kt new file mode 100644 index 00000000..bceb39af --- /dev/null +++ b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupScreenshots.kt @@ -0,0 +1,49 @@ +/* + * 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.ui.authenticator.ui + +import com.amplifyframework.ui.authenticator.ScreenshotTestBase +import com.amplifyframework.ui.authenticator.SignInContinueWithEmailSetupState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldConfig +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.mockFieldData +import com.amplifyframework.ui.authenticator.mockFieldState +import com.amplifyframework.ui.authenticator.mockForm +import org.junit.Test + +class SignInContinueWithEmailSetupScreenshots : ScreenshotTestBase() { + + @Test + fun default_state() { + screenshot { + SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState()) + } + } + + private fun mockSignInContinueWithEmailSetupState() = object : SignInContinueWithEmailSetupState { + override val form = mockForm( + mockFieldData( + config = FieldConfig.Text(FieldKey.Email), + state = mockFieldState(content = "email@email.com") + ) + ) + override fun moveTo(step: AuthenticatorInitialStep) {} + override suspend fun continueSignIn() {} + override val step = AuthenticatorStep.SignInContinueWithEmailSetup + } +} diff --git a/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt new file mode 100644 index 00000000..acc9b433 --- /dev/null +++ b/authenticator-screenshots/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt @@ -0,0 +1,53 @@ +/* + * 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.ui.authenticator.ui + +import com.amplifyframework.auth.MFAType +import com.amplifyframework.ui.authenticator.ScreenshotTestBase +import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSetupSelectionState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldConfig +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.mockFieldData +import com.amplifyframework.ui.authenticator.mockFieldState +import com.amplifyframework.ui.authenticator.mockForm +import org.junit.Test + +class SignInContinueWithMfaSetupSelection : ScreenshotTestBase() { + + @Test + fun default_state() { + screenshot { + SignInContinueWithMfaSetupSelection(mockSignInContinueWithMfaSetupSelectionState()) + } + } + + private fun mockSignInContinueWithMfaSetupSelectionState() = object : SignInContinueWithMfaSetupSelectionState { + override val form = mockForm( + mockFieldData( + config = FieldConfig.Text(FieldKey.MfaSelection), + state = mockFieldState(content = "EMAIL_OTP") + ) + ) + override val allowedMfaTypes: Set + get() = setOf(MFAType.TOTP, MFAType.SMS, MFAType.EMAIL) + + override fun moveTo(step: AuthenticatorInitialStep) {} + override suspend fun continueSignIn() {} + override val step = AuthenticatorStep.SignInContinueWithEmailSetup + } +} diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_default.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_default.png new file mode 100644 index 00000000..781846e3 Binary files /dev/null and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_default.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_incorrect_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_incorrect_code.png new file mode 100644 index 00000000..1ac94933 Binary files /dev/null and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_email_mfa_incorrect_code.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_default.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_default.png new file mode 100644 index 00000000..48a998b4 Binary files /dev/null and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_default.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_incorrect_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_incorrect_code.png new file mode 100644 index 00000000..847c680e Binary files /dev/null and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmMfa_sign_in_confirm_sms_mfa_incorrect_code.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png index 5fcae746..9b1a24fa 100644 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_default_state.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png index 89f0b8db..b12b835d 100644 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInConfirmTotpCodeScreenshots_invalid_code.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithEmailSetupScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithEmailSetupScreenshots_default_state.png new file mode 100644 index 00000000..54134d06 Binary files /dev/null and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithEmailSetupScreenshots_default_state.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png index 60821626..dd99d0d5 100644 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSelectionScreenshots_default_state.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSetupSelection_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSetupSelection_default_state.png new file mode 100644 index 00000000..b1bae586 Binary files /dev/null and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithMfaSetupSelection_default_state.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png index f696a86c..6cb64d76 100644 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_default_state.png differ diff --git a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png index f8a15a55..579ad57e 100644 Binary files a/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png and b/authenticator-screenshots/src/test/snapshots/images/com.amplifyframework.ui.authenticator.ui_SignInContinueWithTotpSetupScreenshots_invalid_code.png differ diff --git a/authenticator/api/authenticator.api b/authenticator/api/authenticator.api index 1293549b..80d20739 100644 --- a/authenticator/api/authenticator.api +++ b/authenticator/api/authenticator.api @@ -81,6 +81,12 @@ public abstract interface class com/amplifyframework/ui/authenticator/SignInConf public abstract fun moveTo (Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorInitialStep;)V } +public abstract interface class com/amplifyframework/ui/authenticator/SignInContinueWithEmailSetupState : com/amplifyframework/ui/authenticator/AuthenticatorStepState { + public abstract fun continueSignIn (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getForm ()Lcom/amplifyframework/ui/authenticator/forms/MutableFormState; + public abstract fun moveTo (Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorInitialStep;)V +} + public abstract interface class com/amplifyframework/ui/authenticator/SignInContinueWithMfaSelectionState : com/amplifyframework/ui/authenticator/AuthenticatorStepState { public abstract fun continueSignIn (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getAllowedMfaTypes ()Ljava/util/Set; @@ -88,6 +94,13 @@ public abstract interface class com/amplifyframework/ui/authenticator/SignInCont public abstract fun moveTo (Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorInitialStep;)V } +public abstract interface class com/amplifyframework/ui/authenticator/SignInContinueWithMfaSetupSelectionState : com/amplifyframework/ui/authenticator/AuthenticatorStepState { + public abstract fun continueSignIn (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getAllowedMfaTypes ()Ljava/util/Set; + public abstract fun getForm ()Lcom/amplifyframework/ui/authenticator/forms/MutableFormState; + public abstract fun moveTo (Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorInitialStep;)V +} + public abstract interface class com/amplifyframework/ui/authenticator/SignInContinueWithTotpSetupState : com/amplifyframework/ui/authenticator/AuthenticatorStepState { public abstract fun continueSignIn (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getForm ()Lcom/amplifyframework/ui/authenticator/forms/MutableFormState; @@ -189,11 +202,21 @@ public final class com/amplifyframework/ui/authenticator/enums/AuthenticatorStep public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInConfirmTotpCode; } +public final class com/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithEmailSetup : com/amplifyframework/ui/authenticator/enums/AuthenticatorStep { + public static final field $stable I + public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithEmailSetup; +} + public final class com/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithMfaSelection : com/amplifyframework/ui/authenticator/enums/AuthenticatorStep { public static final field $stable I public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithMfaSelection; } +public final class com/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithMfaSetupSelection : com/amplifyframework/ui/authenticator/enums/AuthenticatorStep { + public static final field $stable I + public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithMfaSetupSelection; +} + public final class com/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithTotpSetup : com/amplifyframework/ui/authenticator/enums/AuthenticatorStep { public static final field $stable I public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/enums/AuthenticatorStep$SignInContinueWithTotpSetup; @@ -644,7 +667,7 @@ public final class com/amplifyframework/ui/authenticator/ui/AuthenticatorErrorKt } public final class com/amplifyframework/ui/authenticator/ui/AuthenticatorKt { - public static final fun Authenticator (Landroidx/compose/ui/Modifier;Lcom/amplifyframework/ui/authenticator/AuthenticatorState;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;IIII)V + public static final fun Authenticator (Landroidx/compose/ui/Modifier;Lcom/amplifyframework/ui/authenticator/AuthenticatorState;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;IIII)V } public final class com/amplifyframework/ui/authenticator/ui/AuthenticatorLoadingKt { @@ -667,8 +690,10 @@ public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons public static field lambda-13 Lkotlin/jvm/functions/Function3; public static field lambda-14 Lkotlin/jvm/functions/Function3; public static field lambda-15 Lkotlin/jvm/functions/Function3; - public static field lambda-16 Lkotlin/jvm/functions/Function2; - public static field lambda-17 Lkotlin/jvm/functions/Function2; + public static field lambda-16 Lkotlin/jvm/functions/Function3; + public static field lambda-17 Lkotlin/jvm/functions/Function3; + public static field lambda-18 Lkotlin/jvm/functions/Function2; + public static field lambda-19 Lkotlin/jvm/functions/Function2; public static field lambda-2 Lkotlin/jvm/functions/Function3; public static field lambda-3 Lkotlin/jvm/functions/Function3; public static field lambda-4 Lkotlin/jvm/functions/Function3; @@ -685,8 +710,10 @@ public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons public final fun getLambda-13$authenticator_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-14$authenticator_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-15$authenticator_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-16$authenticator_release ()Lkotlin/jvm/functions/Function2; - public final fun getLambda-17$authenticator_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-16$authenticator_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-17$authenticator_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-18$authenticator_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-19$authenticator_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda-2$authenticator_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-3$authenticator_release ()Lkotlin/jvm/functions/Function3; public final fun getLambda-4$authenticator_release ()Lkotlin/jvm/functions/Function3; @@ -794,6 +821,15 @@ public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons public final fun getLambda-2$authenticator_release ()Lkotlin/jvm/functions/Function3; } +public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithEmailSetupKt { + public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithEmailSetupKt; + public static field lambda-1 Lkotlin/jvm/functions/Function3; + public static field lambda-2 Lkotlin/jvm/functions/Function3; + public fun ()V + public final fun getLambda-1$authenticator_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-2$authenticator_release ()Lkotlin/jvm/functions/Function3; +} + public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithMfaSelectionKt { public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithMfaSelectionKt; public static field lambda-1 Lkotlin/jvm/functions/Function3; @@ -803,6 +839,15 @@ public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons public final fun getLambda-2$authenticator_release ()Lkotlin/jvm/functions/Function3; } +public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithMfaSetupSelectionKt { + public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithMfaSetupSelectionKt; + public static field lambda-1 Lkotlin/jvm/functions/Function3; + public static field lambda-2 Lkotlin/jvm/functions/Function3; + public fun ()V + public final fun getLambda-1$authenticator_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-2$authenticator_release ()Lkotlin/jvm/functions/Function3; +} + public final class com/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithTotpSetupKt { public static final field INSTANCE Lcom/amplifyframework/ui/authenticator/ui/ComposableSingletons$SignInContinueWithTotpSetupKt; public static field lambda-1 Lkotlin/jvm/functions/Function3; @@ -909,11 +954,21 @@ public final class com/amplifyframework/ui/authenticator/ui/SignInConfirmTotpCod public static final fun SignInConfirmTotpCodeFooter (Lcom/amplifyframework/ui/authenticator/SignInConfirmTotpCodeState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } +public final class com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupKt { + public static final fun SignInContinueWithEmailSetup (Lcom/amplifyframework/ui/authenticator/SignInContinueWithEmailSetupState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + public static final fun SignInContinueWithEmailSetupFooter (Lcom/amplifyframework/ui/authenticator/SignInContinueWithEmailSetupState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V +} + public final class com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionKt { public static final fun SignInContinueWithMfaSelection (Lcom/amplifyframework/ui/authenticator/SignInContinueWithMfaSelectionState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V public static final fun SignInContinueWithMfaSelectionFooter (Lcom/amplifyframework/ui/authenticator/SignInContinueWithMfaSelectionState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } +public final class com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelectionKt { + public static final fun SignInContinueWithMfaSetupSelection (Lcom/amplifyframework/ui/authenticator/SignInContinueWithMfaSetupSelectionState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + public static final fun SignInContinueWithMfaSetupSelectionFooter (Lcom/amplifyframework/ui/authenticator/SignInContinueWithMfaSetupSelectionState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V +} + public final class com/amplifyframework/ui/authenticator/ui/SignInContinueWithTotpSetupKt { public static final fun SignInContinueWithTotpSetup (Lcom/amplifyframework/ui/authenticator/SignInContinueWithTotpSetupState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V public static final fun SignInContinueWithTotpSetupFooter (Lcom/amplifyframework/ui/authenticator/SignInContinueWithTotpSetupState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt index 01b16b58..483dc409 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt @@ -229,6 +229,53 @@ interface SignInContinueWithTotpSetupState : AuthenticatorStepState { suspend fun continueSignIn() } +/** + * The user has completed the initial Sign In step, and must setup their desired MFA method to continue. + */ +@Stable +interface SignInContinueWithMfaSetupSelectionState : AuthenticatorStepState { + /** + * The input form state holder for this step. + */ + val form: MutableFormState + + /** + * The set of [MFAType] that could be used to continue this sign in. + */ + val allowedMfaTypes: Set + + /** + * Move the user to a different [AuthenticatorInitialStep]. + */ + fun moveTo(step: AuthenticatorInitialStep) + + /** + * Continue the user's sign in using the information entered into the [form]. + */ + suspend fun continueSignIn() +} + +/** + * The user has completed the initial Sign In step, and must setup email MFA method to continue. + */ +@Stable +interface SignInContinueWithEmailSetupState : AuthenticatorStepState { + /** + * The input form state holder for this step. + */ + val form: MutableFormState + + /** + * Move the user to a different [AuthenticatorInitialStep]. + */ + fun moveTo(step: AuthenticatorInitialStep) + + /** + * Continue the user's sign in using the information entered into the [form]. + */ + suspend fun continueSignIn() +} + /** * The user has completed the initial Sign In step, and must select their desired MFA method to continue. */ diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt index 6e9bac97..8ca3e555 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt @@ -328,6 +328,35 @@ internal class AuthenticatorViewModel( moveTo(newState) } + private suspend fun handleMfaSetupSelectionRequired( + username: String, + password: String, + allowedMfaTypes: Set? + ) { + if (allowedMfaTypes.isNullOrEmpty()) { + handleGeneralFailure(AuthException("Missing allowedMfaTypes", "Please open a bug with Amplify")) + return + } + + moveTo( + stateFactory.newSignInContinueWithMfaSetupSelectionState( + allowedMfaTypes = allowedMfaTypes, + onSubmit = { mfaType -> confirmSignIn(username, password, mfaType) } + ) + ) + } + + private suspend fun handleEmailMfaSetupRequired( + username: String, + password: String + ) { + moveTo( + stateFactory.newSignInContinueWithEmailSetupState( + onSubmit = { mfaType -> confirmSignIn(username, password, mfaType) } + ) + ) + } + private suspend fun handleMfaSelectionRequired( username: String, password: String, @@ -349,7 +378,8 @@ internal class AuthenticatorViewModel( private suspend fun handleSignInSuccess(username: String, password: String, result: AuthSignInResult) { when (val nextStep = result.nextStep.signInStep) { AuthSignInStep.DONE -> checkVerificationMechanisms() - AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> moveTo( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> moveTo( stateFactory.newSignInMfaState( result.nextStep.codeDeliveryDetails ) { confirmationCode -> confirmSignIn(username, password, confirmationCode) } @@ -373,6 +403,10 @@ internal class AuthenticatorViewModel( AuthSignInStep.CONFIRM_SIGN_UP -> handleUnconfirmedSignIn(username, password) AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> handleMfaSelectionRequired(username, password, result.nextStep.allowedMFATypes) + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> + handleMfaSetupSelectionRequired(username, password, result.nextStep.allowedMFATypes) + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> + handleEmailMfaSetupRequired(username, password) AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> handleTotpSetupRequired(username, password, result.nextStep.totpSetupDetails) AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> moveTo( diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/AuthenticatorStep.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/AuthenticatorStep.kt index dddd1429..700efecf 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/AuthenticatorStep.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/AuthenticatorStep.kt @@ -75,6 +75,16 @@ abstract class AuthenticatorStep internal constructor() { */ object SignInContinueWithTotpSetup : AuthenticatorStep() + /** + * The user has completed the initial Sign In step, and must register an email MFA to continue. + */ + object SignInContinueWithEmailSetup : AuthenticatorStep() + + /** + * The user has completed the initial Sign In step, but is required to setup a MFA type to continue. + */ + object SignInContinueWithMfaSetupSelection : AuthenticatorStep() + /** * The user has completed the initial Sign In step, and must select their desired MFA method to continue. */ diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInContinueWithEmailSetupStateImpl.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInContinueWithEmailSetupStateImpl.kt new file mode 100644 index 00000000..ba9a4b00 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInContinueWithEmailSetupStateImpl.kt @@ -0,0 +1,42 @@ +/* + * 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.ui.authenticator.states + +import com.amplifyframework.ui.authenticator.SignInContinueWithEmailSetupState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldKey + +internal class SignInContinueWithEmailSetupStateImpl( + private val onSubmit: suspend (email: String) -> Unit, + private val onMoveTo: (step: AuthenticatorInitialStep) -> Unit +) : BaseStateImpl(), SignInContinueWithEmailSetupState { + + init { + form.addFields { + email(required = true) + } + } + + override fun moveTo(step: AuthenticatorInitialStep) = onMoveTo(step) + + override suspend fun continueSignIn() = doSubmit { + val email = form.getTrimmed(FieldKey.Email)!! + onSubmit(email) + } + + override val step = AuthenticatorStep.SignInContinueWithEmailSetup +} diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInContinueWithMfaSetupSelectionStateImpl.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInContinueWithMfaSetupSelectionStateImpl.kt new file mode 100644 index 00000000..d7d26374 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInContinueWithMfaSetupSelectionStateImpl.kt @@ -0,0 +1,47 @@ + +/* + * 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.ui.authenticator.states + +import com.amplifyframework.auth.MFAType +import com.amplifyframework.auth.cognito.challengeResponse +import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSetupSelectionState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldKey + +internal class SignInContinueWithMfaSetupSelectionStateImpl( + override val allowedMfaTypes: Set, + private val onSubmit: suspend (selection: String) -> Unit, + private val onMoveTo: (step: AuthenticatorInitialStep) -> Unit +) : BaseStateImpl(), SignInContinueWithMfaSetupSelectionState { + + init { + form.addFields { + mfaSelection() + } + form.fields[FieldKey.MfaSelection]?.state?.content = allowedMfaTypes.first().challengeResponse + } + + override fun moveTo(step: AuthenticatorInitialStep) = onMoveTo(step) + + override suspend fun continueSignIn() = doSubmit { + val selected = form.fields[FieldKey.MfaSelection]!!.state.content + onSubmit(selected) + } + + override val step = AuthenticatorStep.SignInContinueWithMfaSetupSelection +} diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/StepStateFactory.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/StepStateFactory.kt index fb54abcb..d1df7f5e 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/StepStateFactory.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/StepStateFactory.kt @@ -82,6 +82,22 @@ internal class StepStateFactory( onMoveTo = onMoveTo ) + fun newSignInContinueWithMfaSetupSelectionState( + allowedMfaTypes: Set, + onSubmit: suspend (selection: String) -> Unit + ) = SignInContinueWithMfaSetupSelectionStateImpl( + allowedMfaTypes = allowedMfaTypes, + onSubmit = onSubmit, + onMoveTo = onMoveTo + ) + + fun newSignInContinueWithEmailSetupState( + onSubmit: suspend (email: String) -> Unit + ) = SignInContinueWithEmailSetupStateImpl( + onSubmit = onSubmit, + onMoveTo = onMoveTo + ) + fun newSignInContinueWithMfaSelectionState( allowedMfaTypes: Set, onSubmit: suspend (selection: String) -> Unit diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt index 350b1ff6..1abe05da 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt @@ -46,7 +46,9 @@ import com.amplifyframework.ui.authenticator.SignInConfirmCustomState import com.amplifyframework.ui.authenticator.SignInConfirmMfaState import com.amplifyframework.ui.authenticator.SignInConfirmNewPasswordState import com.amplifyframework.ui.authenticator.SignInConfirmTotpCodeState +import com.amplifyframework.ui.authenticator.SignInContinueWithEmailSetupState import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSelectionState +import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSetupSelectionState import com.amplifyframework.ui.authenticator.SignInContinueWithTotpSetupState import com.amplifyframework.ui.authenticator.SignInState import com.amplifyframework.ui.authenticator.SignUpConfirmState @@ -66,6 +68,8 @@ import com.amplifyframework.ui.authenticator.util.AuthenticatorMessage * @param signInConfirmMfaContent The content to show for the [SignInConfirmMfaState]. * @param signInConfirmCustomContent The content to show for the [SignInConfirmCustomState]. * @param signInConfirmNewPasswordContent The content to show for the [SignInConfirmNewPasswordState]. + * @param signInContinueWithMfaSetupSelectionContent The content to show for the [SignInContinueWithMfaSetupSelectionState] + * @param signInContinueWithEmailSetupContent The content to show for the [SignInContinueWithEmailSetupState] * @param signInConfirmTotpCodeContent The content to show for the [SignInConfirmTotpCodeState]. * @param signInContinueWithTotpSetupContent The content to show for the [SignInContinueWithTotpSetupState]. * @param signUpContent The content to show for the [SignUpState]. @@ -97,6 +101,13 @@ fun Authenticator( signInContinueWithTotpSetupContent: @Composable (state: SignInContinueWithTotpSetupState) -> Unit = { SignInContinueWithTotpSetup(it) }, + signInContinueWithEmailSetupContent: @Composable (state: SignInContinueWithEmailSetupState) -> Unit = { + SignInContinueWithEmailSetup(it) + }, + signInContinueWithMfaSetupSelectionContent: @Composable (state: SignInContinueWithMfaSetupSelectionState) -> + Unit = { + SignInContinueWithMfaSetupSelection(it) + }, signInContinueWithMfaSelectionContent: @Composable (state: SignInContinueWithMfaSelectionState) -> Unit = { SignInContinueWithMfaSelection(it) }, @@ -140,6 +151,9 @@ fun Authenticator( ) is SignInConfirmTotpCodeState -> signInConfirmTotpCodeContent(targetState) is SignInContinueWithTotpSetupState -> signInContinueWithTotpSetupContent(targetState) + is SignInContinueWithEmailSetupState -> signInContinueWithEmailSetupContent(targetState) + is SignInContinueWithMfaSetupSelectionState -> + signInContinueWithMfaSetupSelectionContent(targetState) is SignInContinueWithMfaSelectionState -> signInContinueWithMfaSelectionContent(targetState) is SignUpState -> signUpContent(targetState) is PasswordResetState -> passwordResetContent(targetState) diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetup.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetup.kt new file mode 100644 index 00000000..142c8858 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetup.kt @@ -0,0 +1,73 @@ +/* + * 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.ui.authenticator.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.amplifyframework.ui.authenticator.R +import com.amplifyframework.ui.authenticator.SignInContinueWithEmailSetupState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import kotlinx.coroutines.launch + +@Composable +fun SignInContinueWithEmailSetup( + state: SignInContinueWithEmailSetupState, + modifier: Modifier = Modifier, + headerContent: @Composable (state: SignInContinueWithEmailSetupState) -> Unit = { + AuthenticatorTitle(stringResource(R.string.amplify_ui_authenticator_title_signin_continue_email_setup)) + }, + footerContent: @Composable (state: SignInContinueWithEmailSetupState) -> Unit = { + SignInContinueWithEmailSetupFooter(state = it) + } +) { + val scope = rememberCoroutineScope() + + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + headerContent(state) + Text( + modifier = Modifier.padding(bottom = 16.dp), + text = stringResource(R.string.amplify_ui_authenticator_email_setup_description) + ) + AuthenticatorForm(state = state.form) + AuthenticatorButton( + modifier = modifier.testTag(TestTags.SignInConfirmButton), + onClick = { scope.launch { state.continueSignIn() } }, + loading = !state.form.enabled + ) + footerContent(state) + } +} + +@Composable +fun SignInContinueWithEmailSetupFooter( + state: SignInContinueWithEmailSetupState, + modifier: Modifier = Modifier +) = BackToSignInFooter( + modifier = modifier, + onClickBackToSignIn = { state.moveTo(AuthenticatorStep.SignIn) } +) diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelection.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelection.kt index f0341180..40f5b837 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelection.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelection.kt @@ -3,6 +3,7 @@ package com.amplifyframework.ui.authenticator.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -19,6 +20,8 @@ import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.FieldKey import kotlinx.coroutines.launch +private val MFA_SELECTION_ORDER = setOf(MFAType.TOTP, MFAType.SMS, MFAType.EMAIL) + @Composable fun SignInContinueWithMfaSelection( state: SignInContinueWithMfaSelectionState, @@ -40,7 +43,11 @@ fun SignInContinueWithMfaSelection( .padding(horizontal = 16.dp) ) { headerContent(state) - val items = remember { state.allowedMfaTypes.map { it.challengeResponse } } + Text( + modifier = Modifier.padding(bottom = 16.dp), + text = stringResource(R.string.amplify_ui_authenticator_mfa_selection_description) + ) + val items = remember { MFA_SELECTION_ORDER.intersect(state.allowedMfaTypes).map { it.challengeResponse } } RadioGroup( items = items, selected = fieldState.content, @@ -48,6 +55,7 @@ fun SignInContinueWithMfaSelection( label = { when (it) { MFAType.SMS.challengeResponse -> context.getString(R.string.amplify_ui_authenticator_mfa_sms) + MFAType.EMAIL.challengeResponse -> context.getString(R.string.amplify_ui_authenticator_mfa_email) else -> context.getString(R.string.amplify_ui_authenticator_mfa_totp) } }, @@ -56,7 +64,8 @@ fun SignInContinueWithMfaSelection( AuthenticatorButton( modifier = modifier.testTag(TestTags.SignInConfirmButton), onClick = { scope.launch { state.continueSignIn() } }, - loading = !state.form.enabled + loading = !state.form.enabled, + label = stringResource(R.string.amplify_ui_authenticator_button_continue) ) footerContent(state) } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt new file mode 100644 index 00000000..d62c0dbb --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSetupSelection.kt @@ -0,0 +1,96 @@ +/* + * 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.ui.authenticator.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.amplifyframework.auth.MFAType +import com.amplifyframework.auth.cognito.challengeResponse +import com.amplifyframework.ui.authenticator.R +import com.amplifyframework.ui.authenticator.SignInContinueWithMfaSetupSelectionState +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldKey +import kotlinx.coroutines.launch + +private val MFA_SELECTION_ORDER = setOf(MFAType.TOTP, MFAType.EMAIL) + +@Composable +fun SignInContinueWithMfaSetupSelection( + state: SignInContinueWithMfaSetupSelectionState, + modifier: Modifier = Modifier, + headerContent: @Composable (state: SignInContinueWithMfaSetupSelectionState) -> Unit = { + AuthenticatorTitle(stringResource(R.string.amplify_ui_authenticator_title_signin_continue_setup_mfa_select)) + }, + footerContent: @Composable (state: SignInContinueWithMfaSetupSelectionState) -> Unit = { + SignInContinueWithMfaSetupSelectionFooter(state = it) + } +) { + val scope = rememberCoroutineScope() + val context = LocalContext.current + val fieldState = state.form.fields[FieldKey.MfaSelection]?.state!! + + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + headerContent(state) + Text( + modifier = Modifier.padding(bottom = 16.dp), + text = stringResource(R.string.amplify_ui_authenticator_mfa_setup_description) + ) + val items = remember { MFA_SELECTION_ORDER.intersect(state.allowedMfaTypes).map { it.challengeResponse } } + RadioGroup( + items = items, + selected = fieldState.content, + onSelect = { fieldState.content = it }, + label = { + when (it) { + MFAType.SMS.challengeResponse -> context.getString(R.string.amplify_ui_authenticator_mfa_sms) + MFAType.EMAIL.challengeResponse -> context.getString(R.string.amplify_ui_authenticator_mfa_email) + else -> context.getString(R.string.amplify_ui_authenticator_mfa_totp) + } + }, + modifier = Modifier.padding(bottom = 16.dp) + ) + AuthenticatorButton( + modifier = modifier.testTag(TestTags.SignInConfirmButton), + label = context.getString(R.string.amplify_ui_authenticator_button_continue), + onClick = { scope.launch { state.continueSignIn() } }, + loading = !state.form.enabled + ) + footerContent(state) + } +} + +@Composable +fun SignInContinueWithMfaSetupSelectionFooter( + state: SignInContinueWithMfaSetupSelectionState, + modifier: Modifier = Modifier +) = BackToSignInFooter( + modifier = modifier, + onClickBackToSignIn = { state.moveTo(AuthenticatorStep.SignIn) } +) diff --git a/authenticator/src/main/res/values/buttons.xml b/authenticator/src/main/res/values/buttons.xml index d3460243..2e654d2d 100644 --- a/authenticator/src/main/res/values/buttons.xml +++ b/authenticator/src/main/res/values/buttons.xml @@ -16,6 +16,7 @@ Submit + Continue Sign In Change Password Create Account diff --git a/authenticator/src/main/res/values/errors.xml b/authenticator/src/main/res/values/errors.xml index e6427d4f..b3f46095 100644 --- a/authenticator/src/main/res/values/errors.xml +++ b/authenticator/src/main/res/values/errors.xml @@ -18,7 +18,7 @@ Username or Password is incorrect User password cannot be reset in the current state - Could not send confirmation code + Could not send verification code Code has expired Please check your connectivity You\'ve reached the request limit. Please try again later. diff --git a/authenticator/src/main/res/values/fields.xml b/authenticator/src/main/res/values/fields.xml index 9eedb504..77253cab 100644 --- a/authenticator/src/main/res/values/fields.xml +++ b/authenticator/src/main/res/values/fields.xml @@ -28,7 +28,7 @@ Confirm Password New Password Confirm New Password - Confirmation Code + Verification Code Phone Number Email Birthdate @@ -68,5 +68,5 @@ Passwords do not match - Confirmation code is incorrect + Verification code is incorrect diff --git a/authenticator/src/main/res/values/messages.xml b/authenticator/src/main/res/values/messages.xml index 74f86c33..decc93bc 100644 --- a/authenticator/src/main/res/values/messages.xml +++ b/authenticator/src/main/res/values/messages.xml @@ -15,5 +15,5 @@ Password has been reset - Confirmation code sent + Verification code sent diff --git a/authenticator/src/main/res/values/strings.xml b/authenticator/src/main/res/values/strings.xml index e521fd36..755ec635 100644 --- a/authenticator/src/main/res/values/strings.xml +++ b/authenticator/src/main/res/values/strings.xml @@ -15,8 +15,8 @@ - A confirmation code has been sent to %s. - A confirmation code has been sent. + A verification code has been sent to %s. + A verification code has been sent. Please enter the code from your registered Authenticator app @@ -26,7 +26,14 @@ Open the Authenticator app and scan the QR code or enter the key to get your verification code Step 3: Verify your code Enter the 6 digit code from your Authenticator app + + Select your preferred way to verify your identity when using two-factor authentication (2FA). This will be your default method for added account security. + + Enter the email address you want to use for two-factor authentication (2FA). This email will receive verification codes when you sign in. + + For added security, choose how you want to verify your identity for this sign-in. Text Message (SMS) Authenticator App (TOTP) + Email Message diff --git a/authenticator/src/main/res/values/titles.xml b/authenticator/src/main/res/values/titles.xml index 21428988..e3db56de 100644 --- a/authenticator/src/main/res/values/titles.xml +++ b/authenticator/src/main/res/values/titles.xml @@ -16,13 +16,15 @@ Sign In - Enter your sign in code + Verify your sign-in Change your password to sign in Create Account Reset Password - Enter your confirmation code + Enter your verification code Account recovery requires verified contact information Enter your one-time passcode Enable Two-Factor Auth - Select your preferred Two-Factor Auth method + Choose your preferred two-factor authentication method to set up + Choose your two-factor authentication method + Add Email for Two-Factor Authentication diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt new file mode 100644 index 00000000..29b025b1 --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithEmailSetupTest.kt @@ -0,0 +1,91 @@ +/* + * 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.ui.authenticator.ui + +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.states.SignInContinueWithEmailSetupStateImpl +import com.amplifyframework.ui.authenticator.ui.robots.signInContinueWithEmailSetup +import com.amplifyframework.ui.testing.ComposeTest +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class SignInContinueWithEmailSetupTest : ComposeTest() { + + @Test + fun `title is Setup Two-Factor Auth Method`() { + setContent { + SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState()) + } + + signInContinueWithEmailSetup { + hasTitle("Add Email for Two-Factor Authentication") + } + } + + @Test + fun `Submit button label is Submit`() { + setContent { + SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState()) + } + + signInContinueWithEmailSetup { + hasSubmitButton("Submit") + } + } + + @Test + fun `Submit email address`() { + val onSubmit = mockk<(String) -> Unit>(relaxed = true) + setContent { + SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState(onSubmit = onSubmit)) + } + + signInContinueWithEmailSetup { + setEmail("SuperCool@EmailAddress.com") + clickSubmitButton() + } + + verify { + onSubmit("SuperCool@EmailAddress.com") + } + } + + @Test + fun `Go back to sign in`() { + val onMoveTo = mockk<(AuthenticatorInitialStep) -> Unit>(relaxed = true) + setContent { + SignInContinueWithEmailSetup(mockSignInContinueWithEmailSetupState(onMoveTo = onMoveTo)) + } + + signInContinueWithEmailSetup { + clickBackToSignInButton() + } + + verify { + onMoveTo(AuthenticatorStep.SignIn) + } + } + + private fun mockSignInContinueWithEmailSetupState( + onSubmit: suspend (email: String) -> Unit = {}, + onMoveTo: (step: AuthenticatorInitialStep) -> Unit = {} + ) = SignInContinueWithEmailSetupStateImpl( + onSubmit = onSubmit, + onMoveTo = onMoveTo + ) +} diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt index ca89c5c0..9cd7e382 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInContinueWithMfaSelectionTest.kt @@ -14,22 +14,22 @@ import org.junit.Test class SignInContinueWithMfaSelectionTest : ComposeTest() { @Test - fun `title is Select your preferred Two-Factor Auth method`() { + fun `title is Choose your two-factor authentication method`() { setContent { SignInContinueWithMfaSelection(mockSignInContinueWithMfaSelectionState()) } signInContinueWithMfaSelection { - hasTitle("Select your preferred Two-Factor Auth method") + hasTitle("Choose your two-factor authentication method") } } @Test - fun `Submit button label is Submit`() { + fun `Continue button label is Continue`() { setContent { SignInContinueWithMfaSelection(mockSignInContinueWithMfaSelectionState()) } signInContinueWithMfaSelection { - hasSubmitButton("Submit") + hasSubmitButton("Continue") } } @@ -76,6 +76,22 @@ class SignInContinueWithMfaSelectionTest : ComposeTest() { } } + @Test + fun `Submits Email MFA type`() { + val onSubmit = mockk<(String) -> Unit>(relaxed = true) + setContent { + SignInContinueWithMfaSelection(mockSignInContinueWithMfaSelectionState(onSubmit = onSubmit)) + } + signInContinueWithMfaSelection { + selectMfaType(MFAType.EMAIL) + hasMfaTypeSelected(MFAType.EMAIL) + clickSubmitButton() + } + verify { + onSubmit(MFAType.EMAIL.challengeResponse) + } + } + @Test fun `moves back to sign in`() { val onMoveTo = mockk<(AuthenticatorInitialStep) -> Unit>(relaxed = true) diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInContinueWithEmailSetupRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInContinueWithEmailSetupRobot.kt new file mode 100644 index 00000000..0ae34c06 --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInContinueWithEmailSetupRobot.kt @@ -0,0 +1,34 @@ +/* + * 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.ui.authenticator.ui.robots + +import androidx.compose.ui.test.junit4.ComposeTestRule +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.ui.TestTags +import com.amplifyframework.ui.testing.ComposeTest + +fun ComposeTest.signInContinueWithEmailSetup(func: SignInContinueWithEmailSetupRobot.() -> Unit) = + SignInContinueWithEmailSetupRobot(composeTestRule).func() + +class SignInContinueWithEmailSetupRobot(composeTestRule: ComposeTestRule) : ScreenLevelRobot(composeTestRule) { + fun hasSubmitButton(expected: String) = assertExists(TestTags.SignInConfirmButton, expected) + + fun setEmail(email: String) = setFieldContent(FieldKey.Email, email) + + fun clickSubmitButton() = clickOnTag(TestTags.SignInConfirmButton) + + fun clickBackToSignInButton() = clickOnTag(TestTags.BackToSignInButton) +} diff --git a/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt index c97afd55..b9d6f950 100644 --- a/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -122,7 +122,7 @@ class AndroidLibraryConventionPlugin : Plugin { } composeOptions { - kotlinCompilerExtensionVersion = "1.4.3" + kotlinCompilerExtensionVersion = "1.5.3" } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba401df4..57bdaf35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] agp = "8.1.4" -amplify = "2.23.0" +amplify = "2.24.0" binary-compatibility = "0.14.0" cameraX = "1.2.0" compose = "1.5.4" coroutines = "1.7.3" kotest = "5.7.1" -kotlin = "1.8.10" +kotlin = "1.9.10" kover = "0.7.2" ktlint = "11.0.0" licensee = "1.7.0"