[PM-2311] Allow empty passphrase separator (#5473)

* Change passphrase generator's default wordSeparator to the empty string ''
* Create DefaultPassphraseGenerationOptions
* Use DefaultPassphraseGenerationOptions.wordSeparator in passphrase generation
* Add `empty` separator option to passphrase generator CLI and an example
* Change DefaultPassphraseGenerationOptions numWords to 3
* Use `DefaultPassphraseGenerationOptions.numWords` in CLI passphrase gen
This commit is contained in:
Joseph Yu 2024-02-09 08:07:53 -08:00 committed by GitHub
parent 783ae104a3
commit bb031f6779
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 42 additions and 9 deletions

View File

@ -322,6 +322,7 @@ export class Program {
writeLn(" bw generate -ul");
writeLn(" bw generate -p --separator _");
writeLn(" bw generate -p --words 5 --separator space");
writeLn(" bw generate -p --words 5 --separator empty");
writeLn("", true);
})
.action(async (options) => {

View File

@ -1,4 +1,5 @@
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DefaultPassphraseGenerationOptions } from "@bitwarden/common/tools/generator/passphrase";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options";
@ -65,8 +66,14 @@ class Options {
this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous);
this.length = CliUtils.convertNumberOption(passedOptions?.length, 14);
this.type = passedOptions?.passphrase ? "passphrase" : "password";
this.separator = CliUtils.convertStringOption(passedOptions?.separator, "-");
this.words = CliUtils.convertNumberOption(passedOptions?.words, 3);
this.separator = CliUtils.convertStringOption(
passedOptions?.separator,
DefaultPassphraseGenerationOptions.wordSeparator,
);
this.words = CliUtils.convertNumberOption(
passedOptions?.words,
DefaultPassphraseGenerationOptions.numWords,
);
this.minNumber = CliUtils.convertNumberOption(passedOptions?.minNumber, 1);
this.minSpecial = CliUtils.convertNumberOption(passedOptions?.minSpecial, 1);
@ -83,6 +90,8 @@ class Options {
}
if (this.separator === "space") {
this.separator = " ";
} else if (this.separator === "empty") {
this.separator = "";
} else if (this.separator != null && this.separator.length > 1) {
this.separator = this.separator[0];
}

View File

@ -2,3 +2,4 @@
export { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator";
export { PassphraseGeneratorPolicy } from "./passphrase-generator-policy";
export { PassphraseGeneratorStrategy } from "./passphrase-generator-strategy";
export { DefaultPassphraseGenerationOptions } from "./passphrase-generation-options";

View File

@ -4,7 +4,7 @@
*/
export type PassphraseGenerationOptions = {
/** The number of words to include in the passphrase.
* This value defaults to 4.
* This value defaults to 3.
*/
numWords?: number;
@ -24,3 +24,12 @@ export type PassphraseGenerationOptions = {
*/
includeNumber?: boolean;
};
/** The default options for passphrase generation. */
export const DefaultPassphraseGenerationOptions: Partial<PassphraseGenerationOptions> =
Object.freeze({
numWords: 3,
wordSeparator: "-",
capitalize: false,
includeNumber: false,
});

View File

@ -239,6 +239,16 @@ describe("Password generator options builder", () => {
expect(sanitizedOptions.wordSeparator).toEqual("-");
});
it("should leave `wordSeparator` as the empty string '' when it is the empty string", () => {
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
const options = Object.freeze({ wordSeparator: "" });
const sanitizedOptions = builder.sanitize(options);
expect(sanitizedOptions.wordSeparator).toEqual("");
});
it("should preserve unknown properties", () => {
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
const builder = new PassphraseGeneratorOptionsEvaluator(policy);

View File

@ -1,6 +1,9 @@
import { PolicyEvaluator } from "../abstractions/policy-evaluator.abstraction";
import { PassphraseGenerationOptions } from "./passphrase-generation-options";
import {
DefaultPassphraseGenerationOptions,
PassphraseGenerationOptions,
} from "./passphrase-generation-options";
import { PassphraseGeneratorPolicy } from "./passphrase-generator-policy";
type Boundary = {
@ -108,8 +111,11 @@ export class PassphraseGeneratorOptionsEvaluator
* @returns A passphrase generation request with cascade applied.
*/
sanitize(options: PassphraseGenerationOptions): PassphraseGenerationOptions {
// ensure words are separated by a single character
const wordSeparator = options.wordSeparator?.[0] ?? "-";
// ensure words are separated by a single character or the empty string
const wordSeparator =
options.wordSeparator === ""
? ""
: options.wordSeparator?.[0] ?? DefaultPassphraseGenerationOptions.wordSeparator;
return {
...options,

View File

@ -147,9 +147,6 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
if (o.numWords == null || o.numWords <= 2) {
o.numWords = DefaultOptions.numWords;
}
if (o.wordSeparator == null || o.wordSeparator.length === 0 || o.wordSeparator.length > 1) {
o.wordSeparator = " ";
}
if (o.capitalize == null) {
o.capitalize = false;
}