Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
cipherService: cipherService,
clientService: clientService,
errorReporter: errorReporter,
stateService: stateService,
syncService: syncService,
),
)
Expand All @@ -815,6 +816,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
cipherService: cipherService,
clientService: clientService,
errorReporter: errorReporter,
stateService: stateService,
syncService: syncService,
)
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
/// The service used by the application to report non-fatal errors.
private let errorReporter: ErrorReporter

/// The service used by the application to manage account state.
private let stateService: StateService

/// The service used to handle syncing vault data with the API
private let syncService: SyncService

Expand All @@ -26,16 +29,19 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
/// - cipherService: The service used to manage syncing and updates to the user's ciphers.
/// - clientService: The service that handles common client functionality such as encryption and decryption.
/// - errorReporter: The service used by the application to report non-fatal errors.
/// - stateService: The service used by the application to manage account state.
/// - syncService: The service used to handle syncing vault data with the API.
init(
cipherService: CipherService,
clientService: ClientService,
errorReporter: ErrorReporter,
stateService: StateService,
syncService: SyncService,
) {
self.cipherService = cipherService
self.clientService = clientService
self.errorReporter = errorReporter
self.stateService = stateService
self.syncService = syncService
}

Expand All @@ -57,18 +63,49 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
/// This is used to ensure credentials are only returned for the specific user account.
/// - Returns: All the ciphers that matches the filter.
func findCredentials(ids: [Data]?, ripId: String, userHandle: Data?) async throws -> [BitwardenSdk.CipherView] {
do {
try await syncService.fetchSync(forceSync: false)
} catch {
errorReporter.log(error: error)
try await findCredentials(ids: ids, ripId: ripId, shouldCheckSync: true, userHandle: userHandle)
}

/// Saves a cipher credential that contains a Fido2 credential, either creating it or updating it to server.
/// - Parameter cred: Cipher/Credential to add/update.
func saveCredential(cred: BitwardenSdk.EncryptionContext) async throws {
if cred.cipher.id == nil {
try await cipherService.addCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
} else {
try await cipherService.updateCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
}
}

// MARK: Private methods

/// Finds active login ciphers that have Fido2 credentials, match the `ripId` and if `ids` is sent
/// then filters the one which the Fido2 `credentialId` matches some of the one in `ids`.
/// - Parameters:
/// - ids: An array of possible `credentialId` to filter credentials that matches one of them.
/// When `nil` the `credentialId` filter is not applied.
/// - ripId: The `ripId` to match the Fido2 credential `rpId`.
/// - shouldCheckSync: Whether it should check if sync is needed. This is particular useful to avoid
/// infinite loops by calling this method recursively.
/// - userHandle: The user handle (user.id) to match the Fido2 credential. When `nil`, the filter is not applied.
/// This is used to ensure credentials are only returned for the specific user account.
/// - Returns: All the ciphers that matches the filter.
private func findCredentials(
ids: [Data]?,
ripId: String,
shouldCheckSync: Bool,
userHandle: Data?,
) async throws -> [BitwardenSdk.CipherView] {
let activeCiphersWithFido2Credentials = try await cipherService.fetchAllCiphers()
.filter(\.isActiveWithFido2Credentials)
.asyncMap { cipher in
try await self.clientService.vault().ciphers().decrypt(cipher: cipher)
}

var needsSync = false
if shouldCheckSync {
needsSync = await needsSyncCheckingLocally()
}

var result = [BitwardenSdk.CipherView]()
for cipherView in activeCiphersWithFido2Credentials {
let fido2CredentialAutofillViews = try await clientService.platform()
Expand All @@ -91,18 +128,37 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
continue
}

// Only perform sync if it's needed and there are Fido2 credentials with counter.
if needsSync, fido2CredentialAutofillViews.contains(where: \.hasCounter) {
do {
try await syncService.fetchSync(forceSync: false, isPeriodic: true)

// After sync re-call this function to find the up-to-date credentials.
return try await findCredentials(
ids: ids,
ripId: ripId,
shouldCheckSync: false,
userHandle: userHandle,
)
} catch {
errorReporter.log(error: error)
}
}

result.append(cipherView)
}
return result
}

/// Saves a cipher credential that contains a Fido2 credential, either creating it or updating it to server.
/// - Parameter cred: Cipher/Credential to add/update.
func saveCredential(cred: BitwardenSdk.EncryptionContext) async throws {
if cred.cipher.id == nil {
try await cipherService.addCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
} else {
try await cipherService.updateCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
/// Whether the current user needs to perform a sync. It only performs local verifications.
/// - Returns: `true` if needed, `false` otherwise.
private func needsSyncCheckingLocally() async -> Bool {
do {
let userId = try await stateService.getActiveAccountId()
return try await syncService.needsSync(for: userId, onlyCheckLocalData: true)
} catch {
errorReporter.log(error: error)
return false
}
}
}
Expand All @@ -119,7 +175,7 @@ private extension Cipher {
#if DEBUG

/// A wrapper of a `Fido2CredentialStore` which adds debugging info for the `Fido2DebugginReportBuilder`.
class DebuggingFido2CredentialStoreService: Fido2CredentialStore {
final class DebuggingFido2CredentialStoreService: Fido2CredentialStore {
let fido2CredentialStore: Fido2CredentialStore

init(fido2CredentialStore: Fido2CredentialStore) {
Expand Down
Loading
Loading