diff --git a/lib/PasswordGenerator.ts b/lib/PasswordGenerator.ts index 414cf9f..566be92 100644 --- a/lib/PasswordGenerator.ts +++ b/lib/PasswordGenerator.ts @@ -7,14 +7,35 @@ const AsciiRange = { /** childe mark ~ */ max: 126, } +const optRegexes = { + space: " ", + special: "!\"#\\$%&'\\(\\)\\*\\+,-./:;<=>\\?@\\[\\\\\\]\\^_`\\{|\\}~", + number: "\\d", +}; + +export interface DisableOptions { + space?: boolean; + special?: boolean; + number?: boolean; +} + +export async function generatePassword(length: number, disableOpts?: DisableOptions): Promise { + if (length < 5) { + throw new Error("Password length must be longer than 5."); + } -export async function generatePassword(length: number): Promise { let result = ""; + const regex = createRegex(disableOpts) + while (true) { const bytes = await promisify(crypto.randomBytes)(length * 2); const byteArray = Array.from(bytes); const filtered = byteArray.filter(isInAsciiRange); result += String.fromCharCode(...filtered); + if (regex) { + result = result.replace(regex, ""); + } + if (result.length >= length) { result = result.slice(0, length); break; @@ -25,4 +46,16 @@ export async function generatePassword(length: number): Promise { function isInAsciiRange(value: number) { return AsciiRange.min <= value && value <= AsciiRange.max; +} + +function createRegex(disableOpts?: DisableOptions): RegExp | undefined { + if (!disableOpts) { + return undefined; + } + let reg = ""; + if (disableOpts?.space) { reg += optRegexes.space }; + if (disableOpts?.special) { reg += optRegexes.special }; + if (disableOpts?.number) { reg += optRegexes.number }; + + return new RegExp(`[${reg}]`, "g"); } \ No newline at end of file diff --git a/lib/PasswordGeneratorNode.html b/lib/PasswordGeneratorNode.html index 1697c12..6f1b099 100644 --- a/lib/PasswordGeneratorNode.html +++ b/lib/PasswordGeneratorNode.html @@ -5,8 +5,11 @@ color: "#D8BFD8", defaults: { name: { value: "" }, - length: { value: "", required: true, validate: RED.validators.number() }, + length: { value: "", required: true, validate: (v) => v > 4 }, setTo: { value: "" }, + isNumberDisabled: { value: false, require: true }, + isSpecialCharsDisabled: { value: false, require: true }, + isSpaceDisabled: { value: false, require: true }, }, inputs: 1, outputs: 1, @@ -26,9 +29,21 @@
- +
+
+ + +
+
+ + +
+
+ + +
+ \ No newline at end of file diff --git a/test/PasswordGenerator_spec.ts b/test/PasswordGenerator_spec.ts index ee938b8..b92a009 100644 --- a/test/PasswordGenerator_spec.ts +++ b/test/PasswordGenerator_spec.ts @@ -1,22 +1,60 @@ import "mocha"; -import * as crypto from "crypto"; import { expect } from "chai"; import * as sinon from "sinon"; -import { generatePassword } from "../lib/PasswordGenerator"; +import { generatePassword, DisableOptions } from "../lib/PasswordGenerator"; describe("PasswordGenerator", () => { - it("should return true for the first time", async () => { - const result = await generatePassword(10); - expect(result).to.lengthOf(10); + afterEach(() => { + sinon.restore(); + }); + + it("should throw an error when length is 4", (done) => { + generatePassword(4) + .then(() => done("Error didn't occur.")) + .catch(() => done()); + }); + + it("should return password when length is 5", async () => { + const result = await generatePassword(5); + expect(result).to.lengthOf(5); + }); + + [ + { regex: /0123456789/, title: "number" }, + { regex: / /, title: "space" }, + { regex: /abcdefghijklmnopqrstuvwxyz/, title: "lowercase" }, + { regex: /ABCDEFGHIJKLMNOPQRSTUVWXYZ/, title: "uppercase" }, + { regex: /!\"#\$%&'\(\)\*\+,-.\//, title: "special characters group 1" }, + { regex: /:;<=>\?@/, title: "special characters group 2" }, + { regex: /\[\\\]\^_`/, title: "special characters group 3" }, + { regex: /\{|\}~/, title: "special characters group 4" }, + ].forEach((testData) => { + it(`should contain ${testData.title} without option`, async () => { + const fakeData = Array.from(Array(256).keys()); + sinon.stub(Array, "from").returns(fakeData); + const result = await generatePassword(256); + expect(result).to.match(testData.regex); + }); + }); + + it("should not contain number when option number is true", async () => { + const fakeData = Array.from(Array(256).keys()); + sinon.stub(Array, "from").returns(fakeData); + const result = await generatePassword(256, { number: true }); + expect(result).not.to.match(/\d/); + }); + + it("should not contain space when option space is true", async () => { + const fakeData = Array.from(Array(256).keys()); + sinon.stub(Array, "from").returns(fakeData); + const result = await generatePassword(256, { space: true }); + expect(result).not.to.contain(" "); + }); + + it("should not contain special characters when option space is true", async () => { + const fakeData = Array.from(Array(256).keys()); + sinon.stub(Array, "from").returns(fakeData); + const result = await generatePassword(256, { special: true }); + expect(result).not.to.match(/[\[!\"#\$%&'\(\)\*\+,\-./:;<=>\?@\[\\\]\^_`{|}\]]/); }); - // it.only("should return true for the first time", async () => { - // const testData = Buffer.from([31, 32, 126, 127]); - // sinon.stub(crypto, "randomBytes") - // .callsFake((size: number, cb: (err: Error | null, buf: Buffer) => void) => { - // console.log("HEY") - // cb(null, testData); - // }); - // const result = await generatePassword(10); - // expect(result).to.equal(" ~"); - // }); }); \ No newline at end of file