[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:
parent
783ae104a3
commit
bb031f6779
|
@ -322,6 +322,7 @@ export class Program {
|
||||||
writeLn(" bw generate -ul");
|
writeLn(" bw generate -ul");
|
||||||
writeLn(" bw generate -p --separator _");
|
writeLn(" bw generate -p --separator _");
|
||||||
writeLn(" bw generate -p --words 5 --separator space");
|
writeLn(" bw generate -p --words 5 --separator space");
|
||||||
|
writeLn(" bw generate -p --words 5 --separator empty");
|
||||||
writeLn("", true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options";
|
import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options";
|
||||||
|
|
||||||
|
@ -65,8 +66,14 @@ class Options {
|
||||||
this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous);
|
this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous);
|
||||||
this.length = CliUtils.convertNumberOption(passedOptions?.length, 14);
|
this.length = CliUtils.convertNumberOption(passedOptions?.length, 14);
|
||||||
this.type = passedOptions?.passphrase ? "passphrase" : "password";
|
this.type = passedOptions?.passphrase ? "passphrase" : "password";
|
||||||
this.separator = CliUtils.convertStringOption(passedOptions?.separator, "-");
|
this.separator = CliUtils.convertStringOption(
|
||||||
this.words = CliUtils.convertNumberOption(passedOptions?.words, 3);
|
passedOptions?.separator,
|
||||||
|
DefaultPassphraseGenerationOptions.wordSeparator,
|
||||||
|
);
|
||||||
|
this.words = CliUtils.convertNumberOption(
|
||||||
|
passedOptions?.words,
|
||||||
|
DefaultPassphraseGenerationOptions.numWords,
|
||||||
|
);
|
||||||
this.minNumber = CliUtils.convertNumberOption(passedOptions?.minNumber, 1);
|
this.minNumber = CliUtils.convertNumberOption(passedOptions?.minNumber, 1);
|
||||||
this.minSpecial = CliUtils.convertNumberOption(passedOptions?.minSpecial, 1);
|
this.minSpecial = CliUtils.convertNumberOption(passedOptions?.minSpecial, 1);
|
||||||
|
|
||||||
|
@ -83,6 +90,8 @@ class Options {
|
||||||
}
|
}
|
||||||
if (this.separator === "space") {
|
if (this.separator === "space") {
|
||||||
this.separator = " ";
|
this.separator = " ";
|
||||||
|
} else if (this.separator === "empty") {
|
||||||
|
this.separator = "";
|
||||||
} else if (this.separator != null && this.separator.length > 1) {
|
} else if (this.separator != null && this.separator.length > 1) {
|
||||||
this.separator = this.separator[0];
|
this.separator = this.separator[0];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
export { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator";
|
export { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator";
|
||||||
export { PassphraseGeneratorPolicy } from "./passphrase-generator-policy";
|
export { PassphraseGeneratorPolicy } from "./passphrase-generator-policy";
|
||||||
export { PassphraseGeneratorStrategy } from "./passphrase-generator-strategy";
|
export { PassphraseGeneratorStrategy } from "./passphrase-generator-strategy";
|
||||||
|
export { DefaultPassphraseGenerationOptions } from "./passphrase-generation-options";
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
export type PassphraseGenerationOptions = {
|
export type PassphraseGenerationOptions = {
|
||||||
/** The number of words to include in the passphrase.
|
/** The number of words to include in the passphrase.
|
||||||
* This value defaults to 4.
|
* This value defaults to 3.
|
||||||
*/
|
*/
|
||||||
numWords?: number;
|
numWords?: number;
|
||||||
|
|
||||||
|
@ -24,3 +24,12 @@ export type PassphraseGenerationOptions = {
|
||||||
*/
|
*/
|
||||||
includeNumber?: boolean;
|
includeNumber?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** The default options for passphrase generation. */
|
||||||
|
export const DefaultPassphraseGenerationOptions: Partial<PassphraseGenerationOptions> =
|
||||||
|
Object.freeze({
|
||||||
|
numWords: 3,
|
||||||
|
wordSeparator: "-",
|
||||||
|
capitalize: false,
|
||||||
|
includeNumber: false,
|
||||||
|
});
|
||||||
|
|
|
@ -239,6 +239,16 @@ describe("Password generator options builder", () => {
|
||||||
expect(sanitizedOptions.wordSeparator).toEqual("-");
|
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", () => {
|
it("should preserve unknown properties", () => {
|
||||||
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
const policy = Object.assign({}, DisabledPassphraseGeneratorPolicy);
|
||||||
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
const builder = new PassphraseGeneratorOptionsEvaluator(policy);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { PolicyEvaluator } from "../abstractions/policy-evaluator.abstraction";
|
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";
|
import { PassphraseGeneratorPolicy } from "./passphrase-generator-policy";
|
||||||
|
|
||||||
type Boundary = {
|
type Boundary = {
|
||||||
|
@ -108,8 +111,11 @@ export class PassphraseGeneratorOptionsEvaluator
|
||||||
* @returns A passphrase generation request with cascade applied.
|
* @returns A passphrase generation request with cascade applied.
|
||||||
*/
|
*/
|
||||||
sanitize(options: PassphraseGenerationOptions): PassphraseGenerationOptions {
|
sanitize(options: PassphraseGenerationOptions): PassphraseGenerationOptions {
|
||||||
// ensure words are separated by a single character
|
// ensure words are separated by a single character or the empty string
|
||||||
const wordSeparator = options.wordSeparator?.[0] ?? "-";
|
const wordSeparator =
|
||||||
|
options.wordSeparator === ""
|
||||||
|
? ""
|
||||||
|
: options.wordSeparator?.[0] ?? DefaultPassphraseGenerationOptions.wordSeparator;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
|
|
|
@ -147,9 +147,6 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||||
if (o.numWords == null || o.numWords <= 2) {
|
if (o.numWords == null || o.numWords <= 2) {
|
||||||
o.numWords = DefaultOptions.numWords;
|
o.numWords = DefaultOptions.numWords;
|
||||||
}
|
}
|
||||||
if (o.wordSeparator == null || o.wordSeparator.length === 0 || o.wordSeparator.length > 1) {
|
|
||||||
o.wordSeparator = " ";
|
|
||||||
}
|
|
||||||
if (o.capitalize == null) {
|
if (o.capitalize == null) {
|
||||||
o.capitalize = false;
|
o.capitalize = false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue