lockly 0.1.2 → 0.2.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 +23 -5
- package/dist/cli.js +106 -36
- package/dist/index.cjs +80 -12
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +79 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Cryptographically secure password generator CLI.
|
|
4
4
|
|
|
5
|
-
Generates unpredictable random passwords instantly using `
|
|
5
|
+
Generates unpredictable random passwords instantly using the Web Crypto API (`crypto.getRandomValues()`).
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Cryptographically secure** — CSPRNG via
|
|
9
|
+
- **Cryptographically secure** — CSPRNG via Web Crypto API
|
|
10
|
+
- **Browser compatible** — works in Node.js and browsers
|
|
10
11
|
- **Fast** — under 50ms response time
|
|
11
|
-
- **Customizable** — length, character sets, count
|
|
12
|
+
- **Customizable** — length, character sets, count, charset inclusion guarantee (`--ensure`)
|
|
12
13
|
- **Pipe-friendly** — clean stdout output, no ANSI colors
|
|
13
14
|
- **Zero install** — run instantly with `npx`
|
|
14
15
|
|
|
@@ -52,6 +53,16 @@ lockly --no-uppercase --no-lowercase --no-symbols -l 6
|
|
|
52
53
|
lockly --no-lowercase --no-symbols
|
|
53
54
|
```
|
|
54
55
|
|
|
56
|
+
### Ensure charset inclusion
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Guarantee at least one character from each active charset
|
|
60
|
+
lockly --ensure
|
|
61
|
+
|
|
62
|
+
# Short password with guaranteed diversity
|
|
63
|
+
lockly --ensure -l 4
|
|
64
|
+
```
|
|
65
|
+
|
|
55
66
|
### Piping
|
|
56
67
|
|
|
57
68
|
```bash
|
|
@@ -81,12 +92,13 @@ export DB_PASSWORD=$(lockly -l 24 --no-symbols)
|
|
|
81
92
|
| `--no-lowercase` | Exclude lowercase (a-z) | included | - |
|
|
82
93
|
| `--no-numbers` | Exclude digits (0-9) | included | - |
|
|
83
94
|
| `--no-symbols` | Exclude symbols | included | - |
|
|
95
|
+
| `--ensure` | Guarantee at least one char from each active charset | off | - |
|
|
84
96
|
| `-V, --version` | Show version | - | - |
|
|
85
97
|
| `-h, --help` | Show help | - | - |
|
|
86
98
|
|
|
87
99
|
## Security
|
|
88
100
|
|
|
89
|
-
- **CSPRNG**: Uses `
|
|
101
|
+
- **CSPRNG**: Uses `crypto.getRandomValues()` (Web Crypto API) instead of `Math.random()`
|
|
90
102
|
- **Local execution**: No network requests — passwords are generated locally
|
|
91
103
|
- **Stateless**: Generated passwords are never stored
|
|
92
104
|
- **No modulo bias**: Rejection sampling ensures uniform distribution
|
|
@@ -117,11 +129,17 @@ const customPasswords = generatePassword({
|
|
|
117
129
|
numbers: true,
|
|
118
130
|
symbols: false
|
|
119
131
|
});
|
|
132
|
+
|
|
133
|
+
// Ensure at least one character from each active charset
|
|
134
|
+
const ensured = generatePassword({
|
|
135
|
+
length: 16,
|
|
136
|
+
ensure: true
|
|
137
|
+
});
|
|
120
138
|
```
|
|
121
139
|
|
|
122
140
|
## Requirements
|
|
123
141
|
|
|
124
|
-
- Node.js 20+
|
|
142
|
+
- Node.js 20+ or any modern browser with Web Crypto API support
|
|
125
143
|
|
|
126
144
|
## License
|
|
127
145
|
|
package/dist/cli.js
CHANGED
|
@@ -3,24 +3,76 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname, join } from 'path';
|
|
6
|
-
import { getRandomValues } from 'crypto';
|
|
7
6
|
|
|
7
|
+
// src/generator.ts
|
|
8
|
+
var getRandomValues = globalThis.crypto.getRandomValues.bind(
|
|
9
|
+
globalThis.crypto
|
|
10
|
+
);
|
|
8
11
|
var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
9
12
|
var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
10
13
|
var NUMBERS = "0123456789";
|
|
11
14
|
var SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
|
12
15
|
function buildPool(options2) {
|
|
13
16
|
let pool = "";
|
|
14
|
-
|
|
15
|
-
if (options2.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const charsets = [];
|
|
18
|
+
if (options2.uppercase) {
|
|
19
|
+
pool += UPPERCASE;
|
|
20
|
+
charsets.push(UPPERCASE);
|
|
21
|
+
}
|
|
22
|
+
if (options2.lowercase) {
|
|
23
|
+
pool += LOWERCASE;
|
|
24
|
+
charsets.push(LOWERCASE);
|
|
25
|
+
}
|
|
26
|
+
if (options2.numbers) {
|
|
27
|
+
pool += NUMBERS;
|
|
28
|
+
charsets.push(NUMBERS);
|
|
29
|
+
}
|
|
30
|
+
if (options2.symbols) {
|
|
31
|
+
pool += SYMBOLS;
|
|
32
|
+
charsets.push(SYMBOLS);
|
|
33
|
+
}
|
|
34
|
+
return { pool, charsets };
|
|
35
|
+
}
|
|
36
|
+
function pickRandomChar(charset) {
|
|
37
|
+
const len = charset.length;
|
|
38
|
+
const limit = 256 - 256 % len;
|
|
39
|
+
for (; ; ) {
|
|
40
|
+
const randomBytes = new Uint8Array(2);
|
|
41
|
+
getRandomValues(randomBytes);
|
|
42
|
+
for (let i = 0; i < randomBytes.length; i++) {
|
|
43
|
+
if (randomBytes[i] < limit) {
|
|
44
|
+
return charset[randomBytes[i] % len];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
19
48
|
}
|
|
20
|
-
function
|
|
49
|
+
function cryptoShuffle(arr) {
|
|
50
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
51
|
+
const range = i + 1;
|
|
52
|
+
const limit = 256 - 256 % range;
|
|
53
|
+
let j;
|
|
54
|
+
for (; ; ) {
|
|
55
|
+
const randomBytes = new Uint8Array(1);
|
|
56
|
+
getRandomValues(randomBytes);
|
|
57
|
+
if (randomBytes[0] < limit) {
|
|
58
|
+
j = randomBytes[0] % range;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const tmp = arr[i];
|
|
63
|
+
arr[i] = arr[j];
|
|
64
|
+
arr[j] = tmp;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function generateSingle(length2, pool, charsets) {
|
|
21
68
|
const poolLength = pool.length;
|
|
22
69
|
const limit = 256 - 256 % poolLength;
|
|
23
70
|
const chars = [];
|
|
71
|
+
if (charsets) {
|
|
72
|
+
for (const charset of charsets) {
|
|
73
|
+
chars.push(pickRandomChar(charset));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
24
76
|
while (chars.length < length2) {
|
|
25
77
|
const remaining = length2 - chars.length;
|
|
26
78
|
const bufferSize = remaining * 2;
|
|
@@ -33,6 +85,9 @@ function generateSingle(length2, pool) {
|
|
|
33
85
|
}
|
|
34
86
|
}
|
|
35
87
|
}
|
|
88
|
+
if (charsets) {
|
|
89
|
+
cryptoShuffle(chars);
|
|
90
|
+
}
|
|
36
91
|
return chars.join("");
|
|
37
92
|
}
|
|
38
93
|
function validateOptions(options2) {
|
|
@@ -48,6 +103,14 @@ function validateOptions(options2) {
|
|
|
48
103
|
if (!options2.uppercase && !options2.lowercase && !options2.numbers && !options2.symbols) {
|
|
49
104
|
throw new Error("\uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uBB38\uC790\uC14B\uC744 \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4");
|
|
50
105
|
}
|
|
106
|
+
if (options2.ensure) {
|
|
107
|
+
const activeCount = (options2.uppercase ? 1 : 0) + (options2.lowercase ? 1 : 0) + (options2.numbers ? 1 : 0) + (options2.symbols ? 1 : 0);
|
|
108
|
+
if (options2.length < activeCount) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`ensure \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uAE38\uC774\uAC00 \uD65C\uC131 \uBB38\uC790\uC14B \uC218(${activeCount}) \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
51
114
|
}
|
|
52
115
|
function generatePassword(options2) {
|
|
53
116
|
const resolved = {
|
|
@@ -56,13 +119,20 @@ function generatePassword(options2) {
|
|
|
56
119
|
uppercase: options2?.uppercase ?? true,
|
|
57
120
|
lowercase: options2?.lowercase ?? true,
|
|
58
121
|
numbers: options2?.numbers ?? true,
|
|
59
|
-
symbols: options2?.symbols ?? true
|
|
122
|
+
symbols: options2?.symbols ?? true,
|
|
123
|
+
ensure: options2?.ensure ?? false
|
|
60
124
|
};
|
|
61
125
|
validateOptions(resolved);
|
|
62
|
-
const pool = buildPool(resolved);
|
|
126
|
+
const { pool, charsets } = buildPool(resolved);
|
|
63
127
|
const passwords = [];
|
|
64
128
|
for (let i = 0; i < resolved.count; i++) {
|
|
65
|
-
passwords.push(
|
|
129
|
+
passwords.push(
|
|
130
|
+
generateSingle(
|
|
131
|
+
resolved.length,
|
|
132
|
+
pool,
|
|
133
|
+
resolved.ensure ? charsets : void 0
|
|
134
|
+
)
|
|
135
|
+
);
|
|
66
136
|
}
|
|
67
137
|
return passwords;
|
|
68
138
|
}
|
|
@@ -78,37 +148,37 @@ program.name("lockly").description("Cryptographically secure random password gen
|
|
|
78
148
|
"-l, --length <number>",
|
|
79
149
|
"\uD328\uC2A4\uC6CC\uB4DC \uAE38\uC774 (\uAE30\uBCF8: 16, \uBC94\uC704: 1-1024)",
|
|
80
150
|
"16"
|
|
81
|
-
).option("-c, --count <number>", "\uC0DD\uC131 \uAC1C\uC218 (\uAE30\uBCF8: 1)", "1").option("--no-uppercase", "\uB300\uBB38\uC790(A-Z) \uC81C\uC678").option("--no-lowercase", "\uC18C\uBB38\uC790(a-z) \uC81C\uC678").option("--no-numbers", "\uC22B\uC790(0-9) \uC81C\uC678").option("--no-symbols", "\uD2B9\uC218\uBB38\uC790 \uC81C\uC678").helpOption("-h, --help", "\uB3C4\uC6C0\uB9D0");
|
|
151
|
+
).option("-c, --count <number>", "\uC0DD\uC131 \uAC1C\uC218 (\uAE30\uBCF8: 1)", "1").option("--no-uppercase", "\uB300\uBB38\uC790(A-Z) \uC81C\uC678").option("--no-lowercase", "\uC18C\uBB38\uC790(a-z) \uC81C\uC678").option("--no-numbers", "\uC22B\uC790(0-9) \uC81C\uC678").option("--no-symbols", "\uD2B9\uC218\uBB38\uC790 \uC81C\uC678").option("--ensure", "\uAC01 \uBB38\uC790\uC14B\uC5D0\uC11C \uCD5C\uC18C 1\uC790 \uD3EC\uD568 \uBCF4\uC7A5").helpOption("-h, --help", "\uB3C4\uC6C0\uB9D0");
|
|
82
152
|
program.parse();
|
|
83
153
|
var options = program.opts();
|
|
84
154
|
var length = parseInt(options.length, 10);
|
|
85
155
|
var count = parseInt(options.count, 10);
|
|
86
156
|
if (isNaN(length)) {
|
|
87
157
|
console.error("\uC720\uD6A8\uD55C \uC22B\uC790\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694 (length)");
|
|
88
|
-
process.
|
|
89
|
-
}
|
|
90
|
-
if (isNaN(count)) {
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
} else if (isNaN(count)) {
|
|
91
160
|
console.error("\uC720\uD6A8\uD55C \uC22B\uC790\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694 (count)");
|
|
92
|
-
process.
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} catch (error) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
} else {
|
|
163
|
+
try {
|
|
164
|
+
const passwords = generatePassword({
|
|
165
|
+
length,
|
|
166
|
+
count,
|
|
167
|
+
uppercase: options.uppercase,
|
|
168
|
+
lowercase: options.lowercase,
|
|
169
|
+
numbers: options.numbers,
|
|
170
|
+
symbols: options.symbols,
|
|
171
|
+
ensure: options.ensure
|
|
172
|
+
});
|
|
173
|
+
for (const password of passwords) {
|
|
174
|
+
console.log(password);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof Error) {
|
|
178
|
+
console.error(error.message);
|
|
179
|
+
} else {
|
|
180
|
+
console.error("\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4");
|
|
181
|
+
}
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
}
|
|
114
184
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,29 +1,79 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var crypto = require('crypto');
|
|
4
|
-
|
|
5
3
|
// src/generator.ts
|
|
4
|
+
var getRandomValues = globalThis.crypto.getRandomValues.bind(
|
|
5
|
+
globalThis.crypto
|
|
6
|
+
);
|
|
6
7
|
var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
7
8
|
var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
8
9
|
var NUMBERS = "0123456789";
|
|
9
10
|
var SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
|
10
11
|
function buildPool(options) {
|
|
11
12
|
let pool = "";
|
|
12
|
-
|
|
13
|
-
if (options.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const charsets = [];
|
|
14
|
+
if (options.uppercase) {
|
|
15
|
+
pool += UPPERCASE;
|
|
16
|
+
charsets.push(UPPERCASE);
|
|
17
|
+
}
|
|
18
|
+
if (options.lowercase) {
|
|
19
|
+
pool += LOWERCASE;
|
|
20
|
+
charsets.push(LOWERCASE);
|
|
21
|
+
}
|
|
22
|
+
if (options.numbers) {
|
|
23
|
+
pool += NUMBERS;
|
|
24
|
+
charsets.push(NUMBERS);
|
|
25
|
+
}
|
|
26
|
+
if (options.symbols) {
|
|
27
|
+
pool += SYMBOLS;
|
|
28
|
+
charsets.push(SYMBOLS);
|
|
29
|
+
}
|
|
30
|
+
return { pool, charsets };
|
|
31
|
+
}
|
|
32
|
+
function pickRandomChar(charset) {
|
|
33
|
+
const len = charset.length;
|
|
34
|
+
const limit = 256 - 256 % len;
|
|
35
|
+
for (; ; ) {
|
|
36
|
+
const randomBytes = new Uint8Array(2);
|
|
37
|
+
getRandomValues(randomBytes);
|
|
38
|
+
for (let i = 0; i < randomBytes.length; i++) {
|
|
39
|
+
if (randomBytes[i] < limit) {
|
|
40
|
+
return charset[randomBytes[i] % len];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
17
44
|
}
|
|
18
|
-
function
|
|
45
|
+
function cryptoShuffle(arr) {
|
|
46
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
47
|
+
const range = i + 1;
|
|
48
|
+
const limit = 256 - 256 % range;
|
|
49
|
+
let j;
|
|
50
|
+
for (; ; ) {
|
|
51
|
+
const randomBytes = new Uint8Array(1);
|
|
52
|
+
getRandomValues(randomBytes);
|
|
53
|
+
if (randomBytes[0] < limit) {
|
|
54
|
+
j = randomBytes[0] % range;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const tmp = arr[i];
|
|
59
|
+
arr[i] = arr[j];
|
|
60
|
+
arr[j] = tmp;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function generateSingle(length, pool, charsets) {
|
|
19
64
|
const poolLength = pool.length;
|
|
20
65
|
const limit = 256 - 256 % poolLength;
|
|
21
66
|
const chars = [];
|
|
67
|
+
if (charsets) {
|
|
68
|
+
for (const charset of charsets) {
|
|
69
|
+
chars.push(pickRandomChar(charset));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
22
72
|
while (chars.length < length) {
|
|
23
73
|
const remaining = length - chars.length;
|
|
24
74
|
const bufferSize = remaining * 2;
|
|
25
75
|
const randomBytes = new Uint8Array(bufferSize);
|
|
26
|
-
|
|
76
|
+
getRandomValues(randomBytes);
|
|
27
77
|
for (let i = 0; i < randomBytes.length && chars.length < length; i++) {
|
|
28
78
|
const byte = randomBytes[i];
|
|
29
79
|
if (byte < limit) {
|
|
@@ -31,6 +81,9 @@ function generateSingle(length, pool) {
|
|
|
31
81
|
}
|
|
32
82
|
}
|
|
33
83
|
}
|
|
84
|
+
if (charsets) {
|
|
85
|
+
cryptoShuffle(chars);
|
|
86
|
+
}
|
|
34
87
|
return chars.join("");
|
|
35
88
|
}
|
|
36
89
|
function validateOptions(options) {
|
|
@@ -46,6 +99,14 @@ function validateOptions(options) {
|
|
|
46
99
|
if (!options.uppercase && !options.lowercase && !options.numbers && !options.symbols) {
|
|
47
100
|
throw new Error("\uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uBB38\uC790\uC14B\uC744 \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4");
|
|
48
101
|
}
|
|
102
|
+
if (options.ensure) {
|
|
103
|
+
const activeCount = (options.uppercase ? 1 : 0) + (options.lowercase ? 1 : 0) + (options.numbers ? 1 : 0) + (options.symbols ? 1 : 0);
|
|
104
|
+
if (options.length < activeCount) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`ensure \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uAE38\uC774\uAC00 \uD65C\uC131 \uBB38\uC790\uC14B \uC218(${activeCount}) \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
49
110
|
}
|
|
50
111
|
function generatePassword(options) {
|
|
51
112
|
const resolved = {
|
|
@@ -54,13 +115,20 @@ function generatePassword(options) {
|
|
|
54
115
|
uppercase: options?.uppercase ?? true,
|
|
55
116
|
lowercase: options?.lowercase ?? true,
|
|
56
117
|
numbers: options?.numbers ?? true,
|
|
57
|
-
symbols: options?.symbols ?? true
|
|
118
|
+
symbols: options?.symbols ?? true,
|
|
119
|
+
ensure: options?.ensure ?? false
|
|
58
120
|
};
|
|
59
121
|
validateOptions(resolved);
|
|
60
|
-
const pool = buildPool(resolved);
|
|
122
|
+
const { pool, charsets } = buildPool(resolved);
|
|
61
123
|
const passwords = [];
|
|
62
124
|
for (let i = 0; i < resolved.count; i++) {
|
|
63
|
-
passwords.push(
|
|
125
|
+
passwords.push(
|
|
126
|
+
generateSingle(
|
|
127
|
+
resolved.length,
|
|
128
|
+
pool,
|
|
129
|
+
resolved.ensure ? charsets : void 0
|
|
130
|
+
)
|
|
131
|
+
);
|
|
64
132
|
}
|
|
65
133
|
return passwords;
|
|
66
134
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -36,6 +36,12 @@ interface GenerateOptions {
|
|
|
36
36
|
* @default true
|
|
37
37
|
*/
|
|
38
38
|
symbols?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Ensure at least one character from each enabled charset is included.
|
|
41
|
+
* When enabled, the password length must be >= the number of active charsets.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
ensure?: boolean;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Generate one or more cryptographically secure random passwords.
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,12 @@ interface GenerateOptions {
|
|
|
36
36
|
* @default true
|
|
37
37
|
*/
|
|
38
38
|
symbols?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Ensure at least one character from each enabled charset is included.
|
|
41
|
+
* When enabled, the password length must be >= the number of active charsets.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
ensure?: boolean;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Generate one or more cryptographically secure random passwords.
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,72 @@
|
|
|
1
|
-
import { getRandomValues } from 'crypto';
|
|
2
|
-
|
|
3
1
|
// src/generator.ts
|
|
2
|
+
var getRandomValues = globalThis.crypto.getRandomValues.bind(
|
|
3
|
+
globalThis.crypto
|
|
4
|
+
);
|
|
4
5
|
var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
5
6
|
var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
6
7
|
var NUMBERS = "0123456789";
|
|
7
8
|
var SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
|
8
9
|
function buildPool(options) {
|
|
9
10
|
let pool = "";
|
|
10
|
-
|
|
11
|
-
if (options.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const charsets = [];
|
|
12
|
+
if (options.uppercase) {
|
|
13
|
+
pool += UPPERCASE;
|
|
14
|
+
charsets.push(UPPERCASE);
|
|
15
|
+
}
|
|
16
|
+
if (options.lowercase) {
|
|
17
|
+
pool += LOWERCASE;
|
|
18
|
+
charsets.push(LOWERCASE);
|
|
19
|
+
}
|
|
20
|
+
if (options.numbers) {
|
|
21
|
+
pool += NUMBERS;
|
|
22
|
+
charsets.push(NUMBERS);
|
|
23
|
+
}
|
|
24
|
+
if (options.symbols) {
|
|
25
|
+
pool += SYMBOLS;
|
|
26
|
+
charsets.push(SYMBOLS);
|
|
27
|
+
}
|
|
28
|
+
return { pool, charsets };
|
|
29
|
+
}
|
|
30
|
+
function pickRandomChar(charset) {
|
|
31
|
+
const len = charset.length;
|
|
32
|
+
const limit = 256 - 256 % len;
|
|
33
|
+
for (; ; ) {
|
|
34
|
+
const randomBytes = new Uint8Array(2);
|
|
35
|
+
getRandomValues(randomBytes);
|
|
36
|
+
for (let i = 0; i < randomBytes.length; i++) {
|
|
37
|
+
if (randomBytes[i] < limit) {
|
|
38
|
+
return charset[randomBytes[i] % len];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
15
42
|
}
|
|
16
|
-
function
|
|
43
|
+
function cryptoShuffle(arr) {
|
|
44
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
45
|
+
const range = i + 1;
|
|
46
|
+
const limit = 256 - 256 % range;
|
|
47
|
+
let j;
|
|
48
|
+
for (; ; ) {
|
|
49
|
+
const randomBytes = new Uint8Array(1);
|
|
50
|
+
getRandomValues(randomBytes);
|
|
51
|
+
if (randomBytes[0] < limit) {
|
|
52
|
+
j = randomBytes[0] % range;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const tmp = arr[i];
|
|
57
|
+
arr[i] = arr[j];
|
|
58
|
+
arr[j] = tmp;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function generateSingle(length, pool, charsets) {
|
|
17
62
|
const poolLength = pool.length;
|
|
18
63
|
const limit = 256 - 256 % poolLength;
|
|
19
64
|
const chars = [];
|
|
65
|
+
if (charsets) {
|
|
66
|
+
for (const charset of charsets) {
|
|
67
|
+
chars.push(pickRandomChar(charset));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
20
70
|
while (chars.length < length) {
|
|
21
71
|
const remaining = length - chars.length;
|
|
22
72
|
const bufferSize = remaining * 2;
|
|
@@ -29,6 +79,9 @@ function generateSingle(length, pool) {
|
|
|
29
79
|
}
|
|
30
80
|
}
|
|
31
81
|
}
|
|
82
|
+
if (charsets) {
|
|
83
|
+
cryptoShuffle(chars);
|
|
84
|
+
}
|
|
32
85
|
return chars.join("");
|
|
33
86
|
}
|
|
34
87
|
function validateOptions(options) {
|
|
@@ -44,6 +97,14 @@ function validateOptions(options) {
|
|
|
44
97
|
if (!options.uppercase && !options.lowercase && !options.numbers && !options.symbols) {
|
|
45
98
|
throw new Error("\uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uBB38\uC790\uC14B\uC744 \uD3EC\uD568\uD574\uC57C \uD569\uB2C8\uB2E4");
|
|
46
99
|
}
|
|
100
|
+
if (options.ensure) {
|
|
101
|
+
const activeCount = (options.uppercase ? 1 : 0) + (options.lowercase ? 1 : 0) + (options.numbers ? 1 : 0) + (options.symbols ? 1 : 0);
|
|
102
|
+
if (options.length < activeCount) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`ensure \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uAE38\uC774\uAC00 \uD65C\uC131 \uBB38\uC790\uC14B \uC218(${activeCount}) \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
47
108
|
}
|
|
48
109
|
function generatePassword(options) {
|
|
49
110
|
const resolved = {
|
|
@@ -52,13 +113,20 @@ function generatePassword(options) {
|
|
|
52
113
|
uppercase: options?.uppercase ?? true,
|
|
53
114
|
lowercase: options?.lowercase ?? true,
|
|
54
115
|
numbers: options?.numbers ?? true,
|
|
55
|
-
symbols: options?.symbols ?? true
|
|
116
|
+
symbols: options?.symbols ?? true,
|
|
117
|
+
ensure: options?.ensure ?? false
|
|
56
118
|
};
|
|
57
119
|
validateOptions(resolved);
|
|
58
|
-
const pool = buildPool(resolved);
|
|
120
|
+
const { pool, charsets } = buildPool(resolved);
|
|
59
121
|
const passwords = [];
|
|
60
122
|
for (let i = 0; i < resolved.count; i++) {
|
|
61
|
-
passwords.push(
|
|
123
|
+
passwords.push(
|
|
124
|
+
generateSingle(
|
|
125
|
+
resolved.length,
|
|
126
|
+
pool,
|
|
127
|
+
resolved.ensure ? charsets : void 0
|
|
128
|
+
)
|
|
129
|
+
);
|
|
62
130
|
}
|
|
63
131
|
return passwords;
|
|
64
132
|
}
|