mirror of
https://github.com/bitwarden/browser
synced 2025-12-28 22:23:28 +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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user