diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/CredentialStoreCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/CredentialStoreCognitoActions.kt index 5a634c491..68a37fc45 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/CredentialStoreCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/CredentialStoreCognitoActions.kt @@ -36,13 +36,26 @@ internal object CredentialStoreCognitoActions : CredentialStoreActions { legacyCredentialStore.deleteCredential() } - // migrate device data - val lastAuthUserId = legacyCredentialStore.retrieveLastAuthUserId() - lastAuthUserId?.let { - val deviceMetaData = legacyCredentialStore.retrieveDeviceMetadata(lastAuthUserId) + /* + Migrate Device Metadata + 1. We first need to get the list of userIds that contain device metadata on the device. + 2. For each userId, we check to see if the current credential store has device metadata for that user. + 3. If the current user does not have device metadata in the current store, migrate from legacy. + This is a possibility because of a bug where we were previously attempting to migrate using an aliased + userId lookup. This situation would happen if a user migrated, signed out, then signed back in. Upon + signing back in, they would be granted new device metadata. Since that new metadata is what is + associated with the refresh token, we do not want to overwrite it. + 4. If the current user has device metadata in the current credential store, do not migrate from legacy. + 5. Upon migration completion, we delete the legacy device metadata. + */ + legacyCredentialStore.retrieveDeviceMetadataUserIdList().forEach { userId -> + val deviceMetaData = legacyCredentialStore.retrieveDeviceMetadata(userId) if (deviceMetaData != DeviceMetadata.Empty) { - credentialStore.saveDeviceMetadata(lastAuthUserId, deviceMetaData) - legacyCredentialStore.deleteDeviceKeyCredential(lastAuthUserId) + credentialStore.retrieveDeviceMetadata(userId) + if (credentialStore.retrieveDeviceMetadata(userId) == DeviceMetadata.Empty) { + credentialStore.saveDeviceMetadata(userId, deviceMetaData) + } + legacyCredentialStore.deleteDeviceKeyCredential(userId) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStore.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStore.kt index 060b859be..18b19749d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStore.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStore.kt @@ -29,6 +29,7 @@ import com.amplifyframework.statemachine.codegen.data.DeviceMetadata import com.amplifyframework.statemachine.codegen.data.FederatedToken import com.amplifyframework.statemachine.codegen.data.SignInMethod import com.amplifyframework.statemachine.codegen.data.SignedInData +import java.io.File import java.time.Instant import java.time.temporal.ChronoUnit import java.util.Date @@ -74,6 +75,7 @@ internal class AWSCognitoLegacyCredentialStore( const val TOKEN_KEY = "token" } + private val userDeviceDetailsCacheKeyPrefix = "$APP_DEVICE_INFO_CACHE.${authConfiguration.userPool?.poolId}." private val userDeviceDetailsCacheKey = "$APP_DEVICE_INFO_CACHE.${authConfiguration.userPool?.poolId}.%s" private val idAndCredentialsKeyValue: KeyValueRepository by lazy { @@ -229,6 +231,22 @@ internal class AWSCognitoLegacyCredentialStore( ) } + /* + During migration away from the legacy credential store, we need to find all shared preference files that store + device metadata. These filenames contain the real userId (not aliased) for the tracked device metadata. + */ + fun retrieveDeviceMetadataUserIdList(): List { + return try { + val sharedPrefsSuffix = ".xml" + File(context.dataDir, "shared_prefs").listFiles { _, filename -> + filename.startsWith(userDeviceDetailsCacheKeyPrefix) && filename.endsWith(sharedPrefsSuffix) + }?.map { it.name.substringAfter(userDeviceDetailsCacheKeyPrefix).substringBefore(sharedPrefsSuffix) } + ?.filter { it.isNotBlank() } ?: emptyList() + } catch (e: Exception) { + return emptyList() + } + } + @Synchronized override fun retrieveDeviceMetadata(username: String): DeviceMetadata { val deviceDetailsCacheKey = String.format(userDeviceDetailsCacheKey, username)