mirror of
https://github.com/bitwarden/browser
synced 2026-01-06 10:33:57 +00:00
[PM-16793] port credential generator service to providers (#14071)
* introduce extension service * deprecate legacy forwarder types * eliminate repeat algorithm emissions * extend logging to preference management * align forwarder ids with vendor ids * fix duplicate policy emissions; debugging required logger enhancements ----- Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
This commit is contained in:
@@ -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),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { CredentialAlgorithm } from "@bitwarden/generator-core";
|
||||
import { CredentialType } from "@bitwarden/generator-core";
|
||||
|
||||
/** A credential generation result */
|
||||
export class GeneratedCredential {
|
||||
@@ -14,7 +14,7 @@ export class GeneratedCredential {
|
||||
*/
|
||||
constructor(
|
||||
readonly credential: string,
|
||||
readonly category: CredentialAlgorithm,
|
||||
readonly category: CredentialType,
|
||||
generationDate: Date | number,
|
||||
) {
|
||||
if (typeof generationDate === "number") {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CredentialAlgorithm } from "@bitwarden/generator-core";
|
||||
import { CredentialType } from "@bitwarden/generator-core";
|
||||
|
||||
import { GeneratedCredential } from "./generated-credential";
|
||||
|
||||
@@ -29,7 +29,7 @@ export abstract class GeneratorHistoryService {
|
||||
track: (
|
||||
userId: UserId,
|
||||
credential: string,
|
||||
category: CredentialAlgorithm,
|
||||
category: CredentialType,
|
||||
date?: Date,
|
||||
) => Promise<GeneratedCredential | null>;
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -25,7 +26,8 @@ describe("LocalGeneratorHistoryService", () => {
|
||||
encryptService.encryptString.mockImplementation((p) =>
|
||||
Promise.resolve(p as unknown as EncString),
|
||||
);
|
||||
encryptService.decryptString.mockImplementation((c) => Promise.resolve(c.encryptedString));
|
||||
// in the test environment `c.encryptedString` always has a value
|
||||
encryptService.decryptString.mockImplementation((c) => Promise.resolve(c.encryptedString!));
|
||||
keyService.getUserKey.mockImplementation(() => Promise.resolve(userKey));
|
||||
keyService.userKey$.mockImplementation(() => of(true as unknown as UserKey));
|
||||
});
|
||||
@@ -50,35 +52,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),
|
||||
});
|
||||
});
|
||||
@@ -87,13 +89,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();
|
||||
});
|
||||
|
||||
@@ -101,13 +103,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 () => {
|
||||
@@ -116,12 +118,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();
|
||||
});
|
||||
|
||||
@@ -131,8 +133,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),
|
||||
@@ -143,12 +145,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();
|
||||
});
|
||||
@@ -167,7 +169,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");
|
||||
|
||||
@@ -177,20 +179,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();
|
||||
|
||||
@@ -9,7 +9,7 @@ import { BufferedState } from "@bitwarden/common/tools/state/buffered-state";
|
||||
import { PaddedDataPacker } from "@bitwarden/common/tools/state/padded-data-packer";
|
||||
import { SecretState } from "@bitwarden/common/tools/state/secret-state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CredentialAlgorithm } from "@bitwarden/generator-core";
|
||||
import { CredentialType } from "@bitwarden/generator-core";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { GeneratedCredential } from "./generated-credential";
|
||||
@@ -36,12 +36,7 @@ export class LocalGeneratorHistoryService extends GeneratorHistoryService {
|
||||
private _credentialStates = new Map<UserId, SingleUserState<GeneratedCredential[]>>();
|
||||
|
||||
/** {@link GeneratorHistoryService.track} */
|
||||
track = async (
|
||||
userId: UserId,
|
||||
credential: string,
|
||||
category: CredentialAlgorithm,
|
||||
date?: Date,
|
||||
) => {
|
||||
track = async (userId: UserId, credential: string, category: CredentialType, date?: Date) => {
|
||||
const state = this.getCredentialState(userId);
|
||||
let result: GeneratedCredential = null;
|
||||
|
||||
|
||||
63
libs/tools/generator/extensions/legacy/src/forwarders.ts
Normal file
63
libs/tools/generator/extensions/legacy/src/forwarders.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { IntegrationId } from "@bitwarden/common/tools/integration";
|
||||
|
||||
export type ForwarderId = IntegrationId;
|
||||
|
||||
/** Metadata format for email forwarding services. */
|
||||
export type ForwarderMetadata = {
|
||||
/** The unique identifier for the forwarder. */
|
||||
id: ForwarderId;
|
||||
|
||||
/** The name of the service the forwarder queries. */
|
||||
name: string;
|
||||
|
||||
/** Whether the forwarder is valid for self-hosted instances of Bitwarden. */
|
||||
validForSelfHosted: boolean;
|
||||
};
|
||||
|
||||
/** Metadata about an email forwarding service.
|
||||
* @remarks This is used to populate the forwarder selection list
|
||||
* and to identify forwarding services in error messages.
|
||||
*/
|
||||
export const Forwarders = Object.freeze({
|
||||
/** For https://addy.io/ */
|
||||
AddyIo: Object.freeze({
|
||||
id: "anonaddy",
|
||||
name: "Addy.io",
|
||||
validForSelfHosted: true,
|
||||
} as ForwarderMetadata),
|
||||
|
||||
/** For https://duckduckgo.com/email/ */
|
||||
DuckDuckGo: Object.freeze({
|
||||
id: "duckduckgo",
|
||||
name: "DuckDuckGo",
|
||||
validForSelfHosted: false,
|
||||
} as ForwarderMetadata),
|
||||
|
||||
/** For https://www.fastmail.com. */
|
||||
Fastmail: Object.freeze({
|
||||
id: "fastmail",
|
||||
name: "Fastmail",
|
||||
validForSelfHosted: true,
|
||||
} as ForwarderMetadata),
|
||||
|
||||
/** For https://relay.firefox.com/ */
|
||||
FirefoxRelay: Object.freeze({
|
||||
id: "firefoxrelay",
|
||||
name: "Firefox Relay",
|
||||
validForSelfHosted: false,
|
||||
} as ForwarderMetadata),
|
||||
|
||||
/** For https://forwardemail.net/ */
|
||||
ForwardEmail: Object.freeze({
|
||||
id: "forwardemail",
|
||||
name: "Forward Email",
|
||||
validForSelfHosted: true,
|
||||
} as ForwarderMetadata),
|
||||
|
||||
/** For https://simplelogin.io/ */
|
||||
SimpleLogin: Object.freeze({
|
||||
id: "simplelogin",
|
||||
name: "SimpleLogin",
|
||||
validForSelfHosted: true,
|
||||
} as ForwarderMetadata),
|
||||
});
|
||||
@@ -1,13 +1,12 @@
|
||||
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,
|
||||
DefaultPassphraseGenerationOptions,
|
||||
DefaultPasswordGenerationOptions,
|
||||
Policies,
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy,
|
||||
PasswordGenerationOptions,
|
||||
@@ -38,12 +37,17 @@ const PasswordGeneratorOptionsEvaluator = policies.PasswordGeneratorOptionsEvalu
|
||||
|
||||
function createPassphraseGenerator(
|
||||
options: PassphraseGenerationOptions = {},
|
||||
policy: PassphraseGeneratorPolicy = Policies.Passphrase.disabledValue,
|
||||
policy?: PassphraseGeneratorPolicy,
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<PassphraseGenerationOptions, PassphraseGeneratorPolicy>>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||
const active = policy ?? {
|
||||
minNumberWords: 0,
|
||||
capitalize: false,
|
||||
includeNumber: false,
|
||||
};
|
||||
const evaluator = new PassphraseGeneratorOptionsEvaluator(active);
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
@@ -63,12 +67,21 @@ function createPassphraseGenerator(
|
||||
|
||||
function createPasswordGenerator(
|
||||
options: PasswordGenerationOptions = {},
|
||||
policy: PasswordGeneratorPolicy = Policies.Password.disabledValue,
|
||||
policy?: PasswordGeneratorPolicy,
|
||||
) {
|
||||
let savedOptions = options;
|
||||
const generator = mock<GeneratorService<PasswordGenerationOptions, PasswordGeneratorPolicy>>({
|
||||
evaluator$(id: UserId) {
|
||||
const evaluator = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const active = policy ?? {
|
||||
minLength: 0,
|
||||
useUppercase: false,
|
||||
useLowercase: false,
|
||||
useNumbers: false,
|
||||
numberCount: 0,
|
||||
useSpecial: false,
|
||||
specialCount: 0,
|
||||
};
|
||||
const evaluator = new PasswordGeneratorOptionsEvaluator(active);
|
||||
return of(evaluator);
|
||||
},
|
||||
options$(id: UserId) {
|
||||
@@ -118,7 +131,13 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
describe("generatePassword", () => {
|
||||
it("invokes the inner password generator to generate passwords", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, innerPassword, null, null);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
null!,
|
||||
null!,
|
||||
innerPassword,
|
||||
null!,
|
||||
null!,
|
||||
);
|
||||
const options = { type: "password" } as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassword(options);
|
||||
@@ -129,11 +148,11 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
it("invokes the inner passphrase generator to generate passphrases", async () => {
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
const options = { type: "passphrase" } as PasswordGeneratorOptions;
|
||||
|
||||
@@ -147,11 +166,11 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
it("invokes the inner passphrase generator", async () => {
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
const options = {} as PasswordGeneratorOptions;
|
||||
|
||||
@@ -185,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(
|
||||
@@ -193,7 +212,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
@@ -220,16 +239,16 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
});
|
||||
|
||||
it("sets default options when an inner service lacks a value", async () => {
|
||||
const innerPassword = createPasswordGenerator(null);
|
||||
const innerPassphrase = createPassphraseGenerator(null);
|
||||
const navigation = createNavigationGenerator(null);
|
||||
const innerPassword = createPasswordGenerator(null!);
|
||||
const innerPassphrase = createPassphraseGenerator(null!);
|
||||
const navigation = createNavigationGenerator(null!);
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
@@ -277,7 +296,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
|
||||
const [, policy] = await generator.getOptions();
|
||||
@@ -323,7 +342,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
|
||||
const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options);
|
||||
@@ -363,7 +382,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
|
||||
const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options);
|
||||
@@ -409,7 +428,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
|
||||
const [, policy] = await generator.enforcePasswordGeneratorPoliciesOnOptions({});
|
||||
@@ -441,7 +460,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
const options = {
|
||||
type: "password" as const,
|
||||
@@ -474,7 +493,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
const options = {
|
||||
type: "passphrase" as const,
|
||||
@@ -496,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(
|
||||
@@ -504,7 +523,7 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
null!,
|
||||
);
|
||||
const options = {
|
||||
type: "passphrase" as const,
|
||||
@@ -533,9 +552,9 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
history,
|
||||
);
|
||||
|
||||
@@ -552,9 +571,9 @@ describe("LegacyPasswordGenerationService", () => {
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
history,
|
||||
);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -14,13 +15,13 @@ import {
|
||||
GeneratorService,
|
||||
CatchallGenerationOptions,
|
||||
EffUsernameGenerationOptions,
|
||||
Forwarders,
|
||||
SubaddressGenerationOptions,
|
||||
UsernameGeneratorType,
|
||||
ForwarderId,
|
||||
} from "@bitwarden/generator-core";
|
||||
import { GeneratorNavigationService, GeneratorNavigation } from "@bitwarden/generator-navigation";
|
||||
|
||||
import { Forwarders } from "./forwarders";
|
||||
import { UsernameGeneratorOptions } from "./username-generation-options";
|
||||
import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
|
||||
|
||||
@@ -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);
|
||||
@@ -232,22 +235,24 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic
|
||||
options: MappedOptions,
|
||||
) {
|
||||
switch (forwarder) {
|
||||
case "anonaddy":
|
||||
case Forwarders.AddyIo.id:
|
||||
case Vendor.addyio:
|
||||
await this.addyIo.saveOptions(account, options.forwarders.addyIo);
|
||||
return true;
|
||||
case "duckduckgo":
|
||||
case Forwarders.DuckDuckGo.id:
|
||||
await this.duckDuckGo.saveOptions(account, options.forwarders.duckDuckGo);
|
||||
return true;
|
||||
case "fastmail":
|
||||
case Forwarders.Fastmail.id:
|
||||
await this.fastmail.saveOptions(account, options.forwarders.fastmail);
|
||||
return true;
|
||||
case "firefoxrelay":
|
||||
case Forwarders.FirefoxRelay.id:
|
||||
case Vendor.mozilla:
|
||||
await this.firefoxRelay.saveOptions(account, options.forwarders.firefoxRelay);
|
||||
return true;
|
||||
case "forwardemail":
|
||||
case Forwarders.ForwardEmail.id:
|
||||
await this.forwardEmail.saveOptions(account, options.forwarders.forwardEmail);
|
||||
return true;
|
||||
case "simplelogin":
|
||||
case Forwarders.SimpleLogin.id:
|
||||
await this.simpleLogin.saveOptions(account, options.forwarders.simpleLogin);
|
||||
return true;
|
||||
default:
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("GeneratorNavigationEvaluator", () => {
|
||||
|
||||
describe("applyPolicy", () => {
|
||||
it("returns the input options when a policy is not in effect", () => {
|
||||
const evaluator = new GeneratorNavigationEvaluator(null);
|
||||
const evaluator = new GeneratorNavigationEvaluator(null!);
|
||||
const options = { type: "password" as const };
|
||||
|
||||
const result = evaluator.applyPolicy(options);
|
||||
@@ -54,7 +54,7 @@ describe("GeneratorNavigationEvaluator", () => {
|
||||
});
|
||||
|
||||
it("defaults options to the default generator navigation type when a policy is not in effect", () => {
|
||||
const evaluator = new GeneratorNavigationEvaluator(null);
|
||||
const evaluator = new GeneratorNavigationEvaluator(null!);
|
||||
|
||||
const result = evaluator.sanitize({});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { PasswordAlgorithms, PolicyEvaluator } from "@bitwarden/generator-core";
|
||||
import { AlgorithmsByType, PolicyEvaluator, Type } from "@bitwarden/generator-core";
|
||||
|
||||
import { DefaultGeneratorNavigation } from "./default-generator-navigation";
|
||||
import { GeneratorNavigation } from "./generator-navigation";
|
||||
@@ -19,7 +19,7 @@ export class GeneratorNavigationEvaluator
|
||||
|
||||
/** {@link PolicyEvaluator.policyInEffect} */
|
||||
get policyInEffect(): boolean {
|
||||
return PasswordAlgorithms.includes(this.policy?.overridePasswordType);
|
||||
return AlgorithmsByType[Type.password].includes(this.policy?.overridePasswordType);
|
||||
}
|
||||
|
||||
/** Apply policy to the input options.
|
||||
|
||||
@@ -4,14 +4,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { PasswordType } from "@bitwarden/generator-core";
|
||||
import { PasswordAlgorithm } from "@bitwarden/generator-core";
|
||||
|
||||
/** Policy settings affecting password generator navigation */
|
||||
export type GeneratorNavigationPolicy = {
|
||||
/** The type of generator that should be shown by default when opening
|
||||
* the password generator.
|
||||
*/
|
||||
overridePasswordType?: PasswordType;
|
||||
overridePasswordType?: PasswordAlgorithm;
|
||||
};
|
||||
|
||||
/** Reduces a policy into an accumulator by preferring the password generator
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GeneratorType, ForwarderId, UsernameGeneratorType } 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 = {
|
||||
@@ -6,11 +7,11 @@ export type GeneratorNavigation = {
|
||||
* @remarks The legacy generator only supports "password" and "passphrase".
|
||||
* The componentized generator supports all values.
|
||||
*/
|
||||
type?: GeneratorType;
|
||||
type?: CredentialAlgorithm;
|
||||
|
||||
/** When `type === "username"`, this stores the username algorithm. */
|
||||
username?: UsernameGeneratorType;
|
||||
|
||||
/** When `username === "forwarded"`, this stores the forwarder implementation. */
|
||||
forwarder?: ForwarderId | "";
|
||||
forwarder?: VendorId | "";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user