js-generate-password 1.0.0 → 1.1.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/LICENSE +21 -21
- package/README.md +280 -172
- package/dist/index.cjs +202 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.cts +67 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.d.ts +65 -1
- package/dist/index.mjs +176 -0
- package/dist/index.mjs.map +7 -0
- package/package.json +59 -51
- package/src/index.ts +22 -0
- package/src/lib/check-options-error.ts +52 -0
- package/src/lib/generate-random-number.ts +48 -0
- package/src/lib/password-generator.ts +92 -0
- package/src/lib/process-options.ts +34 -0
- package/src/lib/shuffle-string.ts +21 -0
- package/src/types.ts +57 -0
- package/dist/index.js +0 -1
- package/dist/lib/check-options-error.d.ts +0 -2
- package/dist/lib/generate-random-number.d.ts +0 -1
- package/dist/lib/password-generator.d.ts +0 -2
- package/dist/lib/process-options.d.ts +0 -2
- package/dist/lib/shuffle-string.d.ts +0 -1
- package/dist/types.d.ts +0 -58
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Ahmad Joya
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Ahmad Joya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,172 +1,280 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
</
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
</
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
<p align ="center">
|
|
2
|
+
<a href="https://nodei.co/npm/js-generate-password" target="_blank">
|
|
3
|
+
<img src="https://nodei.co/npm/js-generate-password.png" alt="npm Info" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://npm.im/js-generate-password" target="_blank">
|
|
9
|
+
<img src="https://img.shields.io/npm/v/js-generate-password.svg" alt="npm Version" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://npm.im/js-generate-password" target="_blank">
|
|
12
|
+
<img src="https://img.shields.io/npm/dm/js-generate-password.svg" alt="npm Downloads" />
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://bundlephobia.com/package/js-generate-password" target="_blank">
|
|
15
|
+
<img src="https://img.shields.io/bundlephobia/minzip/js-generate-password" alt="Bundle Size" />
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://github.com/ahmadjoya/generate-password-lite/blob/main/LICENSE" target="_blank">
|
|
18
|
+
<img src="https://img.shields.io/npm/l/js-generate-password.svg" alt="License" />
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<a href="https://github.com/ahmadjoya/generate-password-lite/stargazers" target="_blank">
|
|
24
|
+
<img src="https://img.shields.io/github/stars/ahmadjoya/generate-password-lite" alt="GitHub Stars" />
|
|
25
|
+
</a>
|
|
26
|
+
<a href="https://github.com/ahmadjoya/generate-password-lite/network/members" target="_blank">
|
|
27
|
+
<img src="https://img.shields.io/github/forks/ahmadjoya/generate-password-lite" alt="GitHub Forks" />
|
|
28
|
+
</a>
|
|
29
|
+
<a href="https://github.com/ahmadjoya/generate-password-lite/issues" target="_blank">
|
|
30
|
+
<img src="https://img.shields.io/github/issues/ahmadjoya/generate-password-lite" alt="GitHub Issues" />
|
|
31
|
+
</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
# js-generate-password
|
|
35
|
+
|
|
36
|
+
A tiny, dependency-free password generator for JavaScript and TypeScript projects — Node.js, React, Next.js, Vue, Svelte, Deno, Bun, and the browser.
|
|
37
|
+
|
|
38
|
+
Passwords may contain lowercase letters, uppercase letters, numbers and symbols. The `options` parameter lets you enable or disable each group, exclude specific characters, and require a minimum number of characters from each group.
|
|
39
|
+
|
|
40
|
+
- **Secure by default** — every character comes from the Web Crypto CSPRNG (`crypto.getRandomValues`), never `Math.random()`.
|
|
41
|
+
- **Zero dependencies** — nothing else is pulled into your lockfile.
|
|
42
|
+
- **Dual ESM + CommonJS** — `import` and `require` both work, with matching type declarations.
|
|
43
|
+
- **Fully typed** — TypeScript definitions ship with the package.
|
|
44
|
+
|
|
45
|
+
> **Note**
|
|
46
|
+
> This package is also published under the name [`generate-password-lite`](https://www.npmjs.com/package/generate-password-lite). The two packages are the same library — use whichever name you already depend on.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install js-generate-password
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
yarn add js-generate-password
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pnpm add js-generate-password
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Requirements:** Node.js 18 or newer, or any modern browser. Both provide the Web Crypto API that this package relies on.
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### ES modules / TypeScript
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { GeneratePassword } from 'js-generate-password'
|
|
70
|
+
|
|
71
|
+
const password = GeneratePassword({
|
|
72
|
+
length: 14,
|
|
73
|
+
symbols: true,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
console.log(password) // => "q7#Rk2mV$pLx8w"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A default export is available too, if you prefer it:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import GeneratePassword from 'js-generate-password'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### CommonJS
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const { GeneratePassword } = require('js-generate-password')
|
|
89
|
+
|
|
90
|
+
const password = GeneratePassword({ length: 14, symbols: true })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### React
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { useState } from 'react'
|
|
97
|
+
import { GeneratePassword } from 'js-generate-password'
|
|
98
|
+
|
|
99
|
+
export function PasswordField() {
|
|
100
|
+
const [password, setPassword] = useState(() =>
|
|
101
|
+
GeneratePassword({ length: 16, symbols: true })
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div>
|
|
106
|
+
<input readOnly value={password} />
|
|
107
|
+
<button onClick={() => setPassword(GeneratePassword({ length: 16, symbols: true }))}>
|
|
108
|
+
Regenerate
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Next.js
|
|
116
|
+
|
|
117
|
+
The package works in both the App Router and the Pages Router, on the server and in the browser.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// app/api/password/route.ts
|
|
121
|
+
import { GeneratePassword } from 'js-generate-password'
|
|
122
|
+
|
|
123
|
+
export function GET() {
|
|
124
|
+
return Response.json({ password: GeneratePassword({ length: 20, symbols: true }) })
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Generating a password during render of a **server component** produces a different value on the server and on the client. Generate it in an event handler, in a route handler, or inside `useState`'s initializer in a client component.
|
|
129
|
+
|
|
130
|
+
## Options
|
|
131
|
+
|
|
132
|
+
Every option is optional. Calling `GeneratePassword()` with no argument uses the defaults below.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
GeneratePassword({
|
|
136
|
+
length: 10,
|
|
137
|
+
lowercase: true,
|
|
138
|
+
uppercase: true,
|
|
139
|
+
numbers: true,
|
|
140
|
+
symbols: false,
|
|
141
|
+
exclude: '',
|
|
142
|
+
minLengthLowercase: 1,
|
|
143
|
+
minLengthUppercase: 1,
|
|
144
|
+
minLengthNumbers: 1,
|
|
145
|
+
minLengthSymbols: 0,
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Name | Type | Description | Default |
|
|
150
|
+
| -------------------- | --------- | --------------------------------------------------------------------------------- | ------- |
|
|
151
|
+
| `length` | `number` | Length of the generated password. Must be a positive integer. | `10` |
|
|
152
|
+
| `lowercase` | `boolean` | Include lowercase letters (`a–z`). | `true` |
|
|
153
|
+
| `uppercase` | `boolean` | Include uppercase letters (`A–Z`). | `true` |
|
|
154
|
+
| `numbers` | `boolean` | Include digits (`0–9`). | `true` |
|
|
155
|
+
| `symbols` | `boolean` | Include symbols (see [Symbols](#symbols)). | `false` |
|
|
156
|
+
| `exclude` | `string` | Characters to leave out of the password. | `''` |
|
|
157
|
+
| `minLengthLowercase` | `number` | Minimum number of lowercase letters. Forced to `0` when `lowercase` is `false`. | `1` |
|
|
158
|
+
| `minLengthUppercase` | `number` | Minimum number of uppercase letters. Forced to `0` when `uppercase` is `false`. | `1` |
|
|
159
|
+
| `minLengthNumbers` | `number` | Minimum number of digits. Forced to `0` when `numbers` is `false`. | `1` |
|
|
160
|
+
| `minLengthSymbols` | `number` | Minimum number of symbols. Forced to `0` when `symbols` is `false`. | `1`\* |
|
|
161
|
+
|
|
162
|
+
At least one of `lowercase`, `uppercase`, `numbers` or `symbols` must be `true`.
|
|
163
|
+
|
|
164
|
+
\* `minLengthSymbols` defaults to `1` whenever you turn `symbols` on. Because `symbols` is `false` by default, the effective default for an untouched call is `0`.
|
|
165
|
+
|
|
166
|
+
### Symbols
|
|
167
|
+
|
|
168
|
+
When `symbols` is enabled the following characters are used:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
!#$%&'()*+,-./:;<=>?@[]^_{|}~
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Examples
|
|
175
|
+
|
|
176
|
+
**No options.** Defaults apply: 10 characters, upper and lowercase letters plus digits, no symbols.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { GeneratePassword } from 'js-generate-password'
|
|
180
|
+
|
|
181
|
+
console.log(GeneratePassword())
|
|
182
|
+
// => "xDU6izb3PV"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**A longer password.**
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
console.log(GeneratePassword({ length: 25 }))
|
|
189
|
+
// => "U4c3KpQP5UrbZgTcrqMgFeI3R"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Excluding characters.** Useful for dropping look-alikes such as `O`/`0` and `l`/`1`.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
console.log(GeneratePassword({ length: 16, exclude: 'Ol01Il' }))
|
|
196
|
+
// => "gT7yqW4nZbK9vRhs"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Requiring a mix.** Guarantee at least two of each group.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
console.log(
|
|
203
|
+
GeneratePassword({
|
|
204
|
+
length: 20,
|
|
205
|
+
symbols: true,
|
|
206
|
+
minLengthLowercase: 2,
|
|
207
|
+
minLengthUppercase: 2,
|
|
208
|
+
minLengthNumbers: 2,
|
|
209
|
+
minLengthSymbols: 2,
|
|
210
|
+
})
|
|
211
|
+
)
|
|
212
|
+
// => "k#9Wq2mZ$vT7pL4nRj%x"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Digits only, for a one-time code.**
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
console.log(
|
|
219
|
+
GeneratePassword({
|
|
220
|
+
length: 6,
|
|
221
|
+
lowercase: false,
|
|
222
|
+
uppercase: false,
|
|
223
|
+
numbers: true,
|
|
224
|
+
symbols: false,
|
|
225
|
+
})
|
|
226
|
+
)
|
|
227
|
+
// => "482915"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Errors
|
|
231
|
+
|
|
232
|
+
`GeneratePassword` throws an `Error` when the options cannot produce a valid password:
|
|
233
|
+
|
|
234
|
+
| Condition | Example |
|
|
235
|
+
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
236
|
+
| `length` is not a positive integer | `GeneratePassword({ length: 0 })` |
|
|
237
|
+
| A `minLength*` value is not a non-negative integer | `GeneratePassword({ minLengthNumbers: -1 })` |
|
|
238
|
+
| Every character group is disabled | `GeneratePassword({ lowercase: false, uppercase: false, numbers: false, symbols: false })` |
|
|
239
|
+
| The `minLength*` values add up to more than `length` | `GeneratePassword({ length: 4, symbols: true, minLengthSymbols: 5 })` |
|
|
240
|
+
| `exclude` removes every character of a group that still has a minimum | `GeneratePassword({ exclude: '0123456789' })` — digits are on by default |
|
|
241
|
+
|
|
242
|
+
The last case is worth calling out: excluding all digits while `numbers` is still `true` is a contradiction, so it fails loudly instead of quietly returning a password with no digits.
|
|
243
|
+
|
|
244
|
+
## Security
|
|
245
|
+
|
|
246
|
+
Passwords are generated with `crypto.getRandomValues`, the cryptographically secure random source built into Node.js 18+ and every modern browser. Values are drawn using rejection sampling, so each character in the allowed set is equally likely — a plain modulo would skew the results toward the start of the alphabet.
|
|
247
|
+
|
|
248
|
+
The characters that satisfy your `minLength*` requirements are shuffled through the whole password rather than placed at the front, so the position of each group carries no information.
|
|
249
|
+
|
|
250
|
+
If `globalThis.crypto` is unavailable, the package throws rather than silently falling back to a predictable random source.
|
|
251
|
+
|
|
252
|
+
## TypeScript
|
|
253
|
+
|
|
254
|
+
The `GenerateOptions` type is exported for reuse:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { GeneratePassword, type GenerateOptions } from 'js-generate-password'
|
|
258
|
+
|
|
259
|
+
const strongDefaults: GenerateOptions = {
|
|
260
|
+
length: 24,
|
|
261
|
+
symbols: true,
|
|
262
|
+
minLengthSymbols: 3,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const password = GeneratePassword(strongDefaults)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Contributing
|
|
269
|
+
|
|
270
|
+
Issues and pull requests are welcome at [github.com/ahmadjoya/generate-password-lite](https://github.com/ahmadjoya/generate-password-lite).
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npm install
|
|
274
|
+
npm run build
|
|
275
|
+
npm test
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
[MIT](https://github.com/ahmadjoya/generate-password-lite/blob/main/LICENSE) © Ahmad Joya
|