Silently register backupId on restore failure
This commit is contained in:
parent
989749cd64
commit
1d67bef04b
@ -57,6 +57,11 @@ public class RegistrationCoordinatorBackupErrorPresenterImpl:
|
||||
case _ where error is SignalError:
|
||||
// LibSignalError (e.g. - creating credentials)
|
||||
return .incorrectRecoveryKey
|
||||
case let httpError as OWSHTTPError where httpError.responseStatusCode == 401:
|
||||
// Incorrect AEP
|
||||
// -- and/or --
|
||||
// No public key registered for this backupID (AEP+ACI)
|
||||
return .incorrectRecoveryKey
|
||||
case let httpError as OWSHTTPError where httpError.responseStatusCode == 404:
|
||||
// No backup found in the CDN
|
||||
return .backupNotFound
|
||||
@ -171,7 +176,7 @@ public class RegistrationCoordinatorBackupErrorPresenterImpl:
|
||||
continuation.resume(returning: .skipRestore)
|
||||
})
|
||||
actions.append(ActionSheetAction(title: tryAgainString) { _ in
|
||||
continuation.resume(returning: .tryAgain)
|
||||
continuation.resume(returning: .incorrectRecoveryKey)
|
||||
})
|
||||
case .incorrectRecoveryKey:
|
||||
title = OWSLocalizedString(
|
||||
|
||||
@ -11,6 +11,7 @@ public struct RegistrationCoordinatorDependencies {
|
||||
public let accountEntropyPoolGenerator: () -> AccountEntropyPool
|
||||
public let accountKeyStore: AccountKeyStore
|
||||
public let backupArchiveManager: BackupArchiveManager
|
||||
public let backupIdService: BackupIdService
|
||||
public let backupNonceStore: BackupNonceMetadataStore
|
||||
public let backupRequestManager: BackupRequestManager
|
||||
public let changeNumberPniManager: ChangePhoneNumberPniManager
|
||||
@ -53,6 +54,7 @@ public struct RegistrationCoordinatorDependencies {
|
||||
accountEntropyPoolGenerator: { AccountEntropyPool() },
|
||||
accountKeyStore: DependenciesBridge.shared.accountKeyStore,
|
||||
backupArchiveManager: DependenciesBridge.shared.backupArchiveManager,
|
||||
backupIdService: DependenciesBridge.shared.backupIdService,
|
||||
backupNonceStore: BackupNonceMetadataStore(),
|
||||
backupRequestManager: DependenciesBridge.shared.backupRequestManager,
|
||||
changeNumberPniManager: DependenciesBridge.shared.changePhoneNumberPniManager,
|
||||
|
||||
@ -598,10 +598,9 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
|
||||
// TODO: [Backups] This is currently unsupported, so log and return
|
||||
throw OWSAssertionError("Local backups not supported.")
|
||||
case .remote:
|
||||
let backupServiceAuth = try await self.deps.backupRequestManager.fetchBackupServiceAuthForRegistration(
|
||||
key: backupKey,
|
||||
localAci: identity.aci,
|
||||
chatServiceAuth: identity.chatServiceAuth
|
||||
let backupServiceAuth = try await self.fetchBackupServiceAuth(
|
||||
accountEntropyPool: accountEntropyPool,
|
||||
accountIdentity: identity
|
||||
)
|
||||
fileUrl = try await self.deps.backupArchiveManager.downloadEncryptedBackup(
|
||||
backupKey: backupKey,
|
||||
@ -644,10 +643,9 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
|
||||
nonceSource = .svr🐝(header: metadataHeader, auth: identity.chatServiceAuth)
|
||||
} else {
|
||||
Logger.info("Missing metadata header; refetching from cdn")
|
||||
let backupServiceAuth = try await self.deps.backupRequestManager.fetchBackupServiceAuthForRegistration(
|
||||
key: backupKey,
|
||||
localAci: identity.aci,
|
||||
chatServiceAuth: identity.chatServiceAuth
|
||||
let backupServiceAuth = try await self.fetchBackupServiceAuth(
|
||||
accountEntropyPool: accountEntropyPool,
|
||||
accountIdentity: identity
|
||||
)
|
||||
let metadataHeader = try await self.deps.backupArchiveManager.backupCdnInfo(
|
||||
backupKey: backupKey,
|
||||
@ -1472,10 +1470,9 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
|
||||
do {
|
||||
// For manual restore, fetch the backup info
|
||||
let backupKey = try MessageRootBackupKey(accountEntropyPool: accountEntropyPool, aci: accountIdentity.aci)
|
||||
let backupServiceAuth = try await self.deps.backupRequestManager.fetchBackupServiceAuthForRegistration(
|
||||
key: backupKey,
|
||||
localAci: accountIdentity.aci,
|
||||
chatServiceAuth: accountIdentity.chatServiceAuth
|
||||
let backupServiceAuth = try await self.fetchBackupServiceAuth(
|
||||
accountEntropyPool: accountEntropyPool,
|
||||
accountIdentity: accountIdentity
|
||||
)
|
||||
let cdnInfo = try await self.deps.backupArchiveManager.backupCdnInfo(
|
||||
backupKey: backupKey,
|
||||
@ -1500,11 +1497,6 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
|
||||
|
||||
switch step {
|
||||
case .incorrectRecoveryKey:
|
||||
if self.persistedState.restoreMode == .manualRestore {
|
||||
// If manual restore, there's not much of a recovery path here
|
||||
// so just skip restoring and continue
|
||||
return await updateRestoreMethod(method: .declined).awaitable()
|
||||
}
|
||||
return .enterRecoveryKey(
|
||||
RegistrationEnterAccountEntropyPoolState(
|
||||
canShowBackButton: persistedState.accountIdentity == nil
|
||||
@ -1517,6 +1509,38 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
/// It is possible that, in the time between the last backup and this restore,
|
||||
/// the user has registered without restoring. This can result in the AEP being
|
||||
/// rotated and a new ACI+AEP backupId being registered. If this happens,
|
||||
/// fetching auth credentials using the original AEP will fail.
|
||||
/// The good news is this may be recoverable by re-registering the passed in ACI+AEP
|
||||
/// backupId as the current backupId. Once that is done, silently retry fetching credentials.
|
||||
/// If the fetch still fails, throw an error.
|
||||
private func fetchBackupServiceAuth(
|
||||
accountEntropyPool: SignalServiceKit.AccountEntropyPool,
|
||||
accountIdentity: AccountIdentity
|
||||
) async throws -> BackupServiceAuth {
|
||||
let backupKey = try MessageRootBackupKey(accountEntropyPool: accountEntropyPool, aci: accountIdentity.aci)
|
||||
|
||||
func fetchBackupServiceAuth() async throws -> BackupServiceAuth {
|
||||
return try await self.deps.backupRequestManager.fetchBackupServiceAuthForRegistration(
|
||||
key: backupKey,
|
||||
localAci: accountIdentity.aci,
|
||||
chatServiceAuth: accountIdentity.chatServiceAuth
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
return try await fetchBackupServiceAuth()
|
||||
} catch SignalError.verificationFailed {
|
||||
try await self.deps.backupIdService.updateMessageBackupIdForRegistration(
|
||||
key: backupKey,
|
||||
auth: accountIdentity.chatServiceAuth
|
||||
)
|
||||
return try await fetchBackupServiceAuth()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func updateAccountAttributesAndFinish(
|
||||
accountIdentity: AccountIdentity,
|
||||
|
||||
@ -108,6 +108,7 @@ public class RegistrationCoordinatorTest {
|
||||
accountEntropyPoolGenerator: { Stubs.accountEntropyPoolToGenerate },
|
||||
accountKeyStore: accountKeyStore,
|
||||
backupArchiveManager: BackupArchiveManagerMock(),
|
||||
backupIdService: MockBackupIdService(),
|
||||
backupNonceStore: BackupNonceMetadataStore(),
|
||||
backupRequestManager: BackupRequestManagerMock(),
|
||||
changeNumberPniManager: changeNumberPniManager,
|
||||
|
||||
@ -6905,7 +6905,7 @@
|
||||
"REGISTER_RATE_LIMITING_ERROR" = "You have tried too often. Please wait a minute before trying again.";
|
||||
|
||||
/* Body for a sheet warning users about a missing backup. */
|
||||
"REGISTRATION_BACKUP_RESTORE_ERROR_BACKUP_NOT_FOUND_BODY" = "The recovery key you entered is correct, but there is no backup associated with it. If you still have your old phone, make sure backups are enabled and that a backup has been completed and try again.";
|
||||
"REGISTRATION_BACKUP_RESTORE_ERROR_BACKUP_NOT_FOUND_BODY" = "There is no backup associated with this recovery key. Please ensure the key is correct and try again.";
|
||||
|
||||
/* Title for a sheet warning users about a missing backup. */
|
||||
"REGISTRATION_BACKUP_RESTORE_ERROR_BACKUP_NOT_FOUND_TITLE" = "Backup Not Found";
|
||||
|
||||
@ -17,6 +17,11 @@ public protocol BackupIdService {
|
||||
localAci: Aci,
|
||||
auth: ChatServiceAuth,
|
||||
) async throws
|
||||
|
||||
func updateMessageBackupIdForRegistration(
|
||||
key: MessageRootBackupKey,
|
||||
auth: ChatServiceAuth
|
||||
) async throws
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
@ -101,23 +106,40 @@ final class BackupIdServiceImpl: BackupIdService {
|
||||
}
|
||||
}
|
||||
|
||||
func updateMessageBackupIdForRegistration(
|
||||
key: MessageRootBackupKey,
|
||||
auth: ChatServiceAuth
|
||||
) async throws {
|
||||
guard FeatureFlags.Backups.supported else {
|
||||
return
|
||||
}
|
||||
|
||||
try await registerBackupId(
|
||||
localAci: key.aci,
|
||||
messageBackupKey: key,
|
||||
mediaBackupKey: nil,
|
||||
auth: auth
|
||||
)
|
||||
}
|
||||
|
||||
private func registerBackupId(
|
||||
localAci: Aci,
|
||||
messageBackupKey: MessageRootBackupKey,
|
||||
mediaBackupKey: MediaRootBackupKey,
|
||||
mediaBackupKey: MediaRootBackupKey?,
|
||||
auth: ChatServiceAuth
|
||||
) async throws {
|
||||
let messageBackupRequestContext: BackupAuthCredentialRequestContext = .create(
|
||||
backupKey: messageBackupKey.serialize(),
|
||||
aci: localAci.rawUUID
|
||||
)
|
||||
let mediaBackupRequestContext: BackupAuthCredentialRequestContext = .create(
|
||||
backupKey: mediaBackupKey.serialize(),
|
||||
aci: localAci.rawUUID
|
||||
)
|
||||
|
||||
let base64MessageRequestContext = messageBackupRequestContext.getRequest().serialize().base64EncodedString()
|
||||
let base64MediaRequestContext = mediaBackupRequestContext.getRequest().serialize().base64EncodedString()
|
||||
let base64MediaRequestContext = mediaBackupKey.map {
|
||||
let mediaBackupRequestContext: BackupAuthCredentialRequestContext = .create(
|
||||
backupKey: $0.serialize(),
|
||||
aci: localAci.rawUUID
|
||||
)
|
||||
return mediaBackupRequestContext.getRequest().serialize().base64EncodedString()
|
||||
}
|
||||
|
||||
_ = try await networkManager.asyncRequest(
|
||||
.registerBackupId(
|
||||
@ -134,16 +156,18 @@ final class BackupIdServiceImpl: BackupIdService {
|
||||
private extension TSRequest {
|
||||
static func registerBackupId(
|
||||
backupId: String,
|
||||
mediaBackupId: String,
|
||||
mediaBackupId: String?,
|
||||
auth: ChatServiceAuth
|
||||
) -> TSRequest {
|
||||
var parameters = [ "messagesBackupAuthCredentialRequest": backupId ]
|
||||
if let mediaBackupId {
|
||||
parameters["mediaBackupAuthCredentialRequest"] = mediaBackupId
|
||||
}
|
||||
|
||||
var request = TSRequest(
|
||||
url: URL(string: "v1/archives/backupid")!,
|
||||
method: "PUT",
|
||||
parameters: [
|
||||
"messagesBackupAuthCredentialRequest": backupId,
|
||||
"mediaBackupAuthCredentialRequest": mediaBackupId
|
||||
]
|
||||
parameters: parameters
|
||||
)
|
||||
request.auth = .identified(auth)
|
||||
return request
|
||||
@ -155,6 +179,10 @@ private extension TSRequest {
|
||||
#if TESTABLE_BUILD
|
||||
|
||||
class MockBackupIdService: BackupIdService {
|
||||
func updateMessageBackupIdForRegistration(key: MessageRootBackupKey, auth: ChatServiceAuth) async throws {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func registerBackupIDIfNecessary(localAci: Aci, auth: ChatServiceAuth) async throws {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user