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

Revert "factor out generator metadata provider"

This reverts commit 3f40d72b5c.
This commit is contained in:
✨ Audrey ✨
2025-02-28 11:56:39 -05:00
parent 4347f82c06
commit 5c6a6da101
18 changed files with 418 additions and 49 deletions

View File

@@ -1,10 +1,12 @@
import { VendorId } from "../extension";
import { ExtensionPointId } from "./extension-point-id";
import { IntegrationId } from "./integration-id";
/** The capabilities and descriptive content for an integration */
export type IntegrationMetadata = {
/** Uniquely identifies the integrator. */
id: IntegrationId;
id: IntegrationId & VendorId;
/** Brand name of the integrator. */
name: string;

View File

@@ -1,5 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
@@ -29,8 +30,11 @@ export const Integrations = Object.freeze({
const integrations = new Map(Object.values(Integrations).map((i) => [i.id, i]));
export function getForwarderConfiguration(id: IntegrationId): ForwarderConfiguration<ApiSettings> {
const maybeForwarder = integrations.get(id);
export function getForwarderConfiguration(
id: IntegrationId | VendorId,
): ForwarderConfiguration<ApiSettings> {
// these casts are for compatibility; `IntegrationId` is the old form of `VendorId`
const maybeForwarder = integrations.get(id as string as IntegrationId & VendorId);
if (maybeForwarder && "forwarder" in maybeForwarder) {
return maybeForwarder as ForwarderConfiguration<ApiSettings>;

View File

@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
@@ -100,7 +101,7 @@ const forwarder = Object.freeze({
export const AddyIo = Object.freeze({
// integration
id: "anonaddy" as IntegrationId,
id: "anonaddy" as IntegrationId & VendorId,
name: "Addy.io",
extends: ["forwarder"],

View File

@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -89,7 +90,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const DuckDuckGo = Object.freeze({
id: "duckduckgo" as IntegrationId,
id: "duckduckgo" as IntegrationId & VendorId,
name: "DuckDuckGo",
baseUrl: "https://quack.duckduckgo.com/api",
selfHost: "never",

View File

@@ -5,6 +5,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -159,7 +160,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const Fastmail = Object.freeze({
id: "fastmail" as IntegrationId,
id: "fastmail" as IntegrationId & VendorId,
name: "Fastmail",
baseUrl: "https://api.fastmail.com",
selfHost: "maybe",

View File

@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -97,7 +98,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const FirefoxRelay = Object.freeze({
id: "firefoxrelay" as IntegrationId,
id: "firefoxrelay" as IntegrationId & VendorId,
name: "Firefox Relay",
baseUrl: "https://relay.firefox.com/api",
selfHost: "never",

View File

@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -101,7 +102,7 @@ const forwarder = Object.freeze({
export const ForwardEmail = Object.freeze({
// integration metadata
id: "forwardemail" as IntegrationId,
id: "forwardemail" as IntegrationId & VendorId,
name: "Forward Email",
extends: ["forwarder"],

View File

@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
@@ -103,7 +104,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const SimpleLogin = Object.freeze({
id: "simplelogin" as IntegrationId,
id: "simplelogin" as IntegrationId & VendorId,
name: "SimpleLogin",
selfHost: "maybe",
extends: ["forwarder"],

View File

@@ -1,5 +1,7 @@
import { CredentialAlgorithm, CredentialType } from "./type";
type I18nKeyOrLiteral = string | { literal: string };
/** Credential generator metadata common across credential generators */
export type AlgorithmMetadata = {
/** Uniquely identifies the credential configuration
@@ -23,25 +25,25 @@ export type AlgorithmMetadata = {
/** Localization keys */
i18nKeys: {
/** descriptive name of the algorithm */
name: string;
name: I18nKeyOrLiteral;
/** explanatory text for the algorithm */
description?: string;
description?: I18nKeyOrLiteral;
/** labels the generate action */
generateCredential: string;
generateCredential: I18nKeyOrLiteral;
/** message informing users when the generator produces a new credential */
credentialGenerated: string;
credentialGenerated: I18nKeyOrLiteral;
/* labels the action that assigns a generated value to a domain object */
useCredential: string;
useCredential: I18nKeyOrLiteral;
/** labels the generated output */
credentialType: string;
credentialType: I18nKeyOrLiteral;
/** labels the copy output action */
copyCredential: string;
copyCredential: I18nKeyOrLiteral;
};
/** fine-tunings for generator user experiences */

View File

@@ -19,11 +19,13 @@ describe("email - catchall generator metadata", () => {
});
describe("profiles[account]", () => {
let accountProfile: CoreProfileMetadata<CatchallGenerationOptions> = null;
let accountProfile: CoreProfileMetadata<CatchallGenerationOptions> = null!;
beforeEach(() => {
const profile = catchall.profiles[Profile.account];
if (isCoreProfile(profile)) {
if (isCoreProfile(profile!)) {
accountProfile = profile;
} else {
throw new Error("this branch should never run");
}
});

View File

@@ -1,4 +1,75 @@
// Forwarders are pending integration with the extension API
//
// They use the 300-block of weights and derive their metadata
// using logic similar to `toCredentialGeneratorConfiguration`
import { ExtensionMetadata, ExtensionStorageKey } from "@bitwarden/common/tools/extension/type";
import { SelfHostedApiSettings } from "@bitwarden/common/tools/integration/rpc";
import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
import { getForwarderConfiguration } from "../../data";
import { EmailDomainSettings, EmailPrefixSettings } from "../../engine";
import { Forwarder } from "../../engine/forwarder";
import { GeneratorDependencyProvider } from "../../types";
import { Profile, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
import { ForwarderProfileMetadata } from "../profile-metadata";
// These options are used by all forwarders; each forwarder uses a different set,
// as defined by `GeneratorMetadata<T>.capabilities.fields`.
type ForwarderOptions = Partial<EmailDomainSettings & EmailPrefixSettings & SelfHostedApiSettings>;
// update the extension metadata
export function toForwarderMetadata(
extension: ExtensionMetadata,
): GeneratorMetadata<ForwarderOptions> {
if (extension.site.id !== "forwarder") {
throw new Error(
`expected forwarder extension; received ${extension.site.id} (${extension.product.vendor.id})`,
);
}
const name = { literal: extension.product.name ?? extension.product.vendor.name };
const generator: GeneratorMetadata<ForwarderOptions> = {
id: { forwarder: extension.product.vendor.id },
category: Type.email,
weight: 300,
i18nKeys: {
name,
description: "forwardedEmailDesc",
generateCredential: "generateEmail",
credentialGenerated: "emailGenerated",
useCredential: "useThisEmail",
credentialType: "email",
copyCredential: "copyEmail",
},
capabilities: {
autogenerate: false,
fields: [...extension.requestedFields],
},
engine: {
create(dependencies: GeneratorDependencyProvider) {
const config = getForwarderConfiguration(extension.product.vendor.id);
return new Forwarder(config, dependencies.client, dependencies.i18nService);
},
},
profiles: {
[Profile.account]: {
type: "extension",
site: "forwarder",
storage: {
key: "forwarder",
frame: 512,
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
} satisfies ExtensionStorageKey<ForwarderOptions>,
constraints: {
default: {},
create() {
return new IdentityConstraint<ForwarderOptions>();
},
},
} satisfies ForwarderProfileMetadata<ForwarderOptions>,
},
};
return generator;
}

View File

@@ -19,11 +19,13 @@ describe("email - plus address generator metadata", () => {
});
describe("profiles[account]", () => {
let accountProfile: CoreProfileMetadata<SubaddressGenerationOptions> = null;
let accountProfile: CoreProfileMetadata<SubaddressGenerationOptions> = null!;
beforeEach(() => {
const profile = plusAddress.profiles[Profile.account];
if (isCoreProfile(profile)) {
if (isCoreProfile(profile!)) {
accountProfile = profile;
} else {
throw new Error("this branch should never run");
}
});

View File

@@ -22,19 +22,21 @@ describe("password - eff words generator metadata", () => {
});
describe("profiles[account]", () => {
let accountProfile: CoreProfileMetadata<PassphraseGenerationOptions> = null;
let accountProfile: CoreProfileMetadata<PassphraseGenerationOptions> | null = null;
beforeEach(() => {
const profile = effPassphrase.profiles[Profile.account];
if (isCoreProfile(profile)) {
if (isCoreProfile(profile!)) {
accountProfile = profile;
} else {
accountProfile = null;
}
});
describe("storage.options.deserializer", () => {
it("returns its input", () => {
const value: PassphraseGenerationOptions = { ...accountProfile.storage.initial };
const value: PassphraseGenerationOptions = { ...accountProfile!.storage.initial };
const result = accountProfile.storage.options.deserializer(value);
const result = accountProfile!.storage.options.deserializer(value);
expect(result).toBe(value);
});
@@ -46,15 +48,15 @@ describe("password - eff words generator metadata", () => {
// enclosed behaviors change.
it("creates a passphrase policy constraints", () => {
const context = { defaultConstraints: accountProfile.constraints.default };
const context = { defaultConstraints: accountProfile!.constraints.default };
const constraints = accountProfile.constraints.create([], context);
const constraints = accountProfile!.constraints.create([], context);
expect(constraints).toBeInstanceOf(PassphrasePolicyConstraints);
});
it("forwards the policy to the constraints", () => {
const context = { defaultConstraints: accountProfile.constraints.default };
const context = { defaultConstraints: accountProfile!.constraints.default };
const policies = [
{
type: PolicyType.PasswordGenerator,
@@ -66,13 +68,13 @@ describe("password - eff words generator metadata", () => {
},
] as Policy[];
const constraints = accountProfile.constraints.create(policies, context);
const constraints = accountProfile!.constraints.create(policies, context);
expect(constraints.constraints.numWords.min).toEqual(6);
expect(constraints.constraints.numWords?.min).toEqual(6);
});
it("combines multiple policies in the constraints", () => {
const context = { defaultConstraints: accountProfile.constraints.default };
const context = { defaultConstraints: accountProfile!.constraints.default };
const policies = [
{
type: PolicyType.PasswordGenerator,
@@ -92,10 +94,10 @@ describe("password - eff words generator metadata", () => {
},
] as Policy[];
const constraints = accountProfile.constraints.create(policies, context);
const constraints = accountProfile!.constraints.create(policies, context);
expect(constraints.constraints.numWords.min).toEqual(6);
expect(constraints.constraints.capitalize.requiredValue).toEqual(true);
expect(constraints.constraints.numWords?.min).toEqual(6);
expect(constraints.constraints.capitalize?.requiredValue).toEqual(true);
});
});
});

View File

@@ -22,11 +22,13 @@ describe("password - characters generator metadata", () => {
});
describe("profiles[account]", () => {
let accountProfile: CoreProfileMetadata<PasswordGenerationOptions> = null;
let accountProfile: CoreProfileMetadata<PasswordGenerationOptions> = null!;
beforeEach(() => {
const profile = password.profiles[Profile.account];
if (isCoreProfile(profile)) {
if (isCoreProfile(profile!)) {
accountProfile = profile;
} else {
throw new Error("this branch should never run");
}
});
@@ -69,7 +71,7 @@ describe("password - characters generator metadata", () => {
const constraints = accountProfile.constraints.create(policies, context);
expect(constraints.constraints.length.min).toEqual(10);
expect(constraints.constraints.length?.min).toEqual(10);
});
it("combines multiple policies in the constraints", () => {
@@ -97,8 +99,8 @@ describe("password - characters generator metadata", () => {
const constraints = accountProfile.constraints.create(policies, context);
expect(constraints.constraints.length.min).toEqual(14);
expect(constraints.constraints.special.requiredValue).toEqual(true);
expect(constraints.constraints.length?.min).toEqual(14);
expect(constraints.constraints.special?.requiredValue).toEqual(true);
});
});
});

View File

@@ -20,11 +20,13 @@ describe("username - eff words generator metadata", () => {
});
describe("profiles[account]", () => {
let accountProfile: CoreProfileMetadata<EffUsernameGenerationOptions> = null;
let accountProfile: CoreProfileMetadata<EffUsernameGenerationOptions> = null!;
beforeEach(() => {
const profile = effWordList.profiles[Profile.account];
if (isCoreProfile(profile)) {
if (isCoreProfile(profile!)) {
accountProfile = profile;
} else {
throw new Error("this branch should never run");
}
});

View File

@@ -5,13 +5,41 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
// implement ADR-0002
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { CredentialAlgorithm, EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "..";
import {
CredentialAlgorithm as LegacyAlgorithm,
EmailAlgorithms,
PasswordAlgorithms,
UsernameAlgorithms,
} from "..";
import { CredentialAlgorithm } from "../metadata";
/** Reduces policies to a set of available algorithms
* @param policies the policies to reduce
* @returns the resulting `AlgorithmAvailabilityPolicy`
*/
export function availableAlgorithms(policies: Policy[]): CredentialAlgorithm[] {
export function availableAlgorithms(policies: Policy[]): LegacyAlgorithm[] {
const overridePassword = policies
.filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
.reduce(
(type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)),
null as LegacyAlgorithm,
);
const policy: LegacyAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms];
if (overridePassword) {
policy.push(overridePassword);
} else {
policy.push(...PasswordAlgorithms);
}
return policy;
}
/** Reduces policies to a set of available algorithms
* @param policies the policies to reduce
* @returns the resulting `AlgorithmAvailabilityPolicy`
*/
export function availableAlgorithms_vNext(policies: Policy[]): CredentialAlgorithm[] {
const overridePassword = policies
.filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
.reduce(

View File

@@ -0,0 +1,244 @@
import {
Observable,
distinctUntilChanged,
filter,
first,
map,
shareReplay,
switchMap,
takeUntil,
withLatestFrom,
} from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
import { BoundDependency } from "@bitwarden/common/tools/dependencies";
import { ExtensionSite } from "@bitwarden/common/tools/extension";
import { SemanticLogger } from "@bitwarden/common/tools/log";
import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
import { anyComplete } from "@bitwarden/common/tools/rx";
import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
import {
GeneratorMetadata,
AlgorithmsByType,
CredentialAlgorithm,
CredentialType,
isForwarderExtensionId,
toForwarderMetadata,
Type,
} from "../metadata";
import { availableAlgorithms_vNext } from "../policies/available-algorithms-policy";
import { CredentialPreference } from "../types";
import { PREFERENCES } from "./credential-preferences";
type AlgorithmRequest = { algorithm: CredentialAlgorithm };
type TypeRequest = { category: CredentialType };
type MetadataRequest = Partial<AlgorithmRequest & TypeRequest>;
/** Surfaces contextual information to credential generators */
export class GeneratorMetadataProvider {
/** Instantiates the context provider
* @param providers dependency injectors for user state subjects
* @param policyService settings constraint lookups
*/
constructor(
private readonly providers: UserStateSubjectDependencyProvider,
private readonly system: SystemServiceProvider,
algorithms: GeneratorMetadata<object>[],
) {
this.log = providers.log({ type: "GeneratorMetadataProvider" });
const site = system.extension.site("forwarder");
if (!site) {
this.log.panic("forwarder extension site not found");
}
this.site = site;
this.generators = new Map(algorithms.map((a) => [a.id, a] as const));
}
private readonly site: ExtensionSite;
private readonly log: SemanticLogger;
private generators: Map<CredentialAlgorithm, GeneratorMetadata<unknown & object>>;
// looks up a set of algorithms; does not enforce policy
algorithms(requested: AlgorithmRequest): CredentialAlgorithm[];
algorithms(requested: TypeRequest): CredentialAlgorithm[];
algorithms(requested: MetadataRequest): CredentialAlgorithm[];
algorithms(requested: MetadataRequest): CredentialAlgorithm[] {
let algorithms: CredentialAlgorithm[];
if (requested.category) {
let forwarders: CredentialAlgorithm[] = [];
if (requested.category === Type.email) {
forwarders = Array.from(this.site.extensions.keys()).map((forwarder) => ({ forwarder }));
}
algorithms = AlgorithmsByType[requested.category].concat(forwarders);
} else if (requested.algorithm) {
algorithms = [requested.algorithm];
} else {
this.log.panic(requested, "algorithm or category required");
}
return algorithms;
}
// emits a function that returns `true` when the input algorithm is available
private isAvailable$(
dependencies: BoundDependency<"account", Account>,
): Observable<(a: CredentialAlgorithm) => boolean> {
const account$ = dependencies.account$.pipe(
distinctUntilChanged((previous, current) => previous.id === current.id),
shareReplay({ bufferSize: 1, refCount: true }),
);
const available$ = account$.pipe(
switchMap((account) => {
const policies$ = this.system.policy.getAll$(PolicyType.PasswordGenerator, account.id).pipe(
map((p) => new Set(availableAlgorithms_vNext(p))),
// complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely
takeUntil(anyComplete(account$)),
);
return policies$;
}),
map((available) => (a: CredentialAlgorithm) => isForwarderExtensionId(a) || available.has(a)),
);
return available$;
}
// looks up a set of algorithms; enforces policy - emits empty list when there's no algorithm available
available$(
requested: AlgorithmRequest,
dependencies: BoundDependency<"account", Account>,
): Observable<GeneratorMetadata<object>[]>;
available$(
requested: TypeRequest,
dependencies: BoundDependency<"account", Account>,
): Observable<GeneratorMetadata<object>[]>;
available$(
requested: MetadataRequest,
dependencies: BoundDependency<"account", Account>,
): Observable<GeneratorMetadata<object>[]> {
let available$: Observable<CredentialAlgorithm[]>;
if (requested.category) {
const { category } = requested;
available$ = this.isAvailable$(dependencies).pipe(
map((isAvailable) => AlgorithmsByType[category].filter(isAvailable)),
);
} else if (requested.algorithm) {
const { algorithm } = requested;
available$ = this.isAvailable$(dependencies).pipe(
map((isAvailable) => (isAvailable(algorithm) ? [algorithm] : [])),
);
} else {
this.log.panic(requested, "algorithm or category required");
}
const result$ = available$.pipe(
map((available) => available.map((algorithm) => this.getMetadata(algorithm))),
);
return result$;
}
// looks up a specific algorithm; enforces policy - observable completes without emission when there's no algorithm available.
algorithm$(
requested: AlgorithmRequest,
dependencies: BoundDependency<"account", Account>,
): Observable<GeneratorMetadata<object>>;
algorithm$(
requested: TypeRequest,
dependencies: BoundDependency<"account", Account>,
): Observable<GeneratorMetadata<object>>;
algorithm$(
requested: MetadataRequest,
dependencies: BoundDependency<"account", Account>,
): Observable<GeneratorMetadata<object>> {
const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true }));
let algorithm$: Observable<CredentialAlgorithm | undefined>;
if (requested.category) {
this.log.debug(requested, "retrieving algorithm metadata by category");
const { category } = requested;
algorithm$ = this.preferences({ account$ }).pipe(
withLatestFrom(this.isAvailable$({ account$ })),
map(([preferences, isAvailable]) => {
let algorithm: CredentialAlgorithm | undefined = preferences[category].algorithm;
if (isAvailable(algorithm)) {
return algorithm;
}
const algorithms = AlgorithmsByType[category];
algorithm = algorithms.find(isAvailable)!;
this.log.debug(
{ algorithm, category },
"preference not available; defaulting the generator algorithm",
);
return algorithm;
}),
);
} else if (requested.algorithm) {
this.log.debug(requested, "retrieving algorithm metadata by algorithm");
const { algorithm } = requested;
algorithm$ = this.isAvailable$({ account$ }).pipe(
map((isAvailable) => (isAvailable(algorithm) ? algorithm : undefined)),
first(),
);
} else {
this.log.panic(requested, "algorithm or category required");
}
const result$ = algorithm$.pipe(
filter((value) => !!value),
map((algorithm) => this.getMetadata(algorithm!)),
);
return result$;
}
private getMetadata(algorithm: CredentialAlgorithm) {
let result = null;
if (isForwarderExtensionId(algorithm)) {
const extension = this.site.extensions.get(algorithm.forwarder);
if (!extension) {
this.log.panic(algorithm, "extension not found");
}
result = toForwarderMetadata(extension);
} else {
result = this.generators.get(algorithm);
}
if (!result) {
this.log.panic({ algorithm }, "failed to load metadata");
}
return result;
}
/** Get a subject bound to credential generator preferences.
* @param dependencies.account$ identifies the account to which the preferences are bound
* @returns a subject bound to the user's preferences
* @remarks Preferences determine which algorithms are used when generating a
* credential from a credential category (e.g. `PassX` or `Username`). Preferences
* should not be used to hold navigation history. Use @bitwarden/generator-navigation
* instead.
*/
preferences(
dependencies: BoundDependency<"account", Account>,
): UserStateSubject<CredentialPreference> {
// FIXME: enforce policy
const subject = new UserStateSubject(PREFERENCES, this.providers, dependencies);
return subject;
}
}

View File

@@ -1,6 +1,8 @@
import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "../data/generator-types";
import { AlgorithmsByType, CredentialType } from "../metadata";
/** A type of password that may be generated by the credential generator. */
export type PasswordAlgorithm = (typeof PasswordAlgorithms)[number];
@@ -11,7 +13,7 @@ export type UsernameAlgorithm = (typeof UsernameAlgorithms)[number];
/** A type of email address that may be generated by the credential generator. */
export type EmailAlgorithm = (typeof EmailAlgorithms)[number];
export type ForwarderIntegration = { forwarder: IntegrationId };
export type ForwarderIntegration = { forwarder: IntegrationId & VendorId };
/** Returns true when the input algorithm is a forwarder integration. */
export function isForwarderIntegration(
@@ -74,8 +76,8 @@ export type CredentialCategory = keyof typeof CredentialCategories;
/** The kind of credential to generate using a compound configuration. */
// FIXME: extend the preferences to include a preferred forwarder
export type CredentialPreference = {
[Key in CredentialCategory]: {
algorithm: (typeof CredentialCategories)[Key][number];
[Key in CredentialType & CredentialCategory]: {
algorithm: CredentialAlgorithm & (typeof AlgorithmsByType)[Key][number];
updated: Date;
};
};