mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 18:53:29 +00:00
[PM-5606] Add reactive generator service (#7446)
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
// password generator "v2" interfaces
|
||||
export * from "./password-generation-options";
|
||||
export { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator";
|
||||
export { PasswordGeneratorPolicy } from "./password-generator-policy";
|
||||
export { PasswordGeneratorStrategy } from "./password-generator-strategy";
|
||||
|
||||
// legacy interfaces
|
||||
export { PasswordGeneratorOptions } from "./password-generator-options";
|
||||
export { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction";
|
||||
export { PasswordGenerationService } from "./password-generation.service";
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { DefaultBoundaries } from "./password-generator-options-evaluator";
|
||||
|
||||
/** Request format for password credential generation.
|
||||
* All members of this type may be `undefined` when the user is
|
||||
* generating a passphrase.
|
||||
*
|
||||
* @remarks The name of this type is a bit of a misnomer. This type
|
||||
* it is used with the "password generator" types. The name
|
||||
* `PasswordGeneratorOptions` is already in use by legacy code.
|
||||
*/
|
||||
export type PasswordGenerationOptions = {
|
||||
/** The length of the password selected by the user */
|
||||
length?: number;
|
||||
|
||||
/** The minimum length of the password. This defaults to 5, and increases
|
||||
* to ensure `minLength` is at least as large as the sum of the other minimums.
|
||||
*/
|
||||
minLength?: number;
|
||||
|
||||
/** `true` when ambiguous characters may be included in the output.
|
||||
* `false` when ambiguous characters should not be included in the output.
|
||||
*/
|
||||
ambiguous?: boolean;
|
||||
|
||||
/** `true` when uppercase ASCII characters should be included in the output
|
||||
* This value defaults to `false.
|
||||
*/
|
||||
uppercase?: boolean;
|
||||
|
||||
/** The minimum number of uppercase characters to include in the output.
|
||||
* The value is ignored when `uppercase` is `false`.
|
||||
* The value defaults to 1 when `uppercase` is `true`.
|
||||
*/
|
||||
minUppercase?: number;
|
||||
|
||||
/** `true` when lowercase ASCII characters should be included in the output.
|
||||
* This value defaults to `false`.
|
||||
*/
|
||||
lowercase?: boolean;
|
||||
|
||||
/** The minimum number of lowercase characters to include in the output.
|
||||
* The value defaults to 1 when `lowercase` is `true`.
|
||||
* The value defaults to 0 when `lowercase` is `false`.
|
||||
*/
|
||||
minLowercase?: number;
|
||||
|
||||
/** Whether or not to include ASCII digits in the output
|
||||
* This value defaults to `true` when `minNumber` is at least 1.
|
||||
* This value defaults to `false` when `minNumber` is less than 1.
|
||||
*/
|
||||
number?: boolean;
|
||||
|
||||
/** The minimum number of digits to include in the output.
|
||||
* The value defaults to 1 when `number` is `true`.
|
||||
* The value defaults to 0 when `number` is `false`.
|
||||
*/
|
||||
minNumber?: number;
|
||||
|
||||
/** Whether or not to include special characters in the output.
|
||||
* This value defaults to `true` when `minSpecial` is at least 1.
|
||||
* This value defaults to `false` when `minSpecial` is less than 1.
|
||||
*/
|
||||
special?: boolean;
|
||||
|
||||
/** The minimum number of special characters to include in the output.
|
||||
* This value defaults to 1 when `special` is `true`.
|
||||
* This value defaults to 0 when `special` is `false`.
|
||||
*/
|
||||
minSpecial?: number;
|
||||
};
|
||||
|
||||
/** The default options for password generation. */
|
||||
export const DefaultPasswordGenerationOptions: Partial<PasswordGenerationOptions> = Object.freeze({
|
||||
length: 14,
|
||||
minLength: DefaultBoundaries.length.min,
|
||||
ambiguous: true,
|
||||
uppercase: true,
|
||||
lowercase: true,
|
||||
number: true,
|
||||
minNumber: 1,
|
||||
special: true,
|
||||
minSpecial: 1,
|
||||
});
|
||||
@@ -1,17 +1,19 @@
|
||||
import { PasswordGeneratorPolicyOptions } from "../../../admin-console/models/domain/password-generator-policy-options";
|
||||
/**
|
||||
* include structuredClone in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
|
||||
import { PasswordGenerationOptions } from "./password-generator-options";
|
||||
import {
|
||||
DefaultBoundaries,
|
||||
PasswordGeneratorOptionsEvaluator,
|
||||
} from "./password-generator-options-evaluator";
|
||||
import { DefaultBoundaries } from "./password-generator-options-evaluator";
|
||||
import { DisabledPasswordGeneratorPolicy } from "./password-generator-policy";
|
||||
|
||||
import { PasswordGenerationOptions, PasswordGeneratorOptionsEvaluator } from ".";
|
||||
|
||||
describe("Password generator options builder", () => {
|
||||
const defaultOptions = Object.freeze({ minLength: 0 });
|
||||
|
||||
describe("constructor()", () => {
|
||||
it("should set the policy object to a copy of the input policy", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.minLength = 10; // arbitrary change for deep equality check
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -21,7 +23,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set default boundaries when a default policy is used", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
@@ -35,7 +37,7 @@ describe("Password generator options builder", () => {
|
||||
(minLength) => {
|
||||
expect(minLength).toBeLessThan(DefaultBoundaries.length.min);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.minLength = minLength;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -50,7 +52,7 @@ describe("Password generator options builder", () => {
|
||||
expect(expectedLength).toBeGreaterThan(DefaultBoundaries.length.min);
|
||||
expect(expectedLength).toBeLessThanOrEqual(DefaultBoundaries.length.max);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.minLength = expectedLength;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -65,7 +67,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedLength) => {
|
||||
expect(expectedLength).toBeGreaterThan(DefaultBoundaries.length.max);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.minLength = expectedLength;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -81,7 +83,7 @@ describe("Password generator options builder", () => {
|
||||
expect(expectedMinDigits).toBeGreaterThan(DefaultBoundaries.minDigits.min);
|
||||
expect(expectedMinDigits).toBeLessThanOrEqual(DefaultBoundaries.minDigits.max);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.numberCount = expectedMinDigits;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -96,7 +98,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedMinDigits) => {
|
||||
expect(expectedMinDigits).toBeGreaterThan(DefaultBoundaries.minDigits.max);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.numberCount = expectedMinDigits;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -116,7 +118,7 @@ describe("Password generator options builder", () => {
|
||||
DefaultBoundaries.minSpecialCharacters.max,
|
||||
);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.specialCount = expectedSpecialCharacters;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -135,7 +137,7 @@ describe("Password generator options builder", () => {
|
||||
DefaultBoundaries.minSpecialCharacters.max,
|
||||
);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.specialCount = expectedSpecialCharacters;
|
||||
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
@@ -154,7 +156,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedLength, numberCount, specialCount) => {
|
||||
expect(expectedLength).toBeGreaterThanOrEqual(DefaultBoundaries.length.min);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.numberCount = numberCount;
|
||||
policy.specialCount = specialCount;
|
||||
|
||||
@@ -165,6 +167,71 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe("policyInEffect", () => {
|
||||
it("should return false when the policy has no effect", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return true when the policy has a minlength greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.minLength = DefaultBoundaries.length.min + 1;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return true when the policy has a number count greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.numberCount = DefaultBoundaries.minDigits.min + 1;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return true when the policy has a special character count greater than the default boundary", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.specialCount = DefaultBoundaries.minSpecialCharacters.min + 1;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return true when the policy has uppercase enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useUppercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return true when the policy has lowercase enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useLowercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return true when the policy has numbers enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useNumbers = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return true when the policy has special characters enabled", () => {
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useSpecial = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
|
||||
expect(builder.policyInEffect).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyPolicy(options)", () => {
|
||||
// All tests should freeze the options to ensure they are not modified
|
||||
|
||||
@@ -175,7 +242,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.uppercase` to '%s' when `policy.useUppercase` is false and `options.uppercase` is '%s'",
|
||||
(expectedUppercase, uppercase) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useUppercase = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, uppercase });
|
||||
@@ -189,7 +256,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.uppercase` (= %s) to true when `policy.useUppercase` is true",
|
||||
(uppercase) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useUppercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, uppercase });
|
||||
@@ -207,7 +274,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.lowercase` to '%s' when `policy.useLowercase` is false and `options.lowercase` is '%s'",
|
||||
(expectedLowercase, lowercase) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useLowercase = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, lowercase });
|
||||
@@ -221,7 +288,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.lowercase` (= %s) to true when `policy.useLowercase` is true",
|
||||
(lowercase) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useLowercase = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, lowercase });
|
||||
@@ -239,7 +306,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.number` to '%s' when `policy.useNumbers` is false and `options.number` is '%s'",
|
||||
(expectedNumber, number) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useNumbers = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number });
|
||||
@@ -253,7 +320,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.number` (= %s) to true when `policy.useNumbers` is true",
|
||||
(number) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useNumbers = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number });
|
||||
@@ -271,7 +338,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.special` to '%s' when `policy.useSpecial` is false and `options.special` is '%s'",
|
||||
(expectedSpecial, special) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useSpecial = false;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special });
|
||||
@@ -285,7 +352,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([false, true, undefined])(
|
||||
"should set `options.special` (= %s) to true when `policy.useSpecial` is true",
|
||||
(special) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.useSpecial = true;
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special });
|
||||
@@ -299,7 +366,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2, 3, 4])(
|
||||
"should set `options.length` (= %i) to the minimum it is less than the minimum length",
|
||||
(length) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(length).toBeLessThan(builder.length.min);
|
||||
|
||||
@@ -314,7 +381,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([5, 10, 50, 100, 128])(
|
||||
"should not change `options.length` (= %i) when it is within the boundaries",
|
||||
(length) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(length).toBeGreaterThanOrEqual(builder.length.min);
|
||||
expect(length).toBeLessThanOrEqual(builder.length.max);
|
||||
@@ -330,7 +397,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([129, 500, 9000])(
|
||||
"should set `options.length` (= %i) to the maximum length when it is exceeded",
|
||||
(length) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(length).toBeGreaterThan(builder.length.max);
|
||||
|
||||
@@ -352,7 +419,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.number === %s` when `options.minNumber` (= %i) is set to a value greater than 0",
|
||||
(expectedNumber, minNumber) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, minNumber });
|
||||
|
||||
@@ -363,7 +430,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should set `options.minNumber` to the minimum value when `options.number` is true", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number: true });
|
||||
|
||||
@@ -373,7 +440,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `options.minNumber` to 0 when `options.number` is false", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, number: false });
|
||||
|
||||
@@ -385,7 +452,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2, 3, 4])(
|
||||
"should set `options.minNumber` (= %i) to the minimum it is less than the minimum number",
|
||||
(minNumber) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.numberCount = 5; // arbitrary value greater than minNumber
|
||||
expect(minNumber).toBeLessThan(policy.numberCount);
|
||||
|
||||
@@ -401,7 +468,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 3, 5, 7, 9])(
|
||||
"should not change `options.minNumber` (= %i) when it is within the boundaries",
|
||||
(minNumber) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minNumber).toBeGreaterThanOrEqual(builder.minDigits.min);
|
||||
expect(minNumber).toBeLessThanOrEqual(builder.minDigits.max);
|
||||
@@ -417,7 +484,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([10, 20, 400])(
|
||||
"should set `options.minNumber` (= %i) to the maximum digit boundary when it is exceeded",
|
||||
(minNumber) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minNumber).toBeGreaterThan(builder.minDigits.max);
|
||||
|
||||
@@ -439,7 +506,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should set `options.special === %s` when `options.minSpecial` (= %i) is set to a value greater than 0",
|
||||
(expectedSpecial, minSpecial) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, minSpecial });
|
||||
|
||||
@@ -450,7 +517,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should set `options.minSpecial` to the minimum value when `options.special` is true", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special: true });
|
||||
|
||||
@@ -460,7 +527,7 @@ describe("Password generator options builder", () => {
|
||||
});
|
||||
|
||||
it("should set `options.minSpecial` to 0 when `options.special` is false", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ ...defaultOptions, special: false });
|
||||
|
||||
@@ -472,7 +539,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 2, 3, 4])(
|
||||
"should set `options.minSpecial` (= %i) to the minimum it is less than the minimum special characters",
|
||||
(minSpecial) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
policy.specialCount = 5; // arbitrary value greater than minSpecial
|
||||
expect(minSpecial).toBeLessThan(policy.specialCount);
|
||||
|
||||
@@ -488,7 +555,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([1, 3, 5, 7, 9])(
|
||||
"should not change `options.minSpecial` (= %i) when it is within the boundaries",
|
||||
(minSpecial) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minSpecial).toBeGreaterThanOrEqual(builder.minSpecialCharacters.min);
|
||||
expect(minSpecial).toBeLessThanOrEqual(builder.minSpecialCharacters.max);
|
||||
@@ -504,7 +571,7 @@ describe("Password generator options builder", () => {
|
||||
it.each([10, 20, 400])(
|
||||
"should set `options.minSpecial` (= %i) to the maximum special character boundary when it is exceeded",
|
||||
(minSpecial) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
expect(minSpecial).toBeGreaterThan(builder.minSpecialCharacters.max);
|
||||
|
||||
@@ -517,7 +584,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should preserve unknown properties", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
unknown: "property",
|
||||
@@ -540,7 +607,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minLowercase === %i` when `options.lowercase` is %s",
|
||||
(expectedMinLowercase, lowercase) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ lowercase, ...defaultOptions });
|
||||
|
||||
@@ -556,7 +623,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minUppercase === %i` when `options.uppercase` is %s",
|
||||
(expectedMinUppercase, uppercase) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ uppercase, ...defaultOptions });
|
||||
|
||||
@@ -572,7 +639,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minNumber === %i` when `options.number` is %s and `options.minNumber` is not set",
|
||||
(expectedMinNumber, number) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ number, ...defaultOptions });
|
||||
|
||||
@@ -590,7 +657,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.number === %s` when `options.minNumber` is %i and `options.number` is not set",
|
||||
(expectedNumber, minNumber) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ minNumber, ...defaultOptions });
|
||||
|
||||
@@ -606,7 +673,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.minSpecial === %i` when `options.special` is %s and `options.minSpecial` is not set",
|
||||
(special, expectedMinSpecial) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ special, ...defaultOptions });
|
||||
|
||||
@@ -624,7 +691,7 @@ describe("Password generator options builder", () => {
|
||||
])(
|
||||
"should output `options.special === %s` when `options.minSpecial` is %i and `options.special` is not set",
|
||||
(minSpecial, expectedSpecial) => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({ minSpecial, ...defaultOptions });
|
||||
|
||||
@@ -645,7 +712,7 @@ describe("Password generator options builder", () => {
|
||||
const sumOfMinimums = minLowercase + minUppercase + minNumber + minSpecial;
|
||||
expect(sumOfMinimums).toBeLessThan(DefaultBoundaries.length.min);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
minLowercase,
|
||||
@@ -670,7 +737,7 @@ describe("Password generator options builder", () => {
|
||||
(expectedMinLength, minLowercase, minUppercase, minNumber, minSpecial) => {
|
||||
expect(expectedMinLength).toBeGreaterThanOrEqual(DefaultBoundaries.length.min);
|
||||
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
minLowercase,
|
||||
@@ -687,7 +754,7 @@ describe("Password generator options builder", () => {
|
||||
);
|
||||
|
||||
it("should preserve unknown properties", () => {
|
||||
const policy = new PasswordGeneratorPolicyOptions();
|
||||
const policy = Object.assign({}, DisabledPasswordGeneratorPolicy);
|
||||
const builder = new PasswordGeneratorOptionsEvaluator(policy);
|
||||
const options = Object.freeze({
|
||||
unknown: "property",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PasswordGeneratorPolicyOptions } from "../../../admin-console/models/domain/password-generator-policy-options";
|
||||
import { PolicyEvaluator } from "../abstractions/policy-evaluator.abstraction";
|
||||
|
||||
import { PasswordGenerationOptions } from "./password-generator-options";
|
||||
import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
import { PasswordGeneratorPolicy } from "./password-generator-policy";
|
||||
|
||||
function initializeBoundaries() {
|
||||
const length = Object.freeze({
|
||||
@@ -37,7 +38,9 @@ type Boundary = {
|
||||
|
||||
/** Enforces policy for password generation.
|
||||
*/
|
||||
export class PasswordGeneratorOptionsEvaluator {
|
||||
export class PasswordGeneratorOptionsEvaluator
|
||||
implements PolicyEvaluator<PasswordGeneratorPolicy, PasswordGenerationOptions>
|
||||
{
|
||||
// This design is not ideal, but it is a step towards a more robust password
|
||||
// generator. Ideally, `sanitize` would be implemented on an options class,
|
||||
// and `applyPolicy` would be implemented on a policy class, "mise en place".
|
||||
@@ -62,13 +65,13 @@ export class PasswordGeneratorOptionsEvaluator {
|
||||
|
||||
/** Policy applied by the evaluator.
|
||||
*/
|
||||
readonly policy: PasswordGeneratorPolicyOptions;
|
||||
readonly policy: PasswordGeneratorPolicy;
|
||||
|
||||
/** Instantiates the evaluator.
|
||||
* @param policy The policy applied by the evaluator. When this conflicts with
|
||||
* the defaults, the policy takes precedence.
|
||||
*/
|
||||
constructor(policy: PasswordGeneratorPolicyOptions) {
|
||||
constructor(policy: PasswordGeneratorPolicy) {
|
||||
function createBoundary(value: number, defaultBoundary: Boundary): Boundary {
|
||||
const boundary = {
|
||||
min: Math.max(defaultBoundary.min, value),
|
||||
@@ -78,7 +81,7 @@ export class PasswordGeneratorOptionsEvaluator {
|
||||
return boundary;
|
||||
}
|
||||
|
||||
this.policy = policy.clone();
|
||||
this.policy = structuredClone(policy);
|
||||
this.minDigits = createBoundary(policy.numberCount, DefaultBoundaries.minDigits);
|
||||
this.minSpecialCharacters = createBoundary(
|
||||
policy.specialCount,
|
||||
@@ -96,12 +99,22 @@ export class PasswordGeneratorOptionsEvaluator {
|
||||
};
|
||||
}
|
||||
|
||||
/** Apply policy to a set of options.
|
||||
* @param options The options to build from. These options are not altered.
|
||||
* @returns A complete password generation request with policy applied.
|
||||
* @remarks This method only applies policy overrides.
|
||||
* Pass the result to `sanitize` to ensure consistency.
|
||||
*/
|
||||
/** {@link PolicyEvaluator.policyInEffect} */
|
||||
get policyInEffect(): boolean {
|
||||
const policies = [
|
||||
this.policy.useUppercase,
|
||||
this.policy.useLowercase,
|
||||
this.policy.useNumbers,
|
||||
this.policy.useSpecial,
|
||||
this.policy.minLength > DefaultBoundaries.length.min,
|
||||
this.policy.numberCount > DefaultBoundaries.minDigits.min,
|
||||
this.policy.specialCount > DefaultBoundaries.minSpecialCharacters.min,
|
||||
];
|
||||
|
||||
return policies.includes(true);
|
||||
}
|
||||
|
||||
/** {@link PolicyEvaluator.applyPolicy} */
|
||||
applyPolicy(options: PasswordGenerationOptions): PasswordGenerationOptions {
|
||||
function fitToBounds(value: number, boundaries: Boundary) {
|
||||
const { min, max } = boundaries;
|
||||
@@ -137,13 +150,7 @@ export class PasswordGeneratorOptionsEvaluator {
|
||||
};
|
||||
}
|
||||
|
||||
/** Ensures internal options consistency.
|
||||
* @param options The options to cascade. These options are not altered.
|
||||
* @returns A new password generation request with cascade applied.
|
||||
* @remarks This method fills null and undefined values by looking at
|
||||
* pairs of flags and values (e.g. `number` and `minNumber`). If the flag
|
||||
* and value are inconsistent, the flag cascades to the value.
|
||||
*/
|
||||
/** {@link PolicyEvaluator.sanitize} */
|
||||
sanitize(options: PasswordGenerationOptions): PasswordGenerationOptions {
|
||||
function cascade(enabled: boolean, value: number): [boolean, number] {
|
||||
const enabledResult = enabled ?? value > 0;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
|
||||
/** Request format for credential generation.
|
||||
* This type includes all properties suitable for reactive data binding.
|
||||
*/
|
||||
@@ -12,71 +14,6 @@ export type PasswordGeneratorOptions = PasswordGenerationOptions &
|
||||
type?: "password" | "passphrase";
|
||||
};
|
||||
|
||||
/** Request format for password credential generation.
|
||||
* All members of this type may be `undefined` when the user is
|
||||
* generating a passphrase.
|
||||
*/
|
||||
export type PasswordGenerationOptions = {
|
||||
/** The length of the password selected by the user */
|
||||
length?: number;
|
||||
|
||||
/** The minimum length of the password. This defaults to 5, and increases
|
||||
* to ensure `minLength` is at least as large as the sum of the other minimums.
|
||||
*/
|
||||
minLength?: number;
|
||||
|
||||
/** `true` when ambiguous characters may be included in the output.
|
||||
* `false` when ambiguous characters should not be included in the output.
|
||||
*/
|
||||
ambiguous?: boolean;
|
||||
|
||||
/** `true` when uppercase ASCII characters should be included in the output
|
||||
* This value defaults to `false.
|
||||
*/
|
||||
uppercase?: boolean;
|
||||
|
||||
/** The minimum number of uppercase characters to include in the output.
|
||||
* The value is ignored when `uppercase` is `false`.
|
||||
* The value defaults to 1 when `uppercase` is `true`.
|
||||
*/
|
||||
minUppercase?: number;
|
||||
|
||||
/** `true` when lowercase ASCII characters should be included in the output.
|
||||
* This value defaults to `false`.
|
||||
*/
|
||||
lowercase?: boolean;
|
||||
|
||||
/** The minimum number of lowercase characters to include in the output.
|
||||
* The value defaults to 1 when `lowercase` is `true`.
|
||||
* The value defaults to 0 when `lowercase` is `false`.
|
||||
*/
|
||||
minLowercase?: number;
|
||||
|
||||
/** Whether or not to include ASCII digits in the output
|
||||
* This value defaults to `true` when `minNumber` is at least 1.
|
||||
* This value defaults to `false` when `minNumber` is less than 1.
|
||||
*/
|
||||
number?: boolean;
|
||||
|
||||
/** The minimum number of digits to include in the output.
|
||||
* The value defaults to 1 when `number` is `true`.
|
||||
* The value defaults to 0 when `number` is `false`.
|
||||
*/
|
||||
minNumber?: number;
|
||||
|
||||
/** Whether or not to include special characters in the output.
|
||||
* This value defaults to `true` when `minSpecial` is at least 1.
|
||||
* This value defaults to `false` when `minSpecial` is less than 1.
|
||||
*/
|
||||
special?: boolean;
|
||||
|
||||
/** The minimum number of special characters to include in the output.
|
||||
* This value defaults to 1 when `special` is `true`.
|
||||
* This value defaults to 0 when `special` is `false`.
|
||||
*/
|
||||
minSpecial?: number;
|
||||
};
|
||||
|
||||
/** Request format for passphrase credential generation.
|
||||
* The members of this type may be `undefined` when the user is
|
||||
* generating a password.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/** Policy options enforced during password generation. */
|
||||
export type PasswordGeneratorPolicy = {
|
||||
/** The minimum length of generated passwords.
|
||||
* When this is less than or equal to zero, it is ignored.
|
||||
* If this is less than the total number of characters required by
|
||||
* the policy's other settings, then it is ignored.
|
||||
*/
|
||||
minLength: number;
|
||||
|
||||
/** When this is true, an uppercase character must be part of
|
||||
* the generated password.
|
||||
*/
|
||||
useUppercase: boolean;
|
||||
|
||||
/** When this is true, a lowercase character must be part of
|
||||
* the generated password.
|
||||
*/
|
||||
useLowercase: boolean;
|
||||
|
||||
/** When this is true, at least one digit must be part of the generated
|
||||
* password.
|
||||
*/
|
||||
useNumbers: boolean;
|
||||
|
||||
/** The quantity of digits to include in the generated password.
|
||||
* When this is less than or equal to zero, it is ignored.
|
||||
*/
|
||||
numberCount: number;
|
||||
|
||||
/** When this is true, at least one digit must be part of the generated
|
||||
* password.
|
||||
*/
|
||||
useSpecial: boolean;
|
||||
|
||||
/** The quantity of special characters to include in the generated
|
||||
* password. When this is less than or equal to zero, it is ignored.
|
||||
*/
|
||||
specialCount: number;
|
||||
};
|
||||
|
||||
/** The default options for password generation policy. */
|
||||
export const DisabledPasswordGeneratorPolicy: PasswordGeneratorPolicy = Object.freeze({
|
||||
minLength: 0,
|
||||
useUppercase: false,
|
||||
useLowercase: false,
|
||||
useNumbers: false,
|
||||
numberCount: 0,
|
||||
useSpecial: false,
|
||||
specialCount: 0,
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* include structuredClone in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { PASSWORD_SETTINGS } from "../key-definitions";
|
||||
|
||||
import {
|
||||
PasswordGenerationServiceAbstraction,
|
||||
PasswordGeneratorOptionsEvaluator,
|
||||
PasswordGeneratorStrategy,
|
||||
} from ".";
|
||||
|
||||
describe("Password generation strategy", () => {
|
||||
describe("evaluator()", () => {
|
||||
it("should throw if the policy type is incorrect", () => {
|
||||
const strategy = new PasswordGeneratorStrategy(null);
|
||||
const policy = mock<Policy>({
|
||||
type: PolicyType.DisableSend,
|
||||
});
|
||||
|
||||
expect(() => strategy.evaluator(policy)).toThrow(new RegExp("Mismatched policy type\\. .+"));
|
||||
});
|
||||
|
||||
it("should map to the policy evaluator", () => {
|
||||
const strategy = new PasswordGeneratorStrategy(null);
|
||||
const policy = mock<Policy>({
|
||||
type: PolicyType.PasswordGenerator,
|
||||
data: {
|
||||
minLength: 10,
|
||||
useUpper: true,
|
||||
useLower: true,
|
||||
useNumbers: true,
|
||||
minNumbers: 1,
|
||||
useSpecial: true,
|
||||
minSpecial: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const evaluator = strategy.evaluator(policy);
|
||||
|
||||
expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator);
|
||||
expect(evaluator.policy).toMatchObject({
|
||||
minLength: 10,
|
||||
useUppercase: true,
|
||||
useLowercase: true,
|
||||
useNumbers: true,
|
||||
numberCount: 1,
|
||||
useSpecial: true,
|
||||
specialCount: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("disk", () => {
|
||||
it("should use password settings key", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
const strategy = new PasswordGeneratorStrategy(legacy);
|
||||
|
||||
expect(strategy.disk).toBe(PASSWORD_SETTINGS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy", () => {
|
||||
it("should use password generator policy", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
const strategy = new PasswordGeneratorStrategy(legacy);
|
||||
|
||||
expect(strategy.policy).toBe(PolicyType.PasswordGenerator);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generate()", () => {
|
||||
it("should call the legacy service with the given options", async () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
const strategy = new PasswordGeneratorStrategy(legacy);
|
||||
const options = {
|
||||
type: "password",
|
||||
minLength: 1,
|
||||
useUppercase: true,
|
||||
useLowercase: true,
|
||||
useNumbers: true,
|
||||
numberCount: 1,
|
||||
useSpecial: true,
|
||||
specialCount: 1,
|
||||
};
|
||||
|
||||
await strategy.generate(options);
|
||||
|
||||
expect(legacy.generatePassword).toHaveBeenCalledWith(options);
|
||||
});
|
||||
|
||||
it("should set the generation type to password", async () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
const strategy = new PasswordGeneratorStrategy(legacy);
|
||||
|
||||
await strategy.generate({ type: "foo" } as any);
|
||||
|
||||
expect(legacy.generatePassword).toHaveBeenCalledWith({ type: "password" });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { GeneratorStrategy } from "..";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
// FIXME: use index.ts imports once policy abstractions and models
|
||||
// implement ADR-0002
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { PASSWORD_SETTINGS } from "../key-definitions";
|
||||
|
||||
import { PasswordGenerationOptions } from "./password-generation-options";
|
||||
import { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction";
|
||||
import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator";
|
||||
import { PasswordGeneratorPolicy } from "./password-generator-policy";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
/** {@link GeneratorStrategy} */
|
||||
export class PasswordGeneratorStrategy
|
||||
implements GeneratorStrategy<PasswordGenerationOptions, PasswordGeneratorPolicy>
|
||||
{
|
||||
/** instantiates the password generator strategy.
|
||||
* @param legacy generates the password
|
||||
*/
|
||||
constructor(private legacy: PasswordGenerationServiceAbstraction) {}
|
||||
|
||||
/** {@link GeneratorStrategy.disk} */
|
||||
get disk() {
|
||||
return PASSWORD_SETTINGS;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.policy} */
|
||||
get policy() {
|
||||
return PolicyType.PasswordGenerator;
|
||||
}
|
||||
|
||||
get cache_ms() {
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.evaluator} */
|
||||
evaluator(policy: Policy): PasswordGeneratorOptionsEvaluator {
|
||||
if (policy.type !== this.policy) {
|
||||
const details = `Expected: ${this.policy}. Received: ${policy.type}`;
|
||||
throw Error("Mismatched policy type. " + details);
|
||||
}
|
||||
|
||||
return new PasswordGeneratorOptionsEvaluator({
|
||||
minLength: policy.data.minLength,
|
||||
useUppercase: policy.data.useUpper,
|
||||
useLowercase: policy.data.useLower,
|
||||
useNumbers: policy.data.useNumbers,
|
||||
numberCount: policy.data.minNumbers,
|
||||
useSpecial: policy.data.useSpecial,
|
||||
specialCount: policy.data.minSpecial,
|
||||
});
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.generate} */
|
||||
generate(options: PasswordGenerationOptions): Promise<string> {
|
||||
return this.legacy.generatePassword({ ...options, type: "password" });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user