-
Notifications
You must be signed in to change notification settings - Fork 0
fix: Fix backup restoration #141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ package com.infomaniak.auth.lib.internal.managers | |
|
|
||
| import com.infomaniak.auth.lib.internal.CryptoObjectsBuilder | ||
| import com.infomaniak.auth.lib.internal.Failure | ||
| import com.infomaniak.auth.lib.internal.KeyPairManager | ||
| import com.infomaniak.auth.lib.internal.KeyPairManagerImpl | ||
| import com.infomaniak.auth.lib.internal.extensions.firstOrElse | ||
| import com.infomaniak.auth.lib.internal.models.ClientExtensionResults | ||
|
|
@@ -32,14 +33,15 @@ import com.infomaniak.auth.lib.models.migration.ApiToken | |
| import io.ktor.utils.io.core.toByteArray | ||
| import kotlinx.serialization.json.Json | ||
| import okio.ByteString.Companion.toByteString | ||
| import com.infomaniak.auth.lib.internal.KeyPairManager.Filters as KeyFilters | ||
|
|
||
| internal class AuthenticatorManager( | ||
| private val webAuthnRepository: WebAuthnRepository, | ||
| private val accountsRepository: AccountsRepository, | ||
| ) { | ||
|
|
||
| private val cryptoObjectsBuilder by lazy { CryptoObjectsBuilder() } | ||
| private val keyPairManager by lazy { KeyPairManagerImpl() } | ||
| val keyPairManager: KeyPairManager by lazy { KeyPairManagerImpl() } | ||
|
|
||
|
Comment on lines
43
to
45
|
||
| private val base64NoPadding get() = cryptoObjectsBuilder.base64UrlSafeNoPadding | ||
|
|
||
|
|
@@ -74,7 +76,7 @@ internal class AuthenticatorManager( | |
| userId: Long, | ||
| keyIdOrDefault: String? = null, | ||
| ): Xor<ApiToken, Failure.KeyManagement.KeyNotFound> { | ||
| val keyId = keyIdOrDefault ?: keyPairManager.findKeyIdFor { it.startsWith("$userId-") } | ||
| val keyId = keyIdOrDefault ?: keyPairManager.findKeyIdFor(KeyFilters.forUserId(userId)) | ||
| ?: return Xor.Second(Failure.KeyManagement.KeyNotFound("No key found for user $userId")) | ||
|
|
||
| val authenticationOptions = webAuthnRepository.challenge(clientId) | ||
|
|
@@ -124,22 +126,22 @@ internal class AuthenticatorManager( | |
| } | ||
|
|
||
| suspend fun removeAccount(token: String, userId: Long) { | ||
| val passkeyId = keyPairManager.findKeyIdFor { it.startsWith("$userId-") } | ||
| val passkeyId = keyPairManager.findKeyIdFor(KeyFilters.forUserId(userId)) | ||
|
|
||
| if (passkeyId != null) { | ||
| // If we have a passkey for this account, revoke it against the backend and delete it | ||
| webAuthnRepository.deletePasskey(token, passkeyId) | ||
| val _ = keyPairManager.deleteKeysMatching { "-$passkeyId-" in it } | ||
| val _ = keyPairManager.deleteKeysMatching(KeyFilters.forPasskeyId(passkeyId)) | ||
| } | ||
|
|
||
| accountsRepository.deleteAccount(userId) | ||
| } | ||
|
|
||
| suspend fun deleteKeysFor(userId: Long) { | ||
| val _ = keyPairManager.deleteKeysMatching { it.startsWith("$userId-") } | ||
| val _ = keyPairManager.deleteKeysMatching(KeyFilters.forUserId(userId)) | ||
| } | ||
|
|
||
| suspend fun getKeyIdFor(userId: Long): String? { | ||
| return keyPairManager.findKeyIdFor { it.startsWith("$userId-") } | ||
| return keyPairManager.findKeyIdFor(KeyFilters.forUserId(userId)) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,7 @@ import kotlinx.io.IOException | |
| import org.kotlincrypto.macs.hmac.sha2.HmacSHA256 | ||
| import kotlin.uuid.ExperimentalUuidApi | ||
| import kotlin.uuid.Uuid | ||
| import com.infomaniak.auth.lib.internal.KeyPairManager.Filters as keyFilters | ||
|
|
||
| internal class MigrationManager( | ||
| private val accountsDatabase: AccountsDatabase, | ||
|
|
@@ -61,26 +62,26 @@ internal class MigrationManager( | |
| } | ||
|
|
||
| suspend fun restore(account: AccountEntity, persistToken: suspend (userId: Long, token: String) -> Unit) { | ||
| val keyId = authenticatorManager.getKeyIdFor(account.id) ?: return | ||
| val oldKeyId = authenticatorManager.getKeyIdFor(account.id) ?: return //TODO: Handle multiple passkeys present. | ||
| // Get token with previous passkey | ||
| val token = authenticatorManager.getToken( | ||
| val tokenFromOldPasskey = authenticatorManager.getToken( | ||
| clientId = clientId, | ||
| userId = account.id, | ||
| keyIdOrDefault = keyId, | ||
| keyIdOrDefault = oldKeyId, | ||
| ).firstOrElse { error(it) } | ||
| // Register a new passkey | ||
| val newKeyId = authenticatorManager.registerPasskey(token.accessToken, account.id) | ||
| val newKeyId = authenticatorManager.registerPasskey(tokenFromOldPasskey.accessToken, account.id) | ||
| // Getting a new token with the new passkey | ||
| val tokenWithNewPassKey = authenticatorManager.getToken( | ||
| clientId = clientId, | ||
| userId = account.id, | ||
| keyIdOrDefault = newKeyId, | ||
| ).firstOrElse { error(it) } | ||
| persistToken(account.id, tokenWithNewPassKey.accessToken) | ||
| dao.upsert(account.copy(status = AccountEntity.Status.LoggedIn)) | ||
| // We can safely delete the old passkey, as the new one is working and the old token won't be valid anymore | ||
| authenticatorManager.deleteKeysFor(account.id) | ||
| webAuthnRepository.deletePasskey(tokenWithNewPassKey.accessToken, keyId) | ||
| webAuthnRepository.deletePasskey(tokenWithNewPassKey.accessToken, oldKeyId) | ||
| val _ = authenticatorManager.keyPairManager.deleteKeysMatching(keyFilters.forPasskeyId(oldKeyId)) | ||
| dao.upsert(account.copy(status = AccountEntity.Status.LoggedIn)) | ||
|
Comment on lines
80
to
+84
|
||
| } | ||
|
|
||
| suspend fun addLegacyAccountsToDB() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filters.forPasskeyId()currently matches names containing "-$passkeyId-". That works for Android file names like "$userId-$keyId-private.key", but it won’t match the Apple Keychain tag format used inKeyPairManagerImpl.apple.kt(tag is "$userId-$keyId"), sodeleteKeysMatching(Filters.forPasskeyId(...))will be a no-op on Apple and can leave stale keys that may be picked up later byfindKeyIdFor(Filters.forUserId(...)). Consider making the predicate compatible with both naming schemes (e.g., match "-$passkeyId-" OR endsWith("-$passkeyId"), or parse the keyId out of the name/tag and compare exactly).