From 1a9f17e0784d67766820f351f6c3b193f56b44cd Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Tue, 30 Sep 2025 09:31:29 -0300 Subject: [PATCH] Support passwordless signUp --- .../authenticator/AuthenticatorViewModel.kt | 187 +++++++++--------- .../ui/authenticator/data/UserInfo.kt | 5 + .../ui/authenticator/enums/SignInSource.kt | 12 ++ .../ui/authenticator/forms/FormBuilder.kt | 40 ++-- .../states/SignInSelectAuthFactorStateImpl.kt | 6 +- .../authenticator/states/SignUpStateImpl.kt | 22 ++- .../authenticator/states/StepStateFactory.kt | 54 +++-- .../ui/SignInSelectAuthFactor.kt | 15 +- .../ui/authenticator/ui/TestTags.kt | 2 +- .../ui/authenticator/util/AuthProvider.kt | 8 +- .../AuthenticatorViewModelTest.kt | 6 +- .../ui/authenticator/MockAuthenticatorData.kt | 12 +- .../ui/authenticator/testUtil/MockStates.kt | 13 +- .../ui/SignInSelectAuthFactorTest.kt | 2 +- .../ui/authenticator/ui/SignUpTest.kt | 24 +++ .../ui/robots/SignInSelectAuthFactorRobot.kt | 2 +- .../SignUpTest_passwordless-with-email.png | Bin 0 -> 35052 bytes .../SignUpTest_passwordless-with-username.png | Bin 0 -> 39073 bytes samples/authenticator/app/.gitignore | 2 +- 19 files changed, 223 insertions(+), 189 deletions(-) create mode 100644 authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/UserInfo.kt create mode 100644 authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/SignInSource.kt create mode 100644 authenticator/src/test/screenshots/SignUpTest_passwordless-with-email.png create mode 100644 authenticator/src/test/screenshots/SignUpTest_passwordless-with-username.png 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 d3bef647..8e0046ae 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt @@ -15,11 +15,13 @@ package com.amplifyframework.ui.authenticator +import android.app.Activity import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.amplifyframework.auth.AuthChannelEventName import com.amplifyframework.auth.AuthException +import com.amplifyframework.auth.AuthUser import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.MFAType @@ -50,8 +52,10 @@ import com.amplifyframework.ui.authenticator.auth.AmplifyAuthConfiguration import com.amplifyframework.ui.authenticator.auth.toAttributeKey import com.amplifyframework.ui.authenticator.auth.toFieldKey import com.amplifyframework.ui.authenticator.auth.toVerifiedAttributeKey +import com.amplifyframework.ui.authenticator.data.UserInfo import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.enums.SignInSource import com.amplifyframework.ui.authenticator.forms.FieldError import com.amplifyframework.ui.authenticator.forms.FieldError.ConfirmationCodeIncorrect import com.amplifyframework.ui.authenticator.forms.FieldError.FieldValueExists @@ -81,6 +85,7 @@ import com.amplifyframework.ui.authenticator.util.UnableToResetPasswordMessage import com.amplifyframework.ui.authenticator.util.UnknownErrorMessage import com.amplifyframework.ui.authenticator.util.isConnectivityIssue import com.amplifyframework.ui.authenticator.util.toFieldError +import java.lang.ref.WeakReference import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -152,6 +157,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth } stateFactory = StepStateFactory( + configuration, authConfiguration, buildForm(configuration.signUpForm), ::moveTo @@ -195,22 +201,23 @@ internal class AuthenticatorViewModel(application: Application, private val auth //region SignUp @VisibleForTesting - suspend fun signUp(username: String, password: String, attributes: List) { + suspend fun signUp(username: String, password: String?, attributes: List) { viewModelScope.launch { val options = AuthSignUpOptions.builder().userAttributes(attributes).build() + val info = UserInfo(username = username, password = password, signInSource = SignInSource.SignUp) when (val result = authProvider.signUp(username, password, options)) { is AmplifyResult.Error -> handleSignUpFailure(result.error) - is AmplifyResult.Success -> handleSignUpSuccess(username, password, result.data) + is AmplifyResult.Success -> handleSignUpSuccess(info, result.data) } }.join() } - private suspend fun confirmSignUp(username: String, password: String, code: String) { + private suspend fun confirmSignUp(info: UserInfo, code: String) { viewModelScope.launch { - when (val result = authProvider.confirmSignUp(username, code)) { + when (val result = authProvider.confirmSignUp(info.username, code)) { is AmplifyResult.Error -> handleSignUpConfirmFailure(result.error) - is AmplifyResult.Success -> handleSignUpSuccess(username, password, result.data) + is AmplifyResult.Success -> handleSignUpSuccess(info, result.data) } }.join() } @@ -228,18 +235,18 @@ internal class AuthenticatorViewModel(application: Application, private val auth private suspend fun handleSignUpFailure(error: AuthException) = handleAuthException(error) private suspend fun handleSignUpConfirmFailure(error: AuthException) = handleAuthException(error) - private suspend fun handleSignUpSuccess(username: String, password: String, result: AuthSignUpResult) { + private suspend fun handleSignUpSuccess(info: UserInfo, result: AuthSignUpResult) { when (result.nextStep.signUpStep) { AuthSignUpStep.CONFIRM_SIGN_UP_STEP -> { val newState = stateFactory.newSignUpConfirmState( result.nextStep.codeDeliveryDetails, - onResendCode = { resendSignUpCode(username) }, - onSubmit = { confirmationCode -> confirmSignUp(username, password, confirmationCode) } + onResendCode = { resendSignUpCode(info.username) }, + onSubmit = { confirmationCode -> confirmSignUp(info, confirmationCode) } ) moveTo(newState) } - AuthSignUpStep.DONE -> handleSignedUp(username, password) - AuthSignUpStep.COMPLETE_AUTO_SIGN_IN -> handleAutoSignIn(username, password) + AuthSignUpStep.COMPLETE_AUTO_SIGN_IN -> handleAutoSignIn(info) + AuthSignUpStep.DONE -> handleSignedUp(info) else -> { // Generic error for any other next steps that may be added in the future val exception = AuthException( @@ -252,31 +259,25 @@ internal class AuthenticatorViewModel(application: Application, private val auth } } - private suspend fun handleAutoSignIn(username: String, password: String) { - startSignInJob { - when (val result = authProvider.autoSignIn()) { - is AmplifyResult.Error -> { - // If auto sign in fails then proceed with manually trying to sign in the user. If this also fails the - // user will end up back on the sign in screen. - logger.warn("Unable to complete auto-signIn") - handleSignedUp(username, password) - } - - is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data) + private suspend fun handleAutoSignIn(info: UserInfo) = startSignInJob { + when (val result = authProvider.autoSignIn()) { + is AmplifyResult.Error -> { + // If auto sign in fails then proceed with manually trying to sign in the user. If this also fails the + // user will end up back on the sign in screen. + logger.warn("Unable to complete auto-signIn") + handleSignedUp(info) } + is AmplifyResult.Success -> handleSignInSuccess(info, result.data) } } - private suspend fun handleSignedUp(username: String, password: String) { - startSignInJob { - when (val result = authProvider.signIn(username, password)) { - is AmplifyResult.Error -> { - moveTo(AuthenticatorStep.SignIn) - handleSignInFailure(username, password, result.error) - } - - is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data) + private suspend fun handleSignedUp(info: UserInfo) = startSignInJob { + when (val result = authProvider.signIn(info.username, info.password)) { + is AmplifyResult.Error -> { + moveTo(AuthenticatorStep.SignIn) + handleSignInFailure(info, result.error) } + is AmplifyResult.Success -> handleSignInSuccess(info, result.data) } } @@ -284,54 +285,60 @@ internal class AuthenticatorViewModel(application: Application, private val auth //region SignIn @VisibleForTesting - suspend fun signIn(username: String, password: String) { - startSignInJob { - when (val result = authProvider.signIn(username, password)) { - is AmplifyResult.Error -> handleSignInFailure(username, password, result.error) - is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data) - } + suspend fun signIn(username: String, password: String?) { + val info = UserInfo( + username = username, + password = password, + signInSource = SignInSource.SignIn + ) + startSignIn(info) + } + + private suspend fun startSignIn(info: UserInfo) = startSignInJob { + when (val result = authProvider.signIn(info.username, info.password)) { + is AmplifyResult.Error -> handleSignInFailure(info, result.error) + is AmplifyResult.Success -> handleSignInSuccess(info, result.data) } } - private suspend fun confirmSignIn(username: String, password: String, challengeResponse: String) { - startSignInJob { - when (val result = authProvider.confirmSignIn(challengeResponse)) { - is AmplifyResult.Error -> handleSignInFailure(username, password, result.error) - is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data) - } + private suspend fun confirmSignIn(info: UserInfo, challengeResponse: String) = startSignInJob { + when (val result = authProvider.confirmSignIn(challengeResponse)) { + is AmplifyResult.Error -> handleSignInFailure(info, result.error) + is AmplifyResult.Success -> handleSignInSuccess(info, result.data) } } - private suspend fun setNewSignInPassword(username: String, password: String) { - startSignInJob { - when (val result = authProvider.confirmSignIn(password)) { - // an error here is more similar to a sign up error - is AmplifyResult.Error -> handleSignUpFailure(result.error) - is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data) + private suspend fun setNewSignInPassword(info: UserInfo, newPassword: String) = startSignInJob { + when (val result = authProvider.confirmSignIn(newPassword)) { + // an error here is more similar to a sign up error + is AmplifyResult.Error -> handleSignUpFailure(result.error) + is AmplifyResult.Success -> { + val newUserInfo = info.copy(password = newPassword) + handleSignInSuccess(newUserInfo, result.data) } } } - private suspend fun handleSignInFailure(username: String, password: String, error: AuthException) { + private suspend fun handleSignInFailure(info: UserInfo, error: AuthException) { // UserNotConfirmed and PasswordResetRequired are special cases where we need // to enter different flows when (error) { - is UserNotConfirmedException -> handleUnconfirmedSignIn(username, password) - is PasswordResetRequiredException -> handleResetRequiredSignIn(username) + is UserNotConfirmedException -> handleUnconfirmedSignIn(info) + is PasswordResetRequiredException -> handleResetRequiredSignIn(info.username) is NotAuthorizedException -> sendMessage(InvalidLoginMessage(error)) else -> handleAuthException(error) } } - private suspend fun handleUnconfirmedSignIn(username: String, password: String) { - when (val result = authProvider.resendSignUpCode(username)) { + private suspend fun handleUnconfirmedSignIn(info: UserInfo) { + when (val result = authProvider.resendSignUpCode(info.username)) { is AmplifyResult.Error -> handleAuthException(result.error) is AmplifyResult.Success -> { val details = result.data val newState = stateFactory.newSignUpConfirmState( details, - onResendCode = { resendSignUpCode(username) }, - onSubmit = { confirmationCode -> confirmSignUp(username, password, confirmationCode) } + onResendCode = { resendSignUpCode(info.username) }, + onSubmit = { confirmationCode -> confirmSignUp(info, confirmationCode) } ) moveTo(newState) } @@ -345,11 +352,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth } } - private suspend fun handleTotpSetupRequired( - username: String, - password: String, - totpSetupDetails: TOTPSetupDetails? - ) { + private suspend fun handleTotpSetupRequired(info: UserInfo, totpSetupDetails: TOTPSetupDetails?) { if (totpSetupDetails == null) { val exception = AuthException("Missing TOTPSetupDetails", "Please open a bug with Amplify") handleGeneralFailure(exception) @@ -357,20 +360,16 @@ internal class AuthenticatorViewModel(application: Application, private val auth } val issuer = configuration.totpOptions?.issuer ?: getAppName() - val setupUri = totpSetupDetails.getSetupURI(issuer, username).toString() + val setupUri = totpSetupDetails.getSetupURI(issuer, info.username).toString() val newState = stateFactory.newSignInContinueWithTotpSetupState( sharedSecret = totpSetupDetails.sharedSecret, setupUri = setupUri, - onSubmit = { confirmationCode -> confirmSignIn(username, password, confirmationCode) } + onSubmit = { confirmationCode -> confirmSignIn(info, confirmationCode) } ) moveTo(newState) } - private suspend fun handleMfaSetupSelectionRequired( - username: String, - password: String, - allowedMfaTypes: Set? - ) { + private suspend fun handleMfaSetupSelectionRequired(info: UserInfo, allowedMfaTypes: Set?) { if (allowedMfaTypes.isNullOrEmpty()) { handleGeneralFailure(AuthException("Missing allowedMfaTypes", "Please open a bug with Amplify")) return @@ -379,20 +378,20 @@ internal class AuthenticatorViewModel(application: Application, private val auth moveTo( stateFactory.newSignInContinueWithMfaSetupSelectionState( allowedMfaTypes = allowedMfaTypes, - onSubmit = { mfaType -> confirmSignIn(username, password, mfaType) } + onSubmit = { mfaType -> confirmSignIn(info, mfaType) } ) ) } - private suspend fun handleEmailMfaSetupRequired(username: String, password: String) { + private suspend fun handleEmailMfaSetupRequired(info: UserInfo) { moveTo( stateFactory.newSignInContinueWithEmailSetupState( - onSubmit = { mfaType -> confirmSignIn(username, password, mfaType) } + onSubmit = { mfaType -> confirmSignIn(info, mfaType) } ) ) } - private suspend fun handleMfaSelectionRequired(username: String, password: String, allowedMfaTypes: Set?) { + private suspend fun handleMfaSelectionRequired(info: UserInfo, allowedMfaTypes: Set?) { if (allowedMfaTypes.isNullOrEmpty()) { handleGeneralFailure(AuthException("Missing allowedMfaTypes", "Please open a bug with Amplify")) return @@ -401,48 +400,48 @@ internal class AuthenticatorViewModel(application: Application, private val auth moveTo( stateFactory.newSignInContinueWithMfaSelectionState( allowedMfaTypes = allowedMfaTypes, - onSubmit = { mfaType -> confirmSignIn(username, password, mfaType) } + onSubmit = { mfaType -> confirmSignIn(info, mfaType) } ) ) } - private suspend fun handleSignInSuccess(username: String, password: String, result: AuthSignInResult) { + private suspend fun handleSignInSuccess(info: UserInfo, result: AuthSignInResult) { when (val nextStep = result.nextStep.signInStep) { AuthSignInStep.DONE -> checkVerificationMechanisms() 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) } + codeDeliveryDetails = result.nextStep.codeDeliveryDetails + ) { confirmationCode -> confirmSignIn(info, confirmationCode) } ) AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> moveTo( stateFactory.newSignInConfirmCustomState( result.nextStep.codeDeliveryDetails, result.nextStep.additionalInfo ?: emptyMap() - ) { confirmationCode -> confirmSignIn(username, password, confirmationCode) } + ) { confirmationCode -> confirmSignIn(info, confirmationCode) } ) AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> moveTo( stateFactory.newSignInConfirmNewPasswordState { newPassword -> - setNewSignInPassword(username, newPassword) + setNewSignInPassword(info, newPassword) } ) // This step isn't actually returned, it comes back as a PasswordResetRequiredException. // Handling here for future correctness - AuthSignInStep.RESET_PASSWORD -> handleResetRequiredSignIn(username) + AuthSignInStep.RESET_PASSWORD -> handleResetRequiredSignIn(info.username) // This step isn't actually returned, it comes back as a UserNotConfirmedException. // Handling here for future correctness - AuthSignInStep.CONFIRM_SIGN_UP -> handleUnconfirmedSignIn(username, password) + AuthSignInStep.CONFIRM_SIGN_UP -> handleUnconfirmedSignIn(info) AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> - handleMfaSelectionRequired(username, password, result.nextStep.allowedMFATypes) + handleMfaSelectionRequired(info, result.nextStep.allowedMFATypes) AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> - handleMfaSetupSelectionRequired(username, password, result.nextStep.allowedMFATypes) + handleMfaSetupSelectionRequired(info, result.nextStep.allowedMFATypes) AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> - handleEmailMfaSetupRequired(username, password) + handleEmailMfaSetupRequired(info) AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> - handleTotpSetupRequired(username, password, result.nextStep.totpSetupDetails) + handleTotpSetupRequired(info, result.nextStep.totpSetupDetails) AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> moveTo( stateFactory.newSignInConfirmTotpCodeState { confirmationCode -> - confirmSignIn(username, password, confirmationCode) + confirmSignIn(info, confirmationCode) } ) else -> { @@ -509,7 +508,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth logger.debug("Confirming password reset") when (val result = authProvider.confirmResetPassword(username, password, code)) { is AmplifyResult.Error -> handleResetPasswordError(result.error) - is AmplifyResult.Success -> handlePasswordResetComplete(username, password) + is AmplifyResult.Success -> handlePasswordResetComplete() } }.join() } @@ -536,16 +535,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth private suspend fun handlePasswordResetComplete(username: String? = null, password: String? = null) { logger.debug("Password reset complete") sendMessage(PasswordResetMessage) - if (username != null && password != null) { - startSignInJob { - when (val result = authProvider.signIn(username, password)) { - is AmplifyResult.Error -> moveTo(stateFactory.newSignInState(this::signIn)) - is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data) - } - } - } else { - moveTo(stateFactory.newSignInState(this::signIn)) - } + moveTo(stateFactory.newSignInState(this::signIn)) } private suspend fun handleResetPasswordError(error: AuthException) = handleAuthException(error) @@ -644,14 +634,13 @@ internal class AuthenticatorViewModel(application: Application, private val auth is AmplifyResult.Error -> { if (result.error is SessionExpiredException) { logger.error(result.error.toString()) - logger.error("Current signed in user session has expired, signing out.") signOut() } else { handleGeneralFailure(result.error) } } - is AmplifyResult.Success -> moveTo(stateFactory.newSignedInState(result.data, this::signOut)) + is AmplifyResult.Success -> signInComplete(result.data) } } @@ -661,6 +650,10 @@ internal class AuthenticatorViewModel(application: Application, private val auth expectingSignInEvent = false } + private fun signInComplete(user: AuthUser) { + moveTo(stateFactory.newSignedInState(user, this::signOut)) + } + // Amplify has told us the user signed in. private suspend fun handleSignedInEvent() { if (!expectingSignInEvent && !inPostSignInState()) { diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/UserInfo.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/UserInfo.kt new file mode 100644 index 00000000..f9ed43c5 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/UserInfo.kt @@ -0,0 +1,5 @@ +package com.amplifyframework.ui.authenticator.data + +import com.amplifyframework.ui.authenticator.enums.SignInSource + +internal data class UserInfo(val username: String, val password: String?, val signInSource: SignInSource) diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/SignInSource.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/SignInSource.kt new file mode 100644 index 00000000..f29b16cd --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/enums/SignInSource.kt @@ -0,0 +1,12 @@ +package com.amplifyframework.ui.authenticator.enums + +internal enum class SignInSource { + // Standard sign in + SignIn, + + // Automatic sign in after completing sign up + SignUp, + + // Signed in outside of Authenticator + External +} diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/forms/FormBuilder.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/forms/FormBuilder.kt index f3896119..19594652 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/forms/FormBuilder.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/forms/FormBuilder.kt @@ -23,9 +23,7 @@ import com.amplifyframework.ui.authenticator.auth.toFieldKey internal data class FormData(val fields: List) -internal fun buildForm(func: FormBuilderImpl.() -> Unit): FormData { - return FormBuilderImpl().apply(func).build() -} +internal fun buildForm(func: FormBuilderImpl.() -> Unit): FormData = FormBuilderImpl().apply(func).build() /** * Builder API for supplying custom form metadata for the signup form. @@ -39,12 +37,12 @@ interface SignUpFormBuilder { /** * Adds the standard password field. */ - fun password() + fun password(required: Boolean = true) /** * Adds the standard password confirmation field. */ - fun confirmPassword() + fun confirmPassword(required: Boolean = true) /** * Adds the standard email field. @@ -187,18 +185,23 @@ internal class FormBuilderImpl : SignUpFormBuilder { ) } - override fun password() = password(validator = FieldValidators.None) + override fun password(required: Boolean) = password( + required = required, + validator = FieldValidators.None + ) - fun password(validator: FieldValidator) { + fun password(validator: FieldValidator, required: Boolean = true) { this += FieldConfig.Password( key = FieldKey.Password, + required = required, validator = validator ) } - override fun confirmPassword() { + override fun confirmPassword(required: Boolean) { this += FieldConfig.Password( key = FieldKey.ConfirmPassword, + required = required, validator = FieldValidators.confirmPassword() ) } @@ -317,13 +320,7 @@ internal class FormBuilderImpl : SignUpFormBuilder { ) } - override fun date( - key: FieldKey, - label: String, - hint: String?, - required: Boolean, - validator: FieldValidator - ) { + override fun date(key: FieldKey, label: String, hint: String?, required: Boolean, validator: FieldValidator) { this += FieldConfig.Date( key = key, label = label, @@ -333,13 +330,7 @@ internal class FormBuilderImpl : SignUpFormBuilder { ) } - override fun phone( - key: FieldKey, - label: String, - hint: String?, - required: Boolean, - validator: FieldValidator - ) { + override fun phone(key: FieldKey, label: String, hint: String?, required: Boolean, validator: FieldValidator) { this += FieldConfig.PhoneNumber( key = key, label = label, @@ -392,10 +383,7 @@ internal class FormBuilderImpl : SignUpFormBuilder { fields.putAll(map) } - fun markRequiredFields( - signInMethod: SignInMethod, - requiredKeys: List - ) { + fun markRequiredFields(signInMethod: SignInMethod, requiredKeys: List) { fields.replaceAll { fieldKey, config -> if (fieldKey is FieldKey.UserAttributeKey && requiredKeys.contains(fieldKey.attributeKey)) { config.required() diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInSelectAuthFactorStateImpl.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInSelectAuthFactorStateImpl.kt index d0601eb7..efc59283 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInSelectAuthFactorStateImpl.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignInSelectAuthFactorStateImpl.kt @@ -5,10 +5,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.amplifyframework.ui.authenticator.SignInSelectAuthFactorState import com.amplifyframework.ui.authenticator.auth.SignInMethod -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor +import com.amplifyframework.ui.authenticator.data.containsPassword import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.enums.containsPassword internal class SignInSelectAuthFactorStateImpl( override val username: String, @@ -46,4 +46,4 @@ internal fun SignInSelectAuthFactorState.getPasswordFactor(): AuthFactor = availableAuthFactors.first { it is AuthFactor.Password } internal val SignInSelectAuthFactorState.signInMethod: SignInMethod - get() = (this as SignInSelectAuthFactorStateImpl).signInMethod \ No newline at end of file + get() = (this as SignInSelectAuthFactorStateImpl).signInMethod diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignUpStateImpl.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignUpStateImpl.kt index dfc52cba..945301f1 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignUpStateImpl.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/states/SignUpStateImpl.kt @@ -31,18 +31,28 @@ import com.amplifyframework.ui.authenticator.forms.buildForm internal class SignUpStateImpl( private val signInMethod: SignInMethod, private val signUpAttributes: List, + requirePasswordField: Boolean, private val passwordCriteria: PasswordCriteria, private val signUpForm: FormData, - private val onSubmit: suspend (username: String, password: String, attributes: List) -> Unit, + private val onSubmit: suspend (username: String, password: String?, attributes: List) -> Unit, private val onMoveTo: (step: AuthenticatorInitialStep) -> Unit -) : BaseStateImpl(), SignUpState { +) : BaseStateImpl(), + SignUpState { init { val formData = buildForm { // First add all fields required by configuration in the standard order fieldForSignInMethod(signInMethod) - password(validator = FieldValidators.password(passwordCriteria)) - confirmPassword() + if (requirePasswordField) { + password(validator = FieldValidators.password(passwordCriteria)) + + // We don't add confirm password if the customer supplied a form with password and without confirmPassword + if (signUpForm.containsField(FieldKey.ConfirmPassword) || + !signUpForm.containsField(FieldKey.Password) + ) { + confirmPassword() + } + } signUpAttributes.forEach { attribute -> when (attribute) { AuthUserAttributeKey.birthdate() -> birthdate(required = true) @@ -77,8 +87,10 @@ internal class SignUpStateImpl( override suspend fun signUp() = doSubmit { val username = form.getTrimmed(signInMethod.toFieldKey())!! - val password = form.getTrimmed(FieldKey.Password)!! + val password = form.getTrimmed(FieldKey.Password) val attributes = form.getUserAttributes() onSubmit(username, password, attributes) } + + private fun FormData.containsField(key: FieldKey) = fields.any { it.key == key } } 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 d1df7f5e..4581b2d0 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 @@ -21,27 +21,25 @@ import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.result.AuthSignOutResult +import com.amplifyframework.ui.authenticator.AuthenticatorConfiguration import com.amplifyframework.ui.authenticator.auth.AmplifyAuthConfiguration +import com.amplifyframework.ui.authenticator.data.signUpRequiresPassword import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.forms.FormData internal class StepStateFactory( + private val configuration: AuthenticatorConfiguration, private val authConfiguration: AmplifyAuthConfiguration, private val signUpForm: FormData, private val onMoveTo: (step: AuthenticatorInitialStep) -> Unit ) { - fun newSignedInState( - user: AuthUser, - onSignOut: suspend () -> AuthSignOutResult - ) = SignedInStateImpl( + fun newSignedInState(user: AuthUser, onSignOut: suspend () -> AuthSignOutResult) = SignedInStateImpl( user = user, onSignOut = onSignOut ) - fun newSignInState( - onSubmit: suspend (username: String, password: String) -> Unit - ) = SignInStateImpl( + fun newSignInState(onSubmit: suspend (username: String, password: String) -> Unit) = SignInStateImpl( signInMethod = authConfiguration.signInMethod, onSubmit = onSubmit, onMoveTo = onMoveTo @@ -67,20 +65,18 @@ internal class StepStateFactory( onMoveTo = onMoveTo ) - fun newSignInConfirmNewPasswordState( - onSubmit: suspend (password: String) -> Unit - ) = SignInConfirmNewPasswordStateImpl( - passwordCriteria = authConfiguration.passwordCriteria, - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + fun newSignInConfirmNewPasswordState(onSubmit: suspend (password: String) -> Unit) = + SignInConfirmNewPasswordStateImpl( + passwordCriteria = authConfiguration.passwordCriteria, + onSubmit = onSubmit, + onMoveTo = onMoveTo + ) - fun newSignInConfirmTotpCodeState( - onSubmit: suspend (confirmationCode: String) -> Unit - ) = SignInConfirmTotpCodeStateImpl( - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + fun newSignInConfirmTotpCodeState(onSubmit: suspend (confirmationCode: String) -> Unit) = + SignInConfirmTotpCodeStateImpl( + onSubmit = onSubmit, + onMoveTo = onMoveTo + ) fun newSignInContinueWithMfaSetupSelectionState( allowedMfaTypes: Set, @@ -91,12 +87,11 @@ internal class StepStateFactory( onMoveTo = onMoveTo ) - fun newSignInContinueWithEmailSetupState( - onSubmit: suspend (email: String) -> Unit - ) = SignInContinueWithEmailSetupStateImpl( - onSubmit = onSubmit, - onMoveTo = onMoveTo - ) + fun newSignInContinueWithEmailSetupState(onSubmit: suspend (email: String) -> Unit) = + SignInContinueWithEmailSetupStateImpl( + onSubmit = onSubmit, + onMoveTo = onMoveTo + ) fun newSignInContinueWithMfaSelectionState( allowedMfaTypes: Set, @@ -119,10 +114,11 @@ internal class StepStateFactory( ) fun newSignUpState( - onSubmit: suspend (username: String, password: String, attributes: List) -> Unit + onSubmit: suspend (username: String, password: String?, attributes: List) -> Unit ) = SignUpStateImpl( signInMethod = authConfiguration.signInMethod, signUpAttributes = authConfiguration.signUpAttributes, + requirePasswordField = configuration.authenticationFlow.signUpRequiresPassword, passwordCriteria = authConfiguration.passwordCriteria, signUpForm = signUpForm, onSubmit = onSubmit, @@ -140,9 +136,7 @@ internal class StepStateFactory( onMoveTo = onMoveTo ) - fun newResetPasswordState( - onSubmit: suspend (username: String) -> Unit - ) = PasswordResetStateImpl( + fun newResetPasswordState(onSubmit: suspend (username: String) -> Unit) = PasswordResetStateImpl( signInMethod = authConfiguration.signInMethod, onSubmit = onSubmit, onMoveTo = onMoveTo diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactor.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactor.kt index e7baf5e8..c49f8041 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactor.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactor.kt @@ -16,9 +16,9 @@ import androidx.compose.ui.unit.dp import com.amplifyframework.ui.authenticator.R import com.amplifyframework.ui.authenticator.SignInSelectAuthFactorState import com.amplifyframework.ui.authenticator.auth.toFieldKey -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor +import com.amplifyframework.ui.authenticator.data.containsPassword import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep -import com.amplifyframework.ui.authenticator.enums.containsPassword import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.authenticator.states.getPasswordFactor import com.amplifyframework.ui.authenticator.states.signInMethod @@ -33,7 +33,7 @@ fun SignInSelectAuthFactor( headerContent: @Composable (SignInSelectAuthFactorState) -> Unit = { AuthenticatorTitle(stringResource(R.string.amplify_ui_authenticator_title_select_factor)) }, - footerContent: @Composable (SignInSelectAuthFactorState) -> Unit = { SignInSelectFactorFooter(it) } + footerContent: @Composable (SignInSelectAuthFactorState) -> Unit = { SignInSelectAuthFactorFooter(it) } ) { Column( modifier = modifier.fillMaxWidth().padding(horizontal = 16.dp) @@ -79,10 +79,11 @@ fun SignInSelectAuthFactor( } @Composable -fun SignInSelectFactorFooter(state: SignInSelectAuthFactorState, modifier: Modifier = Modifier) = BackToSignInFooter( - modifier = modifier, - onClickBackToSignIn = { state.moveTo(AuthenticatorStep.SignIn) } -) +fun SignInSelectAuthFactorFooter(state: SignInSelectAuthFactorState, modifier: Modifier = Modifier) = + BackToSignInFooter( + modifier = modifier, + onClickBackToSignIn = { state.moveTo(AuthenticatorStep.SignIn) } + ) @Composable private fun AuthFactorButton( diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt index 5df6ca97..e99d1775 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt @@ -15,7 +15,7 @@ package com.amplifyframework.ui.authenticator.ui -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor import com.amplifyframework.ui.authenticator.forms.FieldKey @Suppress("ConstPropertyName") diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/AuthProvider.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/AuthProvider.kt index 6892de62..2cd90140 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/AuthProvider.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/AuthProvider.kt @@ -52,11 +52,11 @@ import kotlinx.coroutines.flow.callbackFlow * An abstraction of the Amplify.Auth API that allows us to use coroutines with no exceptions */ internal interface AuthProvider { - suspend fun signIn(username: String, password: String): AmplifyResult + suspend fun signIn(username: String, password: String?): AmplifyResult suspend fun confirmSignIn(challengeResponse: String): AmplifyResult - suspend fun signUp(username: String, password: String, options: AuthSignUpOptions): AmplifyResult + suspend fun signUp(username: String, password: String?, options: AuthSignUpOptions): AmplifyResult suspend fun confirmSignUp(username: String, code: String): AmplifyResult @@ -106,7 +106,7 @@ internal class RealAuthProvider : AuthProvider { cognitoPlugin?.addToUserAgent(AWSCognitoAuthMetadataType.Authenticator, BuildConfig.VERSION_NAME) } - override suspend fun signIn(username: String, password: String) = suspendCoroutine { continuation -> + override suspend fun signIn(username: String, password: String?) = suspendCoroutine { continuation -> Amplify.Auth.signIn( username, password, @@ -123,7 +123,7 @@ internal class RealAuthProvider : AuthProvider { ) } - override suspend fun signUp(username: String, password: String, options: AuthSignUpOptions) = + override suspend fun signUp(username: String, password: String?, options: AuthSignUpOptions) = suspendCoroutine { continuation -> Amplify.Auth.signUp( username, diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModelTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModelTest.kt index 5e464327..f8d406b0 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModelTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModelTest.kt @@ -571,7 +571,7 @@ class AuthenticatorViewModelTest { } @Test - fun `Password reset confirmation succeeds, sign in succeeds, state should be signed in`() = runTest { + fun `Password reset confirmation succeeds, state should be sign in`() = runTest { coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false)) coEvery { authProvider.resetPassword(any()) } returns Success( AuthResetPasswordResult( @@ -581,13 +581,12 @@ class AuthenticatorViewModelTest { ) coEvery { authProvider.confirmResetPassword(any(), any(), any()) } returns Success(Unit) - coEvery { authProvider.signIn(any(), any()) } returns Success(mockSignInResult()) viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.PasswordReset)) viewModel.resetPassword("username") viewModel.confirmResetPassword("username", "password", "code") - viewModel.currentStep shouldBe AuthenticatorStep.SignedIn + viewModel.currentStep shouldBe AuthenticatorStep.SignIn } @Test @@ -652,6 +651,7 @@ class AuthenticatorViewModelTest { viewModel.resetPassword("username") } } + //endregion //region helpers private val AuthenticatorViewModel.currentStep: AuthenticatorStep diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt index 071b06be..c27cec1e 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt @@ -35,6 +35,7 @@ import com.amplifyframework.ui.authenticator.auth.AmplifyAuthConfiguration import com.amplifyframework.ui.authenticator.auth.PasswordCriteria import com.amplifyframework.ui.authenticator.auth.SignInMethod import com.amplifyframework.ui.authenticator.auth.VerificationMechanism +import com.amplifyframework.ui.authenticator.data.AuthenticationFlow import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.SignUpFormBuilder @@ -45,11 +46,13 @@ import io.mockk.mockk internal fun mockAuthenticatorConfiguration( initialStep: AuthenticatorInitialStep = AuthenticatorStep.SignIn, signUpForm: SignUpFormBuilder.() -> Unit = {}, - totpOptions: TotpOptions? = null + totpOptions: TotpOptions? = null, + authenticationFlow: AuthenticationFlow = AuthenticationFlow.Password ) = AuthenticatorConfiguration( initialStep = initialStep, signUpForm = signUpForm, - totpOptions = totpOptions + totpOptions = totpOptions, + authenticationFlow = authenticationFlow ) internal fun mockAmplifyAuthConfiguration( @@ -116,10 +119,7 @@ internal fun mockNextSignInStep( availableFactors ) -internal fun mockSignUpResult( - nextStep: AuthNextSignUpStep, - userId: String = "userId" -) = AuthSignUpResult( +internal fun mockSignUpResult(nextStep: AuthNextSignUpStep, userId: String = "userId") = AuthSignUpResult( nextStep.signUpStep != AuthSignUpStep.CONFIRM_SIGN_UP_STEP, nextStep, userId diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt index 2c2bda98..2422c3b2 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt @@ -21,7 +21,7 @@ import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.result.AuthWebAuthnCredential import com.amplifyframework.ui.authenticator.auth.PasswordCriteria import com.amplifyframework.ui.authenticator.auth.SignInMethod -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.forms.FormData import com.amplifyframework.ui.authenticator.mockAuthCodeDeliveryDetails @@ -46,9 +46,14 @@ internal fun mockSignInState() = SignInStateImpl( onMoveTo = { } ) -internal fun mockSignUpState() = SignUpStateImpl( - signInMethod = SignInMethod.Username, - signUpAttributes = listOf(AuthUserAttributeKey.email()), +internal fun mockSignUpState( + signInMethod: SignInMethod = SignInMethod.Username, + signUpAttributes: List = listOf(AuthUserAttributeKey.email()), + requirePasswordField: Boolean = true +) = SignUpStateImpl( + signInMethod = signInMethod, + signUpAttributes = signUpAttributes, + requirePasswordField = requirePasswordField, passwordCriteria = PasswordCriteria(8, false, false, false, false), signUpForm = FormData(emptyList()), onSubmit = { _, _, _ -> }, diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactorTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactorTest.kt index d38a9df3..618b35a1 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactorTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInSelectAuthFactorTest.kt @@ -16,7 +16,7 @@ package com.amplifyframework.ui.authenticator.ui import com.amplifyframework.ui.authenticator.auth.SignInMethod -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest import com.amplifyframework.ui.authenticator.testUtil.mockSignInSelectAuthFactorState import com.amplifyframework.ui.authenticator.ui.robots.signInSelectAuthFactor diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt index a1256705..e0dc01ad 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt @@ -17,6 +17,8 @@ package com.amplifyframework.ui.authenticator.ui import androidx.compose.ui.autofill.AutofillManager import androidx.compose.ui.autofill.ContentType +import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.ui.authenticator.auth.SignInMethod import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.FieldError import com.amplifyframework.ui.authenticator.forms.FieldKey @@ -193,4 +195,26 @@ class SignUpTest : AuthenticatorUiTest() { } state.form.setFieldError(FieldKey.Email, FieldError.InvalidFormat) } + + @Test + @ScreenshotTest + fun `passwordless with email`() { + val state = mockSignUpState(signInMethod = SignInMethod.Email, requirePasswordField = false) + setContent { + SignUp(state = state) + } + } + + @Test + @ScreenshotTest + fun `passwordless with username`() { + val state = mockSignUpState( + signInMethod = SignInMethod.Username, + signUpAttributes = listOf(AuthUserAttributeKey.email()), + requirePasswordField = false + ) + setContent { + SignUp(state = state) + } + } } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInSelectAuthFactorRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInSelectAuthFactorRobot.kt index 143851b4..23341a3e 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInSelectAuthFactorRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInSelectAuthFactorRobot.kt @@ -18,7 +18,7 @@ package com.amplifyframework.ui.authenticator.ui.robots import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.ComposeTestRule -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.authenticator.ui.TestTags import com.amplifyframework.ui.authenticator.ui.testTag diff --git a/authenticator/src/test/screenshots/SignUpTest_passwordless-with-email.png b/authenticator/src/test/screenshots/SignUpTest_passwordless-with-email.png new file mode 100644 index 0000000000000000000000000000000000000000..7b82be8e4c89304b21cc023dd2ebcba1b9450605 GIT binary patch literal 35052 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 literal 0 HcmV?d00001 diff --git a/authenticator/src/test/screenshots/SignUpTest_passwordless-with-username.png b/authenticator/src/test/screenshots/SignUpTest_passwordless-with-username.png new file mode 100644 index 0000000000000000000000000000000000000000..56bb9b62389027d0ec996b1c0852ecf41b339e64 GIT binary patch literal 39073 zcmeFZdpy&B|3B{i?x5Z!NktYal}bpMGxa7ap_4fuBImK>Y-W{`L!(lZITRu`=kswX ztY{V*o6|y!%`DA~&FuGDU7yeQ`dr`ZukSy<-}iUB{xN&)x##P7cs%Zp`{Vw6yq0*` z+Ct(lxxd83#3U}9H@hk(wh=5Qw$*LRX5dP^@J|IXu{&ZH%+6kqbe$iI!e-eDOn$IT zj#6L9dOp%H_t26<^}Sv7|C(U_T{+hyF&DSx8;9c{$E;xfH#uhaRhga-Y%|)Ewn;Im^k4&2|NP57TB`b zMVdjwT$yeB1=7KX`>$q)4LXoaC5^7P3fK9R$XV_3Hl9D{qf+V;y9)%n83yd6bo)gR z)KIWAK>9;`|K-xE?h_=WU8%4=SRs6tTuiLF?PAyp?YYZ;UgPNHo^5Nvy!?!+2h)ew4#v_$O z)Z11(!VS&Gd(?4R*}@-0xB*}d$Q90+W zv2c~>gWUa)tTQ#6pIWAXSnB%&CxA~pkSWpZ)y?O1+qFGKN#a&I)l2HBu?8Ug$Nte8 z8T7Y8+R;6Y~K8g07PkNs(NBFj?W*>|Y~Ek_7M2pM5o-I&sRczknVmY5b8c&h9l}T@S!k znuDoGz$xu)j5yaJ#E*TW+o7vJrzs@`4VUPexvQ6OxdNhajd({T?Umi?{06a8`_GAk z*fb(6>R|%e!m~4Jj{~A;GQ2!@?YnYyRK$1`4V(TXzi}UVI>Rj|J3}c&H*Ywweyl2& zm~Ef~l5G9_dp7!HU((leU!)=|E(A1_{3*gBV@@$Iyvk2i$uvZ>0+MmQleIcesjkjt zHPUhbL-R7`i-~QwsBAZ1YX@aKEI@8yRTGUJn3oM#zo$_~Zwz7`90Kc&*M5B9Nv_)O z``N8hels=V#PY_d^;H_iCnu`Vu_bcxv0CfgAe_}|*Sb6$zzMu1qvmW+eHvVlVlYS` zL}uY$C(vHxq~!|ceK?+rS-9oeat{2YnoSg&pi>=X?&$n;7t3l*twZmT5HNq1Pnc3* zOA5%tCYKD=RkKVMqJ-7~NAE?BEv_P$DLxFg$tbbc9tjwledBHr^ir;|5noBy?7)-3 z9VDCBpJ~u_`y)>WwL4Cvey#HA;VGm#^)J*6SW^Q1>d79-&@zZIZ#JI}Y!nvvL^xRA z_S~p`OZ1>~2!T%aOf6L(xPX^Dr}cz88e$Xnqg~t%Q{EQcV~L#PKt*o*?D#ePHWhr0 z-#}Fer^rNb9BnWUc(DWXlX7krLGQ()E>daYzO?nn9L7tZ913^k#zXmZqu9Q4GG6RQ z_kF!RpYOpx$OPx!}#!1n|gL z%!&S0eQj4fYUj77mTG?c?$k?vvY_{-c2O^A z<3TA%P%R1KKseL3Hmz<4Kwe<@qxi~o(e!?*oF*rD5lh=@jpho{s)i>LBtV|hecp?N zo1=P1N+sslTl<{Pr9EG7p#Z>_K_KmX{Sm;?$EdmMqk2km)X?$p$=hiHF4Y8$0lBSq z+m-NHI1_a94QwV_w57-Ye7U3-*fdw@Fjj&o%Ot7VR~*2AlA2j<%|VcXRf9bdA&m=7 zjH)PcO<+zWT1P7ljk0miw`kXmg^xf}UMF;& zPA@OQZ}IFKeS}{H`2{DM@?s9%vSjq?PCdddyvE@e0{~&bg|urK&L7bvwAMaVGNyPP~3RIv^ZdDYBMRmB7$OW7q_WV)N?( zfxNjTFJ|*Dj8*K~l;RNROzfPcTL~-jN$;MXcL2EZq0j^kynKXn>!Hav8LlO65%Vgy z0C*VUZ!h2+j8%YND!^Z00u3qTotE>S51h-*Ck5q#bN8(L`R+yEp44sq12VXs`EF*t+>uEsCG#KAZu8U#Hc{5PoZt4mma(KS>M;}BC@*$q+?J?$k8x1n44PxP#-sx<{?)%X zOOHeI8gGdT^WB}1Q`=+|o`9tF6A8W|D0&f@v>mM0@a^8b@6^?0q_&MDp zzI~Tk%a8Ziw&zb;G~bK-3hdeSXkO1u`sJWDw5HG80dfD1pG(nZ$H3hgvIV#g#VX}3 zRnyZRl@?YQD%Vwa;S3ADF*WW%q0|{@9mN}1co3_QQw(gj+~m(@@*}XsvCK{vk6&@m z(^UCv$NAX*nuCWrYDw~2!(Nk8P{3S>f5M(W7n$CUCXX9XH!(9O7iRj(8h!T?uIn2w zjPF!Y?H`UOHy@OjrrwL-l_Fy_bI=s zHDHgGGR&}-&eQ&?)K@N*|0o|Z+V@jUpSeIj4LA--rW7J?_O@@1h1{sTfk_BvwQ{{V zglKK@%A^d=zP#c*!zt0o#c}K&A0S7c{jVu;M6uZ8#A&iF0P58ycW^=h5I{Ut5!MIg zu-57AI?0<2vx^3fFeQQ;|Au~h*{k_QXQeXfV5!8|#?upkFL%!*#8;+`xTjnX(Nv(p z3XIlQrYMz4_I~yB1K`$2upu(IbL&7*$oKdszkFX~&pv34 zzhmrTvfItyqYc`J9;}r{UBbXp9#(14sls(GG^fG&aOt*SZn}M7-^pdMx$pgLst!$M8BHSum`GBE`%ZZPGI~|H1sJcjLyXg5nwlrr6%B(r3=TT z>sOiJuK@`?*^YsQ@Q(){-mH~qm4zDq{HWwG=I(KAC^W~u7EC_kuCyrH^T}P%N3WLj z9t5G|AOx}oet#O!r4yA00lST=>6C&llxF?}D62BY5@i-reL4#V3TilL;Em0>z;H|y z9LPIvgSYtba&SihY5G90^3$5KYeiYNFCrKN-a~Fk1VqObAILEnP`f{Ch9MbO{&k`> zz~12=>Mmrgg)`{n5Lo@>7f=Ro1)E>yW&N!~I2(geQ2Dc8FaGwva~}qsVA+HSg7(nO zFr{MKieTrd}1x^L<)tTqEIPm=4kL3V#NVy!YVJl#V@dgG_7-^0yg>(Gn6ti#~MB3Q9TOb zhl|LHLtp~es8nK+T@!xlo`jg#(ab|}C?LH6^Ea$CQw2c~oxy(E?k2w-jxM>zavm>Z zqURx#PD>zIOXc|ED$>P-phxE_`T}UMV~$L21;raJvfS?Hioz8<#jvv2%MqPqbfZ5w zm-n@(s_6;%*Oss!sK*=xLa*Z5H*v~nsoI0B_fQ$t8m|Y4#z2zf^rYR~*&uiQkF`$q zSdIT)lFBtBc@w$Gs}GjQgggy^N@1LW?&x0kl!7jGuYyYM{+Xy!)^G?T|Jko7JDpHH z5fAbXE2dQY_)N8}5@6oH2kT07W1lR~ogbJ+=P$wF<-{so?7Q2=O~ z%>x3!PL=Kb!1tB)s`r4Rn|1#Y!070J! z{@6c5%*`dGyjfbkACw7r#F($_Ta8rIZl|%AhdN^a=zH7wW|t_!b2_ALuB*5F_PfbHF%`W|y1xiVT@x*V*&NbV1{PnoTPf`)FbOi$Hr%3}lKlAxz8Y|{X z036H6Alx@Bxx0eD&}^sPx^xu+u#zT~?#Nu+L?oYC&yX-4pqcbfF4r>UM#-b68XEb^GK# z{^Q;OaT4H}9bHG1a2-!CEjYtE4q4;+~1 zM}qP4)l*$#KK)JBkL)Tma(57r`zxn_9AOw`Yr&KwkZUyMHJ=$&hX%ByG>Zr% z-iX!0u4)SEC=DCXn~Knc+%Bk{0O(W?-8ANC|8V;jKo)!9npRD9 z_o!A6zfLeW%r@wdKLJn&U|B}?*El9t^_hdb04DySV$os+S@R4*>UIxut_&LH`?!Qa$ve|1c`}V((2p{dTndEOCfe;0$b-) zDwnp>#cAb^_xOYQd0zrUzR*F-r$*l_#;vwX#y*y31KG+a`ex0`-?Sj@U6I+i*GbRM zR=)P(#SryqS=jKmJB*gTK-a_DFJKIwnb~W9MJTWTcxZByUHg};D9nL(N&o_iFJvUK zA!bIT$AGEsXD5I>at?ZF^p6vzpwC9!(KLdzXlz()(hl%mbysJ^jArhqby-({N)2z` z8J8%r-mmwe60d+Jfh`%P^@Ovd^7*9c-HQ^pwc7m<%7?Be^jzHRxLGllKSK4!KZ1Q3 zXln0xqd#Z(I?Y{aoEYkCUv={W!~n<#kftPhglgvG>-o}Ck5}X`rAlr73qEOxV56y@ z=|o^C*zi+*6@}>jt~aMdD{BC7G%hCtx8@Z-YNa!#g<*EojRc_yd&&bUkZFgY7xA6h zN&QXjmol}Eqm`F#S7_||<(Xx9aj08><@+GFKQvk<=Q9<5Zb&q#@RaTkZL{wc0$Lcr z^7=Jy93!;<%A=)I^yzPbdoPxTOo8)&)YAnBx-c*8*Cg{|DLGZgnq!#2BbgsWc!kb? zod(O9S}UA-e+HzsU|q&Kz5mzf?C(ByeYxO;#_2XW1`48w8s@P8-BWC3I<* z%Aeo3rxaqR)*V1TYLZ$ANOYMfVAp8!L534{d@N=#7ILB#aA=E+ACdme| z1)SoB!$2qt@9skWOv}aUXj?Jx{06D@nx?gr5Tcp=lfnAifT%~pU!WyCm)(#}^VS)? zWeNt*1FBG588``Vqd!*wg;u-n0Grc)!Tj}@3aLXaD+>`gwBX8qV{RkSHci>4Od;k+ zdy}8Pf(+sF`v=j0sFY-5%iW zWJPQ|QmQ7MKvTGT8FTAUhks`1C4WF)5~i4u(^4dyV69;S_>;}s7h&h2gB1UP;hnfR zK#3!!kS?ydA^{ibo!JslS_?tAW^Q=8<;0=$3ASRc8d`P1f?5|J3I!J9%TDB%BDQuiOm}MJoA3LnCLSRC z76pFXX}LIs?pxB8sHB=I(+aX>bIWdT5vA1OEf9dDeZemBJQPu(P=74Zt=^Hz_yM@! z5e(>faKwwH$T*kfUG28|g$^<4;L%$jqH}aJ=GyiWCT|y66RDB%5kTI$VYA0UIi{{F zc0m-7oEB*P*b$PBj9W}F*rGZzNNU_ntl$5EsX?m(^wIZUQ}TMV6_`>Quwe{98I`nk zW{=N#Z96Eh7~j8NE17h-6nvgJ2xwb_b(Ma;5vXbCW0*VA_5> zP`^2K4TG(^2(Eh$>{E6U%3#3r{+5jX;U}jrf04%M0YSk2^%puAJC;PeszZYT`!Z>i zAf}X!0ZEx&YJ)tKn9E{z1h5L)efPqHwHB)S{H6O=>a<&T z`yQzFAqMeFqY`KtDWH$`xj5^ZrcD9ZqZie~WG_^5&to1dw;i_$KzB~>)%59|=S3Xq zC~VH2FYoULlE024{-uzH&_fH|OMoEm6B2$RR(x{DrMZ^4MB9@9?{}+>S^YU*`GwJL z{*MuVxF`eF>QLhcqd)qd?l*bllxTS^9AJg?LUa&4k`jF)TXXvxJ>MglEh|U$z;pZO zC509A63if9YI_(io(wy%aA+4br7)xjpV%sqi7xal>u=g=Wf`4y*Kn~zn)7D!+QA#*}#%BJKk88s@+IHSq%(70kFE+3y+F#NOPi?hv<8CKl?++J8&-=+eOdyts01G z0u(ukV84r_5=ErKSA@x3SU{`8?r4wau`EukGZQ@Pn?E0212XZqZXr`o8@HJEXI}oh zLdu*cf{1R|8wqe`=PsJ><;fB1O1M5`cUIKQOAcp#v2@|yrfvHE^U>$9F)wsruJXr3 z+6QS7ujr-|+c!E2NOGiY>Mf5^`$~MDdK@u#?%GK^+3TXU7MzH;hfd0X(!2P((I#C1 zqP!O~0>ib&+6)X^qo!A#96I%qcAk3wp#g*kjs@&-I-=}t#L_7qU@9P-CYOpt3Yukw z@mjGylhkMA>!Ag(4WlP#Qbvy8<^P@x*@;gjL{1roh71+;Kk5=${_}zRBJu+Kljgnw zcs-W>pO^nE!N0cx$YOg}vO3Or%$Qc@-*qGdg16FpGwg{I+0C6l_UlZ4_3wI9T{6OI zYL52n%fk}YtSZ~P0MXcvkQL^zyjgTYo87#Uq{2`zU^ZJ`xmi*9?WIpiT#EitxTdI& zg>$L$t>MGSj8_frQ^kOqGk)h~hKnl!zVgJsPsOq-8ff&*BrhU|`{e^^Q$}w$k1^Xg zz;m^`Ua{EQ%z}u^hlNtNQn?i$FnF5!N4lub_H#D!tzqnL6rlH;U>IKKHh~C!{cUNI z<>U5ES{Tuo^tt6a=@7W0eq%Yc?MGElb*3xiQblFVYEo>ErvhT~6IOKIJ?rY6**_YB z-y`j3Res_qIr4j=GSB))qm1h_qaDEK+s7ya>a&by#at5;c#GX0X{d9QKmwt>@{*QA zR1gPR#0RYp5SKV(L!*(`mLo-dtnlZ2ma^U90l=s4K1MS+NDgOA*~UoacL!Q-j5K4> zy|=sNv(_2y4{xS&PPB_ITvn;%?u7#Xh}G_b!~S#W|Du(6ls6>gl>7HEBj!U^D)C<0 z*$van6v0g6LLs_Z4w#5+nP^M9jzx2Fh1l4(K^Sj-9S8RT+O5$sd_u^gSB951(C(D; zk>&Aws4Iubz?)Zx|Igoi-PYcPhPB#`!Knh$gN)viUPEpReV*Mb7a0(Mh`ygq8#cOqx-Ug+13UTW*XpG;0 z&vl<%lh)|9Ps$@HE=@~>*i`i>4v8L*h7}jOMPAVk8*bz@Mt)Niz8#O^j?G7f(m$jc zm*hk(G}4PyUj_KjIiIh0&uJ}UgfsO51wQ|HQJp-|hIWvMTXQ`;Xc@d4afO8;#wa^>z*efZqBgOtbt?8N@_ zS}SZB!{-Fe$E!9*E`e2~L5ksqm$$oKQuY^XW$7*yq+#-HR^VVe-MN~9krdSP;}N4_ z5hIt2D98JVk%~zW(+Lw2N=r&9T;rw+>~)4oKbaFkCcV4`QoU z;hk0qEO3e4y#k`wf&9fbAQ-g1=@@sa&`9c1B1g_mXJ7ysyTVPd87DNA-(^<4-%9vL znj{9|2G5Swmb!n^wdycheGeg!7bu8EueZV0w`>h32d2iRK1}wOSj#e%v&ObgbW!$8 z`0ad>f+FoAU<}tArmRWd^HYnhU)=Vu9a&*hU8LRX$RxK!I$?j+o1zwn)LV%lo36eT&^ln-sRU+Y$JZ~^9} zWu~csk@BD>ks~H^%UdC>%RGiW3Uz1dwS@v9We+@PzKQ0>CoMFx)`7mrrumJrsjb}N zTuVn84=QSAc+1tz+fe=P`3IMok?_@@SmWg(+1cAKbjNLnTr)~{CtAXTTBZl-c8tI; z5DdI8;qL)w{^0YNn0?RL%@in{?=Qe9kb3*xwXS z9?6SZ5Akt+OwvkjRm@~4-MkPW*H2S86zRu}8AgryrQ*IyL^+NadJ_san?NFK{J#Rn zc`7_o*R-q(1pASmnSGk`IhIIE!jymV&#e6mJc+PjWU9t2cJhpXv^es~AlNc4!#?cE zpcaV01uA%ERez(H)*zrVl=eAC3`Ws>w2R4E)lrvXrg8@F+h>1?*=pfD-lwLuRZ+lS zRIML*4ZGxgNb_>!<9O*x6Z4`7)_Hta(tT^l*8R7?UlDXk)KJZwH?MgSu}Am3z7%p`7t70W;MDNHE)!X|s!?s(?tH-2 z;KYux5Zdy-*`G;d)4ZsKhEhMrfo|;0(Ktz?EYf|zxn_STRb}2S@KmZeAK<9Rz4uby zsQz`rk5^F(TMqPRdizxbuV?_cU)sThq45%R-TALqtyL=hl@TpBYN!zame)Tc!?%IW z`|QO*J>#*>;{&g*$Yj`UuRHEtw9b7O$1{4kMY=x|q7XG-89TLk_|jce{f@4~CsV zJ6^;@hT&ZzY4dRnL=!5?73EXtOleZ+p(A3J;)t0JX6sZ1&XGTOPWe13yIS zUhg?*+rEf%7u=i-j%)^b{ww-yk+Sc2!O)=Cg z_QTil(jWGX1Xe6|tgVt1>&A@XR@uii$g;`*!x~-N%|D3hea6D^*&Oq%;G)Mh6sUcG zfNE>!7J&e_-I(jw;gj(4+L^3f{iqF+0j94@b;Q^&w9{}9p|FU^d*$zHNi&Z8#aN0G5uC`!%gLC_wHcITFaP$HqAyp zz^P&=A0QTYem?(YR#%2)gBr(nI9MqcTIKe^a-6)0z5Y#{uKqWqt=FuXI?e{e|6ur zsBAN5SMR$xi9K3dwGs3`d+z`CEr-9*<3H>GD1f!g@gQN*Ru_0SojKi8-6Sg2%dF`s zBMllboefqJl&Ui!GsZv8;Ak>%^8)P~yazHIi~(}yfdk>zZ|k*=Lp6jJHI}7~%$VOH z85VwZ_xv9Y=rAf*)2Q)iq_itgYoO__nG(e?76`w+{&JZtvJ?nh_t%-Lb4N31mvWzw z?%TiFiM9aK4?zRULFC0L#Ji>b*CVlsf%JpXvYybKPy>(U5%{D?9CvW>#1rt7woqcQT-x->Gx|J zjo19jg#>!0=&;_L{ZE6~J{m@Bb;t)T zu-S-x1XyhS70&x)buV`Oe=SPoj|VcmQpYX9#YFZtx7lsFdBMopCl-u7+JrEPk@*@+ zFzlBs`_IFCBXTT zmP?mSLT}fOl{J$HP{$AvZCkw>+(Qlnk86QIuRrGSnQro@R)6qrqqT=dUQwrT&OT#x zR@~nXtVP~Zlxw64CrsE0W3v~B2iW@vF>Lt02pPZn?);26)G5b*@?6hj5DzHe=~z9e za^|NX7%Q43ki4m)Gn{yIEv0#(duHGH82&n5K3w$QMjaa?_9d{*KMcgGLP|wCMQ@^G zzP7fCGMH9E%W<=3*5Gsbf|jw82d@6iau~}=PDk7Bx%F)-Y2^Kyy`C-O&9vUqoY)!4a~`Mew3H|i+EacZ7LeZDaTM`% zkW3XH1InUMF&B3LBWk&sOHN~B5X!ovq9tvgWgP0X2#D_pn-kG<)UgJVErC zzRuKB%YSHR(2BkvGfpsDN#JxptTSxL4rwBIxBsS1L`AyCq!EPXRtk0I$MBdv!k7M&JJ9%AJruZ zC}!D2G`uDs`c;F17OAqK>e2JFwFB$T8#_0FY>fEO<;|a4=70K+e@MNa?CS88B=SU| zR4cpXp+cEjLnvg>&4QRc$Zg8Ihl&_S)a%c_&F5RMv^|t}H8Mt?g^1j6yIM4TwiIm_ z)WRL-JGiK;oFMyJr!8m4M2hII15nc?_N5WoT3Iv9|MCeTSbmV;M@5}~!=w*+i!M^3c1xBKUC(96A5|rmE%R%6_IPf|1yMr_bEUi{_HQCvqe*X>+yf? z^QC)!#IKXb;a^7?k50Fnvv!J3G-~XA>1$s3^3WFHx;QBWh~!c1(t4gkvZa=YAJV%6 z$}zn!jq~;SEo_WRSz{fbFRuo2^X%)O4%+bEEc(-)^0$T72KD+tvPVMIk2|)u_y$8p zP+NBWx3Ww#6wLdPqGUc}%bMqA=#4ZPZ%(#-O=K``DA#!W(myW_D!gK`KkuAi2Dehx z!}v0}Y(Tq?4m2vJdJ;q;Zc|}Y898g|-zc=&ZK;Jih;kXi zj4$o&l+CPe=8c|ONUH<&vOa+i4%l?-X`SdYq$hD}6KFp|!7m6~E z|4okE3-{F;VpVdDNhWS^Rm`?ZmO0OPuQ~wkZ<@BMG!<_G93edd>k05X&pK%x?9sWgUvS5O8(u{+_X~e*)!sX20dnK}>ts zIe-c4EUZUKdTWL!s-mTST4OP^TT_QJp zRQlUME#pvqZ7LUmM?+XR;n~(j-_bp9R$s(P{c{`iYf*1i+x}PAeEHLj)U_Ufw)jH+p8i@Lc5oQ_ou?ToD*-%R4616@;L>#HKzsO@TEa0OgP5R@GL+~R)&i^Tm< zvs<@5Z&mg#&woy+%eijEU3>3eWVAlYqN%s6Tm@8@e|2y4M?eJyB7lR{aMaK|=RT^A zTqOcnj{KFpaC%J$-{_ZAxtjSex3Pp4F4{*mQ7OaS(NM)H@G0a)9A zj#Mprbnr+UiHQ5OFIWkO556R`B}^*NF25pF}ks(VlbtzY92=mj>aAr-34m zoZEvG2P+Z#kMx{FlYkUqJfdn!X!74_!a}7o&5m!qZj;+8oUnkvRGrdEoGKu(*m>5R z{qw~sc2y51O48)20J*%dakQ6#l|)Ey`QxecMZaw|&;CY(3937f-JkgT{O!i=(|^CX zU@Cp_?ioYnV8f49hN+8&pVAC%o~n*KRVA`dI^}#no1@xOb@EdkR3bf4>S;M`Y(kgn z8ju^Ztf&997v2jqBC;lmxx7JzHuYMn(BVVXw7RVTz{)!;`?{E@5I|wnq7fP!O3D?Z zVlI%7d6qKJ`}o|LDhmIbX^DtCiam1uO1=`1!HL*>JPuV6B;d_tC41ZHh_+~!+E`5s z^`Ea==sgrzp)p&l9gxWf&RrUpx;GK}G;M;yvny@F(nL!=;-6yuzXFJu-|yt4=C{n?D0*T>?11X8T9wDYORUcS=WG92 zlK+2ONf)IF1wx;*%+Qt@NO)ed}!Z=5esdf8$=gKy7E7{r`UlX|Mh{|i4>0yV_78R8*A*C)u&b)V%-ZTn3 zGgUtF5__Dv{8k|d`0|!3n&wPd?eAWahW#x~K?qx))A~S}(5{=nXQy1-0{BMgT$Aq& zGZ-1SzR$=Njk9>!Enr?vG_z^uyAvzOIJo2UjM4k;KqGje9lK~uhHz=h&s}?e&ZRr+OT5@gfC|UOXq>P)tF`T!W?RR#1 zgQ#H5=>tFf#o!u=5|W8`jczP)S_QY8PrT8FX(8bYN0xhmChqSOEp#77dL}KSZ<>+w`B13z^AA5Vth095$5>{C3I@Bd-o=g zxJ#HP2l$%UyPG?;VGiOp;nz-($%6h(Br8B|y7TE~*IEE3I`TAnS9=%mlPH-u6J$_W zH84u|(@7@t8MTWywda6?`4MhNwc6XD(?Ca;ycfmSS4@!($~NFMV=_6gY+6}#+3JdP zM%d4rn#FM_rE*+tQ!#My)N`{y=fR$|PX!X--e%hROzQ~q%GhP#lNT5Bz{dPCeo0eQ z)r)8CUGHhGzT@>DYUtj;G~Qj`t^IS1s8&j+eR2nWFQpvoL|t2PeRy3*3ixVa6%se} zIyxir1llrL(o#yFArfM_Z8i z))J6tuVXB}_Fgf~2n&Ox0VR&#uPEE;0GXE9TLk^>wUQ=%%mpdv=O)h^zoW|QbE+K4hVE%_DnDn~{)k$ws6=IXx#p;yIXcN2$TBJ4IMp@8;c<}7hs6Ra9=k&^Y^16~zU zKFo9-(H&)*-`4i<4B~)AhRH`nhl$2_r!v{G8H>|AF9XF!3Q#i$gG(5ZUy7XyzY?JC z#*VgqXcIbj@7G517DYBokkO8C_=+CPEao4*%YDf!r>*FFx`}^X9YHyw9 z&o6~#Zbp*@C6Z#NG-6oRaBYT8n=NTd+M&dKDjuI(5*sS^leeB-sPoVTZdpA;KoQO} z7XXu4cb}n()@N#wfz8 z7<`kKwdoR~8Ru(>U6V22J^5Y!yrsg++zVpn>4CW@z$8alEdwz_%W#N^u2^FXD+R8d zT4qnN-F?n!oi{x53Ip>u4*vdmhPn`SnWoM|26sImjEXDztuLaf^YrF9kdVAKETxI5 zRK{S>eP`_lN{2)0ojwm9=cic=0;63wW6EnsI%L>f_q&I(0JMf!sMt@v68ub{o+rzrE`bG9y>munDZ z4Q2G99>wh2)=_>TKS(-ZoWwULWyZN*n%9RYA^O6#^4^yNqr9b%>}fS3uBq^)v2fUb z?W{1E^+A8C>*Ukf=4e6u%u730hBNZrJ6v+C2k$jW+cfc-TU*TH9>UsP;i*WEX}WhO zh>>Z_XfqnOO*QU|;XG9__~T5Ji|%52^SD64(hc6qfa97b2xTn2(cp%uRfM@GO^@Ad z9MS?isp?-}3u_(Q+T0yru?u9^+r)00X_v9nQpZNMYA#oApw4wE=lFSVH?%RSVZ;JA z-$$(UFdZcu9JcBimR11PO{qn7y2}j+q-RKP74eng8okX0 z@qv{a3bD&of;E&fIPC)uVsC#N$J1IP=8_4?<+#2mup%x8*JG^&cO-+LdfV_n21Nb)_<+Zjs6}xPdsI>WS=#rZSwz;*<+Q z!G8Jb5Tl411q;l*(!AzTdHcYk#whwAfFo= z=RWy#TgC@!#CbLtd36i3A4ODDuDr>>NJPPFSH|P2bKcJwr-^;v_1_-pHXp(}Dkgnx z&9-?|EEG60vK4?W_CdFktXmN`hQjcp_Ap;LBqH@VZ^(H4x{vTq)y_vlaH}C9%F~+h z+03uL0h|ub3T2EDDBMx%*3j;~*H%a1DChMrp4kqP2h+7`Mw)=1c;VLWRQ?1lW@+xN zoX;D}yQ~$TESMw$g6`p)Y`Cj@8Zdy}H_3y{+!C)G&5xH;MdzxbME=of? zp7s3%b|G~|i0=uXaY-(TMSH$=pQ_x0(J!i;kEPfWVz&kPuFu!7$6IFxEOH(9H8Jjj1PdaT7?%;nmVfuXu?{eXWagvv78@H9jws!3g%I{iS zH};g`U#xM`Gd;Roagn@fnDzWo@TJ8x{{V~FQ{NFx@% zZ(1qs5gaiN|E7_B=M}V)YuQYFRVoGTh*eqKUc(p6R2JEBkG8pezIB`$p+!6_rq_vV ze*PP#WP`ea9ix`ZYgA))ChbKZP6xIOcq;l&RF#|4{6Jq2O!z^k_#B-mup7qL)M;|cKcPFD%`43d(p&hi9 z*`2u|{B7ziI!JXn`8Xdn18hjqN`&h_z@N)%u;Z9+cx-X9@7TzBWU*rDX>#Oj=N{YK zIXn>!y*t)PpDY`+)qbOs7o)ZjiUSrnO7PBZxmXlj&Q$=#qE4jL?Pt^~P0mv4 zLMx;??I=a6Npn?*HvHBjfx#zzn>!V@dF94-j&Ek}g-bs}{Ea*Jy>8t&c*%F)v+dfR zM^kWt^<>=ZdFk*hqjHQ}jq8mwPWunWe3hujF*0@-+!)34n%T5;Q(Mhz7fp~KgQt%e z=>(3Yx0~O0@k)Ma1+}5^Fqn*1qm#{g?}9WPiQ{csU0d zXeacoY@B(XdUD^@sMBW9oen7m$GoBYW5-mpTU9(lx=0M2(7%K_RLa*Y>z&AuIFzaY zVZN){Xjs+T!c4<$d7H)fEhjswZa3Er5hyHi)9X3tUGNOS)6Vha@YcL?T_;z_;mJyg zJ;u_i>10g_ZE57-P1A# zu#l}f=n;?2c)TRjxc!ac*&``$RjJKxQ+k7mom`jNAX8FN#!p?+7N>4wK=(O%&u#xo z#O;4L%*vG>MY%i9YK}{{>@;>WX*-PT=tGwCxXbGgaE2GdYTCP8*=2Kd#lW{y`h2Ns z?QWv%4O0s2OQbkx3fN~)O6Gz`47SwJ;qZG*c=8XWB-T#>Un9r!xy5vsy^<;##T{Nk zEjU;P7eFaCqIv&t!e_gZrloB&-KU7w48->RVo__I`((oRnD%+Bno-I#TxLG2tCud{ zlm#k*(fFKkl8Gbqox?I?$SoHhtT$TG{{HvPc^q0%u3KB655)?4N&hK1w0dkZw-v@W zqRR8RHu@L_#}^qjI8WECkq_lqW$)V)7rXjH8jrxb7Og>%6VoJ4ATq2s>*v#%>J2y- zin{mdTBm*AY5@s^d^K;gI^#D7^i)}UxKGT&>$(EhY}Tvjm$# z$!>B@3Hynxf%0R|MoNcT_gyS^ILcJ=pBgVeZoMlJ<`b3yEOl3WY+ zH!;E*?1h+UiLK7x>YH}ibft!TnQgZ|r#|uK*jo2{z2U}fif?%-N65!gnUD5b6vPUx z2QY0DlHNIEdUe{~a?g-~LN}iwuUytcjJpy0<15Pnb4d*0tVMKpl5HOQe z`$N}*?nSfHh!8>iu0PIiJjjHM*K4xq#)QK>VV6dct!F3f>fTX}ki&nXR5c*w)M%|}(a$Lhs$AtENSYl4uk0ujwd`yQTFyG>+kuP-(h!jaVbQ^ z%dNL5MCDl=Id2S2+m-C}q1bb87N-C+y^i<0yyV0H-zscs4R zd?PMBv-k9U6aDEd>qX&n%QD|G_*!w9k~39qg?sJf1>e*?LhP6a5w%4_ME`p2E498S zdMtl(2hq)*ijXm!d4JF1%RgRm5D?LTgixh~h(eGWk&;j&AT>aQkc1Yv`*mi{J-_=0+%q5Yb?>*X zcki{H^*n3uH;xMeTF-uZu6&0&-u=e3fjg!PsS7h)x=dvw*H3ff5@^p1-C!m5n>6RD z!hE_nr!hsejGKNjva4ul42~DeG_5E!UB z+@y)}v%*rTw$I>?L0y#sS%n)qkvp(hr`6^379|$OE6c$X9nodGy>3K$RkacC#G*5P zuM>VZBhFkoT{UaXmNwm_;Hu(o6t*<1g7bBLC4SNzpL`fZXVoF)Px-&wJq#w$FY9%` zv^n0X@`X|EjRn=(Ai`CIQ9pzWRo4u);?y(VeBC5n`#WCX$vIWcXx`4@^ItLcqHCXc zzXj)m;=ED8(ez(Wmny};#ZB-c+AeQTwHQdgt?%R&Ikn6#whsv9lXpNc)S4d1usRb@ zHt5JX>B2PzR%CjbZV=3};pu-(~jyR{Je ze|VL@6GOy}0{9d8hS(e$w*QhCqD6M>y_Ue8*oecN15t*X0!h<2x2o+LUI4hT9>1t9 z%&EVi<|pd0X{W+klU(ktr#zEAT8AL?u+-bXIZgBNN^`hz){(U;7KtZaZ?uc3^K9C+ zx`uK_SPQfEdTuP$ikEh>$ysq) zC+K8=BIvo-9xGLqUsZe_qO1b`qO2fJ4Wg`)h-I&FKl>g+iCMsD=!CVIV=MEx!L|bd zg>#FSG+;m3uq%JFVPic~3IT<}Expj8ghnHKMWEO?V&L8^ji3R1r5fa`4fVJ)m`}-r5o-3AJWJj0rCG zP@b{~;^C(PVns+o2iOZ;;Q6bpF~b=S3I|cV*J@34!-sgSD?$Bvl}n)aU$&i<5a(|N z(ZcF9!R;BS%q4>7R%;>X`xEZgf`6cdRN{YH2CPMM8Z?qkDC3_ZQ8k z9e#}fk{m?J*C2BzyB07r6=a9?XIW#Ig2K z72I;*;XR0w8`Tp-WArQfuR@(cxAXoe;<4AmTQWZhBSgDvDhSC&OsPY;py$ZVGuY!Z zMS1$#UbYRU0TEaQ1LB5x!y99)hj zWA23+j!I7J(9hnY#<&(Lf{3eSJ3b)B`WV0VvFWj$BODW3Nl9a6_>)Mz5!Ts02 z?$vK#PLsYZkFnexJHzJRJW0(koOnL=NoyApbBnt%MQFAti0upB;9vHgq2{c!Gkdk2 zJ0r|ROuQwuhqH2;k88ti&lQF6)y#jIz>84q7?6i5Y|I6A=QiwSPL$87<|MJpRx8+I8~vcjIB55@-R zc;nW}OK#OS;p7Ew9ij<1r^dMCIrMW!NPwu&g0Gdf`sX>lB{!cbY|qHGF7R2K&yP)E z_T02%tCpYv{?$L(Fi&;FlO;+hAN(yGe~JzCTZKdK%5n6G51V0@Jb``sKVQox0(+8P z-r?BV4(q>;O|CB!)Db5wUh{9wzBCxX^w+RNAN*rG6J5CUp{*kiW&9$%xZz~0teLSj zR&1>S_&T0^*v*hGXmS6!q2T~_?#*hluDO&3J1&zU3QOF&vZ)(~#L6Fc=V zn97&M!1-lwp=8RG6m>LqNOElB^Mc~((*#tAa z+$k7jwdc_J2n#p}bBE%e_XM=!F}zneGkekcIY+$PDaxY-2j8c zkWoB-ZK(eef*kU>3#LOteUxma$lbl{PI>HPu&~(ndlupQP;2>ML4oA46leIDT97u1 zQuVs|otcpZn=$%a+6To=$&g-*o8hiDPDTb|mxjH(aEz_>?bW#h7&BIGZhGHR{Q6Di zr<4StRl1;pY2|2}TTdR|iW=92m%+GD$5&h7W_imC*V@LSSK2X#iSJ5C$o{H3ab;K} zX~1?HyTW!aBp&&t&8=nNGicMHjn7)q;L9vTyI!5qq|`8K3N%e?*s}#xc{|Pw(Rg^G zXVGwd;=6xJhTgY{HrC=~wwWh~I9%yxyT1cjNO)%?FA%r)&r$mTYbG0Tk*b}cH<^#4 zywp`tjE_>I=PtD0HwrRjg#qptpYK5J$<36>USM*MzpC(T?C4@=(w!A`&Z#wElf^$h zI_NDt{FEv*dVjBDenGf<$Lp~uhL0x%tgP0{baUKD_p{3TsV=H(D|5w_l!AXz)TpuCeA1ghm`oaPL8?10T%{T(tn|tqiuf0Y zL2$bK;yeqElDmP`Q^GWF7dN_!e%(F+qtUKs2nmp5p<#Yo75qlvw+e$_?m{vzW~gYZ zxDYDTM0s0TYDRFSRPIbEo1w2|*yI&C543p?b=w7BZBye~^3uk7xSnH+Oced)wEol9 zUv>6hj-ps`q)l7C(fXg5a4J`)ZubjbX~JZ8A%9}JAV(GB(;9@!uX*co%CxASLrlg< za@_bqx93kY-Z{YmE7U!{@!s5i$md9T16r@wDLug4dR^&bx;^h=7dKi=C6B0^g;W*M z3w`+k+hM&%f}7?I8YjBv_q``bA-_1aRKBb{$O&AW_8bpz^Q$X*KF=1soE{cpMb%Z@ zEKN1!oUw8yUyB+_j9r+27CRnfWFIxEI5U?O3>5mSW=iQ{GCK8Q5;4O@l31FwCku`r0N&q_+F& zx%PNbL`nV1YU78Xn7JmDEXGTVb3)RqkWl*XQ3NKrBIikzFZ}ANm9CpX-5+r_z2=+* z!(W>GPFI+ZLG;A^hzj)>p_(c3?UrWwuvb%ieS%jTaYz|yJ9QFZtiNIfZ}p-dZ`YZ1 zT6SS&I=I~>uHl3FNVY{TuIyx!?A0re`wSLp_fGjLBnmvMy(rTv6+VjOZs*NzIQn#w zRq&Q9BYMA6GH9?bggIY1v><|b;m^&Q;$p`q2kZYCd&J6g4{kfaXD%hA9o=U)0dkHNUFR zqOM$ki*#^|6AVv@t7599!c~`d>g+$DWQ;=Wv*UQ_4~^+pE=THK$eD)OX|9asa*T0O zRiYc}GOg*yrqN&y1TV2+I6rvfH9b@Nimi?3YfTWkcJZ1@>4bUSNL_l7lQCHefp|yR zDll>uFn~IxdLa7;)bWGXJN@l3=uN#NWQ>SwMxMOwHRAgDN2nJq!k4_Z5PviU#h+1o zfTd*k-5kH0G;iplb4qX4hAoZgfB*gMlz-jVSfE9ynk(a@MNT(ifIi|_hm>q+PGp+L zpwDkfa8bjGBI-Q%jRq4FHAx+ZHY=~%QxPkHC&JYY^_$*H-$j#-c3nkLR zr>e^0ec4LGorm`65*Xvi=LEmr8Sb%g|F-(Hox<=qo2Ge*djvMvq!-^{@>Wq+ONA!U_ixuRC5G4$bogS_ijVE)*`_1Sak_i)F}r7@-%G72BjHtik<5TD(YfZ6mLhO4`<( z>Ehq&jwTnK5c2!Ow2BT!hU7$DOZq(VVg#0eHWHA~_!;mCTcm|uo>HG}?l#?cHeiLl zn71}W9qea4jKy$h63tGmNG-6Zm=o8yXxZKMzR`8SpSlX{ZZ>K%B0g=nZz5sI>A5ys zmw5DLOcrqUo-FX2`xbZ>a zn>@4W8G@dcGVlW7cR)o4*oDb~D4y9uq!6!SHSNG%&GQrKrD#iUjXC=;01|iMU$BMZx~Zn2$D|5fFTHGs24<;Het`M8O*4NK z6=y=KB|p)?<7ybC2}b5@uPS!qGEDxGgh)eP;)rUv_hN_pmin(roB(-RNGO#7zB1yn&agIo2nOp03a?8+W~ zUXg3>a)0HlNRFZm%(CVgd0mJ>Z<*E|O? z)0=mO4=U0G&7I3v(7s8;)I)anFQWSI1fD_INIhaXXMhR!WA9m;-31bhtxXu-GT;^N zV#QM7WOtx2&VQ!*)_&ZI!wmg#R>&IN5ZdV_N?Fg*Wsg;r(~I@kWvEIFscdl<(Jgd3 z^g_^&W7i6WIq5-HwA&`jHS(7q!9j7ulnPDY-aAlA`$4Bmg|^6SpiGG_d1n}pd~991r%({9Dl&q3Q~!m5tTAOWdc+i^uJ>Cx(Wz#taqVNF&5MDI#aeM^qb3Vj1$ zWCe4{wadoa^~A!DqNJ4YwP%%|(5((Q>O zY^YTE&pX}*F;D4YqzcZZ=OaBGeo%jCeP+$u@?IALqwl@p`498AEkD zQhK;-j5>&mr|>yyIqQYoo2_`+cA_zWo&sg+KNB8s?P5UJMZH?`?w59%N(vr|lL$XH%7N%3( zvpSBh0kT)bGn-x4II#?&6GduCptf>tq_p_0MMjrmoi>fi17UY zCL?%D6IX5la+AgQhNhdQBuTJ~g-iEm4cWj`Y{$92b2L%uuZibc$kQ)45`Bkt|IRp{ zA9Z;`vFY|(dXR9}O>99rF7ANoT2%68^G!mtwMTCF8JeFF6mA$J`@JT(lUaErf6*D= zVI>-bt62^s%es@Us?9Fs{o5Ecm?Gy}6xXS6ajuOR=Zl-xPv2h`Lsv>qqh;ryr)Qbz zz9!b0@;9wR3+nDT@|;$Yb7myvS@cHDpEA@dX6 z>f+*y7ow_HNQt6 z75I<{RnJc-nA$I;jksGWnDn8_`?8l<9c_7uI8Z>F06x64gi3dHdrJ+o)~$Ec8s?5%}%`7~#L~Epzvy%0Z#VjSFW^ zTJl{w-$C&MI^RF@;T%6a@xlB*Av*ITGCv~oBQifC^CL1pBJ(3MKO*zVRWJtl+%ho2 zKY?1m-4K5PJc_^;Hj*f_QRoubpou>~`2&IO?m(T literal 0 HcmV?d00001 diff --git a/samples/authenticator/app/.gitignore b/samples/authenticator/app/.gitignore index 7915f57c..f103701d 100644 --- a/samples/authenticator/app/.gitignore +++ b/samples/authenticator/app/.gitignore @@ -1,4 +1,4 @@ /build /buildNative **/awsconfiguration.json -**/amplifyconfiguration.json \ No newline at end of file +**/amplifyconfiguration**.json \ No newline at end of file