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
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
)
val events = _events.asSharedFlow()

// Is there a current Amplify call in progress that could result in a signed in event?
private var expectingSignInEvent: Boolean = false

fun start(configuration: AuthenticatorConfiguration) {
if (::configuration.isInitialized) {
return
Expand Down Expand Up @@ -242,24 +245,30 @@
}

private suspend fun handleAutoSignIn(username: String, password: String) {
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)
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)

Check warning on line 254 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt#L253-L254

Added lines #L253 - L254 were not covered by tests
}

is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
}
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
}
}

private suspend fun handleSignedUp(username: String, password: String) {
when (val result = authProvider.signIn(username, password)) {
is AmplifyResult.Error -> {
moveTo(AuthenticatorStep.SignIn)
handleSignInFailure(username, password, result.error)
startSignInJob {
when (val result = authProvider.signIn(username, password)) {

Check warning on line 264 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt#L263-L264

Added lines #L263 - L264 were not covered by tests
is AmplifyResult.Error -> {
moveTo(AuthenticatorStep.SignIn)
handleSignInFailure(username, password, result.error)

Check warning on line 267 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt#L266-L267

Added lines #L266 - L267 were not covered by tests
}

is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
}
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
}
}

Expand All @@ -268,31 +277,31 @@

@VisibleForTesting
suspend fun signIn(username: String, password: String) {
viewModelScope.launch {
startSignInJob {
when (val result = authProvider.signIn(username, password)) {
is AmplifyResult.Error -> handleSignInFailure(username, password, result.error)
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
}
}.join()
}
}

private suspend fun confirmSignIn(username: String, password: String, challengeResponse: String) {
viewModelScope.launch {
startSignInJob {

Check warning on line 289 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt#L289

Added line #L289 was not covered by tests
when (val result = authProvider.confirmSignIn(challengeResponse)) {
is AmplifyResult.Error -> handleSignInFailure(username, password, result.error)
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
}
}.join()
}
}

private suspend fun setNewSignInPassword(username: String, password: String) {
viewModelScope.launch {
startSignInJob {

Check warning on line 298 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt#L298

Added line #L298 was not covered by tests
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)
}
}.join()
}
}

private suspend fun handleSignInFailure(username: String, password: String, error: AuthException) {
Expand Down Expand Up @@ -520,9 +529,11 @@
logger.debug("Password reset complete")
sendMessage(PasswordResetMessage)
if (username != null && password != null) {
when (val result = authProvider.signIn(username, password)) {
is AmplifyResult.Error -> moveTo(stateFactory.newSignInState(this::signIn))
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
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))
Expand Down Expand Up @@ -636,9 +647,27 @@
}
}

private suspend fun startSignInJob(body: suspend () -> Unit) {
expectingSignInEvent = true
viewModelScope.launch { body() }.join()
expectingSignInEvent = false
}

// Amplify has told us the user signed in.
private suspend fun handleSignedInEvent() {
// TODO : move the user to signedInState *if* we are not in the process of signing in or verifying the user
if (!expectingSignInEvent && !inPostSignInState()) {
handleSignedIn()
}
}

private fun inPostSignInState(): Boolean {
val step = currentState.step
return when (step) {
is AuthenticatorStep.VerifyUser,
is AuthenticatorStep.VerifyUserConfirm,
is AuthenticatorStep.SignedIn -> true
else -> false
}
}

private fun handleSignedOut() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package com.amplifyframework.ui.authenticator

import android.app.Application
import androidx.lifecycle.viewmodel.compose.viewModel
import aws.smithy.kotlin.runtime.http.HttpException
import com.amplifyframework.auth.AuthChannelEventName
import com.amplifyframework.auth.AuthUserAttributeKey.email
import com.amplifyframework.auth.AuthUserAttributeKey.emailVerified
import com.amplifyframework.auth.MFAType
Expand All @@ -28,6 +30,7 @@ import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep
import com.amplifyframework.auth.result.step.AuthResetPasswordStep
import com.amplifyframework.auth.result.step.AuthSignInStep
import com.amplifyframework.auth.result.step.AuthSignUpStep
import com.amplifyframework.hub.HubEvent
import com.amplifyframework.ui.authenticator.auth.VerificationMechanism
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
import com.amplifyframework.ui.authenticator.util.AmplifyResult
Expand All @@ -45,7 +48,11 @@ import io.mockk.every
import io.mockk.mockk
import java.net.UnknownHostException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
Expand All @@ -65,10 +72,13 @@ class AuthenticatorViewModelTest {

private val viewModel = AuthenticatorViewModel(application, authProvider)

private val hubFlow = MutableSharedFlow<HubEvent<*>>(replay = 0)

@Before
fun setup() {
coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration()
coEvery { authProvider.getCurrentUser() } returns Success(mockUser())
coEvery { authProvider.authStatusEvents() } returns hubFlow
}

//region start tests
Expand Down Expand Up @@ -441,6 +451,60 @@ class AuthenticatorViewModelTest {
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
}

@Test
fun `moves to SignedInState when receiving SignedIn event`() = runTest {
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))

viewModel.start(mockAuthenticatorConfiguration())
runCurrent()

viewModel.currentStep shouldBe AuthenticatorStep.SignIn
hubFlow.emit(HubEvent.create(AuthChannelEventName.SIGNED_IN.name))
viewModel.currentStep shouldBe AuthenticatorStep.SignedIn
}

@Test
fun `does not advance to signed in if sign in is in progress when SignedIn event is received`() = runTest {
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
coEvery { authProvider.signIn(any(), any()) } coAnswers {
delay(1000) // delay so that the sign in does not complete until the clock is advanced
Success(mockSignInResult())
}

viewModel.start(mockAuthenticatorConfiguration())
runCurrent()

viewModel.currentStep shouldBe AuthenticatorStep.SignIn

backgroundScope.launch { viewModel.signIn("username", "password") }

hubFlow.emit(HubEvent.create(AuthChannelEventName.SIGNED_IN.name))

// Since sign in is in progress we should not move to SignedIn until after it completes
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
advanceUntilIdle() // advance the clock to complete sign in
viewModel.currentStep shouldBe AuthenticatorStep.SignedIn
}

@Test
fun `does not advance to SignedIn when SignedIn event is received in a post-sign-in state`() = runTest {
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
coEvery { authProvider.signIn(any(), any()) } returns Success(mockSignInResult())
coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
verificationMechanisms = setOf(VerificationMechanism.Email)
)
coEvery { authProvider.fetchUserAttributes() } returns Success(
mockUserAttributes(email() to "email", emailVerified() to "false")
)

viewModel.start(mockAuthenticatorConfiguration())
viewModel.signIn("username", "password")

viewModel.currentStep shouldBe AuthenticatorStep.VerifyUser
hubFlow.emit(HubEvent.create(AuthChannelEventName.SIGNED_IN.name))
viewModel.currentStep shouldBe AuthenticatorStep.VerifyUser // stay in current state
}

//endregion
//region sign up tests

Expand Down