Skip to content

chore(auth): Move SignOut to usecase #3025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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 @@ -404,12 +404,12 @@ class AWSCognitoAuthPlugin : AuthPlugin<AWSCognitoAuthService>() {
override fun signOut(onComplete: Consumer<AuthSignOutResult>) = enqueue(
onComplete,
onError = ::throwIt
) { queueFacade.signOut() }
) { useCaseFactory.signOut().execute() }

override fun signOut(options: AuthSignOutOptions, onComplete: Consumer<AuthSignOutResult>) = enqueue(
onComplete,
onError = ::throwIt
) { queueFacade.signOut(options) }
) { useCaseFactory.signOut().execute(options) }

override fun deleteUser(onSuccess: Action, onError: Consumer<AuthException>) = enqueue(onSuccess, onError) {
useCaseFactory.deleteUser().execute()
Expand Down Expand Up @@ -523,7 +523,7 @@ class AWSCognitoAuthPlugin : AuthPlugin<AWSCognitoAuthService>() {
* @param onError Error callback
*/
fun clearFederationToIdentityPool(onSuccess: Action, onError: Consumer<AuthException>) =
enqueue(onSuccess, onError) { queueFacade.clearFederationToIdentityPool() }
enqueue(onSuccess, onError) { useCaseFactory.clearFederationToIdentityPool().execute() }

fun fetchMFAPreference(onSuccess: Consumer<UserMFAPreference>, onError: Consumer<AuthException>) =
enqueue(onSuccess, onError) { useCaseFactory.fetchMfaPreference().execute() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions
import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult
import com.amplifyframework.auth.options.AuthConfirmSignInOptions
import com.amplifyframework.auth.options.AuthSignInOptions
import com.amplifyframework.auth.options.AuthSignOutOptions
import com.amplifyframework.auth.options.AuthWebUISignInOptions
import com.amplifyframework.auth.result.AuthSignInResult
import com.amplifyframework.auth.result.AuthSignOutResult
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
Expand Down Expand Up @@ -116,14 +114,6 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth
delegate.handleWebUISignInResponse(intent)
}

suspend fun signOut(): AuthSignOutResult = suspendCoroutine { continuation ->
delegate.signOut { continuation.resume(it) }
}

suspend fun signOut(options: AuthSignOutOptions): AuthSignOutResult = suspendCoroutine { continuation ->
delegate.signOut(options) { continuation.resume(it) }
}

suspend fun federateToIdentityPool(
providerToken: String,
authProvider: AuthProvider,
Expand All @@ -137,11 +127,4 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth
{ continuation.resumeWithException(it) }
)
}

suspend fun clearFederationToIdentityPool() = suspendCoroutine { continuation ->
delegate.clearFederationToIdentityPool(
{ continuation.resume(Unit) },
{ continuation.resumeWithException(it) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,18 @@ import com.amplifyframework.auth.cognito.helpers.isMfaSetupSelectionChallenge
import com.amplifyframework.auth.cognito.helpers.value
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmSignInOptions
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignInOptions
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignOutOptions
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthWebUISignInOptions
import com.amplifyframework.auth.cognito.options.AuthFlowType
import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions
import com.amplifyframework.auth.cognito.result.AWSCognitoAuthSignOutResult
import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult
import com.amplifyframework.auth.cognito.result.GlobalSignOutError
import com.amplifyframework.auth.cognito.result.HostedUIError
import com.amplifyframework.auth.cognito.result.RevokeTokenError
import com.amplifyframework.auth.exceptions.InvalidStateException
import com.amplifyframework.auth.exceptions.UnknownException
import com.amplifyframework.auth.options.AuthConfirmSignInOptions
import com.amplifyframework.auth.options.AuthSignInOptions
import com.amplifyframework.auth.options.AuthSignOutOptions
import com.amplifyframework.auth.options.AuthWebUISignInOptions
import com.amplifyframework.auth.result.AuthSignInResult
import com.amplifyframework.auth.result.AuthSignOutResult
import com.amplifyframework.auth.result.step.AuthNextSignInStep
import com.amplifyframework.auth.result.step.AuthSignInStep
import com.amplifyframework.core.Action
import com.amplifyframework.core.Amplify
import com.amplifyframework.core.Consumer
import com.amplifyframework.hub.HubChannel
Expand All @@ -78,7 +70,6 @@ import com.amplifyframework.statemachine.codegen.data.FederatedToken
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.WebAuthnSignInContext
import com.amplifyframework.statemachine.codegen.data.challengeNameType
import com.amplifyframework.statemachine.codegen.errors.SessionError
Expand Down Expand Up @@ -980,114 +971,6 @@ internal class RealAWSCognitoAuthPlugin(
}
}

fun signOut(onComplete: Consumer<AuthSignOutResult>) {
signOut(AuthSignOutOptions.builder().build(), onComplete)
}

fun signOut(options: AuthSignOutOptions, onComplete: Consumer<AuthSignOutResult>) {
authStateMachine.getCurrentState { authState ->
when (authState.authNState) {
is AuthenticationState.NotConfigured ->
onComplete.accept(AWSCognitoAuthSignOutResult.CompleteSignOut)
// Continue sign out and clear auth or guest credentials
is AuthenticationState.SignedIn, is AuthenticationState.SignedOut -> {
// Send SignOut event here instead of OnSubscribedCallback handler to ensure we do not fire
// onComplete immediately, which would happen if calling signOut while signed out
val event = AuthenticationEvent(
AuthenticationEvent.EventType.SignOutRequested(
SignOutData(
options.isGlobalSignOut,
(options as? AWSCognitoAuthSignOutOptions)?.browserPackage
)
)
)
authStateMachine.send(event)
_signOut(onComplete = onComplete)
}
is AuthenticationState.FederatedToIdentityPool -> {
onComplete.accept(
AWSCognitoAuthSignOutResult.FailedSignOut(
InvalidStateException(
"The user is currently federated to identity pool. " +
"You must call clearFederationToIdentityPool to clear credentials."
)
)
)
}
else -> onComplete.accept(
AWSCognitoAuthSignOutResult.FailedSignOut(InvalidStateException())
)
}
}
}

private fun _signOut(sendHubEvent: Boolean = true, onComplete: Consumer<AuthSignOutResult>) {
val token = StateChangeListenerToken()
var cancellationException: UserCancelledException? = null
authStateMachine.listen(
token,
{ authState ->
if (authState is AuthState.Configured) {
val (authNState, authZState) = authState
when {
authNState is AuthenticationState.SignedOut && authZState is AuthorizationState.Configured -> {
authStateMachine.cancel(token)
if (authNState.signedOutData.hasError) {
val signedOutData = authNState.signedOutData
onComplete.accept(
AWSCognitoAuthSignOutResult.PartialSignOut(
hostedUIError = signedOutData.hostedUIErrorData?.let { HostedUIError(it) },
globalSignOutError = signedOutData.globalSignOutErrorData?.let {
GlobalSignOutError(it)
},
revokeTokenError = signedOutData.revokeTokenErrorData?.let {
RevokeTokenError(
it
)
}
)
)
if (sendHubEvent) {
sendHubEvent(AuthChannelEventName.SIGNED_OUT.toString())
}
} else {
onComplete.accept(AWSCognitoAuthSignOutResult.CompleteSignOut)
if (sendHubEvent) {
sendHubEvent(AuthChannelEventName.SIGNED_OUT.toString())
}
}
}
authNState is AuthenticationState.Error -> {
authStateMachine.cancel(token)
onComplete.accept(
AWSCognitoAuthSignOutResult.FailedSignOut(
CognitoAuthExceptionConverter.lookup(authNState.exception, "Sign out failed.")
)
)
}
authNState is AuthenticationState.SigningOut -> {
val state = authNState.signOutState
if (state is SignOutState.Error && state.exception is UserCancelledException) {
cancellationException = state.exception
}
}
authNState is AuthenticationState.SignedIn && cancellationException != null -> {
authStateMachine.cancel(token)
cancellationException?.let {
onComplete.accept(AWSCognitoAuthSignOutResult.FailedSignOut(it))
}
}
else -> {
// No - op
}
}
}
},
{
}
)
}

private fun addAuthStateChangeListener() {
authStateMachine.listen(
StateChangeListenerToken(),
Expand Down Expand Up @@ -1219,46 +1102,6 @@ internal class RealAWSCognitoAuthPlugin(
)
}

fun clearFederationToIdentityPool(onSuccess: Action, onError: Consumer<AuthException>) {
authStateMachine.getCurrentState { authState ->
val authNState = authState.authNState
val authZState = authState.authZState
when {
authState is AuthState.Configured &&
(
authNState is AuthenticationState.FederatedToIdentityPool &&
authZState is AuthorizationState.SessionEstablished
) ||
(
authZState is AuthorizationState.Error &&
authZState.exception is SessionError &&
authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated
) -> {
val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool())
authStateMachine.send(event)
_clearFederationToIdentityPool(onSuccess, onError)
}
else -> {
onError.accept(InvalidStateException("Clearing of federation failed."))
}
}
}
}

private fun _clearFederationToIdentityPool(onSuccess: Action, onError: Consumer<AuthException>) {
_signOut(sendHubEvent = false) {
when (it) {
is AWSCognitoAuthSignOutResult.FailedSignOut -> {
onError.accept(it.exception)
}
else -> {
onSuccess.call()
sendHubEvent(AWSCognitoAuthChannelEventName.FEDERATION_TO_IDENTITY_POOL_CLEARED.toString())
}
}
}
}

private fun sendHubEvent(eventName: String) {
if (lastPublishedHubEventName.get() != eventName) {
lastPublishedHubEventName.set(eventName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,13 @@ internal class AuthUseCaseFactory(
fetchAuthSession = fetchAuthSession(),
stateMachine = stateMachine
)

fun signOut() = SignOutUseCase(
stateMachine = stateMachine
)

fun clearFederationToIdentityPool() = ClearFederationToIdentityPoolUseCase(
stateMachine = stateMachine,
signOut = signOut()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.cognito.AWSCognitoAuthChannelEventName
import com.amplifyframework.auth.cognito.AuthStateMachine
import com.amplifyframework.auth.cognito.result.AWSCognitoAuthSignOutResult
import com.amplifyframework.auth.exceptions.InvalidStateException
import com.amplifyframework.auth.plugins.core.AuthHubEventEmitter
import com.amplifyframework.statemachine.codegen.data.AmplifyCredential
import com.amplifyframework.statemachine.codegen.errors.SessionError
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

internal class ClearFederationToIdentityPoolUseCase(
private val stateMachine: AuthStateMachine,
private val signOut: SignOutUseCase,
private val emitter: AuthHubEventEmitter = AuthHubEventEmitter()
) {
suspend fun execute() {
val authState = stateMachine.getCurrentState()

when {
authState.isFederatedToIdentityPool() -> {
val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool())
stateMachine.send(event)

when (val result = signOut.completeSignOut(sendHubEvent = false)) {
is AWSCognitoAuthSignOutResult.FailedSignOut -> throw result.exception
else -> emitter.sendHubEvent(
AWSCognitoAuthChannelEventName.FEDERATION_TO_IDENTITY_POOL_CLEARED.toString()
)
}
}
else -> throw InvalidStateException("Clearing of federation failed.")
}
}

private fun AuthState.isFederatedToIdentityPool(): Boolean {
val authNState = this.authNState
val authZState = this.authZState

return this is AuthState.Configured &&
(
authNState is AuthenticationState.FederatedToIdentityPool &&
authZState is AuthorizationState.SessionEstablished
) ||
(
authZState is AuthorizationState.Error &&
authZState.exception is SessionError &&
authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated
)
}
}
Loading