From 727f2f81e78492158cd7e71dc6aaea1c6c2f1607 Mon Sep 17 00:00:00 2001 From: Harshdeep Singh <6162866+harsh62@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:28:21 -0400 Subject: [PATCH 1/3] fix(auth): add missing state transitions during auto sign in --- .../SignIn/SignInState+Resolver.swift | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/SignIn/SignInState+Resolver.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/SignIn/SignInState+Resolver.swift index 81b0178c29..78be100f84 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/SignIn/SignInState+Resolver.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/Resolvers/SignIn/SignInState+Resolver.swift @@ -684,7 +684,48 @@ extension SignInState { ) #endif - case .autoSigningIn: + case .autoSigningIn(let signInEventData): + + if let signInEvent = event as? SignInEvent, + case .receivedChallenge(let challenge) = signInEvent.eventType { + let action = InitializeResolveChallenge( + challenge: challenge, + signInMethod: signInEventData.signInMethod + ) + let subState = SignInChallengeState.notStarted + return .init( + newState: + .resolvingChallenge( + subState, + challenge.challenge.authChallengeType, + signInEventData.signInMethod + ), + actions: [action] + ) + } + + if let signInEvent = event as? SignInEvent, + case .initiateDeviceSRP(let username, let challengeResponse) = signInEvent.eventType { + let action = StartDeviceSRPFlow( + username: username, + authResponse: challengeResponse + ) + return .init( + newState: .resolvingDeviceSrpa(.notStarted), + actions: [action] + ) + } + + if let signInEvent = event as? SignInEvent, + case .initiateTOTPSetup(_, let challengeResponse) = signInEvent.eventType { + let action = InitializeTOTPSetup( + authResponse: challengeResponse) + return .init( + newState: .resolvingTOTPSetup(.notStarted, signInEventData), + actions: [action] + ) + } + if case .finalizeSignIn(let signedInData) = event.isSignInEvent { return .init( newState: .signedIn(signedInData), From 0335ad88721476eb441e0d8c12183a815910398c Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Mon, 27 Oct 2025 13:02:29 -0700 Subject: [PATCH 2/3] add unit test --- .../SignIn/AWSAuthAutoSignInTests.swift | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift index fbf482a04b..d2c6b9f21a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift @@ -58,6 +58,41 @@ class AWSAuthAutoSignInTests: BasePluginTest { XCTFail("Received failure with error \(error)") } } + + /// Test auto sign in success + /// + /// - Given: Given an auth plugin with mocked service set up to return `.selectChallenge` with `.password` and `.passwordSrp` + /// for `InitiateAuth` and in signed up state + /// - When: + /// - I invoke autoSignIn + /// - Then: + /// - I should get a result with `.continueSignInWithFirstFactorSelection` + /// + func testAutoSignInSuccessWithContinueFirstFactorSelection() async { + mockIdentityProvider = MockIdentityProvider( + mockInitiateAuthResponse: { input in + return InitiateAuthOutput( + availableChallenges: [.password, .passwordSrp], + challengeName: .selectChallenge, + session: "session" + ) + } + ) + + do { + let result = try await plugin.autoSignIn() + guard case .continueSignInWithFirstFactorSelection(let authFactorTypes) = result.nextStep else { + XCTFail("Result should be .continueSignInWithFirstFactorSelection for next step") + return + } + + XCTAssertTrue(authFactorTypes.count == 2) + XCTAssertTrue(authFactorTypes.contains(.password)) + XCTAssertTrue(authFactorTypes.contains(.passwordSRP)) + } catch { + XCTFail("Received failure with error \(error)") + } + } /// Test auto sign in success /// From c8bfa789953ee7ea71bd5ff932c3e6260fe5881f Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Mon, 27 Oct 2025 13:11:26 -0700 Subject: [PATCH 3/3] fix swiftformat issues --- .../SignIn/AWSAuthAutoSignInTests.swift | 4 +- .../CredentialStoreConfigurationTests.swift | 38 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift index d2c6b9f21a..31097c9161 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthAutoSignInTests.swift @@ -58,7 +58,7 @@ class AWSAuthAutoSignInTests: BasePluginTest { XCTFail("Received failure with error \(error)") } } - + /// Test auto sign in success /// /// - Given: Given an auth plugin with mocked service set up to return `.selectChallenge` with `.password` and `.passwordSrp` @@ -85,7 +85,7 @@ class AWSAuthAutoSignInTests: BasePluginTest { XCTFail("Result should be .continueSignInWithFirstFactorSelection for next step") return } - + XCTAssertTrue(authFactorTypes.count == 2) XCTAssertTrue(authFactorTypes.contains(.password)) XCTAssertTrue(authFactorTypes.contains(.passwordSRP)) diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift index 111c74ac35..55b19e3e74 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift @@ -591,40 +591,40 @@ class CredentialStoreConfigurationTests: AWSAuthBaseTest { Defaults.makeDefaultUserPoolConfigData(), Defaults.makeIdentityConfigData() ) - + #if os(watchOS) let accessGroup = keychainAccessGroupWatch #else let accessGroup = keychainAccessGroup #endif - + let credentialStore = AWSCognitoAuthCredentialStore( authConfiguration: authConfig, accessGroup: accessGroup ) - + do { try credentialStore.saveCredential(initialCognitoCredentials) } catch { XCTFail("Unable to save credentials") } - + // Verify credentials are saved guard let savedCredentials = try? credentialStore.retrieveCredential() else { XCTFail("Unable to retrieve saved credentials") return } XCTAssertNotNil(savedCredentials) - + // When: Simulate fresh install by clearing UserDefaults flag UserDefaults.standard.removeObject(forKey: "amplify_secure_storage_scopes.awsCognitoAuthPlugin.isKeychainConfigured") - + // Initialize new credential store with same access group (simulates app extension scenario) let newCredentialStore = AWSCognitoAuthCredentialStore( authConfiguration: authConfig, accessGroup: accessGroup ) - + // Then: Shared keychain credentials should NOT be cleared guard let retrievedCredentials = try? newCredentialStore.retrieveCredential(), case .userPoolAndIdentityPool( @@ -635,7 +635,7 @@ class CredentialStoreConfigurationTests: AWSAuthBaseTest { XCTFail("Shared keychain credentials should not be cleared") return } - + XCTAssertNotNil(retrievedCredentials) XCTAssertNotNil(retrievedTokens) XCTAssertNotNil(retrievedIdentityID) @@ -663,28 +663,28 @@ class CredentialStoreConfigurationTests: AWSAuthBaseTest { Defaults.makeDefaultUserPoolConfigData(), Defaults.makeIdentityConfigData() ) - + let credentialStore = AWSCognitoAuthCredentialStore(authConfiguration: authConfig) - + do { try credentialStore.saveCredential(initialCognitoCredentials) } catch { XCTFail("Unable to save credentials") } - + // Verify credentials are saved guard let savedCredentials = try? credentialStore.retrieveCredential() else { XCTFail("Unable to retrieve saved credentials") return } XCTAssertNotNil(savedCredentials) - + // When: Simulate fresh install by clearing UserDefaults flag UserDefaults.standard.removeObject(forKey: "amplify_secure_storage_scopes.awsCognitoAuthPlugin.isKeychainConfigured") - + // Initialize new credential store without access group let newCredentialStore = AWSCognitoAuthCredentialStore(authConfiguration: authConfig) - + // Then: Non-shared keychain credentials should be cleared let retrievedCredentials = try? newCredentialStore.retrieveCredential() XCTAssertNil(retrievedCredentials, "Non-shared keychain credentials should be cleared on fresh install") @@ -702,24 +702,24 @@ class CredentialStoreConfigurationTests: AWSAuthBaseTest { Defaults.makeIdentityConfigData() ) let userDefaultsKey = "amplify_secure_storage_scopes.awsCognitoAuthPlugin.isKeychainConfigured" - + // Test without access group UserDefaults.standard.removeObject(forKey: userDefaultsKey) XCTAssertFalse(UserDefaults.standard.bool(forKey: userDefaultsKey)) - + _ = AWSCognitoAuthCredentialStore(authConfiguration: authConfig) XCTAssertTrue(UserDefaults.standard.bool(forKey: userDefaultsKey)) - + // Test with access group UserDefaults.standard.removeObject(forKey: userDefaultsKey) XCTAssertFalse(UserDefaults.standard.bool(forKey: userDefaultsKey)) - + #if os(watchOS) let accessGroup = keychainAccessGroupWatch #else let accessGroup = keychainAccessGroup #endif - + _ = AWSCognitoAuthCredentialStore( authConfiguration: authConfig, accessGroup: accessGroup