From 45a24a2309b4718a5d7f094b63ec8a47157b7e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Mon, 16 Dec 2024 15:29:01 -0500 Subject: [PATCH] flesh out metadata organization --- .../core/src/metadata/algorithm-metadata.ts | 49 ++++++ .../tools/generator/core/src/metadata/data.ts | 32 ++-- .../core/src/metadata/email/catchall.ts | 76 +++++++++ .../metadata/email/forwarder-integration.ts | 59 +++++++ .../core/src/metadata/email/index.ts | 0 .../core/src/metadata/email/plus-address.ts | 79 ++++++++++ .../src/metadata/email/random-catchall.ts | 0 .../src/metadata/email/random-subaddress.ts | 0 .../core/src/metadata/generator-metadata.ts | 67 ++++++++ .../src/metadata/password/eff-word-list.ts | 106 ++++++------- .../src/metadata/password/random-password.ts | 145 ++++++++---------- .../tools/generator/core/src/metadata/type.ts | 23 +-- .../src/metadata/username/eff-word-list.ts | 76 +++++++++ .../tools/generator/core/src/metadata/util.ts | 39 ++++- 14 files changed, 590 insertions(+), 161 deletions(-) create mode 100644 libs/tools/generator/core/src/metadata/algorithm-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/email/catchall.ts delete mode 100644 libs/tools/generator/core/src/metadata/email/index.ts create mode 100644 libs/tools/generator/core/src/metadata/email/plus-address.ts delete mode 100644 libs/tools/generator/core/src/metadata/email/random-catchall.ts delete mode 100644 libs/tools/generator/core/src/metadata/email/random-subaddress.ts create mode 100644 libs/tools/generator/core/src/metadata/generator-metadata.ts diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts new file mode 100644 index 00000000000..abae85af4ae --- /dev/null +++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts @@ -0,0 +1,49 @@ +import { CredentialAlgorithm, CredentialType } from "./type"; + +/** Credential generator metadata common across credential generators */ +export type AlgorithmMetadata = { + /** Uniquely identifies the credential configuration + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : AlgorithmMetadata = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ + id: CredentialAlgorithm; + + /** The kind of credential generated by this configuration */ + category: CredentialType; + + /** Localization keys */ + i18nKeys: { + /** descriptive name of the algorithm */ + name: string; + + /** explanatory text for the algorithm */ + description?: string; + + /** labels the generate action */ + generateCredential: string; + + /** labels the generated output */ + credentialGenerated: string; + + /** labels the copy output action */ + copyCredential: string; + }; + + /** fine-tunings for generator user experiences */ + capabilities: { + /** `true` when the generator supports autogeneration + * @remarks this property is useful when credential generation + * carries side effects, such as configuring a service external + * to Bitwarden. + */ + autogenerate: boolean; + + /** Well-known fields to display on the options panel or collect from the environment. + * @remarks: at present, this is only used by forwarders + */ + fields: string[]; + }; +}; diff --git a/libs/tools/generator/core/src/metadata/data.ts b/libs/tools/generator/core/src/metadata/data.ts index 18714091108..1c8ae59fad1 100644 --- a/libs/tools/generator/core/src/metadata/data.ts +++ b/libs/tools/generator/core/src/metadata/data.ts @@ -1,5 +1,6 @@ import { deepFreeze } from "../util"; +/** algorithms for generating credentials */ export const Algorithm = Object.freeze({ /** A password composed of random characters */ password: "password", @@ -14,26 +15,37 @@ export const Algorithm = Object.freeze({ catchall: "catchall", /** An email username composed of words from the EFF word list */ - plusAddress: "plus-address", + plusAddress: "subaddress", /** An integrated email forwarding service */ forwarder: "forwarder", } as const); -export const Category = Object.freeze({ +/** categorizes credentials according to their use-case outside of Bitwarden */ +export const Type = Object.freeze({ password: "password", username: "username", email: "email", } as const); -/** Credential generation algorithm identifiers grouped by category. */ -export const CategorizedAlgorithm = deepFreeze({ - /** Lists algorithms in the "password" credential category */ - [Category.password]: [Algorithm.password, Algorithm.passphrase] as const, +/** categorizes settings according to their expected use-case within Bitwarden */ +export const Purpose = Object.freeze({ + /** account-level generator options. This is the default. + * @remarks these are the options displayed on the generator tab + */ + account: "account", - /** Lists algorithms in the "username" credential category */ - [Category.username]: [Algorithm.username] as const, + // FIXME: consider adding a purpose for bitwarden's master password +}); - /** Lists algorithms in the "email" credential category */ - [Category.email]: [Algorithm.catchall, Algorithm.plusAddress, Algorithm.forwarder] as const, +/** Credential generation algorithms grouped by purpose. */ +export const AlgorithmsByType = deepFreeze({ + /** Algorithms that produce passwords */ + [Type.password]: [Algorithm.password, Algorithm.passphrase] as const, + + /** Algorithms that produce usernames */ + [Type.username]: [Algorithm.username] as const, + + /** Algorithms that produce email addresses */ + [Type.email]: [Algorithm.catchall, Algorithm.plusAddress, Algorithm.forwarder] as const, } as const); diff --git a/libs/tools/generator/core/src/metadata/email/catchall.ts b/libs/tools/generator/core/src/metadata/email/catchall.ts new file mode 100644 index 00000000000..4cec027e841 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/catchall.ts @@ -0,0 +1,76 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; + +import { EmailRandomizer } from "../../engine"; +import { CatchallConstraints } from "../../policies/catchall-constraints"; +import { + CatchallGenerationOptions, + CredentialGenerator, + GeneratorDependencyProvider, + NoPolicy, +} from "../../types"; +import { deepFreeze } from "../../util"; +import { Algorithm, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const catchall: GeneratorMetadata = deepFreeze({ + id: Algorithm.catchall, + category: Type.email, + i18nKeys: { + name: "catchallEmail", + description: "catchallEmailDesc", + generateCredential: "generateEmail", + credentialGenerated: "email", + copyCredential: "copyEmail", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new EmailRandomizer(dependencies.randomizer); + }, + }, + options: { + constraints: { catchallDomain: { minLength: 1 } }, + account: { + storage: { + key: "catchallGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "catchallType", + "catchallDomain", + ]), + state: GENERATOR_DISK, + initial: { + catchallType: "random", + catchallDomain: "", + }, + options: { + deserializer: (value) => value, + clearOn: ["logout"], + }, + }, + policy: { + type: PolicyType.PasswordGenerator, + disabledValue: {}, + }, + }, + }, + policy: { + combine(_acc: NoPolicy, _policy: Policy) { + return {}; + }, + toConstraints(_policy: NoPolicy, email: string) { + return new CatchallConstraints(email); + }, + }, +}); + +export default catchall; diff --git a/libs/tools/generator/core/src/metadata/email/forwarder-integration.ts b/libs/tools/generator/core/src/metadata/email/forwarder-integration.ts index e69de29bb2d..4621aaf2bac 100644 --- a/libs/tools/generator/core/src/metadata/email/forwarder-integration.ts +++ b/libs/tools/generator/core/src/metadata/email/forwarder-integration.ts @@ -0,0 +1,59 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { ApiSettings } from "@bitwarden/common/tools/integration/rpc"; +import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; + +import { ForwarderConfiguration } from "../../engine"; +import { Forwarder } from "../../engine/forwarder"; +import { GeneratorDependencyProvider, NoPolicy } from "../../types"; +import { deepFreeze } from "../../util"; +import { Purpose, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; +import { toForwarderIntegration } from "../util"; + +export function toGeneratorMetadata( + configuration: ForwarderConfiguration, +): GeneratorMetadata { + const forwarder = deepFreeze({ + id: toForwarderIntegration(configuration), + category: Type.email, + i18nKeys: { + name: configuration.name, + description: "forwardedEmailDesc", + generateCredential: "generateEmail", + credentialGenerated: "email", + copyCredential: "copyEmail", + }, + capabilities: { + autogenerate: false, + fields: configuration.forwarder.request as string[], + }, + engine: { + create(dependencies: GeneratorDependencyProvider) { + // FIXME: figure out why `configuration` fails to typecheck + const config: any = configuration; + return new Forwarder(config, dependencies.client, dependencies.i18nService); + }, + }, + options: { + constraints: configuration.forwarder.settingsConstraints, + [Purpose.account]: { + storage: configuration.forwarder.local.settings, + policy: { + type: PolicyType.PasswordGenerator, + disabledValue: {}, + }, + }, + }, + policy: { + combine(_acc: NoPolicy, _policy: Policy) { + return {}; + }, + toConstraints(_policy: NoPolicy) { + return new IdentityConstraint(); + }, + }, + } satisfies GeneratorMetadata); + + return forwarder; +} diff --git a/libs/tools/generator/core/src/metadata/email/index.ts b/libs/tools/generator/core/src/metadata/email/index.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.ts b/libs/tools/generator/core/src/metadata/email/plus-address.ts new file mode 100644 index 00000000000..0fcc735c069 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/plus-address.ts @@ -0,0 +1,79 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; + +import { EmailRandomizer } from "../../engine"; +import { SubaddressConstraints } from "../../policies/subaddress-constraints"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + NoPolicy, + SubaddressGenerationOptions, +} from "../../types"; +import { deepFreeze } from "../../util"; +import { Algorithm, Purpose, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const plusAddress: GeneratorMetadata = deepFreeze({ + id: Algorithm.plusAddress, + category: Type.email, + i18nKeys: { + name: "plusAddressedEmail", + description: "plusAddressedEmailDesc", + generateCredential: "generateEmail", + credentialGenerated: "email", + copyCredential: "copyEmail", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new EmailRandomizer(dependencies.randomizer); + }, + }, + options: { + constraints: {}, + [Purpose.account]: { + storage: { + key: "subaddressGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "subaddressType", + "subaddressEmail", + ]), + state: GENERATOR_DISK, + initial: { + subaddressType: "random", + subaddressEmail: "", + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + }, + policy: { + type: PolicyType.PasswordGenerator, + disabledValue: {}, + }, + }, + }, + + policy: { + combine(_acc: NoPolicy, _policy: Policy) { + return {}; + }, + toConstraints(_policy: NoPolicy, email: string) { + return new SubaddressConstraints(email); + }, + }, +}); + +export default plusAddress; diff --git a/libs/tools/generator/core/src/metadata/email/random-catchall.ts b/libs/tools/generator/core/src/metadata/email/random-catchall.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libs/tools/generator/core/src/metadata/email/random-subaddress.ts b/libs/tools/generator/core/src/metadata/email/random-subaddress.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libs/tools/generator/core/src/metadata/generator-metadata.ts b/libs/tools/generator/core/src/metadata/generator-metadata.ts new file mode 100644 index 00000000000..6e393d65d04 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/generator-metadata.ts @@ -0,0 +1,67 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy as AdminPolicy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; +import { Constraints } from "@bitwarden/common/tools/types"; + +import { CredentialGenerator, GeneratorConstraints, GeneratorDependencyProvider } from "../types"; + +import { AlgorithmMetadata } from "./algorithm-metadata"; +import { Purpose } from "./data"; + +/** Extends the algorithm metadata with storage and engine configurations. + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : CredentialGeneratorInfo = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ +export type GeneratorMetadata = AlgorithmMetadata & { + /** An algorithm that generates credentials when ran. */ + engine: { + /** Factory for the generator + */ + create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator; + }; + + /** Defines parameters for credential generation */ + options: { + /** global constraints; these apply to *all* generators */ + constraints: Constraints; + + /** account-local generator options */ + [Purpose.account]: { + /** plaintext import buffer */ + import?: ObjectKey, Options> & { format: "plain" }; + + /** persistent storage location */ + storage: ObjectKey; + + /** policy enforced when saving the options */ + policy: { + /** policy administration storage location for the policy */ + type: PolicyType; + + /** The value of the policy when it is not in effect. */ + disabledValue: Policy; + }; + }; + }; + + policy: { + /** Combines multiple policies set by the administrative console into + * a single policy. + */ + combine: (acc: Policy, policy: AdminPolicy) => Policy; + + /** Converts policy service data into actionable policy constraints. + * + * @param policy - the policy to map into policy constraints. + * @param email - the default email to extend. + * + * @remarks this version includes constraints needed for the reactive forms; + * it was introduced so that the constraints can be incrementally introduced + * as the new UI is built. + */ + toConstraints: (policy: Policy, email: string) => GeneratorConstraints; + }; +}; diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts index c0159f74e96..38ad2bcea28 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts @@ -4,32 +4,29 @@ import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; import { PasswordRandomizer } from "../../engine"; -import { - PassphraseGeneratorOptionsEvaluator, - passphraseLeastPrivilege, - PassphrasePolicyConstraints, -} from "../../policies"; +import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; import { CredentialGenerator, - CredentialGeneratorConfiguration, GeneratorDependencyProvider, PassphraseGenerationOptions, PassphraseGeneratorPolicy, } from "../../types"; -import { Algorithm, Category } from "../data"; +import { Algorithm, Purpose, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; -const passphrase: CredentialGeneratorConfiguration< - PassphraseGenerationOptions, - PassphraseGeneratorPolicy -> = { +const passphrase: GeneratorMetadata = { id: Algorithm.passphrase, - category: Category.password, - nameKey: "passphrase", - generateKey: "generatePassphrase", - generatedValueKey: "passphrase", - copyKey: "copyPassphrase", - onlyOnRequest: false, - request: [], + category: Type.password, + i18nKeys: { + name: "passphrase", + generateCredential: "generatePassphrase", + credentialGenerated: "passphrase", + copyCredential: "copyPassphrase", + }, + capabilities: { + autogenerate: false, + fields: [], + }, engine: { create( dependencies: GeneratorDependencyProvider, @@ -37,13 +34,7 @@ const passphrase: CredentialGeneratorConfiguration< return new PasswordRandomizer(dependencies.randomizer); }, }, - settings: { - initial: { - numWords: 6, - wordSeparator: "-", - capitalize: false, - includeNumber: false, - }, + options: { constraints: { numWords: { min: 3, @@ -52,44 +43,45 @@ const passphrase: CredentialGeneratorConfiguration< }, wordSeparator: { maxLength: 1 }, }, - account: { - key: "passphraseGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "numWords", - "wordSeparator", - "capitalize", - "includeNumber", - ]), - state: GENERATOR_DISK, - initial: { - numWords: 6, - wordSeparator: "-", - capitalize: false, - includeNumber: false, - }, - options: { - deserializer(value) { - return value; + [Purpose.account]: { + storage: { + key: "passphraseGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "numWords", + "wordSeparator", + "capitalize", + "includeNumber", + ]), + state: GENERATOR_DISK, + initial: { + numWords: 6, + wordSeparator: "-", + capitalize: false, + includeNumber: false, + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + } satisfies ObjectKey, + policy: { + type: PolicyType.PasswordGenerator, + disabledValue: { + minNumberWords: 0, + capitalize: false, + includeNumber: false, }, - clearOn: ["logout"], }, - } satisfies ObjectKey, + }, }, policy: { - type: PolicyType.PasswordGenerator, - disabledValue: { - minNumberWords: 0, - capitalize: false, - includeNumber: false, - }, combine: passphraseLeastPrivilege, - createEvaluator(policy) { - return new PassphraseGeneratorOptionsEvaluator(policy); - }, toConstraints(policy) { - return new PassphrasePolicyConstraints(policy, passphrase.settings.constraints); + return new PassphrasePolicyConstraints(policy, passphrase.options.constraints); }, }, }; diff --git a/libs/tools/generator/core/src/metadata/password/random-password.ts b/libs/tools/generator/core/src/metadata/password/random-password.ts index e44d7b2da5f..bfb4bf60e2b 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.ts @@ -1,36 +1,32 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; -import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; import { PasswordRandomizer } from "../../engine"; -import { - DynamicPasswordPolicyConstraints, - PasswordGeneratorOptionsEvaluator, - passwordLeastPrivilege, -} from "../../policies"; +import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; import { CredentialGenerator, - CredentialGeneratorConfiguration, GeneratorDependencyProvider, PasswordGenerationOptions, PasswordGeneratorPolicy, } from "../../types"; import { deepFreeze } from "../../util"; -import { Algorithm, Category } from "../data"; +import { Algorithm, Purpose, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; -const password: CredentialGeneratorConfiguration< - PasswordGenerationOptions, - PasswordGeneratorPolicy -> = deepFreeze({ +const password: GeneratorMetadata = deepFreeze({ id: Algorithm.password, - category: Category.password, - nameKey: "password", - generateKey: "generatePassword", - generatedValueKey: "password", - copyKey: "copyPassword", - onlyOnRequest: false, - request: [], + category: Type.password, + i18nKeys: { + name: "password", + generateCredential: "generatePassword", + credentialGenerated: "password", + copyCredential: "copyPassword", + }, + capabilities: { + autogenerate: true, + fields: [], + }, engine: { create( dependencies: GeneratorDependencyProvider, @@ -38,19 +34,7 @@ const password: CredentialGeneratorConfiguration< return new PasswordRandomizer(dependencies.randomizer); }, }, - settings: { - initial: { - length: 14, - ambiguous: true, - uppercase: true, - minUppercase: 1, - lowercase: true, - minLowercase: 1, - number: true, - minNumber: 1, - special: false, - minSpecial: 0, - }, + options: { constraints: { length: { min: 5, @@ -66,60 +50,61 @@ const password: CredentialGeneratorConfiguration< max: 9, }, }, - account: { - key: "passwordGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "length", - "ambiguous", - "uppercase", - "minUppercase", - "lowercase", - "minLowercase", - "number", - "minNumber", - "special", - "minSpecial", - ]), - state: GENERATOR_DISK, - initial: { - length: 14, - ambiguous: true, - uppercase: true, - minUppercase: 1, - lowercase: true, - minLowercase: 1, - number: true, - minNumber: 1, - special: false, - minSpecial: 0, - }, - options: { - deserializer(value) { - return value; + [Purpose.account]: { + storage: { + key: "passwordGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "length", + "ambiguous", + "uppercase", + "minUppercase", + "lowercase", + "minLowercase", + "number", + "minNumber", + "special", + "minSpecial", + ]), + state: GENERATOR_DISK, + initial: { + length: 14, + ambiguous: true, + uppercase: true, + minUppercase: 1, + lowercase: true, + minLowercase: 1, + number: true, + minNumber: 1, + special: false, + minSpecial: 0, + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], }, - clearOn: ["logout"], }, - } satisfies ObjectKey, + policy: { + type: PolicyType.PasswordGenerator, + disabledValue: { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }, + }, + }, }, policy: { - type: PolicyType.PasswordGenerator, - disabledValue: { - minLength: 0, - useUppercase: false, - useLowercase: false, - useNumbers: false, - numberCount: 0, - useSpecial: false, - specialCount: 0, - }, combine: passwordLeastPrivilege, - createEvaluator(policy) { - return new PasswordGeneratorOptionsEvaluator(policy); - }, toConstraints(policy) { - return new DynamicPasswordPolicyConstraints(policy, password.settings.constraints); + return new DynamicPasswordPolicyConstraints(policy, password.options.constraints); }, }, }); diff --git a/libs/tools/generator/core/src/metadata/type.ts b/libs/tools/generator/core/src/metadata/type.ts index 158309fd90d..bc4fcfbe076 100644 --- a/libs/tools/generator/core/src/metadata/type.ts +++ b/libs/tools/generator/core/src/metadata/type.ts @@ -1,25 +1,28 @@ import { IntegrationId } from "@bitwarden/common/tools/integration"; -import { CategorizedAlgorithm, Category } from "./data"; +import { AlgorithmsByType, Purpose, Type } from "./data"; -/** A collection of credentials that all fulfill a specific purpose. */ -export type CredentialCategory = keyof typeof Category; +/** categorizes credentials according to their use-case outside of Bitwarden */ +export type CredentialType = keyof typeof Type; + +/** categorizes credentials according to their expected use-case within Bitwarden */ +export type CredentialPurpose = keyof typeof Purpose; /** A type of password that may be generated by the credential generator. */ -export type PasswordAlgorithm = (typeof CategorizedAlgorithm.password)[number]; +export type PasswordAlgorithm = (typeof AlgorithmsByType.password)[number]; /** A type of username that may be generated by the credential generator. */ -export type UsernameAlgorithm = (typeof CategorizedAlgorithm.username)[number]; +export type UsernameAlgorithm = (typeof AlgorithmsByType.username)[number]; /** A type of email address that may be generated by the credential generator. */ -export type EmailAlgorithm = (typeof CategorizedAlgorithm.email)[number] | ForwarderIntegration; +export type EmailAlgorithm = (typeof AlgorithmsByType.email)[number] | ForwarderIntegration; /** Identifies a forwarding service */ export type ForwarderIntegration = { forwarder: IntegrationId }; -/** A type of credential that may be generated by the credential generator. */ -// this is defined in terms of `CategorizedAlgorithm` to typecheck the keys of -// `CredentialCategory` against the keys of `Category`. +/** A type of credential that can be generated by the credential generator. */ +// this is defined in terms of `AlgorithmsByType` to typecheck the keys of +// `AlgorithmsByType` against the keys of `CredentialType`. export type CredentialAlgorithm = - | (typeof CategorizedAlgorithm)[CredentialCategory][number] + | (typeof AlgorithmsByType)[CredentialType][number] | ForwarderIntegration; diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts index e69de29bb2d..631ac073eba 100644 --- a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts @@ -0,0 +1,76 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; + +import { UsernameRandomizer } from "../../engine"; +import { + CredentialGenerator, + EffUsernameGenerationOptions, + GeneratorDependencyProvider, + NoPolicy, +} from "../../types"; +import { deepFreeze } from "../../util"; +import { Algorithm, Purpose, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const effWordList: GeneratorMetadata = deepFreeze({ + id: Algorithm.username, + category: Type.username, + i18nKeys: { + name: "randomWord", + generateCredential: "generateUsername", + credentialGenerated: "username", + copyCredential: "copyUsername", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new UsernameRandomizer(dependencies.randomizer); + }, + }, + options: { + constraints: {}, + [Purpose.account]: { + storage: { + key: "effUsernameGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "wordCapitalize", + "wordIncludeNumber", + ]), + state: GENERATOR_DISK, + initial: { + wordCapitalize: false, + wordIncludeNumber: false, + website: null, + }, + options: { + deserializer: (value) => value, + clearOn: ["logout"], + }, + }, + policy: { + type: PolicyType.PasswordGenerator, + disabledValue: {}, + }, + }, + }, + policy: { + combine(_acc: NoPolicy, _policy: Policy) { + return {}; + }, + toConstraints(_policy: NoPolicy) { + return new IdentityConstraint(); + }, + }, +}); + +export default effWordList; diff --git a/libs/tools/generator/core/src/metadata/util.ts b/libs/tools/generator/core/src/metadata/util.ts index 84586e17bf7..9d18baa7406 100644 --- a/libs/tools/generator/core/src/metadata/util.ts +++ b/libs/tools/generator/core/src/metadata/util.ts @@ -1,4 +1,10 @@ -import { CategorizedAlgorithm } from "./data"; +import { + IntegrationId, + IntegrationIds, + IntegrationMetadata, +} from "@bitwarden/common/tools/integration"; + +import { AlgorithmsByType } from "./data"; import { CredentialAlgorithm, EmailAlgorithm, @@ -11,14 +17,14 @@ import { export function isPasswordAlgorithm( algorithm: CredentialAlgorithm, ): algorithm is PasswordAlgorithm { - return CategorizedAlgorithm.password.includes(algorithm as any); + return AlgorithmsByType.password.includes(algorithm as any); } /** Returns true when the input algorithm is a username algorithm. */ export function isUsernameAlgorithm( algorithm: CredentialAlgorithm, ): algorithm is UsernameAlgorithm { - return CategorizedAlgorithm.username.includes(algorithm as any); + return AlgorithmsByType.username.includes(algorithm as any); } /** Returns true when the input algorithm is a forwarder integration. */ @@ -30,7 +36,7 @@ export function isForwarderIntegration( /** Returns true when the input algorithm is an email algorithm. */ export function isEmailAlgorithm(algorithm: CredentialAlgorithm): algorithm is EmailAlgorithm { - return CategorizedAlgorithm.email.includes(algorithm as any) || isForwarderIntegration(algorithm); + return AlgorithmsByType.email.includes(algorithm as any) || isForwarderIntegration(algorithm); } export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorithm) { @@ -42,3 +48,28 @@ export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorit return false; } } + +export function toForwarderIntegration(value: IntegrationMetadata): ForwarderIntegration; +export function toForwarderIntegration(value: IntegrationId): ForwarderIntegration; +export function toForwarderIntegration( + value: IntegrationId | IntegrationMetadata, +): ForwarderIntegration { + if (value == null) { + throw new Error("`value` cannot be `null` or `undefined`"); + } + + let possibleId = undefined; + if (typeof value === "string") { + possibleId = value; + } else if (typeof value === "object" && "id" in value) { + possibleId = typeof value.id === "string" ? value.id : undefined; + } else { + throw new Error("Invalid `value` received."); + } + + if (possibleId && IntegrationIds.includes(possibleId)) { + return { forwarder: possibleId } satisfies ForwarderIntegration; + } else { + throw new Error("Invalid `value` received."); + } +}