From 1d196d5bb72c84766f4ea284b449dd2403bf3904 Mon Sep 17 00:00:00 2001 From: Harshdeep Singh <6162866+harsh62@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:55:52 -0500 Subject: [PATCH 1/3] feat(auth): ignore user cancel when session is expired on hostedui sign out --- .../Actions/SignOut/InitiateSignOut.swift | 36 +++++++++++++-- .../Actions/SignOut/ShowHostedUISignOut.swift | 13 +++++- .../AWSCognitoAuthPlugin+ClientBehavior.swift | 5 ++- .../FetchAuthSessionOperationHelper.swift | 45 ++++++++++++++++++- .../CodeGen/Data/SignedInData.swift | 5 ++- .../Task/AWSAuthFetchSessionTask.swift | 5 ++- 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift index dddb53bd49..da85c34940 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(var credentials) = data else { + return signedInData + } + + // Update SignedInData based on credential type + switch credentials { + case .userPoolOnly(var updatedSignedInData): + return updatedSignedInData + case .userPoolAndIdentityPool(var 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..f70dee5233 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift @@ -46,7 +46,17 @@ class ShowHostedUISignOut: NSObject, Action { inPrivate: false, presentationAnchor: signOutEvent.presentationAnchor) await sendEvent(with: nil, dispatcher: dispatcher, environment: environment) - } catch { + } + 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 +111,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 3e7dc8628c..8edd7ab7a5 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift @@ -114,7 +114,10 @@ extension AWSCognitoAuthPlugin: AuthCategoryBehavior { public func fetchAuthSession(options: AuthFetchSessionRequest.Options?) async throws -> AuthSession { let options = options ?? AuthFetchSessionRequest.Options() let request = AuthFetchSessionRequest(options: options) - let task = AWSAuthFetchSessionTask(request, authStateMachine: authStateMachine) + let task = AWSAuthFetchSessionTask( + request, + environment: authEnvironment, + authStateMachine: authStateMachine) 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 f39263578d..3ba7615b53 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift @@ -18,10 +18,13 @@ class AWSAuthFetchSessionTask: AuthFetchSessionTask, DefaultLogger { HubPayload.EventName.Auth.fetchSessionAPI } - init(_ request: AuthFetchSessionRequest, authStateMachine: AuthStateMachine) { + init(_ request: AuthFetchSessionRequest, + environment: Environment, + authStateMachine: AuthStateMachine) { self.request = request self.authStateMachine = authStateMachine self.fetchAuthSessionHelper = FetchAuthSessionOperationHelper() + self.fetchAuthSessionHelper.environment = environment self.taskHelper = AWSAuthTaskHelper(authStateMachine: authStateMachine) } From d1b9685cd53fd7c308846ffae12d7757c32e7103 Mon Sep 17 00:00:00 2001 From: Harshdeep Singh <6162866+harsh62@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:59:17 -0500 Subject: [PATCH 2/3] fix warnings --- .../Actions/SignOut/InitiateSignOut.swift | 6 +++--- .../Actions/SignOut/ShowHostedUISignOut.swift | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift index da85c34940..8f95607b86 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/InitiateSignOut.swift @@ -40,15 +40,15 @@ struct InitiateSignOut: Action { let data = try await credentialStoreClient?.fetchData( type: .amplifyCredentials ) - guard case .amplifyCredentials(var credentials) = data else { + guard case .amplifyCredentials(let credentials) = data else { return signedInData } // Update SignedInData based on credential type switch credentials { - case .userPoolOnly(var updatedSignedInData): + case .userPoolOnly(let updatedSignedInData): return updatedSignedInData - case .userPoolAndIdentityPool(var updatedSignedInData, _, _): + case .userPoolAndIdentityPool(let updatedSignedInData, _, _): return updatedSignedInData case .identityPoolOnly, .identityPoolWithFederation, .noCredentials: return signedInData diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift index f70dee5233..04ccb81fbe 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignOut/ShowHostedUISignOut.swift @@ -46,8 +46,7 @@ class ShowHostedUISignOut: NSObject, Action { inPrivate: false, presentationAnchor: signOutEvent.presentationAnchor) await sendEvent(with: nil, dispatcher: dispatcher, environment: environment) - } - catch HostedUIError.cancelled { + } 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) @@ -55,8 +54,7 @@ class ShowHostedUISignOut: NSObject, Action { self.logVerbose("\(#fileID) Received error \(HostedUIError.cancelled)", environment: environment) await sendEvent(with: HostedUIError.cancelled, dispatcher: dispatcher, environment: environment) } - } - catch { + } catch { self.logVerbose("\(#fileID) Received error \(error)", environment: environment) await sendEvent(with: error, dispatcher: dispatcher, environment: environment) } From 7fba1a764902b0e7760cae971ec82480cf80b63b Mon Sep 17 00:00:00 2001 From: Harshdeep Singh <6162866+harsh62@users.noreply.github.com> Date: Mon, 5 May 2025 15:09:57 -0400 Subject: [PATCH 3/3] fix merge issue. --- .../AWSCognitoAuthPlugin+ClientBehavior.swift | 10 ++++++---- .../Task/AWSAuthFetchSessionTask.swift | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) 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/Task/AWSAuthFetchSessionTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift index e912ef47d8..9a7c744f30 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift @@ -24,6 +24,7 @@ class AWSAuthFetchSessionTask: AuthFetchSessionTask, DefaultLogger { _ request: AuthFetchSessionRequest, authStateMachine: AuthStateMachine, configuration: AuthConfiguration, + environment: Environment, forceReconfigure: Bool = false ) { self.request = request