generate-pw 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -10
- package/dist/generate-pw.min.js +14 -0
- package/package.json +13 -4
- package/generate-pw.js +0 -269
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
### Randomly generate cryptographically-secure passwords.
|
|
18
18
|
|
|
19
19
|
<a href="#%EF%B8%8F-mit-license"><img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
|
|
20
|
-
<a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-1.
|
|
20
|
+
<a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-1.3.0"><img height=31 src="https://img.shields.io/badge/Latest_Build-1.3.0-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
|
|
21
21
|
<a href="https://www.npmjs.com/package/generate-pw?activeTab=code"><img height=31 src="https://img.shields.io/npm/unpacked-size/generate-pw?style=for-the-badge&logo=ebox&logoColor=white&labelColor=464646&color=blue"></a>
|
|
22
22
|
<a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_js-utils:generate-pw/generate-pw.js"><img height=31 src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dadamlui_js-utils%3Agenerate-pw%2Fgenerate-pw.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold"></a>
|
|
23
23
|
|
|
@@ -101,25 +101,35 @@ See: [Available options](#available-options-for-generate-functions)
|
|
|
101
101
|
|
|
102
102
|
#
|
|
103
103
|
|
|
104
|
-
### `strictify(password[, requiredCharTypes])`
|
|
104
|
+
### `strictify(password[, requiredCharTypes, options])`
|
|
105
105
|
|
|
106
106
|
Modifies `password` given to use at least one character of each `requiredCharTypes` element passed, returning a string:
|
|
107
107
|
|
|
108
108
|
```js
|
|
109
|
-
const
|
|
110
|
-
strictPW = pw.strictify(password, ['numbers', 'symbols']);
|
|
111
|
-
|
|
109
|
+
const strictPW = pw.strictify('abcdef', ['numbers', 'symbols']);
|
|
112
110
|
console.log(strictPW);
|
|
113
|
-
|
|
111
|
+
|
|
112
|
+
/* sample output:
|
|
113
|
+
|
|
114
|
+
strictify() » Strictifying password...
|
|
115
|
+
strictify() » Strictification complete!
|
|
116
|
+
a!c2ef
|
|
117
|
+
*/
|
|
114
118
|
```
|
|
115
119
|
|
|
120
|
+
**💡 Note:** If no `requiredCharTypes` array is passed, all available types will be required.
|
|
121
|
+
|
|
116
122
|
Available `requiredCharTypes` are: `['number', 'symbol', 'lower', 'upper']`
|
|
117
123
|
|
|
118
|
-
|
|
124
|
+
Available options (passed as object properties):
|
|
125
|
+
|
|
126
|
+
Name | Type | Description | Default Value
|
|
127
|
+
----------|---------|-----------------------------------|---------------
|
|
128
|
+
`verbose` | Boolean | Show logging in console/terminal. | `true`
|
|
119
129
|
|
|
120
130
|
#
|
|
121
131
|
|
|
122
|
-
### `validateStrength(password)`
|
|
132
|
+
### `validateStrength(password[, options])`
|
|
123
133
|
|
|
124
134
|
Validates the strength of a password, returning an object containing:
|
|
125
135
|
- `strengthScore` (0–100)
|
|
@@ -135,14 +145,27 @@ const password = 'AawiddsE',
|
|
|
135
145
|
console.log(pwStrength);
|
|
136
146
|
|
|
137
147
|
/* outputs:
|
|
148
|
+
|
|
149
|
+
validateStrength() » Validating password strength...
|
|
150
|
+
validateStrength() » Password strength validated!
|
|
151
|
+
validateStrength() » Check returned object for score/recommendations.
|
|
138
152
|
{
|
|
139
153
|
strengthScore: 60,
|
|
140
|
-
recommendations: [
|
|
154
|
+
recommendations: [
|
|
155
|
+
'Make it at least 8 characters long.',
|
|
156
|
+
'Include at least one number.'
|
|
157
|
+
],
|
|
141
158
|
isGood: false
|
|
142
159
|
}
|
|
143
160
|
*/
|
|
144
161
|
```
|
|
145
162
|
|
|
163
|
+
Available options (passed as object properties):
|
|
164
|
+
|
|
165
|
+
Name | Type | Description | Default Value
|
|
166
|
+
----------|---------|-----------------------------------|---------------
|
|
167
|
+
`verbose` | Boolean | Show logging in console/terminal. | `true`
|
|
168
|
+
|
|
146
169
|
#
|
|
147
170
|
|
|
148
171
|
### Available options for `generate*()` functions
|
|
@@ -175,7 +198,7 @@ When installed [globally](#-installation), **generate-pw** can also be used from
|
|
|
175
198
|
$ generate-pw
|
|
176
199
|
```
|
|
177
200
|
|
|
178
|
-
**💡 Note:**
|
|
201
|
+
**💡 Note:** For security reasons, generated password(s) are stored in the clipboard.
|
|
179
202
|
|
|
180
203
|
#
|
|
181
204
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const m=require("crypto")["randomInt"],r=require("child_process")["execSync"],g={lower:"abcdefghijklmnopqrstuvwxyz",upper:"ABCDEFGHIJKLMNOPQRSTUVWXYZ",numbers:"0123456789",symbols:"!@#$%^&*()-_=+[]{}/\\|;:'\",.<>?"};function a(o={}){var e,r={length:8,qty:1,charset:"",exclude:"",numbers:!1,symbols:!1,lowercase:!0,uppercase:!0,strict:!1};o={...r,...o};for(const s of Object.keys(o))if(!Object.prototype.hasOwnProperty.call(r,s))return console.error(`generatePassword() » ERROR: \`${s}\` is an invalid option.
|
|
3
|
+
`+`generatePassword() » Valid options:
|
|
4
|
+
[ ${Object.keys(r).join(", ")} ]`);for(const n of["length","qty"])if(o[n]=parseInt(o[n],10),isNaN(o[n])||o[n]<1)return console.error(`generatePassword() » ERROR: [${n}] option can only be an integer > 0.`);for(const a of["numbers","symbols","lowercase","uppercase","strict"])if("boolean"!=typeof o[a])return console.error(`generatePassword() » ERROR: [${a}] option can only be \`true\` or \`false\`.`);if(1<o.qty){const{qty:l,...i}=o;return c(l,i)}{let r=o.charset||(o.numbers?g.numbers:"")+(o.symbols?g.symbols:"")+(o.lowercase?g.lower:"")+(o.uppercase?g.upper:""),s=(r=(r=""===r?g.lower+g.upper:r).replace(new RegExp(`[${o.exclude}]`,"g"),""),"");for(let e=0;e<o.length;e++){var t=m(0,r.length);s+=r.charAt(t)}return o.strict&&(e=["number","symbol","lower","upper"].filter(e=>o[e+"s"]||o[e+"case"]),s=p(s,e)),s}}function c(r,s={}){var e={length:8,charset:"",exclude:"",numbers:!1,symbols:!1,lowercase:!0,uppercase:!0,strict:!1};if(s={...e,...s},r=parseInt(r,10),isNaN(r)||r<1)return console.error("generatePasswords() » ERROR: 1st arg [qty] can only be an integer > 0.");for(const t of Object.keys(s))if(!Object.prototype.hasOwnProperty.call(e,t))return console.error(`generatePasswords() » ERROR: \`${t}\` is an invalid option.
|
|
5
|
+
`+`generatePasswords() » Valid options:
|
|
6
|
+
[ ${Object.keys(e).join(", ")} ]`);if(s.length=parseInt(s.length),isNaN(s.length)||s.length<1)return console.error("generatePasswords() » ERROR: [length] option can only be an integer > 0.");for(const n of["numbers","symbols","lowercase","uppercase","strict"])if("boolean"!=typeof s[n])return console.error(`generatePasswords() » ERROR: [${n}] option can only be \`true\` or \`false\`.`);var o=[];for(let e=0;e<r;e++)o.push(a(s));return o}function p(r,s=["number","symbol","lower","upper"],e={}){var o={verbose:!0};if(e={...o,...e},"string"!=typeof r)return console.error("strictify() » ERROR: 1st arg <password> must be a string.");var t=["number","symbol","lower","upper"];for(const p of s)if(!t.includes(p))return console.error(`strictify() » ERROR: \`${p}\` is an invalid character type.
|
|
7
|
+
`+`strictify() » Valid character types: [ ${t.join(", ")} ]`);for(const f of Object.keys(e)){if(!Object.prototype.hasOwnProperty.call(o,f))return console.error(`strictify() » ERROR: \`${f}\` is an invalid option.
|
|
8
|
+
`+`strictify() » Valid options: [ ${Object.keys(o).join(", ")} ]`);if("boolean"!=typeof e[f])return console.error(`strictify() » ERROR: \`${f}\` option can only be set to \`true\` or \`false\`.`)}for(const u of s)global["has"+u.charAt(0).toUpperCase()+u.slice(1)]=!1;for(let e=0;e<r.length;e++)for(const d of s)(g[d]||g[d+"s"]).includes(r.charAt(e))&&(global["has"+d.charAt(0).toUpperCase()+d.slice(1)]=!0);e.verbose&&console.info("strictify() » Strictifying password...");var n=Math.min(r.length,s.length),a=[];let l=0,i=r;for(const h of s)if(l<n&&!global["has"+h.charAt(0).toUpperCase()+h.slice(1)]){let e;for(;e=m(0,r.length),a.includes(e););a.push(e);var c=g[h]||g[h+"s"];i=i.substring(0,e)+c.charAt(m(0,c.length))+i.substring(e+1),l++}return e.verbose&&(0<l?console.info("strictify() » Strictification complete!"):console.info(`strictify() » Password already includes ${s.join(" + ")} characters!
|
|
9
|
+
`+"strictify() » No modifications made.")),i}function e(e,r={}){var s={minLength:8,minLower:1,minUpper:1,minNumber:1,minSymbol:1},o={verbose:!0};if(r={...o,...r},"string"!=typeof e)return console.error("validateStrength() » ERROR: 1st arg <password> must be a string.");for(const l of Object.keys(r)){if(!Object.prototype.hasOwnProperty.call(o,l))return console.error(`validateStrength() » ERROR: \`${l}\` is an invalid option.
|
|
10
|
+
`+`validateStrength() » Valid options: [ ${Object.keys(o).join(", ")} ]`);if("boolean"!=typeof r[l])return console.error(`validateStrength() » ERROR: \`${l}\` option can only be set to \`true\` or \`false\`.`)}r.verbose&&console.info("validateStrength() » Validating password strength...");var t={lower:0,upper:0,number:0,symbol:0};for(const i of e)for(const c of Object.keys(t))(g[c]||g[c+"s"]).includes(i)&&t[c]++;var n=[];e.length<s.minLength&&n.push(`Make it at least ${s.minLength} characters long.`);for(const p of Object.keys(t))t[p]<s["min"+p.charAt(0).toUpperCase()+p.slice(1)]&&n.push("Include at least one "+p+`${["upper","lower"].includes(p)?"case letter":""}.`);let a=0;a+=e.length>=s.minLength?20:0;for(const f of Object.keys(t))a+=t[f]>=s["min"+f.charAt(0).toUpperCase()+f.slice(1)]?20:0;return r.verbose&&console.info("validateStrength() » Password strength validated!\n"+(require.main!==module?"validateStrength() » Check returned object for score/recommendations.":"")),{strengthScore:a,recommendations:n,isGood:80<=a}}if(require.main!==module)module.exports={generatePassword:a,generatePasswords:c,strictify:p,validateStrength:e};else{const n="[0m",l="[1;91m",i="[1;33m",f={},u={paramOptions:{length:/^--?length/,qty:/^--?qu?a?n?ti?t?y=.*$/,charset:/^--?chars/,excludeChars:/^--?exclude=/},flags:{includeNums:/^--?(?:n|(?:include-?)?num(?:ber)?s?=?(?:true|1)?)$/,includeSymbols:/^--?(?:s|(?:include-?)?symbols?=?(?:true|1)?)$/,excludeLowerChars:/^--?(?:L|(?:exclude|disable|no)-?lower-?(?:case)?|lower-?(?:case)?=(?:false|0))$/,excludeUpperChars:/^--?(?:U|(?:exclude|disable|no)-?upper-?(?:case)?|upper-?(?:case)?=(?:false|0))$/,strictMode:/^--?s(?:trict)?(?:-?mode)?$/},infoCmds:{help:/^--?h(?:elp)?$/,version:/^--?ve?r?s?i?o?n?$/}};if(process.argv.forEach(r=>{var e,s,o;r.startsWith("-")&&(e=Object.keys(u.paramOptions).find(e=>u.paramOptions[e].test(r)),o=Object.keys(u.flags).find(e=>u.flags[e].test(r)),s=Object.keys(u.infoCmds).find(e=>u.infoCmds[e].test(r)),o?f[o]=!0:e?(o=r.split("=")[1],f[e]=parseInt(o)||o):s||(console.error(`
|
|
11
|
+
${l}ERROR: Arg [${r}] not recognized.`+n),console.info(`
|
|
12
|
+
${i}Valid arguments are below.`+n),t(["paramOptions","flags","infoCmds"]),process.exit(1)))}),process.argv.some(e=>u.infoCmds.help.test(e)))t();else if(process.argv.some(e=>u.infoCmds.version.test(e)))console.info("v"+require("./package.json").version);else{for(const h of["length","qty"])f[h]&&(isNaN(f[h])||f[h]<1)&&(console.error(`
|
|
13
|
+
${l}Error: [${h}] argument can only be > 0.`+n),process.exit(1));const o={length:f.length||8,qty:f.qty||1,charset:f.charset,exclude:f.excludeChars,numbers:!!f.includeNums,symbols:!!f.includeSymbols,lowercase:!f.excludeLowerChars,uppercase:!f.excludeUpperChars,strict:!!f.strictMode},d=a(o);s(Array.isArray(d)?d.join("\n"):d)}function t(e=["cmdFormat","paramOptions","flags","infoCmds"]){const r={cmdFormat:[`
|
|
14
|
+
${i}generate-pw [options|commands]`+n],paramOptions:["\nParameter options:"," --length=n Generate password(s) of n length."," --qty=n Generate n password(s)."," --charset=chars Only include chars in password(s)."," --charset=chars Only include chars in password(s)."," --exclude=chars Exclude chars from password(s)."],flags:["\nBoolean options:"," -n, --include-numbers Allow numbers in password(s)."," -s, --include-symbols Allow symbols in password(s)."," -L, --no-lowercase Disallow lowercase letters in password(s)."," -U, --no-uppercase Disallow uppercase letters in password(s)."],infoCmds:["\nInfo commands:"," -h, --help Display help screen."," -v, --version Show version number."]};e.forEach(e=>{r[e]?.forEach(e=>{{const o=process.stdout.columns||80,t=[],r=e.match(/\S+|\s+/g);let s="";r.forEach(e=>{var r=o-(0===t.length?0:29);s.length+e.length>r&&(t.push(0===t.length?s:s.trimStart()),s=""),s+=e}),t.push(0===t.length?s:s.trimStart()),t.forEach((e,r)=>console.info(0===r?e:" ".repeat(29)+e))}})})}function s(e){e=e.replace(/\s+$/m,"").replace(/"/g,'""'),"darwin"===process.platform?r(`printf "${e}" | pbcopy`):"linux"===process.platform?r(`printf "${e}" | xclip -selection clipboard`):"win32"===process.platform&&r(`Set-Clipboard -Value "${e}"`,{shell:"powershell"})}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "generate-pw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Randomly generate cryptographically-secure passwords.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Adam Lui",
|
|
@@ -9,13 +9,19 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://github.com/adamlui/js-utils",
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"main": "generate-pw.js",
|
|
12
|
+
"main": "dist/generate-pw.min.js",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"docs/",
|
|
16
|
+
"!docs/*/"
|
|
17
|
+
],
|
|
13
18
|
"bin": {
|
|
14
|
-
"generatepw": "generate-pw.js",
|
|
15
|
-
"generate-pw": "generate-pw.js"
|
|
19
|
+
"generatepw": "dist/generate-pw.min.js",
|
|
20
|
+
"generate-pw": "dist/generate-pw.min.js"
|
|
16
21
|
},
|
|
17
22
|
"scripts": {
|
|
18
23
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
24
|
+
"build": "minify-js generate-pw.js dist",
|
|
19
25
|
"bump:patch": "bash utils/bump.sh patch",
|
|
20
26
|
"bump:minor": "bash utils/bump.sh minor",
|
|
21
27
|
"bump:major": "bash utils/bump.sh major",
|
|
@@ -41,5 +47,8 @@
|
|
|
41
47
|
"funding": {
|
|
42
48
|
"type": "github",
|
|
43
49
|
"url": "https://github.com/sponsors/adamlui"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@adamlui/minify.js": "^1.4.6"
|
|
44
53
|
}
|
|
45
54
|
}
|
package/generate-pw.js
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Import crypto.randomInt() for secure RNG
|
|
4
|
-
const { randomInt } = require('crypto');
|
|
5
|
-
|
|
6
|
-
// Init CHARACTER SETS
|
|
7
|
-
const charsets = {
|
|
8
|
-
lower: 'abcdefghijklmnopqrstuvwxyz',
|
|
9
|
-
upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
10
|
-
numbers: '0123456789',
|
|
11
|
-
symbols: '!@#$%^&*()-_=+[]{}/\\|;:\'",.<>?'
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// Define MAIN functions
|
|
15
|
-
|
|
16
|
-
function generatePassword(options = {}) {
|
|
17
|
-
|
|
18
|
-
// Init options
|
|
19
|
-
const defaultOptions = {
|
|
20
|
-
length: 8, // length of password
|
|
21
|
-
qty: 1, // number of passwords to generate
|
|
22
|
-
charset: '', // characters to include
|
|
23
|
-
exclude: '', // characters to exclude
|
|
24
|
-
numbers: false, // include numberChars
|
|
25
|
-
symbols: false, // include symbolChars
|
|
26
|
-
lowercase: true, // include lowercase letters
|
|
27
|
-
uppercase: true, // include uppercase letters
|
|
28
|
-
strict: false // require at least one char from each enabled set
|
|
29
|
-
};
|
|
30
|
-
options = { ...defaultOptions, ...options };
|
|
31
|
-
|
|
32
|
-
// Validate integer args
|
|
33
|
-
for (const numArgType of ['length', 'qty'])
|
|
34
|
-
if (isNaN(options[numArgType]) || options[numArgType] < 1)
|
|
35
|
-
return console.error(
|
|
36
|
-
`ERROR: [${ numArgType }] argument must be 1 or greater.`);
|
|
37
|
-
|
|
38
|
-
if (options.qty > 1) { // return array of [qty] password strings
|
|
39
|
-
const { qty, ...otherOptions } = options;
|
|
40
|
-
return generatePasswords(qty, otherOptions);
|
|
41
|
-
|
|
42
|
-
} else { // return single password string
|
|
43
|
-
|
|
44
|
-
// Init password's char set
|
|
45
|
-
let pwCharset = options.charset || ( // use passed [charset], or construct from options
|
|
46
|
-
(options.numbers ? charsets.numbers : '')
|
|
47
|
-
+ (options.symbols ? charsets.symbols : '')
|
|
48
|
-
+ (options.lowercase ? charsets.lower : '')
|
|
49
|
-
+ (options.uppercase ? charsets.upper : '')
|
|
50
|
-
);
|
|
51
|
-
if (pwCharset === '') // all flags false + no charset passed
|
|
52
|
-
pwCharset = charsets.lower + charsets.upper; // default to upper + lower
|
|
53
|
-
|
|
54
|
-
// Exclude passed `exclude` chars
|
|
55
|
-
pwCharset = pwCharset.replace(new RegExp(`[${ options.exclude }]`, 'g'), '');
|
|
56
|
-
|
|
57
|
-
// Generate unstrict password
|
|
58
|
-
let password = '';
|
|
59
|
-
for (let i = 0; i < options.length; i++) {
|
|
60
|
-
const randomIndex = randomInt(0, pwCharset.length);
|
|
61
|
-
password += pwCharset.charAt(randomIndex);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Enforce strict mode if enabled
|
|
65
|
-
if (options.strict) {
|
|
66
|
-
const charTypes = ['number', 'symbol', 'lower', 'upper'],
|
|
67
|
-
requiredCharTypes = charTypes.filter(charType => options[charType + 's'] || options[charType + 'case']);
|
|
68
|
-
password = strictify(password, requiredCharTypes);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return password;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function generatePasswords(qty, options) {
|
|
76
|
-
qty = parseInt(qty);
|
|
77
|
-
if (isNaN(qty)) return console.error(
|
|
78
|
-
'ERROR: First argument [qty] of generatePasswords() must be an integer');
|
|
79
|
-
const passwords = [];
|
|
80
|
-
for (let i = 0; i < qty; i++) passwords.push(generatePassword(options));
|
|
81
|
-
return passwords;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function strictify(password, requiredCharTypes = ['number', 'symbol', 'lower', 'upper']) {
|
|
85
|
-
|
|
86
|
-
// Init flags
|
|
87
|
-
for (const charType of requiredCharTypes)
|
|
88
|
-
global['has' + charType.charAt(0).toUpperCase() + charType.slice(1)] = false;
|
|
89
|
-
for (let i = 0; i < password.length; i++)
|
|
90
|
-
for (const charType of requiredCharTypes)
|
|
91
|
-
if ((charsets[charType] || charsets[charType + 's']).includes(password.charAt(i)))
|
|
92
|
-
global['has' + charType.charAt(0).toUpperCase() + charType.slice(1)] = true;
|
|
93
|
-
|
|
94
|
-
// Modify password if necessary
|
|
95
|
-
const maxReplacements = Math.min(password.length, requiredCharTypes.length),
|
|
96
|
-
replacedPositions = [];
|
|
97
|
-
let replacementCnt = 0, strictPW = password;
|
|
98
|
-
for (const charType of requiredCharTypes) {
|
|
99
|
-
if (replacementCnt < maxReplacements) {
|
|
100
|
-
if (!global['has' + charType.charAt(0).toUpperCase() + charType.slice(1)]) {
|
|
101
|
-
let replacementPos;
|
|
102
|
-
do replacementPos = randomInt(0, password.length); // pick random pos
|
|
103
|
-
while (replacedPositions.includes(replacementPos)); // check if pos already replaced
|
|
104
|
-
replacedPositions.push(replacementPos); // track new replacement pos
|
|
105
|
-
const replacementCharSet = charsets[charType] || charsets[charType + 's'];
|
|
106
|
-
strictPW = strictPW.substring(0, replacementPos) // perform actual replacement
|
|
107
|
-
+ replacementCharSet.charAt(randomInt(0, replacementCharSet.length))
|
|
108
|
-
+ strictPW.substring(replacementPos + 1);
|
|
109
|
-
replacementCnt++;
|
|
110
|
-
}}}
|
|
111
|
-
|
|
112
|
-
return strictPW;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function validateStrength(password) {
|
|
116
|
-
const strengthCriteria = { minLength: 8, minLower: 1, minUpper: 1, minNumber: 1, minSymbol: 1 };
|
|
117
|
-
|
|
118
|
-
// Count occurrences of each char type
|
|
119
|
-
const charCnts = { 'lower': 0, 'upper': 0, 'number': 0, 'symbol': 0 };
|
|
120
|
-
for (let i = 0; i < password.length; i++) {
|
|
121
|
-
const char = password[i];
|
|
122
|
-
for (const charType of Object.keys(charCnts))
|
|
123
|
-
if ((charsets[charType] || charsets[charType + 's']).includes(char))
|
|
124
|
-
charCnts[charType]++;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Check criteria + add recommendations
|
|
128
|
-
const recommendations = [];
|
|
129
|
-
if (password.length < strengthCriteria.minLength)
|
|
130
|
-
recommendations.push(`Make it at least ${ strengthCriteria.minLength } characters long.`);
|
|
131
|
-
for (const charType of Object.keys(charCnts))
|
|
132
|
-
if (charCnts[charType] < strengthCriteria['min' + charType.charAt(0).toUpperCase() + charType.slice(1)])
|
|
133
|
-
recommendations.push('Include at least one ' + charType
|
|
134
|
-
+ `${ ['upper', 'lower'].includes(charType) ? 'case letter' : '' }.`);
|
|
135
|
-
|
|
136
|
-
// Calculate strength score based on counts and criteria
|
|
137
|
-
let strengthScore = 0;
|
|
138
|
-
strengthScore += ( // +20 for satisfying min length
|
|
139
|
-
password.length >= strengthCriteria.minLength) ? 20 : 0;
|
|
140
|
-
for (const charType of Object.keys(charCnts))
|
|
141
|
-
strengthScore += ( // +20 per char type included
|
|
142
|
-
charCnts[charType] >= strengthCriteria['min' + charType.charAt(0).toUpperCase() + charType.slice(1)]) ? 20 : 0;
|
|
143
|
-
|
|
144
|
-
return { strengthScore, recommendations, isGood: strengthScore >= 80 };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// EXPORT main functions if script was required
|
|
148
|
-
if (require.main !== module) module.exports = { generatePassword, generatePasswords, strictify, validateStrength };
|
|
149
|
-
|
|
150
|
-
else { // run as CLI utility
|
|
151
|
-
|
|
152
|
-
// Init UI colors
|
|
153
|
-
const nc = '\x1b[0m', // no color
|
|
154
|
-
br = '\x1b[1;91m', // bright red
|
|
155
|
-
by = '\x1b[1;33m', // bright yellow
|
|
156
|
-
bw = '\x1b[1;97m'; // bright white
|
|
157
|
-
|
|
158
|
-
// Load settings from ARGS
|
|
159
|
-
const config = {};
|
|
160
|
-
const argRegex = {
|
|
161
|
-
paramOptions: {
|
|
162
|
-
'length': /^--?length/,
|
|
163
|
-
'qty': /^--?qu?a?n?ti?t?y=\d+$/,
|
|
164
|
-
'charset': /^--?chars/,
|
|
165
|
-
'excludeChars': /^--?exclude=/
|
|
166
|
-
},
|
|
167
|
-
flags: {
|
|
168
|
-
'includeNums': /^--?(?:n|(?:include-?)?num(?:ber)?s?=?(?:true|1)?)$/,
|
|
169
|
-
'includeSymbols': /^--?(?:s|(?:include-?)?symbols?=?(?:true|1)?)$/,
|
|
170
|
-
'excludeLowerChars': /^--?(?:L|(?:exclude|disable|no)-?lower-?(?:case)?|lower-?(?:case)?=(?:false|0))$/,
|
|
171
|
-
'excludeUpperChars': /^--?(?:U|(?:exclude|disable|no)-?upper-?(?:case)?|upper-?(?:case)?=(?:false|0))$/,
|
|
172
|
-
'strictMode': /^--?s(?:trict)?(?:-?mode)?$/
|
|
173
|
-
},
|
|
174
|
-
cmds: {
|
|
175
|
-
'help': /^--?h(?:elp)?$/,
|
|
176
|
-
'version': /^--?ve?r?s?i?o?n?$/
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
process.argv.forEach(arg => {
|
|
180
|
-
if (!arg.startsWith('-')) return;
|
|
181
|
-
const matchedFlag = Object.keys(argRegex.flags).find(flag => argRegex.flags[flag].test(arg)),
|
|
182
|
-
matchedArgOption = Object.keys(argRegex.paramOptions).find(option => argRegex.paramOptions[option].test(arg)),
|
|
183
|
-
matchedCmd = Object.keys(argRegex.cmds).find(cmd => argRegex.cmds[cmd].test(arg));
|
|
184
|
-
if (matchedFlag) config[matchedFlag] = true;
|
|
185
|
-
else if (matchedArgOption) {
|
|
186
|
-
const value = arg.split('=')[1];
|
|
187
|
-
config[matchedArgOption] = parseInt(value) || value;
|
|
188
|
-
} else if (!matchedCmd) {
|
|
189
|
-
console.error(`\n${br}ERROR: Arg [${ arg }] not recognized.${nc}`);
|
|
190
|
-
console.info(`\n${by}Valid arguments are below.${nc}`);
|
|
191
|
-
printHelpSections(['paramOptions', 'booelanOptions', 'infoCmds']);
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}});
|
|
194
|
-
|
|
195
|
-
// Show HELP screen if -h or --help passed
|
|
196
|
-
if (process.argv.some(arg => /^--?h(?:elp)?$/.test(arg))) printHelpSections();
|
|
197
|
-
|
|
198
|
-
// Show VERSION number if -v or --version passed
|
|
199
|
-
else if (process.argv.some(arg => /^--?ve?r?s?i?o?n?$/.test(arg)))
|
|
200
|
-
console.info('v' + require('./package.json').version);
|
|
201
|
-
|
|
202
|
-
else { // run MAIN routine
|
|
203
|
-
for (const numArgType of ['length', 'qty'])
|
|
204
|
-
if (config[numArgType] && (isNaN(config[numArgType]) || config[numArgType] < 1))
|
|
205
|
-
return console.error(`\n${br}Error: [${ numArgType }] argument must be 1 or greater.${nc}`);
|
|
206
|
-
const funcOptions = {
|
|
207
|
-
length: config.length || 8, qty: config.qty || 1,
|
|
208
|
-
charset: config.charset, exclude: config.excludeChars,
|
|
209
|
-
numbers: config.includeNums, symbols: config.includeSymbols,
|
|
210
|
-
lowercase: !config.excludeLowerChars, uppercase: !config.excludeUpperChars,
|
|
211
|
-
strict: config.strictMode
|
|
212
|
-
};
|
|
213
|
-
const pwResult = generatePassword(funcOptions);
|
|
214
|
-
console.log('\n' + bw + ( Array.isArray(pwResult) ? pwResult.join('\n') : pwResult ) + nc);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function printHelpSections(includeSections = ['cmdFormat', 'paramOptions', 'booelanOptions', 'infoCmds']) {
|
|
218
|
-
const helpSections = {
|
|
219
|
-
'cmdFormat': [
|
|
220
|
-
`\n${by}generate-pw [options|commands]${nc}`
|
|
221
|
-
],
|
|
222
|
-
'paramOptions': [
|
|
223
|
-
'\nParameter options:',
|
|
224
|
-
' --length=n Generate password(s) of n length.',
|
|
225
|
-
' --qty=n Generate n password(s).',
|
|
226
|
-
' --charset=chars Only include chars in password(s).',
|
|
227
|
-
' --charset=chars Only include chars in password(s).',
|
|
228
|
-
' --exclude=chars Exclude chars from password(s).'
|
|
229
|
-
],
|
|
230
|
-
'booelanOptions': [
|
|
231
|
-
'\nBoolean options:',
|
|
232
|
-
' -n, --include-numbers Allow numbers in password(s).',
|
|
233
|
-
' -s, --include-symbols Allow symbols in password(s).',
|
|
234
|
-
' -L, --no-lowercase Disallow lowercase letters in password(s).',
|
|
235
|
-
' -U, --no-uppercase Disallow uppercase letters in password(s).'
|
|
236
|
-
],
|
|
237
|
-
'infoCmds': [
|
|
238
|
-
'\nInfo commands:',
|
|
239
|
-
' -h, --help Display help screen.',
|
|
240
|
-
' -v, --version Show version number.'
|
|
241
|
-
]
|
|
242
|
-
};
|
|
243
|
-
includeSections.forEach(section => { // print valid arg elems
|
|
244
|
-
helpSections[section]?.forEach(line => printHelpMsg(line)); });
|
|
245
|
-
|
|
246
|
-
function printHelpMsg(msg) { // wrap msg + indent 2nd+ lines (for --help screen)
|
|
247
|
-
const terminalWidth = process.stdout.columns || 80,
|
|
248
|
-
indentation = 29, lines = [], words = msg.match(/\S+|\s+/g);
|
|
249
|
-
|
|
250
|
-
// Split msg into lines of appropriate lengths
|
|
251
|
-
let currentLine = '';
|
|
252
|
-
words.forEach(word => {
|
|
253
|
-
const lineLength = terminalWidth - ( lines.length === 0 ? 0 : indentation );
|
|
254
|
-
if (currentLine.length + word.length > lineLength) { // cap/store it
|
|
255
|
-
lines.push(lines.length === 0 ? currentLine : currentLine.trimStart());
|
|
256
|
-
currentLine = '';
|
|
257
|
-
}
|
|
258
|
-
currentLine += word;
|
|
259
|
-
});
|
|
260
|
-
lines.push(lines.length === 0 ? currentLine : currentLine.trimStart());
|
|
261
|
-
|
|
262
|
-
// Print formatted msg
|
|
263
|
-
lines.forEach((line, index) => console.info(
|
|
264
|
-
index === 0 ? line // print 1st line unindented
|
|
265
|
-
: ' '.repeat(indentation) + line // print subsequent lines indented
|
|
266
|
-
));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|