mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 05:00:10 +00:00
fix unit tests
This commit is contained in:
@@ -12,7 +12,7 @@ export function deepFreeze<T extends object>(value: T): Readonly<T> {
|
||||
for (const key of keys) {
|
||||
const own = value[key];
|
||||
|
||||
if ((own && typeof own === "object") || typeof own === "function") {
|
||||
if (own && typeof own === "object") {
|
||||
deepFreeze(own);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import {
|
||||
Algorithm,
|
||||
AlgorithmMetadata,
|
||||
Type,
|
||||
GeneratorProfile,
|
||||
Profile,
|
||||
} from "@bitwarden/generator-core";
|
||||
import { GeneratorHistoryService } from "@bitwarden/generator-history";
|
||||
|
||||
@@ -104,8 +106,12 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
profile: GeneratorProfile = Profile.account;
|
||||
|
||||
/** Removes bottom margin, passed to downstream components */
|
||||
@Input({ transform: coerceBooleanProperty }) disableMargin = false;
|
||||
@Input({ transform: coerceBooleanProperty })
|
||||
disableMargin = false;
|
||||
|
||||
/** tracks the currently selected credential type */
|
||||
protected credentialType$ = new BehaviorSubject<CredentialAlgorithm>(Algorithm.password);
|
||||
@@ -125,7 +131,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy
|
||||
*/
|
||||
protected async generate(source: string) {
|
||||
const algorithm = await firstValueFrom(this.algorithm$);
|
||||
const request: GenerateRequest = { source, algorithm: algorithm.id };
|
||||
const request: GenerateRequest = { source, algorithm: algorithm.id, profile: this.profile };
|
||||
|
||||
this.log.debug(request, "generation requested");
|
||||
this.generate$.next(request);
|
||||
@@ -176,10 +182,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy
|
||||
|
||||
// wire up the generator
|
||||
this.generatorService
|
||||
.generate$({
|
||||
on$: this.generate$,
|
||||
account$: this.account$,
|
||||
})
|
||||
.generate$({ on$: this.generate$, account$: this.account$ })
|
||||
.pipe(
|
||||
catchError((error: unknown, generator) => {
|
||||
if (typeof error === "string") {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended";
|
||||
|
||||
import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist";
|
||||
|
||||
import { Algorithm, Type } from "../metadata";
|
||||
|
||||
import { Randomizer } from "./abstractions";
|
||||
import { EmailRandomizer } from "./email-randomizer";
|
||||
|
||||
@@ -41,7 +43,8 @@ describe("EmailRandomizer", () => {
|
||||
async (email) => {
|
||||
const emailRandomizer = new EmailRandomizer(randomizer);
|
||||
|
||||
const result = await emailRandomizer.randomAsciiSubaddress(email);
|
||||
// this tests what happens when the type system is subverted
|
||||
const result = await emailRandomizer.randomAsciiSubaddress(email!);
|
||||
|
||||
expect(result).toEqual("");
|
||||
},
|
||||
@@ -100,7 +103,8 @@ describe("EmailRandomizer", () => {
|
||||
it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => {
|
||||
const emailRandomizer = new EmailRandomizer(randomizer);
|
||||
|
||||
const result = await emailRandomizer.randomAsciiCatchall(domain);
|
||||
// this tests what happens when the type system is subverted
|
||||
const result = await emailRandomizer.randomAsciiCatchall(domain!);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
@@ -150,7 +154,8 @@ describe("EmailRandomizer", () => {
|
||||
it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => {
|
||||
const emailRandomizer = new EmailRandomizer(randomizer);
|
||||
|
||||
const result = await emailRandomizer.randomWordsCatchall(domain);
|
||||
// this tests what happens when the type system is subverted
|
||||
const result = await emailRandomizer.randomWordsCatchall(domain!);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
@@ -214,32 +219,32 @@ describe("EmailRandomizer", () => {
|
||||
const email = new EmailRandomizer(randomizer);
|
||||
|
||||
const result = await email.generate(
|
||||
{},
|
||||
{ algorithm: Algorithm.catchall },
|
||||
{
|
||||
catchallDomain: "example.com",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.category).toEqual("catchall");
|
||||
expect(result.category).toEqual(Type.email);
|
||||
});
|
||||
|
||||
it("processes subaddress generation options", async () => {
|
||||
const email = new EmailRandomizer(randomizer);
|
||||
|
||||
const result = await email.generate(
|
||||
{},
|
||||
{ algorithm: Algorithm.plusAddress },
|
||||
{
|
||||
subaddressEmail: "foo@example.com",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.category).toEqual("subaddress");
|
||||
expect(result.category).toEqual(Type.email);
|
||||
});
|
||||
|
||||
it("throws when it cannot recognize the options type", async () => {
|
||||
const email = new EmailRandomizer(randomizer);
|
||||
|
||||
const result = email.generate({}, {});
|
||||
const result = email.generate({ algorithm: Algorithm.password }, {});
|
||||
|
||||
await expect(result).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended";
|
||||
import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist";
|
||||
|
||||
import { Randomizer } from "../abstractions";
|
||||
import { Algorithm, Type } from "../metadata";
|
||||
|
||||
import { Ascii } from "./data";
|
||||
import { PasswordRandomizer } from "./password-randomizer";
|
||||
@@ -341,32 +342,32 @@ describe("PasswordRandomizer", () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
|
||||
const result = await password.generate(
|
||||
{},
|
||||
{ algorithm: Algorithm.password },
|
||||
{
|
||||
length: 10,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.category).toEqual("password");
|
||||
expect(result.category).toEqual(Type.password);
|
||||
});
|
||||
|
||||
it("processes passphrase generation options", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
|
||||
const result = await password.generate(
|
||||
{},
|
||||
{ algorithm: Algorithm.passphrase },
|
||||
{
|
||||
numWords: 10,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.category).toEqual("passphrase");
|
||||
expect(result.category).toEqual(Type.password);
|
||||
});
|
||||
|
||||
it("throws when it cannot recognize the options type", async () => {
|
||||
const password = new PasswordRandomizer(randomizer);
|
||||
|
||||
const result = password.generate({}, {});
|
||||
const result = password.generate({ algorithm: Algorithm.username }, {});
|
||||
|
||||
await expect(result).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended";
|
||||
|
||||
import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist";
|
||||
|
||||
import { Algorithm, Type } from "../metadata";
|
||||
|
||||
import { Randomizer } from "./abstractions";
|
||||
import { UsernameRandomizer } from "./username-randomizer";
|
||||
|
||||
@@ -108,19 +110,19 @@ describe("UsernameRandomizer", () => {
|
||||
const username = new UsernameRandomizer(randomizer);
|
||||
|
||||
const result = await username.generate(
|
||||
{},
|
||||
{ algorithm: Algorithm.username },
|
||||
{
|
||||
wordIncludeNumber: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.category).toEqual("username");
|
||||
expect(result.category).toEqual(Type.username);
|
||||
});
|
||||
|
||||
it("throws when it cannot recognize the options type", async () => {
|
||||
const username = new UsernameRandomizer(randomizer);
|
||||
|
||||
const result = username.generate({}, {});
|
||||
const result = username.generate({ algorithm: Algorithm.passphrase }, {});
|
||||
|
||||
await expect(result).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ export {
|
||||
Type,
|
||||
Profile,
|
||||
GeneratorMetadata,
|
||||
GeneratorProfile,
|
||||
AlgorithmMetadata,
|
||||
AlgorithmsByType,
|
||||
} from "./metadata";
|
||||
|
||||
@@ -2,7 +2,8 @@ import { mock } from "jest-mock-extended";
|
||||
|
||||
import { EmailRandomizer } from "../../engine";
|
||||
import { CatchallConstraints } from "../../policies/catchall-constraints";
|
||||
import { CatchallGenerationOptions, GeneratorDependencyProvider } from "../../types";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { CatchallGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
@@ -2,7 +2,8 @@ import { mock } from "jest-mock-extended";
|
||||
|
||||
import { EmailRandomizer } from "../../engine";
|
||||
import { SubaddressConstraints } from "../../policies/subaddress-constraints";
|
||||
import { SubaddressGenerationOptions, GeneratorDependencyProvider } from "../../types";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { SubaddressGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
@@ -35,5 +35,11 @@ export { toForwarderMetadata } from "./email/forwarder";
|
||||
export { AlgorithmMetadata } from "./algorithm-metadata";
|
||||
export { GeneratorMetadata } from "./generator-metadata";
|
||||
export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata";
|
||||
export { GeneratorProfile, CredentialAlgorithm, PasswordAlgorithm, CredentialType } from "./type";
|
||||
export {
|
||||
GeneratorProfile,
|
||||
CredentialAlgorithm,
|
||||
PasswordAlgorithm,
|
||||
CredentialType,
|
||||
ForwarderExtensionId,
|
||||
} from "./type";
|
||||
export { isForwarderProfile, toVendorId, isForwarderExtensionId } from "./util";
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
import { PasswordRandomizer } from "../../engine";
|
||||
import { PassphrasePolicyConstraints } from "../../policies";
|
||||
import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { PassphraseGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
import { PasswordRandomizer } from "../../engine";
|
||||
import { DynamicPasswordPolicyConstraints } from "../../policies";
|
||||
import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { PasswordGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
@@ -3,7 +3,8 @@ import { mock } from "jest-mock-extended";
|
||||
import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
|
||||
|
||||
import { UsernameRandomizer } from "../../engine";
|
||||
import { EffUsernameGenerationOptions, GeneratorDependencyProvider } from "../../types";
|
||||
import { GeneratorDependencyProvider } from "../../providers";
|
||||
import { EffUsernameGenerationOptions } from "../../types";
|
||||
import { Profile } from "../data";
|
||||
import { CoreProfileMetadata } from "../profile-metadata";
|
||||
import { isCoreProfile } from "../util";
|
||||
|
||||
@@ -14,16 +14,16 @@ import { passphraseLeastPrivilege } from "./passphrase-least-privilege";
|
||||
const Passphrase: PolicyConfiguration<PassphraseGeneratorPolicy, PassphraseGenerationOptions> =
|
||||
deepFreeze({
|
||||
type: PolicyType.PasswordGenerator,
|
||||
disabledValue: Object.freeze({
|
||||
disabledValue: {
|
||||
minNumberWords: 0,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
}),
|
||||
},
|
||||
combine: passphraseLeastPrivilege,
|
||||
createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy),
|
||||
});
|
||||
|
||||
describe("Password generator options builder", () => {
|
||||
describe("Passphrase generator options builder", () => {
|
||||
describe("constructor()", () => {
|
||||
it("should set the policy object to a copy of the input policy", () => {
|
||||
const policy: any = Object.assign({}, Passphrase.disabledValue);
|
||||
|
||||
@@ -24,34 +24,56 @@ describe("PREFERENCES", () => {
|
||||
// this case tests what happens when the type system is bypassed
|
||||
const result = PREFERENCES.deserializer(value!);
|
||||
|
||||
expect(result).toEqual(SomeCredentialPreferences);
|
||||
expect(result).toMatchObject({
|
||||
email: {
|
||||
algorithm: AlgorithmsByType[Type.email][0],
|
||||
},
|
||||
password: {
|
||||
algorithm: AlgorithmsByType[Type.password][0],
|
||||
},
|
||||
username: {
|
||||
algorithm: AlgorithmsByType[Type.username][0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("fills missing password preferences", () => {
|
||||
const input: any = { ...SomeCredentialPreferences };
|
||||
const input: any = structuredClone(SomeCredentialPreferences);
|
||||
delete input.password;
|
||||
|
||||
const result = PREFERENCES.deserializer(input);
|
||||
|
||||
expect(result).toEqual(SomeCredentialPreferences);
|
||||
expect(result).toMatchObject({
|
||||
password: {
|
||||
algorithm: AlgorithmsByType[Type.password][0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("fills missing email preferences", () => {
|
||||
const input: any = { ...SomeCredentialPreferences };
|
||||
const input: any = structuredClone(SomeCredentialPreferences);
|
||||
delete input.email;
|
||||
|
||||
const result = PREFERENCES.deserializer(input);
|
||||
|
||||
expect(result).toEqual(SomeCredentialPreferences);
|
||||
expect(result).toMatchObject({
|
||||
email: {
|
||||
algorithm: AlgorithmsByType[Type.email][0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("fills missing username preferences", () => {
|
||||
const input: any = { ...SomeCredentialPreferences };
|
||||
const input: any = structuredClone(SomeCredentialPreferences);
|
||||
delete input.username;
|
||||
|
||||
const result = PREFERENCES.deserializer(input);
|
||||
|
||||
expect(result).toEqual(SomeCredentialPreferences);
|
||||
expect(result).toMatchObject({
|
||||
username: {
|
||||
algorithm: AlgorithmsByType[Type.username][0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("converts string fields to Dates", () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { FakeAccountService, FakeStateProvider } from "@bitwarden/common/spec";
|
||||
import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider";
|
||||
import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction";
|
||||
import {
|
||||
@@ -24,6 +23,7 @@ import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/stat
|
||||
import { deepFreeze } from "@bitwarden/common/tools/util";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { FakeAccountService, FakeStateProvider } from "../../../../../common/spec";
|
||||
import { Algorithm, AlgorithmsByType, CredentialAlgorithm, Type, Types } from "../metadata";
|
||||
import catchall from "../metadata/email/catchall";
|
||||
import plusAddress from "../metadata/email/plus-address";
|
||||
|
||||
@@ -6,7 +6,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
import { FakeStateProvider, FakeAccountService, awaitAsync } from "@bitwarden/common/spec";
|
||||
import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider";
|
||||
import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction";
|
||||
import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log";
|
||||
@@ -16,6 +15,7 @@ import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/stat
|
||||
import { StateConstraints } from "@bitwarden/common/tools/types";
|
||||
import { OrganizationId, PolicyId, UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { FakeStateProvider, FakeAccountService, awaitAsync } from "../../../../../common/spec";
|
||||
import { CoreProfileMetadata, ProfileContext } from "../metadata/profile-metadata";
|
||||
import { GeneratorConstraints } from "../types";
|
||||
|
||||
|
||||
@@ -1,5 +1,355 @@
|
||||
describe("CredentialGeneratorService", () => {
|
||||
describe("settings", () => {});
|
||||
import { BehaviorSubject, firstValueFrom, of } from "rxjs";
|
||||
|
||||
describe("policy$", () => {});
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { Site, VendorId } from "@bitwarden/common/tools/extension";
|
||||
import { Bitwarden } from "@bitwarden/common/tools/extension/vendor/bitwarden";
|
||||
import { Vendor } from "@bitwarden/common/tools/extension/vendor/data";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
Algorithm,
|
||||
CredentialAlgorithm,
|
||||
CredentialType,
|
||||
ForwarderExtensionId,
|
||||
GeneratorMetadata,
|
||||
Profile,
|
||||
} from "../metadata";
|
||||
import { CredentialGeneratorProviders } from "../providers";
|
||||
|
||||
import { DefaultCredentialGeneratorService } from "./credential-generator.service";
|
||||
|
||||
// Custom type for jest.fn() mocks to preserve their type
|
||||
type JestMockFunction<T extends (...args: any) => any> = jest.Mock<ReturnType<T>, Parameters<T>>;
|
||||
|
||||
// two-level partial that preserves jest.fn() mock types
|
||||
type MockTwoLevelPartial<T> = {
|
||||
[K in keyof T]?: T[K] extends object
|
||||
? {
|
||||
[P in keyof T[K]]?: T[K][P] extends (...args: any) => any
|
||||
? JestMockFunction<T[K][P]>
|
||||
: T[K][P];
|
||||
}
|
||||
: T[K];
|
||||
};
|
||||
|
||||
describe("CredentialGeneratorService", () => {
|
||||
let service: DefaultCredentialGeneratorService;
|
||||
let providers: MockTwoLevelPartial<CredentialGeneratorProviders>;
|
||||
let system: any;
|
||||
let mockLogger: any;
|
||||
let mockExtension: { settings: jest.Mock };
|
||||
let account: Account;
|
||||
let createService: (overrides?: any) => DefaultCredentialGeneratorService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
info: jest.fn(),
|
||||
panic: jest.fn().mockImplementationOnce((c, m) => {
|
||||
throw new Error(m ?? c);
|
||||
}),
|
||||
};
|
||||
|
||||
mockExtension = { settings: jest.fn() };
|
||||
|
||||
// Use a hard-coded value for mockAccount
|
||||
account = {
|
||||
id: "test-account-id" as UserId,
|
||||
emailVerified: true,
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
};
|
||||
|
||||
system = {
|
||||
log: jest.fn().mockReturnValue(mockLogger),
|
||||
extension: mockExtension,
|
||||
};
|
||||
|
||||
providers = {
|
||||
metadata: {
|
||||
metadata: jest.fn(),
|
||||
preference$: jest.fn(),
|
||||
algorithms$: jest.fn(),
|
||||
algorithms: jest.fn(),
|
||||
preferences: jest.fn(),
|
||||
},
|
||||
profile: {
|
||||
settings: jest.fn(),
|
||||
constraints$: jest.fn(),
|
||||
},
|
||||
generator: {},
|
||||
};
|
||||
|
||||
// Creating the service instance with a cast to the expected type
|
||||
createService = (overrides = {}) => {
|
||||
// Force cast the incomplete providers to the required type
|
||||
// similar to how the overrides are applied
|
||||
const providersCast = providers as unknown as CredentialGeneratorProviders;
|
||||
|
||||
const instance = new DefaultCredentialGeneratorService(providersCast, system);
|
||||
Object.assign(instance, overrides);
|
||||
return instance;
|
||||
};
|
||||
|
||||
service = createService();
|
||||
});
|
||||
|
||||
describe("generate$", () => {
|
||||
it("should generate credentials when provided a specific algorithm", async () => {
|
||||
const mockEngine = { generate: jest.fn().mockReturnValue(of("generatedPassword")) };
|
||||
const mockMetadata = {
|
||||
id: "testAlgorithm",
|
||||
engine: { create: jest.fn().mockReturnValue(mockEngine) },
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
const mockSettings = new BehaviorSubject({ length: 12 });
|
||||
|
||||
providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata);
|
||||
service = createService({
|
||||
settings: () => mockSettings as any,
|
||||
});
|
||||
|
||||
const dependencies = {
|
||||
on$: of({ algorithm: "testAlgorithm" as CredentialAlgorithm }),
|
||||
account$: of(account),
|
||||
};
|
||||
|
||||
const result = await firstValueFrom(service.generate$(dependencies));
|
||||
|
||||
expect(result).toBe("generatedPassword");
|
||||
expect(providers.metadata!.metadata).toHaveBeenCalledWith("testAlgorithm");
|
||||
expect(mockMetadata.engine.create).toHaveBeenCalled();
|
||||
expect(mockEngine.generate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should determine preferred algorithm from credential type and generate credentials", async () => {
|
||||
const mockEngine = { generate: jest.fn().mockReturnValue(of("generatedPassword")) };
|
||||
const mockMetadata = {
|
||||
id: "testAlgorithm",
|
||||
engine: { create: jest.fn().mockReturnValue(mockEngine) },
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
const mockSettings = new BehaviorSubject({ length: 12 });
|
||||
|
||||
providers.metadata!.preference$ = jest
|
||||
.fn()
|
||||
.mockReturnValue(of("testAlgorithm" as CredentialAlgorithm));
|
||||
providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata);
|
||||
service = createService({
|
||||
settings: () => mockSettings as any,
|
||||
});
|
||||
|
||||
const dependencies = {
|
||||
on$: of({ type: "password" as CredentialType }),
|
||||
account$: of(account),
|
||||
};
|
||||
|
||||
const result = await firstValueFrom(service.generate$(dependencies));
|
||||
|
||||
expect(result).toBe("generatedPassword");
|
||||
expect(providers.metadata!.metadata).toHaveBeenCalledWith("testAlgorithm");
|
||||
});
|
||||
});
|
||||
|
||||
describe("algorithms$", () => {
|
||||
it("should retrieve and map available algorithms for a credential type", async () => {
|
||||
const mockAlgorithms = [Algorithm.password, Algorithm.passphrase] as CredentialAlgorithm[];
|
||||
const mockMetadata1 = { id: Algorithm.password } as GeneratorMetadata<any>;
|
||||
const mockMetadata2 = { id: Algorithm.passphrase } as GeneratorMetadata<any>;
|
||||
|
||||
providers.metadata!.algorithms$ = jest.fn().mockReturnValue(of(mockAlgorithms));
|
||||
providers.metadata!.metadata = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(mockMetadata1)
|
||||
.mockReturnValueOnce(mockMetadata2);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
service.algorithms$("password" as CredentialType, { account$: of(account) }),
|
||||
);
|
||||
|
||||
expect(result).toEqual([mockMetadata1, mockMetadata2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("algorithms", () => {
|
||||
it("should list algorithm metadata for a single credential type", () => {
|
||||
providers.metadata!.algorithms = jest
|
||||
.fn()
|
||||
.mockReturnValue([Algorithm.password, Algorithm.passphrase] as CredentialAlgorithm[]);
|
||||
service = createService({
|
||||
algorithm: (id: CredentialAlgorithm) => ({ id }) as GeneratorMetadata<any>,
|
||||
});
|
||||
|
||||
const result = service.algorithms("password" as CredentialType);
|
||||
|
||||
expect(result).toEqual([{ id: Algorithm.password }, { id: Algorithm.passphrase }]);
|
||||
expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "password" });
|
||||
});
|
||||
|
||||
it("should list combined algorithm metadata for multiple credential types", () => {
|
||||
providers.metadata!.algorithms = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce([Algorithm.password] as CredentialAlgorithm[])
|
||||
.mockReturnValueOnce([Algorithm.username] as CredentialAlgorithm[]);
|
||||
|
||||
service = createService({
|
||||
algorithm: (id: CredentialAlgorithm) => ({ id }) as GeneratorMetadata<any>,
|
||||
});
|
||||
|
||||
const result = service.algorithms(["password", "username"] as CredentialType[]);
|
||||
|
||||
expect(result).toEqual([{ id: Algorithm.password }, { id: Algorithm.username }]);
|
||||
expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "password" });
|
||||
expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "username" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("algorithm", () => {
|
||||
it("should retrieve metadata for a specific generator algorithm", () => {
|
||||
const mockMetadata = { id: Algorithm.password } as GeneratorMetadata<any>;
|
||||
providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata);
|
||||
|
||||
const result = service.algorithm(Algorithm.password);
|
||||
|
||||
expect(result).toBe(mockMetadata);
|
||||
expect(providers.metadata!.metadata).toHaveBeenCalledWith(Algorithm.password);
|
||||
});
|
||||
|
||||
it("should log a panic when algorithm ID is invalid", () => {
|
||||
providers.metadata!.metadata = jest.fn().mockReturnValue(null);
|
||||
|
||||
expect(() => service.algorithm("invalidAlgo" as CredentialAlgorithm)).toThrow(
|
||||
"invalid credential algorithm",
|
||||
);
|
||||
expect(mockLogger.panic).toHaveBeenCalledWith(
|
||||
{ algorithm: "invalidAlgo" },
|
||||
"invalid credential algorithm",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("forwarder", () => {
|
||||
it("should retrieve forwarder metadata for a specific vendor", () => {
|
||||
const vendorId = Vendor.bitwarden;
|
||||
const forwarderExtensionId: ForwarderExtensionId = { forwarder: vendorId };
|
||||
const mockMetadata = {
|
||||
id: forwarderExtensionId,
|
||||
type: "email" as CredentialType,
|
||||
} as GeneratorMetadata<any>;
|
||||
|
||||
providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata);
|
||||
|
||||
const result = service.forwarder(vendorId);
|
||||
|
||||
expect(result).toBe(mockMetadata);
|
||||
expect(providers.metadata!.metadata).toHaveBeenCalledWith(forwarderExtensionId);
|
||||
});
|
||||
|
||||
it("should log a panic when vendor ID is invalid", () => {
|
||||
const invalidVendorId = "invalid-vendor" as VendorId;
|
||||
providers.metadata!.metadata = jest.fn().mockReturnValue(null);
|
||||
|
||||
expect(() => service.forwarder(invalidVendorId)).toThrow("invalid vendor");
|
||||
expect(mockLogger.panic).toHaveBeenCalledWith(
|
||||
{ algorithm: invalidVendorId },
|
||||
"invalid vendor",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("preferences", () => {
|
||||
it("should retrieve credential preferences bound to the user's account", () => {
|
||||
const mockPreferences = { defaultType: "password" };
|
||||
providers.metadata!.preferences = jest.fn().mockReturnValue(mockPreferences);
|
||||
|
||||
const result = service.preferences({ account$: of(account) });
|
||||
|
||||
expect(result).toBe(mockPreferences);
|
||||
});
|
||||
});
|
||||
|
||||
describe("settings", () => {
|
||||
it("should load user settings for account-bound profiles", () => {
|
||||
const mockSettings = { value: { length: 12 } };
|
||||
const mockMetadata = {
|
||||
id: "test",
|
||||
profiles: {
|
||||
[Profile.account]: { id: "accountProfile" },
|
||||
},
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
|
||||
providers.profile!.settings = jest.fn().mockReturnValue(mockSettings);
|
||||
|
||||
const result = service.settings(mockMetadata, { account$: of(account) });
|
||||
|
||||
expect(result).toBe(mockSettings);
|
||||
});
|
||||
|
||||
it("should load user settings for extension-bound profiles", () => {
|
||||
const mockSettings = new BehaviorSubject({ value: { length: 12 } });
|
||||
const vendorId = Vendor.bitwarden;
|
||||
const forwarderProfile = {
|
||||
id: { forwarder: Bitwarden.id },
|
||||
site: Site.forwarder,
|
||||
type: "extension",
|
||||
};
|
||||
const mockMetadata = {
|
||||
id: { forwarder: vendorId } as ForwarderExtensionId,
|
||||
profiles: {
|
||||
[Profile.account]: forwarderProfile,
|
||||
},
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
|
||||
mockExtension.settings.mockReturnValue(mockSettings);
|
||||
|
||||
const result = service.settings(mockMetadata, { account$: of(account) });
|
||||
|
||||
expect(result).toBe(mockSettings);
|
||||
});
|
||||
|
||||
it("should log a panic when profile metadata is not found", () => {
|
||||
const mockMetadata = {
|
||||
id: "test",
|
||||
profiles: {},
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
|
||||
expect(() => service.settings(mockMetadata, { account$: of(account) })).toThrow(
|
||||
"failed to load settings; profile metadata not found",
|
||||
);
|
||||
expect(mockLogger.panic).toHaveBeenCalledWith(
|
||||
{ algorithm: "test", profile: "account" },
|
||||
"failed to load settings; profile metadata not found",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy$", () => {
|
||||
it("should retrieve policy constraints for a specific profile", async () => {
|
||||
const mockConstraints = { minLength: 8 };
|
||||
const mockMetadata = {
|
||||
id: "test",
|
||||
profiles: {
|
||||
[Profile.account]: { id: "accountProfile" },
|
||||
},
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
|
||||
providers.profile!.constraints$ = jest.fn().mockReturnValue(of(mockConstraints));
|
||||
|
||||
const result = await firstValueFrom(service.policy$(mockMetadata, { account$: of(account) }));
|
||||
|
||||
expect(result).toEqual(mockConstraints);
|
||||
});
|
||||
|
||||
it("should log a panic when profile metadata is not found for policy retrieval", () => {
|
||||
const mockMetadata = {
|
||||
id: "test",
|
||||
profiles: {},
|
||||
} as unknown as GeneratorMetadata<any>;
|
||||
|
||||
expect(() => service.policy$(mockMetadata, { account$: of(account) })).toThrow(
|
||||
"failed to load policy; profile metadata not found",
|
||||
);
|
||||
expect(mockLogger.panic).toHaveBeenCalledWith(
|
||||
{ algorithm: "test", profile: "account" },
|
||||
"failed to load policy; profile metadata not found",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ import { DefaultGeneratorService } from "./default-generator.service";
|
||||
function mockPolicyService(config?: { state?: BehaviorSubject<Policy[]> }) {
|
||||
const service = mock<PolicyService>();
|
||||
|
||||
const stateValue = config?.state ?? new BehaviorSubject<Policy[]>([null]);
|
||||
const stateValue = config?.state ?? new BehaviorSubject<Policy[]>([]);
|
||||
service.getAll$.mockReturnValue(stateValue);
|
||||
|
||||
return service;
|
||||
@@ -119,22 +119,22 @@ describe("Password generator service", () => {
|
||||
|
||||
it("should update the evaluator when the password generator policy changes", async () => {
|
||||
// set up dependencies
|
||||
const state = new BehaviorSubject<Policy[]>([null]);
|
||||
const state = new BehaviorSubject<Policy[]>([]);
|
||||
const policy = mockPolicyService({ state });
|
||||
const strategy = mockGeneratorStrategy();
|
||||
const service = new DefaultGeneratorService(strategy, policy);
|
||||
|
||||
// model responses for the observable update. The map is called multiple times,
|
||||
// and the array shift ensures reference equality is maintained.
|
||||
const firstEvaluator = mock<PolicyEvaluator<any, any>>();
|
||||
const secondEvaluator = mock<PolicyEvaluator<any, any>>();
|
||||
const firstEvaluator: PolicyEvaluator<any, any> = mock<PolicyEvaluator<any, any>>();
|
||||
const secondEvaluator: PolicyEvaluator<any, any> = mock<PolicyEvaluator<any, any>>();
|
||||
const evaluators = [firstEvaluator, secondEvaluator];
|
||||
strategy.toEvaluator.mockReturnValueOnce(pipe(map(() => evaluators.shift())));
|
||||
strategy.toEvaluator.mockReturnValueOnce(pipe(map(() => evaluators.shift()!)));
|
||||
|
||||
// act
|
||||
const evaluator$ = service.evaluator$(SomeUser);
|
||||
const firstResult = await firstValueFrom(evaluator$);
|
||||
state.next([null]);
|
||||
state.next([]);
|
||||
const secondResult = await firstValueFrom(evaluator$);
|
||||
|
||||
// assert
|
||||
|
||||
@@ -13,7 +13,7 @@ describe("GeneratedCredential", () => {
|
||||
it("assigns category", () => {
|
||||
const result = new GeneratedCredential("example", Type.password, new Date(100));
|
||||
|
||||
expect(result.category).toEqual("passphrase");
|
||||
expect(result.category).toEqual(Type.password);
|
||||
});
|
||||
|
||||
it("passes through date parameters", () => {
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
import { GeneratorCategory, GeneratedCredential } from ".";
|
||||
import { Type } from "@bitwarden/generator-core";
|
||||
|
||||
import { GeneratedCredential } from ".";
|
||||
|
||||
describe("GeneratedCredential", () => {
|
||||
describe("constructor", () => {
|
||||
it("assigns credential", () => {
|
||||
const result = new GeneratedCredential("example", "passphrase", new Date(100));
|
||||
const result = new GeneratedCredential("example", Type.password, new Date(100));
|
||||
|
||||
expect(result.credential).toEqual("example");
|
||||
});
|
||||
|
||||
it("assigns category", () => {
|
||||
const result = new GeneratedCredential("example", "passphrase", new Date(100));
|
||||
const result = new GeneratedCredential("example", Type.password, new Date(100));
|
||||
|
||||
expect(result.category).toEqual("passphrase");
|
||||
expect(result.category).toEqual(Type.password);
|
||||
});
|
||||
|
||||
it("passes through date parameters", () => {
|
||||
const result = new GeneratedCredential("example", "password", new Date(100));
|
||||
const result = new GeneratedCredential("example", Type.password, new Date(100));
|
||||
|
||||
expect(result.generationDate).toEqual(new Date(100));
|
||||
});
|
||||
|
||||
it("converts numeric dates to Dates", () => {
|
||||
const result = new GeneratedCredential("example", "password", 100);
|
||||
const result = new GeneratedCredential("example", Type.password, 100);
|
||||
|
||||
expect(result.generationDate).toEqual(new Date(100));
|
||||
});
|
||||
});
|
||||
|
||||
it("toJSON converts from a credential into a JSON object", () => {
|
||||
const credential = new GeneratedCredential("example", "password", new Date(100));
|
||||
const credential = new GeneratedCredential("example", Type.password, new Date(100));
|
||||
|
||||
const result = credential.toJSON();
|
||||
|
||||
expect(result).toEqual({
|
||||
credential: "example",
|
||||
category: "password" as GeneratorCategory,
|
||||
category: Type.password,
|
||||
generationDate: 100,
|
||||
});
|
||||
});
|
||||
@@ -42,7 +44,7 @@ describe("GeneratedCredential", () => {
|
||||
it("fromJSON converts Json objects into credentials", () => {
|
||||
const jsonValue = {
|
||||
credential: "example",
|
||||
category: "password" as GeneratorCategory,
|
||||
category: Type.password,
|
||||
generationDate: 100,
|
||||
};
|
||||
|
||||
@@ -51,7 +53,7 @@ describe("GeneratedCredential", () => {
|
||||
expect(result).toBeInstanceOf(GeneratedCredential);
|
||||
expect(result).toEqual({
|
||||
credential: "example",
|
||||
category: "password",
|
||||
category: Type.password,
|
||||
generationDate: new Date(100),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey } from "@bitwarden/common/types/key";
|
||||
import { Type } from "@bitwarden/generator-core";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { FakeStateProvider, awaitAsync, mockAccountServiceWith } from "../../../../../common/spec";
|
||||
@@ -23,7 +24,8 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
encryptService.encrypt.mockImplementation((p) => Promise.resolve(p as unknown as EncString));
|
||||
encryptService.decryptToUtf8.mockImplementation((c) => Promise.resolve(c.encryptedString));
|
||||
// tests always provide a value for c.encryptedString
|
||||
encryptService.decryptToUtf8.mockImplementation((c) => Promise.resolve(c.encryptedString!));
|
||||
keyService.getUserKey.mockImplementation(() => Promise.resolve(userKey));
|
||||
keyService.userKey$.mockImplementation(() => of(true as unknown as UserKey));
|
||||
});
|
||||
@@ -48,35 +50,35 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
await awaitAsync();
|
||||
const [result] = await firstValueFrom(history.credentials$(SomeUser));
|
||||
|
||||
expect(result).toMatchObject({ credential: "example", category: "password" });
|
||||
expect(result).toMatchObject({ credential: "example", category: Type.password });
|
||||
});
|
||||
|
||||
it("stores a passphrase", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
|
||||
await history.track(SomeUser, "example", "passphrase");
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
await awaitAsync();
|
||||
const [result] = await firstValueFrom(history.credentials$(SomeUser));
|
||||
|
||||
expect(result).toMatchObject({ credential: "example", category: "passphrase" });
|
||||
expect(result).toMatchObject({ credential: "example", category: Type.password });
|
||||
});
|
||||
|
||||
it("stores a specific date when one is provided", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
|
||||
await history.track(SomeUser, "example", "password", new Date(100));
|
||||
await history.track(SomeUser, "example", Type.password, new Date(100));
|
||||
await awaitAsync();
|
||||
const [result] = await firstValueFrom(history.credentials$(SomeUser));
|
||||
|
||||
expect(result).toEqual({
|
||||
credential: "example",
|
||||
category: "password",
|
||||
category: Type.password,
|
||||
generationDate: new Date(100),
|
||||
});
|
||||
});
|
||||
@@ -85,13 +87,13 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "example", "passphrase");
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
await awaitAsync();
|
||||
const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser));
|
||||
|
||||
expect(firstResult).toMatchObject({ credential: "example", category: "password" });
|
||||
expect(firstResult).toMatchObject({ credential: "example", category: Type.password });
|
||||
expect(secondResult).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -99,13 +101,13 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
|
||||
await history.track(SomeUser, "secondResult", "password");
|
||||
await history.track(SomeUser, "firstResult", "password");
|
||||
await history.track(SomeUser, "secondResult", Type.password);
|
||||
await history.track(SomeUser, "firstResult", Type.password);
|
||||
await awaitAsync();
|
||||
const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser));
|
||||
|
||||
expect(firstResult).toMatchObject({ credential: "firstResult", category: "password" });
|
||||
expect(secondResult).toMatchObject({ credential: "secondResult", category: "password" });
|
||||
expect(firstResult).toMatchObject({ credential: "firstResult", category: Type.password });
|
||||
expect(secondResult).toMatchObject({ credential: "secondResult", category: Type.password });
|
||||
});
|
||||
|
||||
it("removes history items exceeding maxTotal configuration", async () => {
|
||||
@@ -114,12 +116,12 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
maxTotal: 1,
|
||||
});
|
||||
|
||||
await history.track(SomeUser, "removed result", "password");
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "removed result", Type.password);
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
await awaitAsync();
|
||||
const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser));
|
||||
|
||||
expect(firstResult).toMatchObject({ credential: "example", category: "password" });
|
||||
expect(firstResult).toMatchObject({ credential: "example", category: Type.password });
|
||||
expect(secondResult).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -129,8 +131,8 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
maxTotal: 1,
|
||||
});
|
||||
|
||||
await history.track(SomeUser, "some user example", "password");
|
||||
await history.track(AnotherUser, "another user example", "password");
|
||||
await history.track(SomeUser, "some user example", Type.password);
|
||||
await history.track(AnotherUser, "another user example", Type.password);
|
||||
await awaitAsync();
|
||||
const [someFirstResult, someSecondResult] = await firstValueFrom(
|
||||
history.credentials$(SomeUser),
|
||||
@@ -141,12 +143,12 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
|
||||
expect(someFirstResult).toMatchObject({
|
||||
credential: "some user example",
|
||||
category: "password",
|
||||
category: Type.password,
|
||||
});
|
||||
expect(someSecondResult).toBeUndefined();
|
||||
expect(anotherFirstResult).toMatchObject({
|
||||
credential: "another user example",
|
||||
category: "password",
|
||||
category: Type.password,
|
||||
});
|
||||
expect(anotherSecondResult).toBeUndefined();
|
||||
});
|
||||
@@ -165,7 +167,7 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
it("returns null when the credential wasn't found", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
|
||||
const result = await history.take(SomeUser, "not found");
|
||||
|
||||
@@ -175,20 +177,20 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
it("returns a matching credential", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
|
||||
const result = await history.take(SomeUser, "example");
|
||||
|
||||
expect(result).toMatchObject({
|
||||
credential: "example",
|
||||
category: "password",
|
||||
category: Type.password,
|
||||
});
|
||||
});
|
||||
|
||||
it("removes a matching credential", async () => {
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider);
|
||||
await history.track(SomeUser, "example", "password");
|
||||
await history.track(SomeUser, "example", Type.password);
|
||||
|
||||
await history.take(SomeUser, "example");
|
||||
await awaitAsync();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { IntegrationId } from "@bitwarden/common/tools/integration";
|
||||
import { VendorId } from "@bitwarden/common/tools/extension";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
GeneratorService,
|
||||
@@ -204,7 +204,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
const navigation = createNavigationGenerator({
|
||||
type: "passphrase",
|
||||
username: "word",
|
||||
forwarder: "simplelogin" as IntegrationId,
|
||||
forwarder: "simplelogin" as VendorId,
|
||||
});
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
@@ -515,7 +515,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
const navigation = createNavigationGenerator({
|
||||
type: "password",
|
||||
username: "forwarded",
|
||||
forwarder: "firefoxrelay" as IntegrationId,
|
||||
forwarder: "firefoxrelay" as VendorId,
|
||||
});
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { AddyIo } from "@bitwarden/common/tools/extension/vendor/addyio";
|
||||
import { DuckDuckGo } from "@bitwarden/common/tools/extension/vendor/duckduckgo";
|
||||
import { Fastmail } from "@bitwarden/common/tools/extension/vendor/fastmail";
|
||||
import { ForwardEmail } from "@bitwarden/common/tools/extension/vendor/forwardemail";
|
||||
import { Mozilla } from "@bitwarden/common/tools/extension/vendor/mozilla";
|
||||
import { SimpleLogin } from "@bitwarden/common/tools/extension/vendor/simplelogin";
|
||||
import { IntegrationId } from "@bitwarden/common/tools/integration";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
ApiOptions,
|
||||
@@ -13,13 +22,6 @@ import {
|
||||
DefaultCatchallOptions,
|
||||
DefaultEffUsernameOptions,
|
||||
EffUsernameGenerationOptions,
|
||||
DefaultAddyIoOptions,
|
||||
DefaultDuckDuckGoOptions,
|
||||
DefaultFastmailOptions,
|
||||
DefaultFirefoxRelayOptions,
|
||||
DefaultForwardEmailOptions,
|
||||
DefaultSimpleLoginOptions,
|
||||
Forwarders,
|
||||
DefaultSubaddressOptions,
|
||||
SubaddressGenerationOptions,
|
||||
policies,
|
||||
@@ -169,7 +171,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
// set up an arbitrary forwarder for the username test; all forwarders tested in their own tests
|
||||
const options = {
|
||||
type: "forwarded",
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedService: AddyIo.id,
|
||||
} as UsernameGeneratorOptions;
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(null, null);
|
||||
addyIo.generate.mockResolvedValue("addyio@example.com");
|
||||
@@ -249,7 +251,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
describe("generateForwarded", () => {
|
||||
it("should generate a AddyIo username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedService: AddyIo.id,
|
||||
forwardedAnonAddyApiToken: "token",
|
||||
forwardedAnonAddyBaseUrl: "https://example.com",
|
||||
forwardedAnonAddyDomain: "example.com",
|
||||
@@ -284,7 +286,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
|
||||
it("should generate a DuckDuckGo username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.DuckDuckGo.id,
|
||||
forwardedService: DuckDuckGo.id,
|
||||
forwardedDuckDuckGoToken: "token",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
@@ -315,7 +317,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
|
||||
it("should generate a Fastmail username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.Fastmail.id,
|
||||
forwardedService: Fastmail.id,
|
||||
forwardedFastmailApiToken: "token",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
@@ -346,7 +348,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
|
||||
it("should generate a FirefoxRelay username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.FirefoxRelay.id,
|
||||
forwardedService: Mozilla.id,
|
||||
forwardedFirefoxApiToken: "token",
|
||||
website: "example.com",
|
||||
} as UsernameGeneratorOptions;
|
||||
@@ -377,7 +379,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
|
||||
it("should generate a ForwardEmail username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.ForwardEmail.id,
|
||||
forwardedService: ForwardEmail.id,
|
||||
forwardedForwardEmailApiToken: "token",
|
||||
forwardedForwardEmailDomain: "example.com",
|
||||
website: "example.com",
|
||||
@@ -410,7 +412,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
|
||||
it("should generate a SimpleLogin username", async () => {
|
||||
const options = {
|
||||
forwardedService: Forwarders.SimpleLogin.id,
|
||||
forwardedService: SimpleLogin.id,
|
||||
forwardedSimpleLoginApiKey: "token",
|
||||
forwardedSimpleLoginBaseUrl: "https://example.com",
|
||||
website: "example.com",
|
||||
@@ -449,7 +451,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
const navigation = createNavigationGenerator({
|
||||
type: "username",
|
||||
username: "catchall",
|
||||
forwarder: Forwarders.AddyIo.id,
|
||||
forwarder: AddyIo.id,
|
||||
});
|
||||
|
||||
const catchall = createGenerator<CatchallGenerationOptions>(
|
||||
@@ -557,7 +559,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
subaddressEmail: "foo@example.com",
|
||||
catchallType: "random",
|
||||
catchallDomain: "example.com",
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedService: AddyIo.id,
|
||||
forwardedAnonAddyApiToken: "addyIoToken",
|
||||
forwardedAnonAddyDomain: "addyio.example.com",
|
||||
forwardedAnonAddyBaseUrl: "https://addyio.api.example.com",
|
||||
@@ -583,21 +585,36 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
null,
|
||||
DefaultSubaddressOptions,
|
||||
);
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(
|
||||
null,
|
||||
DefaultAddyIoOptions,
|
||||
);
|
||||
const duckDuckGo = createGenerator<ApiOptions>(null, DefaultDuckDuckGoOptions);
|
||||
const fastmail = createGenerator<ApiOptions & EmailPrefixOptions>(
|
||||
null,
|
||||
DefaultFastmailOptions,
|
||||
);
|
||||
const firefoxRelay = createGenerator<ApiOptions>(null, DefaultFirefoxRelayOptions);
|
||||
const forwardEmail = createGenerator<ApiOptions & EmailDomainOptions>(
|
||||
null,
|
||||
DefaultForwardEmailOptions,
|
||||
);
|
||||
const simpleLogin = createGenerator<SelfHostedApiOptions>(null, DefaultSimpleLoginOptions);
|
||||
const addyIo = createGenerator<SelfHostedApiOptions & EmailDomainOptions>(null, {
|
||||
website: null,
|
||||
baseUrl: "https://app.addy.io",
|
||||
token: "",
|
||||
domain: "",
|
||||
});
|
||||
const duckDuckGo = createGenerator<ApiOptions>(null, {
|
||||
website: null,
|
||||
token: "",
|
||||
});
|
||||
const fastmail = createGenerator<ApiOptions & EmailPrefixOptions>(null, {
|
||||
website: "",
|
||||
domain: "",
|
||||
prefix: "",
|
||||
token: "",
|
||||
});
|
||||
const firefoxRelay = createGenerator<ApiOptions>(null, {
|
||||
website: null,
|
||||
token: "",
|
||||
});
|
||||
const forwardEmail = createGenerator<ApiOptions & EmailDomainOptions>(null, {
|
||||
website: null,
|
||||
token: "",
|
||||
domain: "",
|
||||
});
|
||||
const simpleLogin = createGenerator<SelfHostedApiOptions>(null, {
|
||||
website: null,
|
||||
baseUrl: "https://app.simplelogin.io",
|
||||
token: "",
|
||||
});
|
||||
|
||||
const generator = new LegacyUsernameGenerationService(
|
||||
account,
|
||||
@@ -624,16 +641,16 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
subaddressType: DefaultSubaddressOptions.subaddressType,
|
||||
subaddressEmail: DefaultSubaddressOptions.subaddressEmail,
|
||||
forwardedService: DefaultGeneratorNavigation.forwarder,
|
||||
forwardedAnonAddyApiToken: DefaultAddyIoOptions.token,
|
||||
forwardedAnonAddyDomain: DefaultAddyIoOptions.domain,
|
||||
forwardedAnonAddyBaseUrl: DefaultAddyIoOptions.baseUrl,
|
||||
forwardedDuckDuckGoToken: DefaultDuckDuckGoOptions.token,
|
||||
forwardedFastmailApiToken: DefaultFastmailOptions.token,
|
||||
forwardedFirefoxApiToken: DefaultFirefoxRelayOptions.token,
|
||||
forwardedForwardEmailApiToken: DefaultForwardEmailOptions.token,
|
||||
forwardedForwardEmailDomain: DefaultForwardEmailOptions.domain,
|
||||
forwardedSimpleLoginApiKey: DefaultSimpleLoginOptions.token,
|
||||
forwardedSimpleLoginBaseUrl: DefaultSimpleLoginOptions.baseUrl,
|
||||
forwardedAnonAddyApiToken: "",
|
||||
forwardedAnonAddyDomain: "",
|
||||
forwardedAnonAddyBaseUrl: "https://app.addy.io",
|
||||
forwardedDuckDuckGoToken: "",
|
||||
forwardedFastmailApiToken: "",
|
||||
forwardedFirefoxApiToken: "",
|
||||
forwardedForwardEmailApiToken: "",
|
||||
forwardedForwardEmailDomain: "",
|
||||
forwardedSimpleLoginApiKey: "",
|
||||
forwardedSimpleLoginBaseUrl: "https://app.simplelogin.io",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -678,7 +695,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
subaddressEmail: "foo@example.com",
|
||||
catchallType: "random",
|
||||
catchallDomain: "example.com",
|
||||
forwardedService: Forwarders.AddyIo.id,
|
||||
forwardedService: AddyIo.id as IntegrationId,
|
||||
forwardedAnonAddyApiToken: "addyIoToken",
|
||||
forwardedAnonAddyDomain: "addyio.example.com",
|
||||
forwardedAnonAddyBaseUrl: "https://addyio.api.example.com",
|
||||
@@ -697,7 +714,7 @@ describe("LegacyUsernameGenerationService", () => {
|
||||
expect(navigation.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
type: "password",
|
||||
username: "catchall",
|
||||
forwarder: Forwarders.AddyIo.id,
|
||||
forwarder: AddyIo.id,
|
||||
});
|
||||
|
||||
expect(catchall.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { zip, firstValueFrom, map, concatMap, combineLatest } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { Vendor } from "@bitwarden/common/tools/extension/vendor/data";
|
||||
import { IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
@@ -89,12 +90,14 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic
|
||||
const stored = this.toStoredOptions(options);
|
||||
switch (options.forwardedService) {
|
||||
case Forwarders.AddyIo.id:
|
||||
case Vendor.addyio:
|
||||
return this.addyIo.generate(stored.forwarders.addyIo);
|
||||
case Forwarders.DuckDuckGo.id:
|
||||
return this.duckDuckGo.generate(stored.forwarders.duckDuckGo);
|
||||
case Forwarders.Fastmail.id:
|
||||
return this.fastmail.generate(stored.forwarders.fastmail);
|
||||
case Forwarders.FirefoxRelay.id:
|
||||
case Vendor.mozilla:
|
||||
return this.firefoxRelay.generate(stored.forwarders.firefoxRelay);
|
||||
case Forwarders.ForwardEmail.id:
|
||||
return this.forwardEmail.generate(stored.forwarders.forwardEmail);
|
||||
@@ -233,6 +236,7 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic
|
||||
) {
|
||||
switch (forwarder) {
|
||||
case "anonaddy":
|
||||
case "addyio":
|
||||
await this.addyIo.saveOptions(account, options.forwarders.addyIo);
|
||||
return true;
|
||||
case "duckduckgo":
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ForwarderId, UsernameGeneratorType, CredentialAlgorithm } from "@bitwarden/generator-core";
|
||||
import { VendorId } from "@bitwarden/common/tools/extension";
|
||||
import { UsernameGeneratorType, CredentialAlgorithm } from "@bitwarden/generator-core";
|
||||
|
||||
/** Stores credential generator UI state. */
|
||||
export type GeneratorNavigation = {
|
||||
@@ -12,5 +13,5 @@ export type GeneratorNavigation = {
|
||||
username?: UsernameGeneratorType;
|
||||
|
||||
/** When `username === "forwarded"`, this stores the forwarder implementation. */
|
||||
forwarder?: ForwarderId | "";
|
||||
forwarder?: VendorId | "";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user