Skip to content

Commit b438686

Browse files
authoredJul 2, 2024··
fix: automatically sign in after password reset succeeds (#162)
1 parent 61cdef6 commit b438686

File tree

2 files changed

+136
-8
lines changed

2 files changed

+136
-8
lines changed
 

‎authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,8 @@ internal class AuthenticatorViewModel(
424424

425425
//endregion
426426
//region Password Reset
427-
428-
private suspend fun resetPassword(username: String) {
427+
@VisibleForTesting
428+
suspend fun resetPassword(username: String) {
429429
viewModelScope.launch {
430430
logger.debug("Initiating reset password")
431431
when (val result = authProvider.resetPassword(username)) {
@@ -435,12 +435,13 @@ internal class AuthenticatorViewModel(
435435
}.join()
436436
}
437437

438-
private suspend fun confirmResetPassword(username: String, password: String, code: String) {
438+
@VisibleForTesting
439+
suspend fun confirmResetPassword(username: String, password: String, code: String) {
439440
viewModelScope.launch {
440441
logger.debug("Confirming password reset")
441442
when (val result = authProvider.confirmResetPassword(username, password, code)) {
442443
is AmplifyResult.Error -> handleResetPasswordError(result.error)
443-
is AmplifyResult.Success -> handlePasswordResetComplete()
444+
is AmplifyResult.Success -> handlePasswordResetComplete(username, password)
444445
}
445446
}.join()
446447
}
@@ -467,12 +468,17 @@ internal class AuthenticatorViewModel(
467468
}
468469
}
469470

470-
private suspend fun handlePasswordResetComplete() {
471+
private suspend fun handlePasswordResetComplete(username: String? = null, password: String? = null) {
471472
logger.debug("Password reset complete")
472473
sendMessage(PasswordResetMessage)
473-
moveTo(
474-
stateFactory.newSignInState(this::signIn)
475-
)
474+
if (username != null && password != null) {
475+
when (val result = authProvider.signIn(username, password)) {
476+
is AmplifyResult.Error -> moveTo(stateFactory.newSignInState(this::signIn))
477+
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
478+
}
479+
} else {
480+
moveTo(stateFactory.newSignInState(this::signIn))
481+
}
476482
}
477483

478484
private suspend fun handleResetPasswordError(error: AuthException) = handleAuthException(error)

‎authenticator/src/test/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModelTest.kt

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import com.amplifyframework.auth.AuthUserAttributeKey.email
2121
import com.amplifyframework.auth.AuthUserAttributeKey.emailVerified
2222
import com.amplifyframework.auth.MFAType
2323
import com.amplifyframework.auth.exceptions.UnknownException
24+
import com.amplifyframework.auth.result.AuthResetPasswordResult
25+
import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep
26+
import com.amplifyframework.auth.result.step.AuthResetPasswordStep
2427
import com.amplifyframework.auth.result.step.AuthSignInStep
2528
import com.amplifyframework.ui.authenticator.auth.VerificationMechanism
2629
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
@@ -353,6 +356,125 @@ class AuthenticatorViewModelTest {
353356
}
354357

355358
//endregion
359+
//region password reset tests
360+
361+
@Test
362+
fun `Sign in with temporary password requires password reset`() = runTest {
363+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
364+
coEvery { authProvider.signIn(any(), any()) } returns Success(
365+
mockSignInResult(
366+
signInStep = AuthSignInStep.RESET_PASSWORD
367+
)
368+
)
369+
370+
viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.SignIn))
371+
372+
viewModel.signIn("username", "password")
373+
viewModel.currentStep shouldBe AuthenticatorStep.PasswordReset
374+
}
375+
376+
@Test
377+
fun `Password reset returns a result of DONE, state should be sign in`() = runTest {
378+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
379+
coEvery { authProvider.resetPassword(any()) } returns Success(
380+
AuthResetPasswordResult(
381+
true,
382+
AuthNextResetPasswordStep(AuthResetPasswordStep.DONE, emptyMap(), null)
383+
)
384+
)
385+
viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.PasswordReset))
386+
387+
viewModel.resetPassword("username")
388+
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
389+
}
390+
391+
@Test
392+
fun `Password reset fails with an error, state should stay in PasswordReset`() = runTest {
393+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
394+
coEvery { authProvider.resetPassword(any()) } returns Error(
395+
mockk<UnknownException> {
396+
every { cause } returns mockk<HttpException> {
397+
every { cause } returns mockk<UnknownHostException>()
398+
}
399+
}
400+
)
401+
viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.PasswordReset))
402+
403+
viewModel.resetPassword("username")
404+
viewModel.currentStep shouldBe AuthenticatorStep.PasswordReset
405+
}
406+
407+
@Test
408+
fun `Password reset confirmation succeeds, sign in succeeds, state should be signed in`() = runTest {
409+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
410+
coEvery { authProvider.resetPassword(any()) } returns Success(
411+
AuthResetPasswordResult(
412+
true,
413+
AuthNextResetPasswordStep(AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE, emptyMap(), null)
414+
)
415+
)
416+
417+
coEvery { authProvider.confirmResetPassword(any(), any(), any()) } returns Success(Unit)
418+
coEvery { authProvider.signIn(any(), any()) } returns Success(mockSignInResult())
419+
420+
viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.PasswordReset))
421+
422+
viewModel.resetPassword("username")
423+
viewModel.confirmResetPassword("username", "password", "code")
424+
viewModel.currentStep shouldBe AuthenticatorStep.SignedIn
425+
}
426+
427+
@Test
428+
fun `Password reset confirmation fails, state should stay in PasswordResetConfirm`() = runTest {
429+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
430+
coEvery { authProvider.resetPassword(any()) } returns Success(
431+
AuthResetPasswordResult(
432+
true,
433+
AuthNextResetPasswordStep(AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE, emptyMap(), null)
434+
)
435+
)
436+
437+
coEvery { authProvider.confirmResetPassword(any(), any(), any()) } returns Error(
438+
mockk<UnknownException> {
439+
every { cause } returns mockk<HttpException> {
440+
every { cause } returns mockk<UnknownHostException>()
441+
}
442+
}
443+
)
444+
445+
viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.PasswordReset))
446+
447+
viewModel.resetPassword("username")
448+
viewModel.confirmResetPassword("username", "password", "code")
449+
viewModel.currentStep shouldBe AuthenticatorStep.PasswordResetConfirm
450+
}
451+
452+
@Test
453+
fun `Password reset confirmation succeeds, sign in fails, state should be sign in`() = runTest {
454+
coEvery { authProvider.fetchAuthSession() } returns Success(mockAuthSession(isSignedIn = false))
455+
coEvery { authProvider.resetPassword(any()) } returns Success(
456+
AuthResetPasswordResult(
457+
true,
458+
AuthNextResetPasswordStep(AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE, emptyMap(), null)
459+
)
460+
)
461+
462+
coEvery { authProvider.confirmResetPassword(any(), any(), any()) } returns Success(Unit)
463+
coEvery { authProvider.signIn(any(), any()) } returns Error(
464+
mockk<UnknownException> {
465+
every { cause } returns mockk<HttpException> {
466+
every { cause } returns mockk<UnknownHostException>()
467+
}
468+
}
469+
)
470+
471+
viewModel.start(mockAuthenticatorConfiguration(initialStep = AuthenticatorStep.PasswordReset))
472+
473+
viewModel.resetPassword("username")
474+
viewModel.confirmResetPassword("username", "password", "code")
475+
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
476+
}
477+
//endregion
356478
//region helpers
357479
private val AuthenticatorViewModel.currentStep: AuthenticatorStep
358480
get() = stepState.value.step

0 commit comments

Comments
 (0)
Please sign in to comment.