diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift index dddb53bd49..8f95607b86 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift @@ -17,21 +17,49 @@ struct InitiateSignOut: Action { func execute(withDispatcher dispatcher: EventDispatcher, environment: Environment) async { logVerbose("\(#fileID) Starting execution", environment: environment) - + let updatedSignedInData = await getUpdatedSignedInData(environment: environment) let event: SignOutEvent if case .hostedUI(let options) = signedInData.signInMethod, options.preferPrivateSession == false { event = SignOutEvent(eventType: .invokeHostedUISignOut(signOutEventData, - signedInData)) + updatedSignedInData)) } else if signOutEventData.globalSignOut { - event = SignOutEvent(eventType: .signOutGlobally(signedInData)) + event = SignOutEvent(eventType: .signOutGlobally(updatedSignedInData)) } else { - event = SignOutEvent(eventType: .revokeToken(signedInData)) + event = SignOutEvent(eventType: .revokeToken(updatedSignedInData)) } logVerbose("\(#fileID) Sending event \(event.type)", environment: environment) await dispatcher.send(event) } + private func getUpdatedSignedInData( + environment: Environment + ) async -> SignedInData { + let credentialStoreClient = (environment as? AuthEnvironment)?.credentialsClient + do { + let data = try await credentialStoreClient?.fetchData( + type: .amplifyCredentials + ) + guard case .amplifyCredentials(let credentials) = data else { + return signedInData + } + + // Update SignedInData based on credential type + switch credentials { + case .userPoolOnly(let updatedSignedInData): + return updatedSignedInData + case .userPoolAndIdentityPool(let updatedSignedInData, _, _): + return updatedSignedInData + case .identityPoolOnly, .identityPoolWithFederation, .noCredentials: + return signedInData + } + } catch { + let logger = (environment as? LoggerProvider)?.logger + logger?.error("Unable to update credentials with error: \(error)") + return signedInData + } + } + } extension InitiateSignOut: CustomDebugDictionaryConvertible { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift index 9cdf64017b..04ccb81fbe 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift @@ -46,6 +46,14 @@ class ShowHostedUISignOut: NSObject, Action { inPrivate: false, presentationAnchor: signOutEvent.presentationAnchor) await sendEvent(with: nil, dispatcher: dispatcher, environment: environment) + } catch HostedUIError.cancelled { + if signInData.isRefreshTokenExpired == true { + self.logVerbose("\(#fileID) Received user cancelled error, but session is expired and continue signing out.", environment: environment) + await sendEvent(with: nil, dispatcher: dispatcher, environment: environment) + } else { + self.logVerbose("\(#fileID) Received error \(HostedUIError.cancelled)", environment: environment) + await sendEvent(with: HostedUIError.cancelled, dispatcher: dispatcher, environment: environment) + } } catch { self.logVerbose("\(#fileID) Received error \(error)", environment: environment) await sendEvent(with: error, dispatcher: dispatcher, environment: environment) @@ -101,4 +109,3 @@ extension ShowHostedUISignOut { debugDictionary.debugDescription } } -//#endif diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift index e5c0500043..6edeb58aa1 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift @@ -115,10 +115,12 @@ extension AWSCognitoAuthPlugin: AuthCategoryBehavior { let options = options ?? AuthFetchSessionRequest.Options() let request = AuthFetchSessionRequest(options: options) let forceReconfigure = secureStoragePreferences?.accessGroup?.name != nil - let task = AWSAuthFetchSessionTask(request, - authStateMachine: authStateMachine, - configuration: authConfiguration, - forceReconfigure: forceReconfigure) + let task = AWSAuthFetchSessionTask( + request, + authStateMachine: authStateMachine, + configuration: authConfiguration, + environment: authEnvironment, + forceReconfigure: forceReconfigure) return try await taskQueue.sync { return try await task.value } as! AuthSession diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift index 8069b50163..11252fb2b3 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift @@ -7,10 +7,12 @@ import Foundation import Amplify +import AWSPluginsCore class FetchAuthSessionOperationHelper { typealias FetchAuthSessionCompletion = (Result) -> Void + var environment: Environment? = nil func fetch(_ authStateMachine: AuthStateMachine, forceRefresh: Bool = false) async throws -> AuthSession { @@ -98,7 +100,7 @@ class FetchAuthSessionOperationHelper { case .sessionEstablished(let credentials): return credentials.cognitoSession case .error(let authorizationError): - return try sessionResultWithError( + return try await sessionResultWithError( authorizationError, authenticationState: authenticationState) default: continue @@ -111,7 +113,7 @@ class FetchAuthSessionOperationHelper { func sessionResultWithError( _ error: AuthorizationError, authenticationState: AuthenticationState - ) throws -> AuthSession { + ) async throws -> AuthSession { log.verbose("Received fetch auth session error - \(error)") var isSignedIn = false @@ -129,8 +131,10 @@ class FetchAuthSessionOperationHelper { authError = fetchError.authError } case .sessionExpired(let error): + await setRefreshTokenExpiredInSignedInData() let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession( underlyingError: error) + return session default: break @@ -143,6 +147,43 @@ class FetchAuthSessionOperationHelper { cognitoTokensResult: .failure(authError)) return session } + + func setRefreshTokenExpiredInSignedInData() async { + let credentialStoreClient = (environment as? AuthEnvironment)?.credentialsClient + do { + let data = try await credentialStoreClient?.fetchData( + type: .amplifyCredentials + ) + guard case .amplifyCredentials(var credentials) = data else { + return + } + + // Update SignedInData based on credential type + switch credentials { + case .userPoolOnly(var signedInData): + signedInData.isRefreshTokenExpired = true + credentials = .userPoolOnly(signedInData: signedInData) + + case .userPoolAndIdentityPool(var signedInData, let identityId, let awsCredentials): + signedInData.isRefreshTokenExpired = true + credentials = .userPoolAndIdentityPool( + signedInData: signedInData, + identityID: identityId, + credentials: awsCredentials) + + case .identityPoolOnly, .identityPoolWithFederation, .noCredentials: + return + } + + try await credentialStoreClient?.storeData(data: .amplifyCredentials(credentials)) + } catch KeychainStoreError.itemNotFound { + let logger = (environment as? LoggerProvider)?.logger + logger?.info("No existing credentials found.") + } catch { + let logger = (environment as? LoggerProvider)?.logger + logger?.error("Unable to update credentials with error: \(error)") + } + } } extension FetchAuthSessionOperationHelper: DefaultLogger { } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignedInData.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignedInData.swift index da3dc0bf02..ebadc980e3 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignedInData.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignedInData.swift @@ -14,6 +14,7 @@ struct SignedInData { let signInMethod: SignInMethod let deviceMetadata: DeviceMetadata let cognitoUserPoolTokens: AWSCognitoUserPoolTokens + var isRefreshTokenExpired: Bool? init(signedInDate: Date, signInMethod: SignInMethod, @@ -27,6 +28,7 @@ struct SignedInData { self.signInMethod = signInMethod self.deviceMetadata = deviceMetadata self.cognitoUserPoolTokens = cognitoUserPoolTokens + self.isRefreshTokenExpired = false } } @@ -42,7 +44,8 @@ extension SignedInData: CustomDebugDictionaryConvertible { "signedInDate": signedInDate, "signInMethod": signInMethod, "deviceMetadata": deviceMetadata, - "tokens": cognitoUserPoolTokens + "tokens": cognitoUserPoolTokens, + "refreshTokenExpired": isRefreshTokenExpired ?? false ] } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift index 808363e6ee..9a7c744f30 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift @@ -24,11 +24,13 @@ class AWSAuthFetchSessionTask: AuthFetchSessionTask, DefaultLogger { _ request: AuthFetchSessionRequest, authStateMachine: AuthStateMachine, configuration: AuthConfiguration, + environment: Environment, forceReconfigure: Bool = false ) { self.request = request self.authStateMachine = authStateMachine self.fetchAuthSessionHelper = FetchAuthSessionOperationHelper() + self.fetchAuthSessionHelper.environment = environment self.taskHelper = AWSAuthTaskHelper(authStateMachine: authStateMachine) self.configuration = configuration self.forceReconfigure = forceReconfigure