smart-passphrase 1.0.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 -0
- package/README.md +47 -0
- package/dist/cjs/generator.js +100 -0
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils.js +59 -0
- package/dist/cjs/wordlist.js +113 -0
- package/dist/esm/generator.js +95 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.js +51 -0
- package/dist/esm/wordlist.js +110 -0
- package/dist/generator.d.ts +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/types.d.ts +21 -0
- package/dist/utils.d.ts +6 -0
- package/dist/wordlist.d.ts +3 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ayush Solanki
|
|
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
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# smart-passphrase
|
|
2
|
+
|
|
3
|
+
A lightweight, secure, and memorable passphrase generator for Node.js and the browser.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* Human-readable passwords
|
|
10
|
+
* Cryptographically secure randomness
|
|
11
|
+
* Works with React, Next.js, Vite, Node
|
|
12
|
+
* TypeScript support
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @ayushsolanki29/smart-passphrase
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { generatePassword } from "@ayushsolanki29/smart-passphrase";
|
|
28
|
+
|
|
29
|
+
const password = generatePassword();
|
|
30
|
+
console.log(password);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Example Output
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
theSOON^goose324
|
|
39
|
+
Ishow#mark=down
|
|
40
|
+
34Roto)mAsk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 📄 License
|
|
46
|
+
|
|
47
|
+
MIT
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generatePassphrase = generatePassphrase;
|
|
4
|
+
exports.generatePassword = generatePassword;
|
|
5
|
+
exports.entropyEstimate = entropyEstimate;
|
|
6
|
+
const wordlist_1 = require("./wordlist");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
const strengthDefaults = {
|
|
9
|
+
medium: { words: 3, symbols: true, digits: 3 },
|
|
10
|
+
strong: { words: 4, symbols: true, digits: 4 },
|
|
11
|
+
ultra: { words: 5, symbols: true, digits: 5 }
|
|
12
|
+
};
|
|
13
|
+
function buildDictionary(overrides) {
|
|
14
|
+
return {
|
|
15
|
+
adjectives: overrides?.adjectives ?? wordlist_1.adjectives,
|
|
16
|
+
nouns: overrides?.nouns ?? wordlist_1.nouns,
|
|
17
|
+
verbs: overrides?.verbs ?? wordlist_1.verbs
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function chooseWords(dictionary, kinds, unique) {
|
|
21
|
+
const used = new Set();
|
|
22
|
+
const output = [];
|
|
23
|
+
for (const kind of kinds) {
|
|
24
|
+
const list = kind === "adj" ? dictionary.adjectives : kind === "noun" ? dictionary.nouns : dictionary.verbs;
|
|
25
|
+
if (list.length === 0) {
|
|
26
|
+
throw new Error(`Dictionary list for ${kind} is empty`);
|
|
27
|
+
}
|
|
28
|
+
let picked = (0, utils_1.randomItem)(list);
|
|
29
|
+
if (unique) {
|
|
30
|
+
let attempts = 0;
|
|
31
|
+
while (used.has(picked) && attempts < 25) {
|
|
32
|
+
picked = (0, utils_1.randomItem)(list);
|
|
33
|
+
attempts += 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
used.add(picked);
|
|
37
|
+
output.push(picked);
|
|
38
|
+
}
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
function defaultPattern(words) {
|
|
42
|
+
const pool = ["adj", "noun", "verb"];
|
|
43
|
+
const output = [];
|
|
44
|
+
for (let i = 0; i < words; i += 1) {
|
|
45
|
+
output.push(pool[i % pool.length]);
|
|
46
|
+
}
|
|
47
|
+
return output;
|
|
48
|
+
}
|
|
49
|
+
function generatePassphrase(options = {}) {
|
|
50
|
+
const defaults = options.strength ? strengthDefaults[options.strength] : strengthDefaults.medium;
|
|
51
|
+
const wordCount = options.words ?? defaults.words;
|
|
52
|
+
const useSymbols = options.symbols ?? defaults.symbols;
|
|
53
|
+
const numbersOption = options.numbers ?? true;
|
|
54
|
+
const digits = typeof numbersOption === "object" ? numbersOption.digits ?? defaults.digits : defaults.digits;
|
|
55
|
+
const separator = options.separator ?? "";
|
|
56
|
+
const uppercaseStyle = options.uppercaseStyle ?? "random";
|
|
57
|
+
const unique = options.unique ?? true;
|
|
58
|
+
const dictionary = buildDictionary(options.dictionary);
|
|
59
|
+
const pattern = options.pattern ?? defaultPattern(wordCount);
|
|
60
|
+
if (pattern.length !== wordCount) {
|
|
61
|
+
throw new Error("pattern length must match words");
|
|
62
|
+
}
|
|
63
|
+
const words = chooseWords(dictionary, pattern, unique).map((word) => (0, utils_1.applyCase)(word, uppercaseStyle));
|
|
64
|
+
const parts = [];
|
|
65
|
+
for (const word of words) {
|
|
66
|
+
parts.push(word);
|
|
67
|
+
}
|
|
68
|
+
if (useSymbols) {
|
|
69
|
+
const symbols = Array.isArray(useSymbols) ? useSymbols : utils_1.defaultSymbols;
|
|
70
|
+
parts.push((0, utils_1.randomItem)(symbols));
|
|
71
|
+
}
|
|
72
|
+
if (numbersOption) {
|
|
73
|
+
parts.push(String((0, utils_1.randomNumberByDigits)(digits)));
|
|
74
|
+
}
|
|
75
|
+
if (separator) {
|
|
76
|
+
return parts.join(separator);
|
|
77
|
+
}
|
|
78
|
+
return parts.join("");
|
|
79
|
+
}
|
|
80
|
+
function generatePassword(options = {}) {
|
|
81
|
+
return generatePassphrase(options);
|
|
82
|
+
}
|
|
83
|
+
function entropyEstimate(options = {}) {
|
|
84
|
+
const defaults = options.strength ? strengthDefaults[options.strength] : strengthDefaults.medium;
|
|
85
|
+
const wordCount = options.words ?? defaults.words;
|
|
86
|
+
const useSymbols = options.symbols ?? defaults.symbols;
|
|
87
|
+
const numbersOption = options.numbers ?? true;
|
|
88
|
+
const digits = typeof numbersOption === "object" ? numbersOption.digits ?? defaults.digits : defaults.digits;
|
|
89
|
+
const dictionary = buildDictionary(options.dictionary);
|
|
90
|
+
const pattern = options.pattern ?? defaultPattern(wordCount);
|
|
91
|
+
const sizes = pattern.map((kind) => {
|
|
92
|
+
const list = kind === "adj" ? dictionary.adjectives : kind === "noun" ? dictionary.nouns : dictionary.verbs;
|
|
93
|
+
return list.length || 1;
|
|
94
|
+
});
|
|
95
|
+
const wordSpace = sizes.reduce((acc, size) => acc * size, 1);
|
|
96
|
+
const symbolSpace = useSymbols ? (Array.isArray(useSymbols) ? useSymbols.length : utils_1.defaultSymbols.length) : 1;
|
|
97
|
+
const numberSpace = numbersOption ? Math.pow(10, digits) : 1;
|
|
98
|
+
const total = wordSpace * symbolSpace * numberSpace;
|
|
99
|
+
return Math.log2(total);
|
|
100
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.entropyEstimate = exports.generatePassword = exports.generatePassphrase = void 0;
|
|
4
|
+
var generator_1 = require("./generator");
|
|
5
|
+
Object.defineProperty(exports, "generatePassphrase", { enumerable: true, get: function () { return generator_1.generatePassphrase; } });
|
|
6
|
+
Object.defineProperty(exports, "generatePassword", { enumerable: true, get: function () { return generator_1.generatePassword; } });
|
|
7
|
+
Object.defineProperty(exports, "entropyEstimate", { enumerable: true, get: function () { return generator_1.entropyEstimate; } });
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultSymbols = void 0;
|
|
4
|
+
exports.secureRandomInt = secureRandomInt;
|
|
5
|
+
exports.randomItem = randomItem;
|
|
6
|
+
exports.randomBool = randomBool;
|
|
7
|
+
exports.applyCase = applyCase;
|
|
8
|
+
exports.randomNumberByDigits = randomNumberByDigits;
|
|
9
|
+
exports.defaultSymbols = ["@", "#", "^", "=", ")", "!", "*", "%"];
|
|
10
|
+
function secureRandomInt(maxExclusive) {
|
|
11
|
+
if (!Number.isInteger(maxExclusive) || maxExclusive <= 0) {
|
|
12
|
+
throw new Error("maxExclusive must be a positive integer");
|
|
13
|
+
}
|
|
14
|
+
const cryptoObj = globalThis.crypto;
|
|
15
|
+
if (!cryptoObj || typeof cryptoObj.getRandomValues !== "function") {
|
|
16
|
+
throw new Error("Secure crypto is not available in this environment");
|
|
17
|
+
}
|
|
18
|
+
const range = 0x100000000;
|
|
19
|
+
const limit = range - (range % maxExclusive);
|
|
20
|
+
const buf = new Uint32Array(1);
|
|
21
|
+
let value = 0;
|
|
22
|
+
do {
|
|
23
|
+
cryptoObj.getRandomValues(buf);
|
|
24
|
+
value = buf[0];
|
|
25
|
+
} while (value >= limit);
|
|
26
|
+
return value % maxExclusive;
|
|
27
|
+
}
|
|
28
|
+
function randomItem(items) {
|
|
29
|
+
if (items.length === 0) {
|
|
30
|
+
throw new Error("Cannot choose from an empty list");
|
|
31
|
+
}
|
|
32
|
+
return items[secureRandomInt(items.length)];
|
|
33
|
+
}
|
|
34
|
+
function randomBool() {
|
|
35
|
+
return secureRandomInt(2) === 1;
|
|
36
|
+
}
|
|
37
|
+
function applyCase(word, style) {
|
|
38
|
+
if (style === "none")
|
|
39
|
+
return word;
|
|
40
|
+
if (style === "upper")
|
|
41
|
+
return word.toUpperCase();
|
|
42
|
+
if (style === "lower")
|
|
43
|
+
return word.toLowerCase();
|
|
44
|
+
if (style === "title")
|
|
45
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
46
|
+
return word
|
|
47
|
+
.split("")
|
|
48
|
+
.map((ch) => (randomBool() ? ch.toUpperCase() : ch.toLowerCase()))
|
|
49
|
+
.join("");
|
|
50
|
+
}
|
|
51
|
+
function randomNumberByDigits(digits) {
|
|
52
|
+
if (!Number.isInteger(digits) || digits <= 0) {
|
|
53
|
+
throw new Error("digits must be a positive integer");
|
|
54
|
+
}
|
|
55
|
+
const min = Math.pow(10, digits - 1);
|
|
56
|
+
const max = Math.pow(10, digits) - 1;
|
|
57
|
+
const span = max - min + 1;
|
|
58
|
+
return min + secureRandomInt(span);
|
|
59
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verbs = exports.nouns = exports.adjectives = void 0;
|
|
4
|
+
exports.adjectives = [
|
|
5
|
+
// Original
|
|
6
|
+
"quick", "silent", "brave", "fancy", "eager", "bright",
|
|
7
|
+
"gentle", "proud", "curious", "steady", "bold", "calm",
|
|
8
|
+
// Advanced originals
|
|
9
|
+
"resilient", "ephemeral", "luminous", "tenacious", "serene",
|
|
10
|
+
"voracious", "ethereal", "stoic", "audacious", "fleeting",
|
|
11
|
+
"mellifluous", "incisive", "unyielding", "sublime", "transient",
|
|
12
|
+
"ferocious", "cunning", "reclusive", "radiant", "somber",
|
|
13
|
+
"deft", "keen", "vigilant", "nimble", "profound",
|
|
14
|
+
// New: memorial + solemn / meaningful tone
|
|
15
|
+
"memorial",
|
|
16
|
+
"immortal",
|
|
17
|
+
"reverent",
|
|
18
|
+
"hallowed",
|
|
19
|
+
"eternal",
|
|
20
|
+
"venerable",
|
|
21
|
+
"sacred",
|
|
22
|
+
"mournful",
|
|
23
|
+
"remnant",
|
|
24
|
+
"honored",
|
|
25
|
+
"legendary",
|
|
26
|
+
"fabled",
|
|
27
|
+
"timeless",
|
|
28
|
+
"perpetual",
|
|
29
|
+
"solemn",
|
|
30
|
+
"devoted",
|
|
31
|
+
"faithful",
|
|
32
|
+
"righteous",
|
|
33
|
+
"valiant",
|
|
34
|
+
"gallant",
|
|
35
|
+
"exalted",
|
|
36
|
+
"revered",
|
|
37
|
+
"venerated",
|
|
38
|
+
"undying",
|
|
39
|
+
"everlasting"
|
|
40
|
+
];
|
|
41
|
+
exports.nouns = [
|
|
42
|
+
// Original
|
|
43
|
+
"tiger", "goose", "rocket", "mask", "forest", "ocean",
|
|
44
|
+
"ember", "comet", "cipher", "garden", "river", "mountain",
|
|
45
|
+
// Advanced originals
|
|
46
|
+
"phantom", "echo", "horizon", "sentinel", "abyss", "beacon",
|
|
47
|
+
"shroud", "monolith", "specter", "harbinger", "labyrinth",
|
|
48
|
+
"oracle", "crucible", "vigil", "rift", "veil", "tempest",
|
|
49
|
+
"solstice", "requiem", "synapse", "nexus", "axiom", "enigma",
|
|
50
|
+
"relic", "citadel",
|
|
51
|
+
// New: memorial + meaningful / legacy words
|
|
52
|
+
"memorial",
|
|
53
|
+
"monument",
|
|
54
|
+
"epitaph",
|
|
55
|
+
"shrine",
|
|
56
|
+
"tomb",
|
|
57
|
+
"grave",
|
|
58
|
+
"remembrance",
|
|
59
|
+
"legacy",
|
|
60
|
+
"heritage",
|
|
61
|
+
"echo",
|
|
62
|
+
"keepsake",
|
|
63
|
+
"memento",
|
|
64
|
+
"relic",
|
|
65
|
+
"vestige",
|
|
66
|
+
"tribute",
|
|
67
|
+
"offering",
|
|
68
|
+
"vow",
|
|
69
|
+
"oath",
|
|
70
|
+
"covenant",
|
|
71
|
+
"pillar",
|
|
72
|
+
"obelisk",
|
|
73
|
+
"chapel",
|
|
74
|
+
"sanctum",
|
|
75
|
+
"archive",
|
|
76
|
+
"chronicle"
|
|
77
|
+
];
|
|
78
|
+
exports.verbs = [
|
|
79
|
+
// Original
|
|
80
|
+
"run", "jump", "build", "mark", "drift", "spark",
|
|
81
|
+
"forge", "glide", "shift", "trace", "craft", "sprint",
|
|
82
|
+
// Advanced originals
|
|
83
|
+
"lunge", "flicker", "shatter", "conceal", "summon", "evade",
|
|
84
|
+
"descend", "ascend", "sunder", "kindle", "lurk", "surge",
|
|
85
|
+
"fracture", "unravel", "coalesce", "sever", "ignite", "linger",
|
|
86
|
+
"plummet", "scatter", "converge", "diverge", "emanate", "sear", "wither",
|
|
87
|
+
// New: memorial / reverent / legacy actions
|
|
88
|
+
"honor",
|
|
89
|
+
"cherish",
|
|
90
|
+
"enshrine",
|
|
91
|
+
"lament",
|
|
92
|
+
"mourn",
|
|
93
|
+
"remember",
|
|
94
|
+
"immortalize",
|
|
95
|
+
"consecrate",
|
|
96
|
+
"dedicate",
|
|
97
|
+
"sanctify",
|
|
98
|
+
"preserve",
|
|
99
|
+
"bequeath",
|
|
100
|
+
"endure",
|
|
101
|
+
"inscribe",
|
|
102
|
+
"carve",
|
|
103
|
+
"engrave",
|
|
104
|
+
"invoke",
|
|
105
|
+
"recall",
|
|
106
|
+
"revere",
|
|
107
|
+
"venerate",
|
|
108
|
+
"exalt",
|
|
109
|
+
"glorify",
|
|
110
|
+
"commemorate",
|
|
111
|
+
"memorialize",
|
|
112
|
+
"pay homage"
|
|
113
|
+
];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { adjectives, nouns, verbs } from "./wordlist";
|
|
2
|
+
import { applyCase, defaultSymbols, randomItem, randomNumberByDigits } from "./utils";
|
|
3
|
+
const strengthDefaults = {
|
|
4
|
+
medium: { words: 3, symbols: true, digits: 3 },
|
|
5
|
+
strong: { words: 4, symbols: true, digits: 4 },
|
|
6
|
+
ultra: { words: 5, symbols: true, digits: 5 }
|
|
7
|
+
};
|
|
8
|
+
function buildDictionary(overrides) {
|
|
9
|
+
return {
|
|
10
|
+
adjectives: overrides?.adjectives ?? adjectives,
|
|
11
|
+
nouns: overrides?.nouns ?? nouns,
|
|
12
|
+
verbs: overrides?.verbs ?? verbs
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function chooseWords(dictionary, kinds, unique) {
|
|
16
|
+
const used = new Set();
|
|
17
|
+
const output = [];
|
|
18
|
+
for (const kind of kinds) {
|
|
19
|
+
const list = kind === "adj" ? dictionary.adjectives : kind === "noun" ? dictionary.nouns : dictionary.verbs;
|
|
20
|
+
if (list.length === 0) {
|
|
21
|
+
throw new Error(`Dictionary list for ${kind} is empty`);
|
|
22
|
+
}
|
|
23
|
+
let picked = randomItem(list);
|
|
24
|
+
if (unique) {
|
|
25
|
+
let attempts = 0;
|
|
26
|
+
while (used.has(picked) && attempts < 25) {
|
|
27
|
+
picked = randomItem(list);
|
|
28
|
+
attempts += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
used.add(picked);
|
|
32
|
+
output.push(picked);
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
function defaultPattern(words) {
|
|
37
|
+
const pool = ["adj", "noun", "verb"];
|
|
38
|
+
const output = [];
|
|
39
|
+
for (let i = 0; i < words; i += 1) {
|
|
40
|
+
output.push(pool[i % pool.length]);
|
|
41
|
+
}
|
|
42
|
+
return output;
|
|
43
|
+
}
|
|
44
|
+
export function generatePassphrase(options = {}) {
|
|
45
|
+
const defaults = options.strength ? strengthDefaults[options.strength] : strengthDefaults.medium;
|
|
46
|
+
const wordCount = options.words ?? defaults.words;
|
|
47
|
+
const useSymbols = options.symbols ?? defaults.symbols;
|
|
48
|
+
const numbersOption = options.numbers ?? true;
|
|
49
|
+
const digits = typeof numbersOption === "object" ? numbersOption.digits ?? defaults.digits : defaults.digits;
|
|
50
|
+
const separator = options.separator ?? "";
|
|
51
|
+
const uppercaseStyle = options.uppercaseStyle ?? "random";
|
|
52
|
+
const unique = options.unique ?? true;
|
|
53
|
+
const dictionary = buildDictionary(options.dictionary);
|
|
54
|
+
const pattern = options.pattern ?? defaultPattern(wordCount);
|
|
55
|
+
if (pattern.length !== wordCount) {
|
|
56
|
+
throw new Error("pattern length must match words");
|
|
57
|
+
}
|
|
58
|
+
const words = chooseWords(dictionary, pattern, unique).map((word) => applyCase(word, uppercaseStyle));
|
|
59
|
+
const parts = [];
|
|
60
|
+
for (const word of words) {
|
|
61
|
+
parts.push(word);
|
|
62
|
+
}
|
|
63
|
+
if (useSymbols) {
|
|
64
|
+
const symbols = Array.isArray(useSymbols) ? useSymbols : defaultSymbols;
|
|
65
|
+
parts.push(randomItem(symbols));
|
|
66
|
+
}
|
|
67
|
+
if (numbersOption) {
|
|
68
|
+
parts.push(String(randomNumberByDigits(digits)));
|
|
69
|
+
}
|
|
70
|
+
if (separator) {
|
|
71
|
+
return parts.join(separator);
|
|
72
|
+
}
|
|
73
|
+
return parts.join("");
|
|
74
|
+
}
|
|
75
|
+
export function generatePassword(options = {}) {
|
|
76
|
+
return generatePassphrase(options);
|
|
77
|
+
}
|
|
78
|
+
export function entropyEstimate(options = {}) {
|
|
79
|
+
const defaults = options.strength ? strengthDefaults[options.strength] : strengthDefaults.medium;
|
|
80
|
+
const wordCount = options.words ?? defaults.words;
|
|
81
|
+
const useSymbols = options.symbols ?? defaults.symbols;
|
|
82
|
+
const numbersOption = options.numbers ?? true;
|
|
83
|
+
const digits = typeof numbersOption === "object" ? numbersOption.digits ?? defaults.digits : defaults.digits;
|
|
84
|
+
const dictionary = buildDictionary(options.dictionary);
|
|
85
|
+
const pattern = options.pattern ?? defaultPattern(wordCount);
|
|
86
|
+
const sizes = pattern.map((kind) => {
|
|
87
|
+
const list = kind === "adj" ? dictionary.adjectives : kind === "noun" ? dictionary.nouns : dictionary.verbs;
|
|
88
|
+
return list.length || 1;
|
|
89
|
+
});
|
|
90
|
+
const wordSpace = sizes.reduce((acc, size) => acc * size, 1);
|
|
91
|
+
const symbolSpace = useSymbols ? (Array.isArray(useSymbols) ? useSymbols.length : defaultSymbols.length) : 1;
|
|
92
|
+
const numberSpace = numbersOption ? Math.pow(10, digits) : 1;
|
|
93
|
+
const total = wordSpace * symbolSpace * numberSpace;
|
|
94
|
+
return Math.log2(total);
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generatePassphrase, generatePassword, entropyEstimate } from "./generator";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const defaultSymbols = ["@", "#", "^", "=", ")", "!", "*", "%"];
|
|
2
|
+
export function secureRandomInt(maxExclusive) {
|
|
3
|
+
if (!Number.isInteger(maxExclusive) || maxExclusive <= 0) {
|
|
4
|
+
throw new Error("maxExclusive must be a positive integer");
|
|
5
|
+
}
|
|
6
|
+
const cryptoObj = globalThis.crypto;
|
|
7
|
+
if (!cryptoObj || typeof cryptoObj.getRandomValues !== "function") {
|
|
8
|
+
throw new Error("Secure crypto is not available in this environment");
|
|
9
|
+
}
|
|
10
|
+
const range = 0x100000000;
|
|
11
|
+
const limit = range - (range % maxExclusive);
|
|
12
|
+
const buf = new Uint32Array(1);
|
|
13
|
+
let value = 0;
|
|
14
|
+
do {
|
|
15
|
+
cryptoObj.getRandomValues(buf);
|
|
16
|
+
value = buf[0];
|
|
17
|
+
} while (value >= limit);
|
|
18
|
+
return value % maxExclusive;
|
|
19
|
+
}
|
|
20
|
+
export function randomItem(items) {
|
|
21
|
+
if (items.length === 0) {
|
|
22
|
+
throw new Error("Cannot choose from an empty list");
|
|
23
|
+
}
|
|
24
|
+
return items[secureRandomInt(items.length)];
|
|
25
|
+
}
|
|
26
|
+
export function randomBool() {
|
|
27
|
+
return secureRandomInt(2) === 1;
|
|
28
|
+
}
|
|
29
|
+
export function applyCase(word, style) {
|
|
30
|
+
if (style === "none")
|
|
31
|
+
return word;
|
|
32
|
+
if (style === "upper")
|
|
33
|
+
return word.toUpperCase();
|
|
34
|
+
if (style === "lower")
|
|
35
|
+
return word.toLowerCase();
|
|
36
|
+
if (style === "title")
|
|
37
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
38
|
+
return word
|
|
39
|
+
.split("")
|
|
40
|
+
.map((ch) => (randomBool() ? ch.toUpperCase() : ch.toLowerCase()))
|
|
41
|
+
.join("");
|
|
42
|
+
}
|
|
43
|
+
export function randomNumberByDigits(digits) {
|
|
44
|
+
if (!Number.isInteger(digits) || digits <= 0) {
|
|
45
|
+
throw new Error("digits must be a positive integer");
|
|
46
|
+
}
|
|
47
|
+
const min = Math.pow(10, digits - 1);
|
|
48
|
+
const max = Math.pow(10, digits) - 1;
|
|
49
|
+
const span = max - min + 1;
|
|
50
|
+
return min + secureRandomInt(span);
|
|
51
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export const adjectives = [
|
|
2
|
+
// Original
|
|
3
|
+
"quick", "silent", "brave", "fancy", "eager", "bright",
|
|
4
|
+
"gentle", "proud", "curious", "steady", "bold", "calm",
|
|
5
|
+
// Advanced originals
|
|
6
|
+
"resilient", "ephemeral", "luminous", "tenacious", "serene",
|
|
7
|
+
"voracious", "ethereal", "stoic", "audacious", "fleeting",
|
|
8
|
+
"mellifluous", "incisive", "unyielding", "sublime", "transient",
|
|
9
|
+
"ferocious", "cunning", "reclusive", "radiant", "somber",
|
|
10
|
+
"deft", "keen", "vigilant", "nimble", "profound",
|
|
11
|
+
// New: memorial + solemn / meaningful tone
|
|
12
|
+
"memorial",
|
|
13
|
+
"immortal",
|
|
14
|
+
"reverent",
|
|
15
|
+
"hallowed",
|
|
16
|
+
"eternal",
|
|
17
|
+
"venerable",
|
|
18
|
+
"sacred",
|
|
19
|
+
"mournful",
|
|
20
|
+
"remnant",
|
|
21
|
+
"honored",
|
|
22
|
+
"legendary",
|
|
23
|
+
"fabled",
|
|
24
|
+
"timeless",
|
|
25
|
+
"perpetual",
|
|
26
|
+
"solemn",
|
|
27
|
+
"devoted",
|
|
28
|
+
"faithful",
|
|
29
|
+
"righteous",
|
|
30
|
+
"valiant",
|
|
31
|
+
"gallant",
|
|
32
|
+
"exalted",
|
|
33
|
+
"revered",
|
|
34
|
+
"venerated",
|
|
35
|
+
"undying",
|
|
36
|
+
"everlasting"
|
|
37
|
+
];
|
|
38
|
+
export const nouns = [
|
|
39
|
+
// Original
|
|
40
|
+
"tiger", "goose", "rocket", "mask", "forest", "ocean",
|
|
41
|
+
"ember", "comet", "cipher", "garden", "river", "mountain",
|
|
42
|
+
// Advanced originals
|
|
43
|
+
"phantom", "echo", "horizon", "sentinel", "abyss", "beacon",
|
|
44
|
+
"shroud", "monolith", "specter", "harbinger", "labyrinth",
|
|
45
|
+
"oracle", "crucible", "vigil", "rift", "veil", "tempest",
|
|
46
|
+
"solstice", "requiem", "synapse", "nexus", "axiom", "enigma",
|
|
47
|
+
"relic", "citadel",
|
|
48
|
+
// New: memorial + meaningful / legacy words
|
|
49
|
+
"memorial",
|
|
50
|
+
"monument",
|
|
51
|
+
"epitaph",
|
|
52
|
+
"shrine",
|
|
53
|
+
"tomb",
|
|
54
|
+
"grave",
|
|
55
|
+
"remembrance",
|
|
56
|
+
"legacy",
|
|
57
|
+
"heritage",
|
|
58
|
+
"echo",
|
|
59
|
+
"keepsake",
|
|
60
|
+
"memento",
|
|
61
|
+
"relic",
|
|
62
|
+
"vestige",
|
|
63
|
+
"tribute",
|
|
64
|
+
"offering",
|
|
65
|
+
"vow",
|
|
66
|
+
"oath",
|
|
67
|
+
"covenant",
|
|
68
|
+
"pillar",
|
|
69
|
+
"obelisk",
|
|
70
|
+
"chapel",
|
|
71
|
+
"sanctum",
|
|
72
|
+
"archive",
|
|
73
|
+
"chronicle"
|
|
74
|
+
];
|
|
75
|
+
export const verbs = [
|
|
76
|
+
// Original
|
|
77
|
+
"run", "jump", "build", "mark", "drift", "spark",
|
|
78
|
+
"forge", "glide", "shift", "trace", "craft", "sprint",
|
|
79
|
+
// Advanced originals
|
|
80
|
+
"lunge", "flicker", "shatter", "conceal", "summon", "evade",
|
|
81
|
+
"descend", "ascend", "sunder", "kindle", "lurk", "surge",
|
|
82
|
+
"fracture", "unravel", "coalesce", "sever", "ignite", "linger",
|
|
83
|
+
"plummet", "scatter", "converge", "diverge", "emanate", "sear", "wither",
|
|
84
|
+
// New: memorial / reverent / legacy actions
|
|
85
|
+
"honor",
|
|
86
|
+
"cherish",
|
|
87
|
+
"enshrine",
|
|
88
|
+
"lament",
|
|
89
|
+
"mourn",
|
|
90
|
+
"remember",
|
|
91
|
+
"immortalize",
|
|
92
|
+
"consecrate",
|
|
93
|
+
"dedicate",
|
|
94
|
+
"sanctify",
|
|
95
|
+
"preserve",
|
|
96
|
+
"bequeath",
|
|
97
|
+
"endure",
|
|
98
|
+
"inscribe",
|
|
99
|
+
"carve",
|
|
100
|
+
"engrave",
|
|
101
|
+
"invoke",
|
|
102
|
+
"recall",
|
|
103
|
+
"revere",
|
|
104
|
+
"venerate",
|
|
105
|
+
"exalt",
|
|
106
|
+
"glorify",
|
|
107
|
+
"commemorate",
|
|
108
|
+
"memorialize",
|
|
109
|
+
"pay homage"
|
|
110
|
+
];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GenerateOptions } from "./types";
|
|
2
|
+
export declare function generatePassphrase(options?: GenerateOptions): string;
|
|
3
|
+
export declare function generatePassword(options?: GenerateOptions): string;
|
|
4
|
+
export declare function entropyEstimate(options?: GenerateOptions): number;
|
package/dist/index.d.ts
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type UppercaseStyle = "none" | "random" | "title" | "upper" | "lower";
|
|
2
|
+
export type Strength = "medium" | "strong" | "ultra";
|
|
3
|
+
export type WordKind = "adj" | "noun" | "verb";
|
|
4
|
+
export interface Dictionary {
|
|
5
|
+
adjectives: string[];
|
|
6
|
+
nouns: string[];
|
|
7
|
+
verbs: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface GenerateOptions {
|
|
10
|
+
words?: number;
|
|
11
|
+
numbers?: boolean | {
|
|
12
|
+
digits?: number;
|
|
13
|
+
};
|
|
14
|
+
symbols?: boolean | string[];
|
|
15
|
+
uppercaseStyle?: UppercaseStyle;
|
|
16
|
+
separator?: string;
|
|
17
|
+
unique?: boolean;
|
|
18
|
+
strength?: Strength;
|
|
19
|
+
pattern?: WordKind[];
|
|
20
|
+
dictionary?: Partial<Dictionary>;
|
|
21
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const defaultSymbols: string[];
|
|
2
|
+
export declare function secureRandomInt(maxExclusive: number): number;
|
|
3
|
+
export declare function randomItem<T>(items: T[]): T;
|
|
4
|
+
export declare function randomBool(): boolean;
|
|
5
|
+
export declare function applyCase(word: string, style: "none" | "random" | "title" | "upper" | "lower"): string;
|
|
6
|
+
export declare function randomNumberByDigits(digits: number): number;
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "smart-passphrase",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Memorable, secure passphrase generator for web and Node.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ayush Solanki <ayushsolanki2901@gmail.com> (https://github.com/ayushsolanki29)",
|
|
7
|
+
"type": "module",
|
|
8
|
+
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ayushsolanki29/smart-passphrase"
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
"keywords": [
|
|
15
|
+
"password",
|
|
16
|
+
"passphrase",
|
|
17
|
+
"generator",
|
|
18
|
+
"security",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/esm/index.js",
|
|
26
|
+
"require": "./dist/cjs/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
"main": "./dist/cjs/index.cjs",
|
|
31
|
+
"module": "./dist/esm/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
|
|
34
|
+
"files": ["dist"],
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
39
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
40
|
+
"build": "npm run build:esm && npm run build:cjs"
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.5.4"
|
|
45
|
+
}
|
|
46
|
+
}
|