diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 289e94e6ef..bd9bb6e5f6 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -1,7 +1,7 @@ import { TextEncoder } from "util"; import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; import { UserId } from "../../../types/guid"; @@ -53,7 +53,9 @@ describe("FidoAuthenticatorService", () => { userInterface = mock(); userInterfaceSession = mock(); userInterface.newSession.mockResolvedValue(userInterfaceSession); - syncService = mock(); + syncService = mock({ + activeUserLastSync$: () => of(new Date()), + }); accountService = mock(); authenticator = new Fido2AuthenticatorService( cipherService, diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index ddcc079eb9..8f0523769d 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -94,7 +94,14 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr } await userInterfaceSession.ensureUnlockedVault(); - await this.syncService.fullSync(false); + + // Avoid syncing if we did it reasonably soon as the only reason for syncing is to validate excludeCredentials + const lastSync = await firstValueFrom(this.syncService.activeUserLastSync$()); + const threshold = new Date().getTime() - 1000 * 60 * 30; // 30 minutes ago + + if (!lastSync || lastSync.getTime() < threshold) { + await this.syncService.fullSync(false); + } const existingCipherIds = await this.findExcludedCredentials( params.excludeCredentialDescriptorList, @@ -223,15 +230,17 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr let cipherOptions: CipherView[]; await userInterfaceSession.ensureUnlockedVault(); - await this.syncService.fullSync(false); - if (params.allowCredentialDescriptorList?.length > 0) { - cipherOptions = await this.findCredentialsById( - params.allowCredentialDescriptorList, - params.rpId, - ); - } else { - cipherOptions = await this.findCredentialsByRp(params.rpId); + // Try to find the passkey locally before causing a sync to speed things up + // only skip syncing if we found credentials AND all of them have a counter = 0 + cipherOptions = await this.findCredential(params, cipherOptions); + if ( + cipherOptions.length === 0 || + cipherOptions.some((c) => c.login.fido2Credentials.some((p) => p.counter > 0)) + ) { + // If no passkey is found, or any had a non-zero counter, sync to get the latest data + await this.syncService.fullSync(false); + cipherOptions = await this.findCredential(params, cipherOptions); } if (cipherOptions.length === 0) { @@ -335,6 +344,21 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr } } + private async findCredential( + params: Fido2AuthenticatorGetAssertionParams, + cipherOptions: CipherView[], + ) { + if (params.allowCredentialDescriptorList?.length > 0) { + cipherOptions = await this.findCredentialsById( + params.allowCredentialDescriptorList, + params.rpId, + ); + } else { + cipherOptions = await this.findCredentialsByRp(params.rpId); + } + return cipherOptions; + } + private requiresUserVerificationPrompt( params: Fido2AuthenticatorGetAssertionParams, cipherOptions: CipherView[],