mirror of
https://github.com/bitwarden/clients.git
synced 2025-12-10 00:08:42 -06:00
* feat(user-decryption-options) [PM-26413]: Update UserDecryptionOptionsService and tests to use UserId-only APIs. * feat(user-decryption-options) [PM-26413]: Update InternalUserDecryptionOptionsService call sites to use UserId-only API. * feat(user-decryption-options) [PM-26413] Update userDecryptionOptions$ call sites to use the UserId-only API. * feat(user-decryption-options) [PM-26413]: Update additional call sites. * feat(user-decryption-options) [PM-26413]: Update dependencies and an additional call site. * feat(user-verification-service) [PM-26413]: Replace where allowed by unrestricted imports invocation of UserVerificationService.hasMasterPassword (deprecated) with UserDecryptionOptions.hasMasterPasswordById$. Additional work to complete as tech debt tracked in PM-27009. * feat(user-decryption-options) [PM-26413]: Update for non-null strict adherence. * feat(user-decryption-options) [PM-26413]: Update type safety and defensive returns. * chore(user-decryption-options) [PM-26413]: Comment cleanup. * feat(user-decryption-options) [PM-26413]: Update tests. * feat(user-decryption-options) [PM-26413]: Standardize null-checking on active account id for new API consumption. * feat(vault-timeout-settings-service) [PM-26413]: Add test cases to illustrate null active account from AccountService. * fix(fido2-user-verification-service-spec) [PM-26413]: Update test harness to use FakeAccountService. * fix(downstream-components) [PM-26413]: Prefer use of the getUserId operator in all authenticated contexts for user id provided to UserDecryptionOptionsService. --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
258 lines
9.9 KiB
TypeScript
258 lines
9.9 KiB
TypeScript
import { MockProxy, mock } from "jest-mock-extended";
|
|
import { of } from "rxjs";
|
|
|
|
import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
|
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
|
import { UserId } from "@bitwarden/common/types/guid";
|
|
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
|
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
|
import { DialogService } from "@bitwarden/components";
|
|
import { newGuid } from "@bitwarden/guid";
|
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
|
|
|
// FIXME (PM-22628): Popup imports are forbidden in background
|
|
// eslint-disable-next-line no-restricted-imports
|
|
import { SetPinComponent } from "./../../auth/popup/components/set-pin.component";
|
|
import { Fido2UserVerificationService } from "./fido2-user-verification.service";
|
|
|
|
jest.mock("@bitwarden/auth/angular", () => ({
|
|
UserVerificationDialogComponent: {
|
|
open: jest.fn().mockResolvedValue({ userAction: "confirm", verificationSuccess: true }),
|
|
},
|
|
}));
|
|
|
|
jest.mock("../../auth/popup/components/set-pin.component", () => {
|
|
return {
|
|
SetPinComponent: {
|
|
open: jest.fn(),
|
|
},
|
|
};
|
|
});
|
|
|
|
describe("Fido2UserVerificationService", () => {
|
|
let fido2UserVerificationService: Fido2UserVerificationService;
|
|
|
|
let passwordRepromptService: MockProxy<PasswordRepromptService>;
|
|
let userDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
|
let dialogService: MockProxy<DialogService>;
|
|
let accountService: FakeAccountService;
|
|
let cipher: CipherView;
|
|
|
|
beforeEach(() => {
|
|
passwordRepromptService = mock<PasswordRepromptService>();
|
|
userDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
|
dialogService = mock<DialogService>();
|
|
accountService = mockAccountServiceWith(newGuid() as UserId);
|
|
|
|
cipher = createCipherView();
|
|
|
|
fido2UserVerificationService = new Fido2UserVerificationService(
|
|
passwordRepromptService,
|
|
userDecryptionOptionsService,
|
|
dialogService,
|
|
accountService,
|
|
);
|
|
|
|
(UserVerificationDialogComponent.open as jest.Mock).mockResolvedValue({
|
|
userAction: "confirm",
|
|
verificationSuccess: true,
|
|
});
|
|
});
|
|
|
|
describe("handleUserVerification", () => {
|
|
describe("user verification requested is true", () => {
|
|
it("should return true if user is redirected from lock screen and master password reprompt is not required", async () => {
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
true,
|
|
cipher,
|
|
true,
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call master password reprompt dialog if user is redirected from lock screen, has master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(true));
|
|
passwordRepromptService.showPasswordPrompt.mockResolvedValue(true);
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
true,
|
|
cipher,
|
|
true,
|
|
);
|
|
|
|
expect(passwordRepromptService.showPasswordPrompt).toHaveBeenCalled();
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call user verification dialog if user is redirected from lock screen, does not have a master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
true,
|
|
cipher,
|
|
true,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call user verification dialog if user is not redirected from lock screen, does not have a master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
true,
|
|
cipher,
|
|
false,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call master password reprompt dialog if user is not redirected from lock screen, has a master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
|
passwordRepromptService.showPasswordPrompt.mockResolvedValue(true);
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
true,
|
|
cipher,
|
|
false,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call user verification dialog if user is not redirected from lock screen and no master password reprompt is required", async () => {
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
true,
|
|
cipher,
|
|
false,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should prompt user to set pin if user has no verification method", async () => {
|
|
(UserVerificationDialogComponent.open as jest.Mock).mockResolvedValue({
|
|
userAction: "confirm",
|
|
verificationSuccess: false,
|
|
noAvailableClientVerificationMethods: true,
|
|
});
|
|
|
|
await fido2UserVerificationService.handleUserVerification(true, cipher, false);
|
|
|
|
expect(SetPinComponent.open).toHaveBeenCalledWith(dialogService);
|
|
});
|
|
});
|
|
|
|
describe("user verification requested is false", () => {
|
|
it("should return false if user is redirected from lock screen and master password reprompt is not required", async () => {
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
false,
|
|
cipher,
|
|
true,
|
|
);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it("should return false if user is not redirected from lock screen and master password reprompt is not required", async () => {
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
false,
|
|
cipher,
|
|
false,
|
|
);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it("should call master password reprompt dialog if user is redirected from lock screen, has master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(true));
|
|
passwordRepromptService.showPasswordPrompt.mockResolvedValue(true);
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
false,
|
|
cipher,
|
|
true,
|
|
);
|
|
|
|
expect(result).toBe(true);
|
|
expect(passwordRepromptService.showPasswordPrompt).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should call user verification dialog if user is redirected from lock screen, does not have a master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
false,
|
|
cipher,
|
|
true,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call user verification dialog if user is not redirected from lock screen, does not have a master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
false,
|
|
cipher,
|
|
false,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should call master password reprompt dialog if user is not redirected from lock screen, has a master password and master password reprompt is required", async () => {
|
|
cipher.reprompt = CipherRepromptType.Password;
|
|
userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false));
|
|
passwordRepromptService.showPasswordPrompt.mockResolvedValue(true);
|
|
|
|
const result = await fido2UserVerificationService.handleUserVerification(
|
|
false,
|
|
cipher,
|
|
false,
|
|
);
|
|
|
|
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
|
|
verificationType: "client",
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
function createCipherView() {
|
|
const cipher = new CipherView();
|
|
cipher.id = Utils.newGuid();
|
|
cipher.type = CipherType.Login;
|
|
cipher.reprompt = CipherRepromptType.None;
|
|
return cipher;
|
|
}
|