1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-05 03:03:26 +00:00

Add FIDO2 UV tests

This commit is contained in:
Isaiah Inuwa
2026-01-14 08:15:28 -06:00
parent ec12c7281c
commit 7ea5b11412

View File

@@ -0,0 +1,195 @@
import { Router } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import {
Fido2UserInterfaceService,
Fido2UserVerificationService,
UserInteractionRequired,
} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { mockAccountServiceWith, mockAccountInfoWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherListView } from "@bitwarden/sdk-internal";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
import {
DesktopFido2UserInterfaceSession,
NativeWindowObject,
} from "./desktop-fido2-user-interface.service";
describe("User Verification Tests", () => {
const userId = "testId" as UserId;
const activeAccountSubject = new BehaviorSubject<Account | null>({
id: userId,
...mockAccountInfoWith({
email: "test@example.com",
name: "Test User",
}),
});
let abortController!: AbortController;
let accountService!: AccountService;
let authService!: MockProxy<AuthService>;
let cipherService!: MockProxy<CipherService>;
let desktopSettingsService!: MockProxy<DesktopSettingsService>;
const logService = new ConsoleLogService();
let nativeWindowObject!: NativeWindowObject;
let router!: MockProxy<Router>;
let userInterface!: MockProxy<Fido2UserInterfaceService<NativeWindowObject>>;
let userInterfaceSession!: DesktopFido2UserInterfaceSession;
let userVerificationService!: MockProxy<Fido2UserVerificationService>;
const userVerification = true;
const cipherId1 = "b8da924b-5f69-4d06-8a6f-2d7809e56bb9";
const credId1 = "1234";
const cipherId2 = "b48af562-a186-4131-92f5-ea09ef04d32d";
const credId2 = "5678";
const mockCiphers = [
{
id: cipherId1,
name: "FidoCred1",
type: {
login: {
hasFido2: true,
fido2Credentials: [
{
credentialId: credId2,
rpId: "example.com",
userHandle: "5678",
userName: "bobparr@acme.org",
userDisplayName: "Robert Parr",
counter: "0",
},
],
totp: undefined,
uris: [],
},
},
},
{
id: cipherId2,
name: "FidoCred2",
type: {
login: {
hasFido2: true,
username: undefined,
fido2Credentials: [
{
credentialId: credId1,
rpId: "example.com",
userHandle: "1234",
userName: "mr.incredible@heroes.com",
userDisplayName: "Mr. Incredible",
counter: "0",
},
],
totp: undefined,
uris: [],
},
},
},
] as unknown as CipherListView[];
beforeEach(async () => {
authService = mock<AuthService>();
accountService = mockAccountServiceWith(userId);
cipherService = mock<CipherService>();
cipherService.cipherListViews$.mockReturnValue(of(mockCiphers));
desktopSettingsService = mock<DesktopSettingsService>();
userInterface = mock<Fido2UserInterfaceService<NativeWindowObject>>();
userInterface.newSession.mockResolvedValue(userInterfaceSession);
userVerificationService = mock<Fido2UserVerificationService>();
abortController = new AbortController();
accountService.activeAccount$ = activeAccountSubject;
nativeWindowObject = {
windowXy: { x: 640, y: 480 },
handle: new Uint8Array([6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
};
transactionContext = "AABBCCDDEE==";
userInterfaceSession = new DesktopFido2UserInterfaceSession(
authService,
cipherService,
accountService,
logService,
router,
desktopSettingsService,
userVerificationService,
nativeWindowObject,
abortController,
);
});
describe("UserInteractionRequired on silent assertion", () => {
it("throws when more than one credential found", async () => {
await expect(
async () =>
await userInterfaceSession.pickCredential({
cipherIds: [cipherId1, cipherId2],
userVerification,
assumeUserPresence: false,
isSilent: true,
masterPasswordRepromptRequired: false,
}),
).rejects.toThrow(UserInteractionRequired);
});
it("throws when master password reprompt required", async () => {
await expect(
async () =>
await userInterfaceSession.pickCredential({
cipherIds: [cipherId1],
userVerification,
assumeUserPresence: false,
isSilent: true,
masterPasswordRepromptRequired: true,
}),
).rejects.toThrow(UserInteractionRequired);
});
it("throws when neither userVerification nor assumeUserPresence is true", async () => {
await expect(
async () =>
await userInterfaceSession.pickCredential({
cipherIds: [cipherId1],
userVerification: false,
assumeUserPresence: false,
isSilent: true,
masterPasswordRepromptRequired: false,
}),
).rejects.toThrow(UserInteractionRequired);
});
it("throws when user verification prompt fails", async () => {
userVerificationService.promptForUserVerification.mockRejectedValue("user cancelled");
await expect(
async () =>
await userInterfaceSession.pickCredential({
cipherIds: [cipherId1],
userVerification,
assumeUserPresence: false,
isSilent: true,
masterPasswordRepromptRequired: false,
}),
).rejects.toThrow(UserInteractionRequired);
});
});
it("succeeds when user verification succeeds", async () => {
userVerificationService.promptForUserVerification.mockResolvedValue(true);
const { cipherId, userVerified } = await userInterfaceSession.pickCredential({
cipherIds: [cipherId1],
userVerification,
assumeUserPresence: false,
isSilent: true,
masterPasswordRepromptRequired: false,
});
expect(cipherId).toStrictEqual(cipherId1);
expect(userVerified).toStrictEqual(true);
});
});