Skip to content

Allow passing a range for length #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ __Note__: ambiguous characters are only removed if there is more than one ambigu
'WG86SAH22SWB' // output will never contain an 'O' (or a '0' for that matter)
```

#### Random Length

In case you want the exact `length` to be random, you can pass in a range:

```javascript
> password.randomPassword({ length: [6, 8], characters: password.digits })
'6324'
```

The generated password will have at least 6 characters, but no more than 8 characters. In other words, both the lower and the upper bounds are inclusive.

#### Predicate

If you need the password to meet some arbitrary complexity requirement, you can pass in a `predicate` function.
Expand Down
23 changes: 18 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,23 @@ function randomPassword(opts) {

var characterRules = translateRules(opts);

if (!util.isInteger(opts.length)) {
if (util.isInteger(opts.length)) {
opts.length = [opts.length, opts.length];
}

var lowerLength = opts.length[0];
var upperLength = opts.length[1];

if (!util.isInteger(lowerLength) || !util.isInteger(upperLength)) {
throw new Error('length must be an integer');
}
if (opts.length < 1) {
if (upperLength < lowerLength) {
throw new Error('length upper bound must be greater than the lower bound');
}
if (lowerLength < 1) {
throw new Error('length must be > 0');
}
if (opts.length < characterRules.length) {
if (upperLength < characterRules.length) {
throw new Error('length must be >= # of character sets passed');
}
if (characterRules.some(function (rule) { return !rule.characters })) {
Expand All @@ -51,9 +61,10 @@ function randomPassword(opts) {
var minimumLength = characterRules
.map(function (rule) { return rule.exactly || 1 })
.reduce(function (l, r) { return l + r }, 0);
if (opts.length < minimumLength) {
if (upperLength < minimumLength) {
throw new Error('length is too short for character set rules');
}
lowerLength = Math.max(lowerLength, minimumLength);

var allExactly = characterRules.every(function (rule) { return rule.exactly });
if (allExactly) {
Expand All @@ -63,9 +74,11 @@ function randomPassword(opts) {
}
}

var length = opts.random.between(lowerLength, upperLength);

var result;
do {
result = generatePassword(characterRules, opts.length, opts.random);
result = generatePassword(characterRules, length, opts.random);
} while (!opts.predicate(result));
return result;
}
Expand Down
48 changes: 48 additions & 0 deletions index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ describe('passwordGenerator', () => {

beforeEach(() => {
random = {
between: jest.fn((x, y) => y),
choose: jest.fn(),
shuffle: jest.fn(x => x),
};
Expand All @@ -21,6 +22,10 @@ describe('passwordGenerator', () => {
expect(() => randomPassword({ length: 'not-an-integer' })).toThrow('length must be an integer');
});

it('throws an error when passed an upper length that is not an integer', () => {
expect(() => randomPassword({ length: [123, 'not-an-integer'] })).toThrow('length must be an integer');
});

it('throws an error when passed a length less than 1', () => {
expect(() => randomPassword({ length: 0 })).toThrow('length must be > 0');
});
Expand Down Expand Up @@ -82,6 +87,49 @@ describe('passwordGenerator', () => {
expect(result).toBe('cbaabc');
});

describe('when given a range for length', () => {

it('throws an error when upper bound is less than the lower bound', () => {
expect(() => randomPassword({
length: [42, 41],
})).toThrow('length upper bound must be greater than the lower bound');
});

it('returns sequence with length between the passed range', () => {
random.between.mockReturnValueOnce(5);
random.choose.mockImplementation(x => x);

expect(randomPassword({
length: [2, 10],
characters: 'a',
random
})).toBe('aaaaa');
expect(random.between).toHaveBeenCalledWith(2, 10);
});

it('throws an error when upper bound is less than the totaled number of exactly character sets + required sets', () => {
expect(() => randomPassword({
length: [1, 42],
characters: [
'abc',
{ characters: '123', exactly: 42 }
]
})).toThrow('length is too short for character set rules');
});

it('uses the effective minimum length as the lower bound when it is greater than the passed lower bound', () => {
const password = randomPassword({
length: [1, 42],
characters: [
'abc',
{ characters: '123', exactly: 41 }
]
});
expect(password.length).toBe(42);
});

});

describe('when passed multiple character sets', () => {
it('throws an error if the passed length is fewer than the number of sets passed', () => {
expect(() => randomPassword({
Expand Down
4 changes: 4 additions & 0 deletions lib/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Random.prototype.choose = function (choices) {
return choices[this._getInt(choices.length)];
};

Random.prototype.between = function (lowerBoundInclusive, upperBoundInclusive) {
return lowerBoundInclusive + this.getInt(upperBoundInclusive - lowerBoundInclusive + 1);
};

Random.prototype.getInt = function (upperBoundExclusive) {
if (upperBoundExclusive === undefined) {
throw new Error('Must pass an upper bound');
Expand Down
20 changes: 20 additions & 0 deletions lib/random.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ describe('Random', () => {
expect(() => new Random(123)).toThrow('Must pass a randomSource function');
});

describe('numberBetween', () => {

it('uses the randomSource to pick a number in between the given range', () => {
const lower = 2;
const upper = 5;

testCase(0, 2);
testCase(1, 3);
testCase(2, 4);
testCase(3, 5);

function testCase(randomValue, expected) {
randomSource.mockReturnValueOnce([randomValue]);

expect(subject.between(lower, upper)).toBe(expected);
}
});

});

describe('getInt', () => {
it('throws an error when not passed an upper bound', () => {
expect(() => subject.getInt()).toThrow('Must pass an upper bound');
Expand Down