@@ -571,4 +571,159 @@ class CredentialStoreConfigurationTests: AWSAuthBaseTest {
571571 XCTAssertNotNil ( retrievedCredentials)
572572 XCTAssertNotEqual ( retrievedCredentials, awsCredentials)
573573 }
574+
575+ /// Test that shared keychain credentials are NOT cleared on fresh install when using access group
576+ ///
577+ /// - Given: A user has credentials stored in shared keychain
578+ /// - When: The credential store is initialized with fresh UserDefaults but same access group
579+ /// - Then: The shared keychain credentials should NOT be cleared
580+ ///
581+ func testSharedKeychainCredentialsNotClearedOnFreshInstall( ) {
582+ // Given: Save credentials to shared keychain
583+ let identityId = " identityId "
584+ let awsCredentials = AuthAWSCognitoCredentials . testData
585+ let initialCognitoCredentials = AmplifyCredentials . userPoolAndIdentityPool (
586+ signedInData: . testData,
587+ identityID: identityId,
588+ credentials: awsCredentials
589+ )
590+ let authConfig = AuthConfiguration . userPoolsAndIdentityPools (
591+ Defaults . makeDefaultUserPoolConfigData ( ) ,
592+ Defaults . makeIdentityConfigData ( )
593+ )
594+
595+ #if os(watchOS)
596+ let accessGroup = keychainAccessGroupWatch
597+ #else
598+ let accessGroup = keychainAccessGroup
599+ #endif
600+
601+ let credentialStore = AWSCognitoAuthCredentialStore (
602+ authConfiguration: authConfig,
603+ accessGroup: accessGroup
604+ )
605+
606+ do {
607+ try credentialStore. saveCredential ( initialCognitoCredentials)
608+ } catch {
609+ XCTFail ( " Unable to save credentials " )
610+ }
611+
612+ // Verify credentials are saved
613+ guard let savedCredentials = try ? credentialStore. retrieveCredential ( ) else {
614+ XCTFail ( " Unable to retrieve saved credentials " )
615+ return
616+ }
617+ XCTAssertNotNil ( savedCredentials)
618+
619+ // When: Simulate fresh install by clearing UserDefaults flag
620+ UserDefaults . standard. removeObject ( forKey: " amplify_secure_storage_scopes.awsCognitoAuthPlugin.isKeychainConfigured " )
621+
622+ // Initialize new credential store with same access group (simulates app extension scenario)
623+ let newCredentialStore = AWSCognitoAuthCredentialStore (
624+ authConfiguration: authConfig,
625+ accessGroup: accessGroup
626+ )
627+
628+ // Then: Shared keychain credentials should NOT be cleared
629+ guard let retrievedCredentials = try ? newCredentialStore. retrieveCredential ( ) ,
630+ case . userPoolAndIdentityPool(
631+ let retrievedTokens,
632+ let retrievedIdentityID,
633+ let retrievedAWSCredentials
634+ ) = retrievedCredentials else {
635+ XCTFail ( " Shared keychain credentials should not be cleared " )
636+ return
637+ }
638+
639+ XCTAssertNotNil ( retrievedCredentials)
640+ XCTAssertNotNil ( retrievedTokens)
641+ XCTAssertNotNil ( retrievedIdentityID)
642+ XCTAssertNotNil ( retrievedAWSCredentials)
643+ XCTAssertEqual ( retrievedIdentityID, identityId)
644+ XCTAssertEqual ( retrievedAWSCredentials, awsCredentials)
645+ }
646+
647+ /// Test that non-shared keychain credentials ARE cleared on fresh install
648+ ///
649+ /// - Given: A user has credentials stored in non-shared keychain
650+ /// - When: The credential store is initialized with fresh UserDefaults and no access group
651+ /// - Then: The keychain credentials should be cleared
652+ ///
653+ func testNonSharedKeychainCredentialsClearedOnFreshInstall( ) {
654+ // Given: Save credentials to non-shared keychain
655+ let identityId = " identityId "
656+ let awsCredentials = AuthAWSCognitoCredentials . testData
657+ let initialCognitoCredentials = AmplifyCredentials . userPoolAndIdentityPool (
658+ signedInData: . testData,
659+ identityID: identityId,
660+ credentials: awsCredentials
661+ )
662+ let authConfig = AuthConfiguration . userPoolsAndIdentityPools (
663+ Defaults . makeDefaultUserPoolConfigData ( ) ,
664+ Defaults . makeIdentityConfigData ( )
665+ )
666+
667+ let credentialStore = AWSCognitoAuthCredentialStore ( authConfiguration: authConfig)
668+
669+ do {
670+ try credentialStore. saveCredential ( initialCognitoCredentials)
671+ } catch {
672+ XCTFail ( " Unable to save credentials " )
673+ }
674+
675+ // Verify credentials are saved
676+ guard let savedCredentials = try ? credentialStore. retrieveCredential ( ) else {
677+ XCTFail ( " Unable to retrieve saved credentials " )
678+ return
679+ }
680+ XCTAssertNotNil ( savedCredentials)
681+
682+ // When: Simulate fresh install by clearing UserDefaults flag
683+ UserDefaults . standard. removeObject ( forKey: " amplify_secure_storage_scopes.awsCognitoAuthPlugin.isKeychainConfigured " )
684+
685+ // Initialize new credential store without access group
686+ let newCredentialStore = AWSCognitoAuthCredentialStore ( authConfiguration: authConfig)
687+
688+ // Then: Non-shared keychain credentials should be cleared
689+ let retrievedCredentials = try ? newCredentialStore. retrieveCredential ( )
690+ XCTAssertNil ( retrievedCredentials, " Non-shared keychain credentials should be cleared on fresh install " )
691+ }
692+
693+ /// Test that UserDefaults flag is properly set regardless of access group usage
694+ ///
695+ /// - Given: Fresh UserDefaults state
696+ /// - When: Credential store is initialized with or without access group
697+ /// - Then: UserDefaults flag should be set in both cases
698+ ///
699+ func testUserDefaultsFlagSetRegardlessOfAccessGroup( ) {
700+ let authConfig = AuthConfiguration . userPoolsAndIdentityPools (
701+ Defaults . makeDefaultUserPoolConfigData ( ) ,
702+ Defaults . makeIdentityConfigData ( )
703+ )
704+ let userDefaultsKey = " amplify_secure_storage_scopes.awsCognitoAuthPlugin.isKeychainConfigured "
705+
706+ // Test without access group
707+ UserDefaults . standard. removeObject ( forKey: userDefaultsKey)
708+ XCTAssertFalse ( UserDefaults . standard. bool ( forKey: userDefaultsKey) )
709+
710+ _ = AWSCognitoAuthCredentialStore ( authConfiguration: authConfig)
711+ XCTAssertTrue ( UserDefaults . standard. bool ( forKey: userDefaultsKey) )
712+
713+ // Test with access group
714+ UserDefaults . standard. removeObject ( forKey: userDefaultsKey)
715+ XCTAssertFalse ( UserDefaults . standard. bool ( forKey: userDefaultsKey) )
716+
717+ #if os(watchOS)
718+ let accessGroup = keychainAccessGroupWatch
719+ #else
720+ let accessGroup = keychainAccessGroup
721+ #endif
722+
723+ _ = AWSCognitoAuthCredentialStore (
724+ authConfiguration: authConfig,
725+ accessGroup: accessGroup
726+ )
727+ XCTAssertTrue ( UserDefaults . standard. bool ( forKey: userDefaultsKey) )
728+ }
574729}
0 commit comments