Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import com.amplifyframework.ui.authenticator.auth.toFieldKey

internal data class FormData(val fields: List<FieldConfig>)

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.
Expand All @@ -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.
Expand Down Expand Up @@ -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()
)
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -392,10 +383,7 @@ internal class FormBuilderImpl : SignUpFormBuilder {
fields.putAll(map)
}

fun markRequiredFields(
signInMethod: SignInMethod,
requiredKeys: List<AuthUserAttributeKey>
) {
fun markRequiredFields(signInMethod: SignInMethod, requiredKeys: List<AuthUserAttributeKey>) {
fields.replaceAll { fieldKey, config ->
if (fieldKey is FieldKey.UserAttributeKey && requiredKeys.contains(fieldKey.attributeKey)) {
config.required()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
get() = (this as SignInSelectAuthFactorStateImpl).signInMethod
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,28 @@ import com.amplifyframework.ui.authenticator.forms.buildForm
internal class SignUpStateImpl(
private val signInMethod: SignInMethod,
private val signUpAttributes: List<AuthUserAttributeKey>,
requirePasswordField: Boolean,
private val passwordCriteria: PasswordCriteria,
private val signUpForm: FormData,
private val onSubmit: suspend (username: String, password: String, attributes: List<AuthUserAttribute>) -> Unit,
private val onSubmit: suspend (username: String, password: String?, attributes: List<AuthUserAttribute>) -> 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)
Expand Down Expand Up @@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<MFAType>,
Expand All @@ -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<MFAType>,
Expand All @@ -119,10 +114,11 @@ internal class StepStateFactory(
)

fun newSignUpState(
onSubmit: suspend (username: String, password: String, attributes: List<AuthUserAttribute>) -> Unit
onSubmit: suspend (username: String, password: String?, attributes: List<AuthUserAttribute>) -> Unit
) = SignUpStateImpl(
signInMethod = authConfiguration.signInMethod,
signUpAttributes = authConfiguration.signUpAttributes,
requirePasswordField = configuration.authenticationFlow.signUpRequiresPassword,
passwordCriteria = authConfiguration.passwordCriteria,
signUpForm = signUpForm,
onSubmit = onSubmit,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AuthSignInResult>
suspend fun signIn(username: String, password: String?): AmplifyResult<AuthSignInResult>

suspend fun confirmSignIn(challengeResponse: String): AmplifyResult<AuthSignInResult>

suspend fun signUp(username: String, password: String, options: AuthSignUpOptions): AmplifyResult<AuthSignUpResult>
suspend fun signUp(username: String, password: String?, options: AuthSignUpOptions): AmplifyResult<AuthSignUpResult>

suspend fun confirmSignUp(username: String, code: String): AmplifyResult<AuthSignUpResult>

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -652,6 +651,7 @@ class AuthenticatorViewModelTest {
viewModel.resetPassword("username")
}
}

//endregion
//region helpers
private val AuthenticatorViewModel.currentStep: AuthenticatorStep
Expand Down
Loading