Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import com.amplifyframework.auth.cognito.exceptions.service.PasswordResetRequire
import com.amplifyframework.auth.cognito.exceptions.service.UserNotConfirmedException
import com.amplifyframework.auth.cognito.exceptions.service.UserNotFoundException
import com.amplifyframework.auth.cognito.exceptions.service.UsernameExistsException
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmSignInOptions
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignInOptions
import com.amplifyframework.auth.exceptions.NotAuthorizedException
import com.amplifyframework.auth.exceptions.SessionExpiredException
import com.amplifyframework.auth.exceptions.UnknownException
Expand All @@ -52,7 +54,12 @@ 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.AuthFactor
import com.amplifyframework.ui.authenticator.data.AuthenticationFlow
import com.amplifyframework.ui.authenticator.data.UserInfo
import com.amplifyframework.ui.authenticator.data.challengeResponse
import com.amplifyframework.ui.authenticator.data.toAuthFactors
import com.amplifyframework.ui.authenticator.data.toAuthFlowType
import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
import com.amplifyframework.ui.authenticator.enums.SignInSource
Expand All @@ -69,6 +76,7 @@ import com.amplifyframework.ui.authenticator.states.BaseStateImpl
import com.amplifyframework.ui.authenticator.states.StepStateFactory
import com.amplifyframework.ui.authenticator.util.AmplifyResult
import com.amplifyframework.ui.authenticator.util.AuthConfigurationResult
import com.amplifyframework.ui.authenticator.util.AuthFlowSessionExpiredMessage
import com.amplifyframework.ui.authenticator.util.AuthProvider
import com.amplifyframework.ui.authenticator.util.AuthenticatorMessage
import com.amplifyframework.ui.authenticator.util.CannotSendCodeMessage
Expand All @@ -83,7 +91,11 @@ import com.amplifyframework.ui.authenticator.util.PasswordResetMessage
import com.amplifyframework.ui.authenticator.util.RealAuthProvider
import com.amplifyframework.ui.authenticator.util.UnableToResetPasswordMessage
import com.amplifyframework.ui.authenticator.util.UnknownErrorMessage
import com.amplifyframework.ui.authenticator.util.authFlow
import com.amplifyframework.ui.authenticator.util.callingActivity
import com.amplifyframework.ui.authenticator.util.isAuthFlowSessionExpiredError
import com.amplifyframework.ui.authenticator.util.isConnectivityIssue
import com.amplifyframework.ui.authenticator.util.preferredFirstFactor
import com.amplifyframework.ui.authenticator.util.toFieldError
import java.lang.ref.WeakReference
import kotlinx.coroutines.channels.BufferOverflow
Expand Down Expand Up @@ -272,7 +284,8 @@ internal class AuthenticatorViewModel(application: Application, private val auth
}

private suspend fun handleSignedUp(info: UserInfo) = startSignInJob {
when (val result = authProvider.signIn(info.username, info.password)) {
val options = getSignInOptions()
when (val result = authProvider.signIn(info.username, info.password, options)) {
is AmplifyResult.Error -> {
moveTo(AuthenticatorStep.SignIn)
handleSignInFailure(info, result.error)
Expand All @@ -295,21 +308,35 @@ internal class AuthenticatorViewModel(application: Application, private val auth
}

private suspend fun startSignIn(info: UserInfo) = startSignInJob {
when (val result = authProvider.signIn(info.username, info.password)) {
val options = getSignInOptions()
when (val result = authProvider.signIn(info.username, info.password, options)) {
is AmplifyResult.Error -> handleSignInFailure(info, result.error)
is AmplifyResult.Success -> handleSignInSuccess(info, result.data)
}
}

private fun getSignInOptions(preferredFirstFactorOverride: AuthFactor? = null) =
AWSCognitoAuthSignInOptions.builder()
.authFlow(configuration.authenticationFlow.toAuthFlowType())
.callingActivity(activity)
.preferredFirstFactor(configuration.authenticationFlow, preferredFirstFactorOverride)
.build()

private suspend fun confirmSignIn(info: UserInfo, challengeResponse: String) = startSignInJob {
when (val result = authProvider.confirmSignIn(challengeResponse)) {
is AmplifyResult.Error -> handleSignInFailure(info, result.error)
val options = AWSCognitoAuthConfirmSignInOptions.builder()
.callingActivity(activity)
.build()
when (val result = authProvider.confirmSignIn(challengeResponse, options)) {
is AmplifyResult.Error -> handleConfirmSignInFailure(info, result.error)
is AmplifyResult.Success -> handleSignInSuccess(info, result.data)
}
}

private suspend fun setNewSignInPassword(info: UserInfo, newPassword: String) = startSignInJob {
when (val result = authProvider.confirmSignIn(newPassword)) {
val options = AWSCognitoAuthConfirmSignInOptions.builder()
.callingActivity(activity)
.build()
when (val result = authProvider.confirmSignIn(newPassword, options)) {
// an error here is more similar to a sign up error
is AmplifyResult.Error -> handleSignUpFailure(result.error)
is AmplifyResult.Success -> {
Expand All @@ -330,6 +357,17 @@ internal class AuthenticatorViewModel(application: Application, private val auth
}
}

private suspend fun handleConfirmSignInFailure(info: UserInfo, error: AuthException) {
if (configuration.authenticationFlow is AuthenticationFlow.UserChoice &&
error.isAuthFlowSessionExpiredError()
) {
moveTo(AuthenticatorStep.SignIn)
sendMessage(AuthFlowSessionExpiredMessage(error))
} else {
handleSignInFailure(info, error)
}
}

private suspend fun handleUnconfirmedSignIn(info: UserInfo) {
when (val result = authProvider.resendSignUpCode(info.username)) {
is AmplifyResult.Error -> handleAuthException(result.error)
Expand All @@ -352,7 +390,33 @@ internal class AuthenticatorViewModel(application: Application, private val auth
}
}

private suspend fun handleTotpSetupRequired(info: UserInfo, totpSetupDetails: TOTPSetupDetails?) {
private suspend fun handleFactorSelectionRequired(info: UserInfo, availableFactors: Set<AuthFactor>?) {
if (availableFactors == null) {
val exception = AuthException("Missing available AuthFactorTypes", "Please open a bug with Amplify")
handleGeneralFailure(exception)
return
}

// Auto-select a single auth factor
if (availableFactors.size == 1) {
confirmSignIn(info, availableFactors.first().challengeResponse)
return
}

val newState = stateFactory.newSignInSelectFactorState(
username = info.username,
availableFactors = availableFactors,
onSelect = { authFactor ->
val passwordField = (currentState as? BaseStateImpl)?.form?.fields?.get(Password)
val password = passwordField?.state?.content
val newInfo = info.copy(password = password)
confirmSignIn(newInfo, authFactor.challengeResponse)
}
)
moveTo(newState)
}

private fun handleTotpSetupRequired(info: UserInfo, totpSetupDetails: TOTPSetupDetails?) {
if (totpSetupDetails == null) {
val exception = AuthException("Missing TOTPSetupDetails", "Please open a bug with Amplify")
handleGeneralFailure(exception)
Expand Down Expand Up @@ -411,6 +475,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth
AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE,
AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> moveTo(
stateFactory.newSignInMfaState(
longCode = configuration.authenticationFlow is AuthenticationFlow.UserChoice,
codeDeliveryDetails = result.nextStep.codeDeliveryDetails
) { confirmationCode -> confirmSignIn(info, confirmationCode) }
)
Expand Down Expand Up @@ -444,6 +509,23 @@ internal class AuthenticatorViewModel(application: Application, private val auth
confirmSignIn(info, confirmationCode)
}
)
AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION ->
handleFactorSelectionRequired(
info,
result.nextStep.availableFactors?.toAuthFactors()
)
AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> {
if (info.password != null) {
confirmSignIn(info, info.password)
} else {
moveTo(
stateFactory.newSignInConfirmPasswordState(username = info.username) { password ->
val newInfo = info.copy(password = password)
confirmSignIn(newInfo, password)
}
)
}
}
else -> {
// Generic error for any other next steps that may be added in the future
val exception = AuthException(
Expand Down Expand Up @@ -532,7 +614,7 @@ internal class AuthenticatorViewModel(application: Application, private val auth
}
}

private suspend fun handlePasswordResetComplete(username: String? = null, password: String? = null) {
private suspend fun handlePasswordResetComplete() {
logger.debug("Password reset complete")
sendMessage(PasswordResetMessage)
moveTo(stateFactory.newSignInState(this::signIn))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ internal val AuthenticationFlow.signInRequiresPassword: Boolean get() = when (th
}

internal fun AuthenticationFlow.toAuthFlowType() = when (this) {
is AuthenticationFlow.Password -> AuthFlowType.USER_SRP_AUTH
is AuthenticationFlow.UserChoice -> AuthFlowType.USER_AUTH
is AuthenticationFlow.Password -> null // Use whatever is defined in the user's config file
is AuthenticationFlow.UserChoice -> AuthFlowType.USER_AUTH // Requires USER_AUTH
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ internal operator fun FieldValidator.plus(other: FieldValidator): FieldValidator
internal object FieldValidators {

private val usernamePattern = """[\p{L}\p{M}\p{S}\p{N}\p{P}]+""".toPattern()
private val confirmationCodePattern = """\d{6}""".toPattern()
private val specialRegex = """[\^\$\{}\*\.\[\]\{}\(\)\?\-"!@#%&/\\,><':;|_~`+=\s]+""".toRegex()
private val numbersRegex = "\\d+".toRegex()
private val upperRegex = "[A-Z]+".toRegex()
Expand Down Expand Up @@ -105,7 +104,9 @@ internal object FieldValidators {
}
}

internal fun confirmationCode() = pattern(confirmationCodePattern)
internal fun confirmationCode(
digits: Int
) = pattern("\\d{$digits}".toPattern())

internal fun password(
criteria: PasswordCriteria
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,12 @@ internal class FormBuilderImpl : SignUpFormBuilder {
)
}

fun confirmationCode() {
fun confirmationCode(digits: Int = 6) {
this += FieldConfig.Text(
key = FieldKey.ConfirmationCode,
validator = FieldValidators.confirmationCode(),
validator = FieldValidators.confirmationCode(digits),
keyboardType = KeyboardType.Number,
maxLength = 6
maxLength = digits
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
import com.amplifyframework.ui.authenticator.forms.FieldKey

internal class SignInConfirmMfaStateImpl(
expectedDigits: Int,
override val deliveryDetails: AuthCodeDeliveryDetails?,
private val onSubmit: suspend (confirmationCode: String) -> Unit,
private val onMoveTo: (step: AuthenticatorInitialStep) -> Unit
) : BaseStateImpl(), SignInConfirmMfaState {
) : BaseStateImpl(),
SignInConfirmMfaState {

init {
form.addFields {
confirmationCode()
confirmationCode(digits = expectedDigits)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ import com.amplifyframework.ui.authenticator.forms.FieldKey

internal class SignInStateImpl(
private val signInMethod: SignInMethod,
private val onSubmit: suspend (username: String, password: String) -> Unit,
showPasswordField: Boolean,
private val onSubmit: suspend (username: String, password: String?) -> Unit,
private val onMoveTo: (step: AuthenticatorInitialStep) -> Unit
) : BaseStateImpl(), SignInState {

init {
form.addFields {
fieldForSignInMethod(signInMethod)
password()
if (showPasswordField) {
password()
}
}
}

Expand All @@ -40,7 +43,7 @@ internal class SignInStateImpl(

override suspend fun signIn() = doSubmit {
val username = form.getTrimmed(signInMethod.toFieldKey())!!
val password = form.getTrimmed(FieldKey.Password)!!
val password = form.getTrimmed(FieldKey.Password)
onSubmit(username, password)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ 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.AuthFactor
import com.amplifyframework.ui.authenticator.data.signInRequiresPassword
import com.amplifyframework.ui.authenticator.data.signUpRequiresPassword
import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep
import com.amplifyframework.ui.authenticator.forms.FormData
Expand All @@ -39,16 +41,31 @@ internal class StepStateFactory(
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,
showPasswordField = configuration.authenticationFlow.signInRequiresPassword,
onSubmit = onSubmit,
onMoveTo = onMoveTo
)

fun newSignInSelectFactorState(
username: String,
availableFactors: Set<AuthFactor>,
onSelect: suspend (AuthFactor) -> Unit
) = SignInSelectAuthFactorStateImpl(
username = username,
signInMethod = authConfiguration.signInMethod,
availableAuthFactors = availableFactors,
onSubmit = onSelect,
onMoveTo = onMoveTo
)

fun newSignInMfaState(
longCode: Boolean,
codeDeliveryDetails: AuthCodeDeliveryDetails?,
onSubmit: suspend (confirmationCode: String) -> Unit
) = SignInConfirmMfaStateImpl(
expectedDigits = if (longCode) 8 else 6,
deliveryDetails = codeDeliveryDetails,
onSubmit = onSubmit,
onMoveTo = onMoveTo
Expand All @@ -72,6 +89,14 @@ internal class StepStateFactory(
onMoveTo = onMoveTo
)

fun newSignInConfirmPasswordState(username: String, onSubmit: suspend (password: String) -> Unit) =
SignInConfirmPasswordStateImpl(
username = username,
signInMethod = authConfiguration.signInMethod,
onSubmit = onSubmit,
onMoveTo = onMoveTo
)

fun newSignInConfirmTotpCodeState(onSubmit: suspend (confirmationCode: String) -> Unit) =
SignInConfirmTotpCodeStateImpl(
onSubmit = onSubmit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fun SignInConfirmPassword(
value = state.username,
onValueChange = {},
label = { Text(usernameLabel) },
readOnly = true
enabled = false
)
Spacer(modifier = Modifier.size(AuthenticatorUiConstants.spaceBetweenFields))
AuthenticatorForm(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -60,7 +61,7 @@ fun SignInSelectAuthFactor(
if (state.availableAuthFactors.size > 1) {
DividerWithText(
text = stringResource(R.string.amplify_ui_authenticator_or),
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(0.5f).align(Alignment.CenterHorizontally)
)
}
}
Expand Down
Loading