Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -459,7 +459,7 @@ class AWSCognitoAuthPlugin : AuthPlugin<AWSCognitoAuthService>() {
) = enqueue(onSuccess, onError) { useCaseFactory.listWebAuthnCredentials().execute(options) }

override fun autoSignIn(onSuccess: Consumer<AuthSignInResult>, onError: Consumer<AuthException>) =
enqueue(onSuccess, onError) { queueFacade.autoSignIn() }
enqueue(onSuccess, onError) { useCaseFactory.autoSignIn().execute() }

override fun deleteWebAuthnCredential(credentialId: String, onSuccess: Action, onError: Consumer<AuthException>) =
deleteWebAuthnCredential(credentialId, AuthDeleteWebAuthnCredentialOptions.defaults(), onSuccess, onError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,4 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth
{ continuation.resumeWithException(it) }
)
}

suspend fun autoSignIn(): AuthSignInResult = suspendCoroutine { continuation ->
delegate.autoSignIn(
{ continuation.resume(it) },
{ continuation.resumeWithException(it) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ import com.amplifyframework.statemachine.codegen.data.HostedUIErrorData
import com.amplifyframework.statemachine.codegen.data.SignInData
import com.amplifyframework.statemachine.codegen.data.SignInMethod
import com.amplifyframework.statemachine.codegen.data.SignOutData
import com.amplifyframework.statemachine.codegen.data.SignUpData
import com.amplifyframework.statemachine.codegen.data.WebAuthnSignInContext
import com.amplifyframework.statemachine.codegen.data.challengeNameType
import com.amplifyframework.statemachine.codegen.errors.SessionError
Expand All @@ -110,7 +109,6 @@ import com.amplifyframework.statemachine.codegen.states.SetupTOTPState
import com.amplifyframework.statemachine.codegen.states.SignInChallengeState
import com.amplifyframework.statemachine.codegen.states.SignInState
import com.amplifyframework.statemachine.codegen.states.SignOutState
import com.amplifyframework.statemachine.codegen.states.SignUpState
import com.amplifyframework.statemachine.codegen.states.WebAuthnSignInState
import java.lang.ref.WeakReference
import java.util.concurrent.CountDownLatch
Expand Down Expand Up @@ -174,107 +172,6 @@ internal class RealAWSCognitoAuthPlugin(
authStateMachine.state.takeWhile { it !is AuthState.Configured && it !is AuthState.Error }.collect()
}

fun autoSignIn(onSuccess: Consumer<AuthSignInResult>, onError: Consumer<AuthException>) {
authStateMachine.getCurrentState { authState ->
when (authState.authNState) {
is AuthenticationState.NotConfigured -> onError.accept(
InvalidUserPoolConfigurationException()
)
is AuthenticationState.SignedIn -> {
onError.accept(InvalidStateException())
}
is AuthenticationState.SignedOut -> GlobalScope.launch {
when (val signUpState = authState.authSignUpState) {
is SignUpState.SignedUp -> {
_autoSignIn(signUpState.signUpData, onSuccess, onError)
}
else -> onError.accept(InvalidStateException())
}
}
is AuthenticationState.SigningIn -> {
val token = StateChangeListenerToken()
authStateMachine.listen(
token,
{ authState ->
when (authState.authNState) {
is AuthenticationState.SignedOut -> {
authStateMachine.cancel(token)
when (val signUpState = authState.authSignUpState) {
is SignUpState.SignedUp -> GlobalScope.launch {
_autoSignIn(signUpState.signUpData, onSuccess, onError)
}
else -> onError.accept(InvalidStateException())
}
}
else -> Unit
}
},
{
authStateMachine.send(AuthenticationEvent(AuthenticationEvent.EventType.CancelSignIn()))
}
)
}
else -> onError.accept(InvalidStateException())
}
}
}

private suspend fun _autoSignIn(
signUpData: SignUpData,
onSuccess: Consumer<AuthSignInResult>,
onError: Consumer<AuthException>
) {
val token = StateChangeListenerToken()
authStateMachine.listen(
token,
{ authState ->
val authNState = authState.authNState
val authZState = authState.authZState
when {
authNState is AuthenticationState.SigningIn -> {
val signInState = authNState.signInState
when {
signInState is SignInState.Error -> {
authStateMachine.cancel(token)
onError.accept(
CognitoAuthExceptionConverter.lookup(signInState.exception, "Sign in failed.")
)
}
}
}
authNState is AuthenticationState.SignedIn &&
authZState is AuthorizationState.SessionEstablished -> {
authStateMachine.cancel(token)
val authSignInResult = AuthSignInResult(
true,
AuthNextSignInStep(
AuthSignInStep.DONE,
mapOf(),
null,
null,
null,
null
)
)
onSuccess.accept(authSignInResult)
sendHubEvent(AuthChannelEventName.SIGNED_IN.toString())
}
else -> Unit
}
},
{
val signInData = SignInData.AutoSignInData(
signUpData.username,
signUpData.session,
signUpData.clientMetadata ?: mapOf(),
signUpData.userId
)
val event = AuthenticationEvent(AuthenticationEvent.EventType.SignInRequested(signInData))
authStateMachine.send(event)
}
)
}

fun signIn(
username: String?,
password: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import com.amplifyframework.auth.cognito.AuthStateMachine
import com.amplifyframework.auth.cognito.RealAWSCognitoAuthPlugin
import com.amplifyframework.auth.cognito.helpers.WebAuthnHelper
import com.amplifyframework.auth.cognito.requireIdentityProviderClient
import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter

internal class AuthUseCaseFactory(
private val plugin: RealAWSCognitoAuthPlugin,
private val authEnvironment: AuthEnvironment,
private val stateMachine: AuthStateMachine
private val stateMachine: AuthStateMachine,
private val hubEmitter: AuthHubEventEmitter = AuthHubEventEmitter()
) {

fun fetchAuthSession() = FetchAuthSessionUseCase(plugin)
Expand Down Expand Up @@ -140,6 +142,11 @@ internal class AuthUseCaseFactory(
stateMachine = stateMachine
)

fun autoSignIn() = AutoSignInUseCase(
stateMachine = stateMachine,
hubEmitter = hubEmitter
)

fun fetchMfaPreference() = FetchMfaPreferenceUseCase(
client = authEnvironment.requireIdentityProviderClient(),
fetchAuthSession = fetchAuthSession(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amplifyframework.auth.cognito.usecases

import com.amplifyframework.auth.AuthChannelEventName
import com.amplifyframework.auth.cognito.AuthStateMachine
import com.amplifyframework.auth.cognito.CognitoAuthExceptionConverter
import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException
import com.amplifyframework.auth.exceptions.InvalidStateException
import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter
import com.amplifyframework.auth.result.AuthSignInResult
import com.amplifyframework.auth.result.step.AuthNextSignInStep
import com.amplifyframework.auth.result.step.AuthSignInStep
import com.amplifyframework.statemachine.codegen.data.SignInData
import com.amplifyframework.statemachine.codegen.data.SignUpData
import com.amplifyframework.statemachine.codegen.events.AuthenticationEvent
import com.amplifyframework.statemachine.codegen.states.AuthState
import com.amplifyframework.statemachine.codegen.states.AuthenticationState
import com.amplifyframework.statemachine.codegen.states.AuthorizationState
import com.amplifyframework.statemachine.codegen.states.SignInState
import com.amplifyframework.statemachine.codegen.states.SignUpState
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.transformWhile

internal class AutoSignInUseCase(
private val stateMachine: AuthStateMachine,
private val hubEmitter: AuthHubEventEmitter
) {
suspend fun execute(): AuthSignInResult {
val authState = waitForSignedOutState()
val signUpData = getSignUpData(authState)
val result = completeAutoSignIn(signUpData)
return result
}

private suspend fun waitForSignedOutState(): AuthState {
val authState = stateMachine.state.transformWhile { authState ->
when (val authNState = authState.authNState) {
is AuthenticationState.NotConfigured -> throw InvalidUserPoolConfigurationException()
is AuthenticationState.SignedOut -> {
emit(authState)
false
}
is AuthenticationState.SigningOut -> true
is AuthenticationState.SigningIn -> {
// Cancel the sign in
stateMachine.send(AuthenticationEvent(AuthenticationEvent.EventType.CancelSignIn()))
true
}
is AuthenticationState.Error -> {
throw CognitoAuthExceptionConverter.lookup(authNState.exception, "Sign in failed.")
}
else -> throw InvalidStateException()
}
}.first()
return authState
}

private fun getSignUpData(authState: AuthState): SignUpData = when (val signUpState = authState.authSignUpState) {
is SignUpState.SignedUp -> signUpState.signUpData
else -> throw InvalidStateException()
}

private suspend fun completeAutoSignIn(signUpData: SignUpData): AuthSignInResult {
val signInData = SignInData.AutoSignInData(
signUpData.username,
signUpData.session,
signUpData.clientMetadata ?: mapOf(),
signUpData.userId
)

val result = stateMachine.stateTransitions
.onStart {
val event = AuthenticationEvent(AuthenticationEvent.EventType.SignInRequested(signInData))
stateMachine.send(event)
}
.transformWhile { authState ->
val authNState = authState.authNState
val authZState = authState.authZState
when {
authNState is AuthenticationState.SigningIn -> {
val signInState = authNState.signInState
if (signInState is SignInState.Error) {
throw CognitoAuthExceptionConverter.lookup(signInState.exception, "Sign in failed.")
}
true
}
authNState is AuthenticationState.SignedIn &&
authZState is AuthorizationState.SessionEstablished -> {
// There are never any next steps for autoSignIn - if it succeeds then the user is fully
// signed in
val authSignInResult = AuthSignInResult(
true,
AuthNextSignInStep(
AuthSignInStep.DONE,
mapOf(),
null,
null,
null,
null
)
)
emit(authSignInResult)
hubEmitter.sendHubEvent(AuthChannelEventName.SIGNED_IN.toString())
false
}
else -> true
}
}.first()
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,15 @@ class AWSCognitoAuthPluginTest {
}
}

@Test
fun `auto sign in`() {
val useCase = authPlugin.useCaseFactory.autoSignIn()
authPlugin.autoSignIn({}, {})
coVerify(timeout = CHANNEL_TIMEOUT) {
useCase.execute()
}
}

@Test
fun verifyPluginKey() {
assertEquals("awsCognitoAuthPlugin", authPlugin.pluginKey)
Expand Down
Loading