diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountdetails/ActionRequiredCard.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountdetails/ActionRequiredCard.kt index 048be932..3e6d9e26 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountdetails/ActionRequiredCard.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountdetails/ActionRequiredCard.kt @@ -134,7 +134,9 @@ fun ActionRequiredCard( } } } - is Account.Status.LoggedIn, is Account.Status.NotConnected.AttemptingToConnect -> Unit + is Account.Status.LoggedIn, + is Account.Status.NotConnected.AttemptingToConnect, + is Account.Status.PasswordChanged -> Unit } } diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListScreen.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListScreen.kt index 2aabf829..1ff26aa6 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListScreen.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListScreen.kt @@ -64,6 +64,7 @@ import com.infomaniak.auth.ui.components.Avatar import com.infomaniak.auth.ui.components.StatusCard import com.infomaniak.auth.ui.components.StatusCardVariant import com.infomaniak.auth.ui.previewparameter.fakeAccountPairs +import com.infomaniak.auth.ui.screen.accountlist.AccountListViewModel.AccountListUiState import com.infomaniak.auth.ui.screen.accountlist.AccountSecurityLevel.Companion.toAccountSecurityLevel import com.infomaniak.auth.ui.theme.AppDimens.DefaultCornerRadius import com.infomaniak.auth.ui.theme.AuthenticatorTheme @@ -86,6 +87,7 @@ fun AccountListScreen( uiState = { state }, onAccountClicked = onAccountClicked, onChallengesRefreshRequested = viewModel::refreshChallenges, + onUserProfilesRefreshRequested = viewModel::refreshUserProfiles, ) } is AccountListUiState.Loading -> Unit @@ -97,6 +99,7 @@ fun AccountListScreen( uiState: () -> AccountListUiState.Success, onAccountClicked: (Account) -> Unit, onChallengesRefreshRequested: () -> Unit, + onUserProfilesRefreshRequested: () -> Unit, modifier: Modifier = Modifier ) { val state = uiState() @@ -122,6 +125,7 @@ fun AccountListScreen( onRefresh = { isRefreshing = true onChallengesRefreshRequested() + onUserProfilesRefreshRequested() }, ) { Column( @@ -247,6 +251,7 @@ private fun AccountListScreenPreview() { uiState = { AccountListUiState.Success(fakeAccountPairs) }, onAccountClicked = {}, onChallengesRefreshRequested = {}, + onUserProfilesRefreshRequested = {}, ) } } diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListViewModel.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListViewModel.kt index 642989c7..c88019f5 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListViewModel.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/accountlist/AccountListViewModel.kt @@ -28,6 +28,7 @@ import com.infomaniak.core.twofactorauth.back.TwoFactorAuthManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -39,7 +40,7 @@ import javax.inject.Inject @HiltViewModel class AccountListViewModel @Inject constructor( - accountUtils: AccountUtils, + private val accountUtils: AccountUtils, private val authenticatorFacade: AuthenticatorFacade, private val twoFactorAuthManager: TwoFactorAuthManager ) : ViewModel() { @@ -70,10 +71,19 @@ class AccountListViewModel @Inject constructor( } } } -} -@Immutable -sealed interface AccountListUiState { - data object Loading : AccountListUiState - data class Success(val accountPairs: ImmutableList>) : AccountListUiState + fun refreshUserProfiles() { + viewModelScope.launch(Dispatchers.IO) { + val userIds = authenticatorFacade.accounts.first().map { it.id.toInt() }.toIntArray() + accountUtils.getUsersById(userIds).forEach { user -> + authenticatorFacade.refreshUserProfileFor(user.apiToken.accessToken, user.id.toLong()) + } + } + } + + @Immutable + sealed interface AccountListUiState { + data object Loading : AccountListUiState + data class Success(val accountPairs: ImmutableList>) : AccountListUiState + } } diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt index c8f51a66..003245f5 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainScreen.kt @@ -24,7 +24,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.NavBackStack @@ -40,6 +42,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.rememberPermissionState +import com.infomaniak.auth.lib.Account import com.infomaniak.auth.lib.AppStatus import com.infomaniak.auth.ui.navigation.NavDestination import com.infomaniak.auth.ui.navigation.baseEntryProvider @@ -85,10 +88,17 @@ fun MainScreen( } } + var showPasswordDialogFor: Account? by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + viewModel.accountsWithPasswordUpdate.collect { accounts -> + accounts.firstOrNull()?.let { showPasswordDialogFor = it } + } + } + MainScreen(backStack, entryDecorators) } - @OptIn(ExperimentalPermissionsApi::class) private fun handleAppStatus( appStatus: AppStatus, @@ -99,7 +109,10 @@ private fun handleAppStatus( val targetDestination = when (appStatus) { is AppStatus.LoginRequired.NotMigrating -> NavDestination.Onboarding.Start is AppStatus.LoginRequired.MigratingFromLegacyKAuth -> NavDestination.Onboarding.Migration - is AppStatus.LoginRequired.MustReLogin -> NavDestination.LoginInApp(legacyAccountId = appStatus.accountId, isOnboarding = true) + is AppStatus.LoginRequired.MustReLogin -> NavDestination.LoginInApp( + legacyAccountId = appStatus.accountId, + isOnboarding = true + ) is AppStatus.LoggingIn -> NavDestination.SecuringAccount is AppStatus.EverythingReady -> NavDestination.Onboarding.Complete is AppStatus.SetupComplete -> { diff --git a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt index d490a806..195a8ee8 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/ui/screen/main/MainViewModel.kt @@ -39,6 +39,8 @@ class MainViewModel @Inject constructor( ) : ViewModel() { val appStatus = authenticatorFacade.appStatus + val accountsWithPasswordUpdate = authenticatorFacade.accountsWithUpdatedPassword + val isAppLocked = appSettingsRepository.getSettings().mapNotNull { it?.isAppLockEnabled } val hasTriggeredNotificationPermission: StateFlow = flow { emitAll(PermissionPreferences().hasTriggeredNotificationPermissionFlow) diff --git a/app/src/main/kotlin/com/infomaniak/auth/utils/AccountUtils.kt b/app/src/main/kotlin/com/infomaniak/auth/utils/AccountUtils.kt index fbbbd7f0..e302e4f6 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/utils/AccountUtils.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/utils/AccountUtils.kt @@ -33,4 +33,5 @@ class AccountUtils @Inject constructor( suspend fun isUserConnected(): Boolean = users.first().isNotEmpty() suspend fun getUserById(id: Int): User? = userDao.findById(id) + suspend fun getUsersById(userIds: IntArray): List = userDao.loadAllByIds(userIds) } diff --git a/app/src/main/kotlin/com/infomaniak/auth/utils/MigrationUtils.kt b/app/src/main/kotlin/com/infomaniak/auth/utils/MigrationUtils.kt index 16fe90ea..11d23007 100644 --- a/app/src/main/kotlin/com/infomaniak/auth/utils/MigrationUtils.kt +++ b/app/src/main/kotlin/com/infomaniak/auth/utils/MigrationUtils.kt @@ -18,10 +18,10 @@ package com.infomaniak.auth.utils import com.infomaniak.auth.lib.models.migration.user.SharedUserProfile +import com.infomaniak.auth.lib.models.migration.user.preferences.Preferences import com.infomaniak.auth.lib.models.migration.user.preferences.SharedCountry import com.infomaniak.auth.lib.models.migration.user.preferences.SharedLanguage import com.infomaniak.auth.lib.models.migration.user.preferences.SharedOrganizationPreference -import com.infomaniak.auth.lib.models.migration.user.preferences.Preferences import com.infomaniak.auth.lib.models.migration.user.preferences.SharedTimeZone import com.infomaniak.auth.lib.models.migration.user.preferences.security.SharedAuthDevices import com.infomaniak.auth.lib.models.migration.user.preferences.security.SharedSecurity @@ -60,7 +60,7 @@ fun SharedUserProfile.toUser(): User { } private fun Preferences.toCorePreferences() = CorePreferences( - security = security?.toCoreSecurity(), + security = security.toCoreSecurity(), organizationPreference = organizationPreference.toCoreOrganizationPreference(), language = language.toCoreLanguage(), country = country.toCoreCountry(), diff --git a/multiplatform-lib/schemas/com.infomaniak.auth.lib.internal.db.AccountsDatabase/1.json b/multiplatform-lib/schemas/com.infomaniak.auth.lib.internal.db.AccountsDatabase/1.json index 7c7b64d9..118a84a8 100644 --- a/multiplatform-lib/schemas/com.infomaniak.auth.lib.internal.db.AccountsDatabase/1.json +++ b/multiplatform-lib/schemas/com.infomaniak.auth.lib.internal.db.AccountsDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "ab7e6c6aadc7811b61b0ffe94bc67ec1", + "identityHash": "9a233d24627b386b646a6f9418812e76", "entities": [ { "tableName": "AccountEntity", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `fullName` TEXT NOT NULL, `initials` TEXT NOT NULL, `email` TEXT NOT NULL, `avatarUrl` TEXT, `status` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `fullName` TEXT NOT NULL, `initials` TEXT NOT NULL, `email` TEXT NOT NULL, `avatarUrl` TEXT, `status` INTEGER NOT NULL, `securityScore` INTEGER NOT NULL, `lastPasswordUpdate` INTEGER, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -42,6 +42,17 @@ "columnName": "status", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "securityScore", + "columnName": "securityScore", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastPasswordUpdate", + "columnName": "lastPasswordUpdate", + "affinity": "INTEGER" } ], "primaryKey": { @@ -54,7 +65,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab7e6c6aadc7811b61b0ffe94bc67ec1')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9a233d24627b386b646a6f9418812e76')" ] } } \ No newline at end of file diff --git a/multiplatform-lib/src/commonMain/kotlin/Account.kt b/multiplatform-lib/src/commonMain/kotlin/Account.kt index db4a5d00..542ffc0e 100644 --- a/multiplatform-lib/src/commonMain/kotlin/Account.kt +++ b/multiplatform-lib/src/commonMain/kotlin/Account.kt @@ -55,5 +55,7 @@ data class Account( data class LoginFailed(val issue: Issue) : NotConnected } + + data class PasswordChanged(val hasBeenHandled: (Unit) -> Unit) : Status } } diff --git a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt index e18b45a1..f556b0e7 100644 --- a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt +++ b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt @@ -38,6 +38,7 @@ import kotlin.time.Duration.Companion.seconds abstract class AuthenticatorFacade internal constructor() { abstract val accounts: Flow> + abstract val accountsWithUpdatedPassword: Flow> abstract val appStatus: SharedFlow @@ -62,6 +63,8 @@ abstract class AuthenticatorFacade internal constructor() { @Throws(Exception::class) abstract suspend fun refreshTokenFor(userId: Long) + abstract suspend fun refreshUserProfileFor(token: String, userId: Long) + companion object { fun create( diff --git a/multiplatform-lib/src/commonMain/kotlin/DummyAuthenticatorFacade.kt b/multiplatform-lib/src/commonMain/kotlin/DummyAuthenticatorFacade.kt index 589adf5b..1988c1d0 100644 --- a/multiplatform-lib/src/commonMain/kotlin/DummyAuthenticatorFacade.kt +++ b/multiplatform-lib/src/commonMain/kotlin/DummyAuthenticatorFacade.kt @@ -44,6 +44,8 @@ class DummyAuthenticatorFacade internal constructor( resetAfter: Duration, ) : AuthenticatorFacade() { override val accounts: Flow> + override val accountsWithUpdatedPassword: Flow> + get() = TODO("Not yet implemented") private var _accounts: List by MutableStateFlow>(emptyList()).also { accounts = accountsRepository.getAccounts().map { @@ -119,4 +121,8 @@ class DummyAuthenticatorFacade internal constructor( override suspend fun refreshTokenFor(userId: Long) { TODO("Not yet implemented") } + + override suspend fun refreshUserProfileFor(token: String, userId: Long) { + TODO("Not yet implemented") + } } diff --git a/multiplatform-lib/src/commonMain/kotlin/internal/AuthenticatorFacadeImpl.kt b/multiplatform-lib/src/commonMain/kotlin/internal/AuthenticatorFacadeImpl.kt index 5e83f250..15a52598 100644 --- a/multiplatform-lib/src/commonMain/kotlin/internal/AuthenticatorFacadeImpl.kt +++ b/multiplatform-lib/src/commonMain/kotlin/internal/AuthenticatorFacadeImpl.kt @@ -27,6 +27,7 @@ import com.infomaniak.auth.lib.CredentialsForMigration import com.infomaniak.auth.lib.Issue import com.infomaniak.auth.lib.Issue.Retriable.Cause import com.infomaniak.auth.lib.internal.db.AccountEntity +import com.infomaniak.auth.lib.internal.db.AccountEntity.Status import com.infomaniak.auth.lib.internal.db.AccountsDatabase import com.infomaniak.auth.lib.internal.extensions.cancellable import com.infomaniak.auth.lib.internal.extensions.firstOrElse @@ -41,6 +42,7 @@ import com.infomaniak.auth.lib.internal.utils.raceOf import com.infomaniak.auth.lib.internal.utils.sharedFlow import com.infomaniak.auth.lib.internal.utils.waitForComplete import com.infomaniak.auth.lib.internal.utils.withTimeoutOrNull +import com.infomaniak.auth.lib.models.migration.user.SharedUserProfile import com.infomaniak.auth.lib.network.exceptions.ApiException import com.infomaniak.auth.lib.network.exceptions.NetworkException import com.infomaniak.auth.lib.network.interfaces.AuthenticatorBridge @@ -72,6 +74,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.launch import kotlinx.io.IOException import kotlin.time.Duration.Companion.seconds @@ -112,7 +115,9 @@ internal class AuthenticatorFacadeImpl( override val accounts: Flow> = channelFlow { accountEntities.collectLatest { entities -> - val idsOfAccountsToLogIn = entities.mapNotNull { entity -> entity.id.takeUnless { entity.isLoggedIn } }.toSet() + val idsOfAccountsToLogIn = + entities.mapNotNull { entity -> entity.id.takeUnless { entity.isLoggedIn } } + .toSet() accountsToLogin.useElements(idsOfAccountsToLogIn) { map -> accountsFlow(entities, map).collectLatest { send(it) } awaitCancellation() // Stay in the useElements scope until a new list of accounts is received. @@ -120,6 +125,40 @@ internal class AuthenticatorFacadeImpl( } }.flowOn(Dispatchers.Default).distinctUntilChanged().shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + override val accountsWithUpdatedPassword: Flow> = channelFlow { + accountEntities.collectLatest { entities -> + val idsOfAccountsWithUpdatedPassword = + entities.mapNotNull { entity -> entity.id.takeUnless { entity.status != AccountEntity.Status.PasswordChanged } } + .toSet() + val accountsWithUpdatedPassword = accounts.first().filter { it.id in idsOfAccountsWithUpdatedPassword } + + val passwordUpdatedHandledList = mutableListOf>>() + val passwordUpdatedAccounts = accountsWithUpdatedPassword.map { + val passwordUpdatedHandled = CompletableDeferred() + passwordUpdatedHandledList.add(it.id to passwordUpdatedHandled) + val modified = + it.copy(status = Account.Status.PasswordChanged(hasBeenHandled = passwordUpdatedHandled::complete)) + modified + } + + send(passwordUpdatedAccounts) + + launch { + kotlinx.coroutines.selects.select { + passwordUpdatedHandledList.forEach { (userId, signal) -> + signal.onAwait { + dao.getAccount(userId)?.let { account -> + if (account.status == AccountEntity.Status.PasswordChanged) { + dao.upsert(account.copy(status = AccountEntity.Status.LoggedIn)) + } + } + } + } + } + } + } + }.flowOn(Dispatchers.Default).distinctUntilChanged().shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + override val appStatus: SharedFlow = appStatusFlow() .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) @@ -142,6 +181,10 @@ internal class AuthenticatorFacadeImpl( authenticatorBridge.persistTokenForAccount(userId, token.accessToken) } + override suspend fun refreshUserProfileFor(token: String, userId: Long) { + syncAccountWithUserProfile(token, userId) + } + private fun accountsFlow( entities: List, accountsToLogin: Map> @@ -231,7 +274,7 @@ internal class AuthenticatorFacadeImpl( AccountEntity.Status.RestoringFromBackup, AccountEntity.Status.DeletingOldKeyAfterRestoration -> { restoreFromBackupAttempts(account = entity) } - AccountEntity.Status.LoggedIn, null -> Unit // Should not happen in practice. + AccountEntity.Status.LoggedIn, Status.PasswordChanged, null -> Unit // Should not happen in practice. } } @@ -268,7 +311,14 @@ internal class AuthenticatorFacadeImpl( userId = userId, ).firstOrElse { error("Key not found: ${it.details}") } authenticatorBridge.persistTokenForAccount(userId, token.accessToken) - dao.upsert(notRegisteredAccount.copy(status = AccountEntity.Status.LoggedIn)) + val profile = authenticatorManager.getUserProfile(token.accessToken) + dao.upsert( + notRegisteredAccount.copy( + securityScore = profile.preferences.security.score, + lastPasswordUpdate = profile.preferences.security.dateLastChangedPassword, + status = AccountEntity.Status.LoggedIn + ) + ) } } @@ -332,7 +382,7 @@ internal class AuthenticatorFacadeImpl( userId = userId, authentication = authentication, persistUser = { apiToken -> - val userProfile = authenticatorManager.getUserProfile(apiToken.accessToken) + val userProfile = syncAccountWithUserProfile(apiToken.accessToken, userId) userProfile.apiToken = apiToken authenticatorBridge.persistUserProfile(userProfile) }, @@ -345,6 +395,22 @@ internal class AuthenticatorFacadeImpl( return true } + private suspend fun syncAccountWithUserProfile(token: String, userId: Long): SharedUserProfile { + val userProfile = authenticatorManager.getUserProfile(token) + dao.getAccount(userId)?.let { account -> + val passwordHasBeenUpdated = + account.lastPasswordUpdate != null && userProfile.preferences.security.dateLastChangedPassword > account.lastPasswordUpdate + dao.upsert( + account.copy( + status = if (passwordHasBeenUpdated) Status.PasswordChanged else account.status, + securityScore = userProfile.preferences.security.score, + lastPasswordUpdate = userProfile.preferences.security.dateLastChangedPassword + ) + ) + } + return userProfile + } + private suspend fun FlowCollector.tryMigratingWithReLogin(accountToMigrate: AccountEntity) { var status = ReLogin( legacyAccount = accountToMigrate.toAccount(null), diff --git a/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountEntity.kt b/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountEntity.kt index 9d417017..5b00f859 100644 --- a/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountEntity.kt +++ b/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountEntity.kt @@ -29,8 +29,10 @@ internal data class AccountEntity( val email: String, val avatarUrl: String? = null, val status: Status, + val securityScore: Int = 0, + val lastPasswordUpdate: Long? = null ) { - val isLoggedIn: Boolean get() = status == Status.LoggedIn + val isLoggedIn: Boolean get() = status == Status.LoggedIn || status == Status.PasswordChanged enum class Status { @@ -58,5 +60,7 @@ internal data class AccountEntity( RestoringFromBackup, DeletingOldKeyAfterRestoration, + + PasswordChanged, } } diff --git a/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountsDao.kt b/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountsDao.kt index 1f7cbfce..b30ea05a 100644 --- a/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountsDao.kt +++ b/multiplatform-lib/src/commonMain/kotlin/internal/db/AccountsDao.kt @@ -32,6 +32,9 @@ internal interface AccountsDao { @Query("SELECT * FROM AccountEntity WHERE id = :id") fun getAccountAsFlow(id: Long): Flow + @Query("SELECT * FROM AccountEntity WHERE id = :id") + suspend fun getAccount(id: Long): AccountEntity? + @Upsert suspend fun upsert(account: AccountEntity) diff --git "a/multiplatform-lib/src/commonMain/kotlin/internal/extensions/Models \342\206\224 Entities.kt" "b/multiplatform-lib/src/commonMain/kotlin/internal/extensions/Models \342\206\224 Entities.kt" index d2a05c5c..ed1555dd 100644 --- "a/multiplatform-lib/src/commonMain/kotlin/internal/extensions/Models \342\206\224 Entities.kt" +++ "b/multiplatform-lib/src/commonMain/kotlin/internal/extensions/Models \342\206\224 Entities.kt" @@ -22,7 +22,7 @@ import com.infomaniak.auth.lib.internal.db.AccountEntity import com.infomaniak.auth.lib.internal.db.AccountEntity.Status import com.infomaniak.auth.lib.internal.models.LegacyUser -internal fun AccountEntity.toAccount(action: Account.Status.NotConnected?): Account { +internal fun AccountEntity.toAccount(action: Account.Status?): Account { return Account( id = id, fullName = fullName, @@ -31,6 +31,7 @@ internal fun AccountEntity.toAccount(action: Account.Status.NotConnected?): Acco avatarUrl = avatarUrl, status = when (status) { AccountEntity.Status.LoggedIn -> Account.Status.LoggedIn + AccountEntity.Status.PasswordChanged -> action ?: Account.Status.LoggedIn else -> action ?: Account.Status.NotConnected.AttemptingToConnect } ) diff --git a/multiplatform-lib/src/commonMain/kotlin/internal/managers/AccountRestorer.kt b/multiplatform-lib/src/commonMain/kotlin/internal/managers/AccountRestorer.kt index a1b890a3..d326511e 100644 --- a/multiplatform-lib/src/commonMain/kotlin/internal/managers/AccountRestorer.kt +++ b/multiplatform-lib/src/commonMain/kotlin/internal/managers/AccountRestorer.kt @@ -80,6 +80,7 @@ internal class AccountRestorer( private fun AccountEntity.hasNewKeyAlreadyBeenRegistered() = when (status) { AccountEntity.Status.RestoringFromBackup -> false AccountEntity.Status.DeletingOldKeyAfterRestoration -> true + AccountEntity.Status.PasswordChanged, AccountEntity.Status.ToBeMigrated, AccountEntity.Status.PasskeyRegistrationPending, AccountEntity.Status.FirstPasskeyAuthenticationPending, diff --git a/multiplatform-lib/src/commonMain/kotlin/models/migration/user/preferences/Preferences.kt b/multiplatform-lib/src/commonMain/kotlin/models/migration/user/preferences/Preferences.kt index 1f91fd9e..91c35ce6 100644 --- a/multiplatform-lib/src/commonMain/kotlin/models/migration/user/preferences/Preferences.kt +++ b/multiplatform-lib/src/commonMain/kotlin/models/migration/user/preferences/Preferences.kt @@ -23,7 +23,7 @@ import kotlinx.serialization.Serializable @Serializable data class Preferences( - var security: SharedSecurity? = null, + var security: SharedSecurity, @SerialName("account") var organizationPreference: SharedOrganizationPreference, var language: SharedLanguage,