diff --git a/libs/tools/generator/core/src/engine/index.ts b/libs/tools/generator/core/src/engine/index.ts index 2d272e7c11b..f8008a866e4 100644 --- a/libs/tools/generator/core/src/engine/index.ts +++ b/libs/tools/generator/core/src/engine/index.ts @@ -5,4 +5,5 @@ export * from "./settings"; export { EmailRandomizer } from "./email-randomizer"; export { EmailCalculator } from "./email-calculator"; export { PasswordRandomizer } from "./password-randomizer"; +export { SdkPasswordRandomizer } from "./sdk-password-randomizer"; export { UsernameRandomizer } from "./username-randomizer"; diff --git a/libs/tools/generator/core/src/engine/sdk-password-randomizer.ts b/libs/tools/generator/core/src/engine/sdk-password-randomizer.ts new file mode 100644 index 00000000000..ba125266ec0 --- /dev/null +++ b/libs/tools/generator/core/src/engine/sdk-password-randomizer.ts @@ -0,0 +1,98 @@ +import { + BitwardenClient, + PassphraseGeneratorRequest, + PasswordGeneratorRequest, +} from "@bitwarden/sdk-internal"; + +import { + CredentialGenerator, + GenerateRequest, + GeneratedCredential, + PassphraseGenerationOptions, + PasswordGenerationOptions, +} from "../types"; + +/** Generation algorithms that produce randomized secrets */ +export class SdkPasswordRandomizer + implements + CredentialGenerator, + CredentialGenerator +{ + /** Instantiates the password randomizer + * @param randomizer data source for random data + */ + constructor(private client: BitwardenClient) {} + + generate( + request: GenerateRequest, + settings: PasswordGenerationOptions, + ): Promise; + generate( + request: GenerateRequest, + settings: PassphraseGenerationOptions, + ): Promise; + async generate( + request: GenerateRequest, + settings: PasswordGenerationOptions | PassphraseGenerationOptions, + ) { + if (isPasswordGenerationOptions(settings)) { + const password = await this.client.generator().password(convertPasswordRequest(settings)); + + return new GeneratedCredential( + password, + "password", + Date.now(), + request.source, + request.website, + ); + } else if (isPassphraseGenerationOptions(settings)) { + const passphrase = await this.client + .generator() + .passphrase(convertPassphraseRequest(settings)); + + return new GeneratedCredential( + passphrase, + "password", + Date.now(), + request.source, + request.website, + ); + } + + throw new Error("Invalid settings received by generator."); + } +} + +function convertPasswordRequest(settings: PasswordGenerationOptions): PasswordGeneratorRequest { + return { + lowercase: settings.lowercase, + uppercase: settings.uppercase, + numbers: settings.number, + special: settings.special, + length: settings.length, + avoidAmbiguous: settings.ambiguous, + minLowercase: settings.minLowercase, + minUppercase: settings.minUppercase, + minNumber: settings.minNumber, + minSpecial: settings.minSpecial, + }; +} + +function convertPassphraseRequest( + settings: PassphraseGenerationOptions, +): PassphraseGeneratorRequest { + return { + numWords: settings.numWords, + wordSeparator: settings.wordSeparator, + capitalize: settings.capitalize, + includeNumber: settings.includeNumber, + }; +} + +function isPasswordGenerationOptions(settings: any): settings is PasswordGenerationOptions { + return "length" in (settings ?? {}); +} + +function isPassphraseGenerationOptions(settings: any): settings is PassphraseGenerationOptions { + return "numWords" in (settings ?? {}); +} diff --git a/libs/tools/generator/core/src/metadata/data.ts b/libs/tools/generator/core/src/metadata/data.ts index 2b9dad50557..b8b65af5d43 100644 --- a/libs/tools/generator/core/src/metadata/data.ts +++ b/libs/tools/generator/core/src/metadata/data.ts @@ -8,6 +8,12 @@ export const Algorithm = Object.freeze({ /** A password composed of random words from the EFF word list */ passphrase: "passphrase", + /** A password composed of random characters, retrieved from SDK */ + sdkPassword: "sdkpassword", + + /** A password composed of random words from the EFF word list, retrieved from SDK */ + sdkPassphrase: "sdkpassphrase", + /** A username composed of words from the EFF word list */ username: "username", @@ -38,7 +44,12 @@ export const Profile = Object.freeze({ /** Credential generation algorithms grouped by purpose. */ export const AlgorithmsByType = deepFreeze({ /** Algorithms that produce passwords */ - [Type.password]: [Algorithm.password, Algorithm.passphrase] as const, + [Type.password]: [ + Algorithm.password, + Algorithm.passphrase, + Algorithm.sdkPassword, + Algorithm.sdkPassphrase, + ] as const, /** Algorithms that produce usernames */ [Type.username]: [Algorithm.username] as const, diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts new file mode 100644 index 00000000000..f41cf1d520b --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts @@ -0,0 +1,89 @@ +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 { BitwardenClient } from "@bitwarden/sdk-internal"; + +import { SdkPasswordRandomizer } from "../../engine"; +import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PassphraseGenerationOptions } from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const sdkPassphrase: GeneratorMetadata = { + id: Algorithm.sdkPassphrase, + type: Type.password, + weight: 130, + i18nKeys: { + name: "passphrase", + credentialType: "passphrase", + generateCredential: "generatePassphrase", + credentialGenerated: "passphraseGenerated", + copyCredential: "copyPassphrase", + useCredential: "useThisPassphrase", + }, + capabilities: { + autogenerate: false, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new SdkPasswordRandomizer(new BitwardenClient()); // @TODO hook up a real SDK client + }, + }, + profiles: { + [Profile.account]: { + type: "core", + 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, + constraints: { + type: PolicyType.PasswordGenerator, + default: { + wordSeparator: { maxLength: 1 }, + numWords: { + min: 3, + max: 20, + recommendation: 6, + }, + }, + create(policies, context) { + const initial = { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }; + const policy = policies.reduce(passphraseLeastPrivilege, initial); + const constraints = new PassphrasePolicyConstraints(policy, context.defaultConstraints); + return constraints; + }, + }, + }, + }, +}; + +export default sdkPassphrase; diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts new file mode 100644 index 00000000000..e63730225e3 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts @@ -0,0 +1,115 @@ +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 { deepFreeze } from "@bitwarden/common/tools/util"; +import { BitwardenClient } from "@bitwarden/sdk-internal"; + +import { SdkPasswordRandomizer } from "../../engine"; +import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PasswordGeneratorSettings } from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const sdkPassword: GeneratorMetadata = deepFreeze({ + id: Algorithm.sdkPassword, + type: Type.password, + weight: 120, + i18nKeys: { + name: "password", + generateCredential: "generatePassword", + credentialGenerated: "passwordGenerated", + credentialType: "password", + copyCredential: "copyPassword", + useCredential: "useThisPassword", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new SdkPasswordRandomizer(new BitwardenClient()); // @TODO hook up a real SDK client + }, + }, + profiles: { + [Profile.account]: { + type: "core", + 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"], + }, + }, + constraints: { + type: PolicyType.PasswordGenerator, + default: { + length: { + min: 5, + max: 128, + recommendation: 14, + }, + minNumber: { + min: 0, + max: 9, + }, + minSpecial: { + min: 0, + max: 9, + }, + }, + create(policies, context) { + const initial = { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }; + const policy = policies.reduce(passwordLeastPrivilege, initial); + const constraints = new DynamicPasswordPolicyConstraints( + policy, + context.defaultConstraints, + ); + return constraints; + }, + }, + }, + }, +}); + +export default sdkPassword; diff --git a/libs/tools/generator/core/src/providers/generator-dependency-provider.ts b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts index 14942698cdb..a333745bb53 100644 --- a/libs/tools/generator/core/src/providers/generator-dependency-provider.ts +++ b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts @@ -1,4 +1,5 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { RestClient } from "@bitwarden/common/tools/integration/rpc"; import { Randomizer } from "../abstractions"; @@ -9,4 +10,5 @@ export type GeneratorDependencyProvider = { // FIXME: introduce `I18nKeyOrLiteral` into forwarder // structures and remove this dependency i18nService: I18nService; + sdkService: SdkService; };