Skip to content

Commit 0786720

Browse files
authored
fix(authenticator): Add support for auto-sign-in (#216)
1 parent ef1ec82 commit 0786720

File tree

5 files changed

+120
-106
lines changed

5 files changed

+120
-106
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[*.{kt,kts}]
22
#this is to match java checkstyle
33
max_line_length=120
4+
ktlint_code_style=android_studio

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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,8 @@ import kotlinx.coroutines.launch
9090
import kotlinx.coroutines.withContext
9191
import org.jetbrains.annotations.VisibleForTesting
9292

93-
internal class AuthenticatorViewModel(
94-
application: Application,
95-
private val authProvider: AuthProvider
96-
) : AndroidViewModel(application) {
93+
internal class AuthenticatorViewModel(application: Application, private val authProvider: AuthProvider) :
94+
AndroidViewModel(application) {
9795

9896
// Constructor for compose viewModels provider
9997
constructor(application: Application) : this(application, RealAuthProvider())
@@ -185,7 +183,8 @@ internal class AuthenticatorViewModel(
185183

186184
//region SignUp
187185

188-
private suspend fun signUp(username: String, password: String, attributes: List<AuthUserAttribute>) {
186+
@VisibleForTesting
187+
suspend fun signUp(username: String, password: String, attributes: List<AuthUserAttribute>) {
189188
viewModelScope.launch {
190189
val options = AuthSignUpOptions.builder().userAttributes(attributes).build()
191190

@@ -229,6 +228,7 @@ internal class AuthenticatorViewModel(
229228
moveTo(newState)
230229
}
231230
AuthSignUpStep.DONE -> handleSignedUp(username, password)
231+
AuthSignUpStep.COMPLETE_AUTO_SIGN_IN -> handleAutoSignIn(username, password)
232232
else -> {
233233
// Generic error for any other next steps that may be added in the future
234234
val exception = AuthException(
@@ -241,6 +241,18 @@ internal class AuthenticatorViewModel(
241241
}
242242
}
243243

244+
private suspend fun handleAutoSignIn(username: String, password: String) {
245+
when (val result = authProvider.autoSignIn()) {
246+
is AmplifyResult.Error -> {
247+
// If auto sign in fails then proceed with manually trying to sign in the user. If this also fails the
248+
// user will end up back on the sign in screen.
249+
logger.warn("Unable to complete auto-signIn")
250+
handleSignedUp(username, password)
251+
}
252+
is AmplifyResult.Success -> handleSignInSuccess(username, password, result.data)
253+
}
254+
}
255+
244256
private suspend fun handleSignedUp(username: String, password: String) {
245257
when (val result = authProvider.signIn(username, password)) {
246258
is AmplifyResult.Error -> {
@@ -355,22 +367,15 @@ internal class AuthenticatorViewModel(
355367
)
356368
}
357369

358-
private suspend fun handleEmailMfaSetupRequired(
359-
username: String,
360-
password: String
361-
) {
370+
private suspend fun handleEmailMfaSetupRequired(username: String, password: String) {
362371
moveTo(
363372
stateFactory.newSignInContinueWithEmailSetupState(
364373
onSubmit = { mfaType -> confirmSignIn(username, password, mfaType) }
365374
)
366375
)
367376
}
368377

369-
private suspend fun handleMfaSelectionRequired(
370-
username: String,
371-
password: String,
372-
allowedMfaTypes: Set<MFAType>?
373-
) {
378+
private suspend fun handleMfaSelectionRequired(username: String, password: String, allowedMfaTypes: Set<MFAType>?) {
374379
if (allowedMfaTypes.isNullOrEmpty()) {
375380
handleGeneralFailure(AuthException("Missing allowedMfaTypes", "Please open a bug with Amplify"))
376381
return
@@ -492,10 +497,7 @@ internal class AuthenticatorViewModel(
492497
}.join()
493498
}
494499

495-
private suspend fun handleResetPasswordSuccess(
496-
username: String,
497-
result: AuthResetPasswordResult
498-
) {
500+
private suspend fun handleResetPasswordSuccess(username: String, result: AuthResetPasswordResult) {
499501
when (result.nextStep.resetPasswordStep) {
500502
AuthResetPasswordStep.DONE -> handlePasswordResetComplete()
501503
AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE -> {

authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/AuthProvider.kt

Lines changed: 60 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -52,33 +52,19 @@ import kotlinx.coroutines.flow.callbackFlow
5252
* An abstraction of the Amplify.Auth API that allows us to use coroutines with no exceptions
5353
*/
5454
internal interface AuthProvider {
55-
suspend fun signIn(
56-
username: String,
57-
password: String
58-
): AmplifyResult<AuthSignInResult>
55+
suspend fun signIn(username: String, password: String): AmplifyResult<AuthSignInResult>
5956

60-
suspend fun confirmSignIn(
61-
challengeResponse: String
62-
): AmplifyResult<AuthSignInResult>
57+
suspend fun confirmSignIn(challengeResponse: String): AmplifyResult<AuthSignInResult>
6358

64-
suspend fun signUp(
65-
username: String,
66-
password: String,
67-
options: AuthSignUpOptions
68-
): AmplifyResult<AuthSignUpResult>
59+
suspend fun signUp(username: String, password: String, options: AuthSignUpOptions): AmplifyResult<AuthSignUpResult>
6960

70-
suspend fun confirmSignUp(
71-
username: String,
72-
code: String
73-
): AmplifyResult<AuthSignUpResult>
61+
suspend fun confirmSignUp(username: String, code: String): AmplifyResult<AuthSignUpResult>
7462

75-
suspend fun resendSignUpCode(
76-
username: String
77-
): AmplifyResult<AuthCodeDeliveryDetails>
63+
suspend fun resendSignUpCode(username: String): AmplifyResult<AuthCodeDeliveryDetails>
7864

79-
suspend fun resetPassword(
80-
username: String
81-
): AmplifyResult<AuthResetPasswordResult>
65+
suspend fun autoSignIn(): AmplifyResult<AuthSignInResult>
66+
67+
suspend fun resetPassword(username: String): AmplifyResult<AuthResetPasswordResult>
8268

8369
suspend fun confirmResetPassword(
8470
username: String,
@@ -92,10 +78,7 @@ internal interface AuthProvider {
9278

9379
suspend fun fetchUserAttributes(): AmplifyResult<List<AuthUserAttribute>>
9480

95-
suspend fun confirmUserAttribute(
96-
key: AuthUserAttributeKey,
97-
confirmationCode: String
98-
): AmplifyResult<Unit>
81+
suspend fun confirmUserAttribute(key: AuthUserAttributeKey, confirmationCode: String): AmplifyResult<Unit>
9982

10083
suspend fun resendUserAttributeConfirmationCode(key: AuthUserAttributeKey): AmplifyResult<AuthCodeDeliveryDetails>
10184

@@ -108,15 +91,16 @@ internal interface AuthProvider {
10891

10992
internal sealed interface AuthConfigurationResult {
11093
data class Valid(val configuration: AmplifyAuthConfiguration) : AuthConfigurationResult
94+
11195
data class Invalid(val message: String, val cause: Exception? = null) : AuthConfigurationResult
96+
11297
object Missing : AuthConfigurationResult
11398
}
11499

115100
/**
116101
* The [AuthProvider] implementation that calls through to [Amplify.Auth]
117102
*/
118103
internal class RealAuthProvider : AuthProvider {
119-
120104
init {
121105
val cognitoPlugin = getCognitoPlugin()
122106
cognitoPlugin?.addToUserAgent(AWSCognitoAuthMetadataType.Authenticator, BuildConfig.VERSION_NAME)
@@ -139,24 +123,18 @@ internal class RealAuthProvider : AuthProvider {
139123
)
140124
}
141125

142-
override suspend fun signUp(
143-
username: String,
144-
password: String,
145-
options: AuthSignUpOptions
146-
) = suspendCoroutine { continuation ->
147-
Amplify.Auth.signUp(
148-
username,
149-
password,
150-
options,
151-
{ continuation.resume(AmplifyResult.Success(it)) },
152-
{ continuation.resume(AmplifyResult.Error(it)) }
153-
)
154-
}
126+
override suspend fun signUp(username: String, password: String, options: AuthSignUpOptions) =
127+
suspendCoroutine { continuation ->
128+
Amplify.Auth.signUp(
129+
username,
130+
password,
131+
options,
132+
{ continuation.resume(AmplifyResult.Success(it)) },
133+
{ continuation.resume(AmplifyResult.Error(it)) }
134+
)
135+
}
155136

156-
override suspend fun confirmSignUp(
157-
username: String,
158-
code: String
159-
) = suspendCoroutine { continuation ->
137+
override suspend fun confirmSignUp(username: String, code: String) = suspendCoroutine { continuation ->
160138
Amplify.Auth.confirmSignUp(
161139
username,
162140
code,
@@ -165,40 +143,40 @@ internal class RealAuthProvider : AuthProvider {
165143
)
166144
}
167145

168-
override suspend fun resendSignUpCode(
169-
username: String
170-
) = suspendCoroutine { continuation ->
146+
override suspend fun resendSignUpCode(username: String) = suspendCoroutine { continuation ->
171147
Amplify.Auth.resendSignUpCode(
172148
username,
173149
{ continuation.resume(AmplifyResult.Success(it)) },
174150
{ continuation.resume(AmplifyResult.Error(it)) }
175151
)
176152
}
177153

178-
override suspend fun resetPassword(
179-
username: String
180-
) = suspendCoroutine { continuation ->
181-
Amplify.Auth.resetPassword(
182-
username,
154+
override suspend fun autoSignIn() = suspendCoroutine { continuation ->
155+
Amplify.Auth.autoSignIn(
183156
{ continuation.resume(AmplifyResult.Success(it)) },
184157
{ continuation.resume(AmplifyResult.Error(it)) }
185158
)
186159
}
187160

188-
override suspend fun confirmResetPassword(
189-
username: String,
190-
newPassword: String,
191-
confirmationCode: String
192-
) = suspendCoroutine { continuation ->
193-
Amplify.Auth.confirmResetPassword(
161+
override suspend fun resetPassword(username: String) = suspendCoroutine { continuation ->
162+
Amplify.Auth.resetPassword(
194163
username,
195-
newPassword,
196-
confirmationCode,
197-
{ continuation.resume(AmplifyResult.Success(Unit)) },
164+
{ continuation.resume(AmplifyResult.Success(it)) },
198165
{ continuation.resume(AmplifyResult.Error(it)) }
199166
)
200167
}
201168

169+
override suspend fun confirmResetPassword(username: String, newPassword: String, confirmationCode: String) =
170+
suspendCoroutine { continuation ->
171+
Amplify.Auth.confirmResetPassword(
172+
username,
173+
newPassword,
174+
confirmationCode,
175+
{ continuation.resume(AmplifyResult.Success(Unit)) },
176+
{ continuation.resume(AmplifyResult.Error(it)) }
177+
)
178+
}
179+
202180
override suspend fun signOut() = suspendCoroutine { continuation ->
203181
Amplify.Auth.signOut { continuation.resume(it) }
204182
}
@@ -217,27 +195,24 @@ internal class RealAuthProvider : AuthProvider {
217195
)
218196
}
219197

220-
override suspend fun confirmUserAttribute(
221-
key: AuthUserAttributeKey,
222-
confirmationCode: String
223-
) = suspendCoroutine { continuation ->
224-
Amplify.Auth.confirmUserAttribute(
225-
key,
226-
confirmationCode,
227-
{ continuation.resume(AmplifyResult.Success(Unit)) },
228-
{ continuation.resume(AmplifyResult.Error(it)) }
229-
)
230-
}
198+
override suspend fun confirmUserAttribute(key: AuthUserAttributeKey, confirmationCode: String) =
199+
suspendCoroutine { continuation ->
200+
Amplify.Auth.confirmUserAttribute(
201+
key,
202+
confirmationCode,
203+
{ continuation.resume(AmplifyResult.Success(Unit)) },
204+
{ continuation.resume(AmplifyResult.Error(it)) }
205+
)
206+
}
231207

232-
override suspend fun resendUserAttributeConfirmationCode(
233-
key: AuthUserAttributeKey
234-
) = suspendCoroutine { continuation ->
235-
Amplify.Auth.resendUserAttributeConfirmationCode(
236-
key,
237-
{ continuation.resume(AmplifyResult.Success(it)) },
238-
{ continuation.resume(AmplifyResult.Error(it)) }
239-
)
240-
}
208+
override suspend fun resendUserAttributeConfirmationCode(key: AuthUserAttributeKey) =
209+
suspendCoroutine { continuation ->
210+
Amplify.Auth.resendUserAttributeConfirmationCode(
211+
key,
212+
{ continuation.resume(AmplifyResult.Success(it)) },
213+
{ continuation.resume(AmplifyResult.Error(it)) }
214+
)
215+
}
241216

242217
override suspend fun getCurrentUser() = suspendCoroutine { continuation ->
243218
Amplify.Auth.getCurrentUser(
@@ -282,13 +257,10 @@ internal class RealAuthProvider : AuthProvider {
282257
return AuthConfigurationResult.Valid(amplifyAuthConfiguration)
283258
}
284259

285-
private fun getCognitoPlugin(): AWSCognitoAuthPlugin? {
286-
return try {
287-
Amplify.Auth.getPlugin("awsCognitoAuthPlugin")
288-
as AWSCognitoAuthPlugin
289-
} catch (e: Throwable) {
290-
null
291-
}
260+
private fun getCognitoPlugin(): AWSCognitoAuthPlugin? = try {
261+
Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as AWSCognitoAuthPlugin
262+
} catch (e: Throwable) {
263+
null
292264
}
293265

294266
private fun getSignInMethod(attributes: List<UsernameAttribute>) = when {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.amplifyframework.auth.result.AuthResetPasswordResult
2727
import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep
2828
import com.amplifyframework.auth.result.step.AuthResetPasswordStep
2929
import com.amplifyframework.auth.result.step.AuthSignInStep
30+
import com.amplifyframework.auth.result.step.AuthSignUpStep
3031
import com.amplifyframework.ui.authenticator.auth.VerificationMechanism
3132
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
3233
import com.amplifyframework.ui.authenticator.util.AmplifyResult
@@ -440,6 +441,22 @@ class AuthenticatorViewModelTest {
440441
viewModel.currentStep shouldBe AuthenticatorStep.SignIn
441442
}
442443

444+
//endregion
445+
//region sign up tests
446+
447+
@Test
448+
fun `user can autoSignIn after sign up`() = runTest {
449+
val result = mockSignUpResult(nextStep = mockNextSignUpStep(signUpStep = AuthSignUpStep.COMPLETE_AUTO_SIGN_IN))
450+
coEvery { authProvider.signUp("username", "password", any()) } returns Success(result)
451+
coEvery { authProvider.autoSignIn() } returns Success(mockSignInResult())
452+
453+
viewModel.start(mockAuthenticatorConfiguration())
454+
viewModel.signUp("username", "password", emptyList())
455+
advanceUntilIdle()
456+
457+
viewModel.currentStep shouldBe AuthenticatorStep.SignedIn
458+
}
459+
443460
//endregion
444461
//region password reset tests
445462

authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import com.amplifyframework.auth.AuthUserAttributeKey
2525
import com.amplifyframework.auth.MFAType
2626
import com.amplifyframework.auth.TOTPSetupDetails
2727
import com.amplifyframework.auth.result.AuthSignInResult
28+
import com.amplifyframework.auth.result.AuthSignUpResult
2829
import com.amplifyframework.auth.result.step.AuthNextSignInStep
30+
import com.amplifyframework.auth.result.step.AuthNextSignUpStep
2931
import com.amplifyframework.auth.result.step.AuthSignInStep
32+
import com.amplifyframework.auth.result.step.AuthSignUpStep
3033
import com.amplifyframework.ui.authenticator.auth.AmplifyAuthConfiguration
3134
import com.amplifyframework.ui.authenticator.auth.PasswordCriteria
3235
import com.amplifyframework.ui.authenticator.auth.SignInMethod
@@ -112,6 +115,25 @@ internal fun mockNextSignInStep(
112115
availableFactors
113116
)
114117

118+
internal fun mockSignUpResult(
119+
nextStep: AuthNextSignUpStep,
120+
userId: String = "userId"
121+
) = AuthSignUpResult(
122+
nextStep.signUpStep != AuthSignUpStep.CONFIRM_SIGN_UP_STEP,
123+
nextStep,
124+
userId
125+
)
126+
127+
internal fun mockNextSignUpStep(
128+
signUpStep: AuthSignUpStep = AuthSignUpStep.DONE,
129+
additionalInfo: Map<String, String> = emptyMap(),
130+
codeDeliveryDetails: AuthCodeDeliveryDetails? = null
131+
) = AuthNextSignUpStep(
132+
signUpStep,
133+
additionalInfo,
134+
codeDeliveryDetails
135+
)
136+
115137
internal fun mockUserAttributes(vararg attribute: Pair<AuthUserAttributeKey, String>) =
116138
attribute.map { AuthUserAttribute(it.first, it.second) }
117139

0 commit comments

Comments
 (0)