@virastack/password 1.0.2 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strength/index.ts","../src/strength/common.ts","../src/strength/core.ts"],"sourcesContent":["export { definePolicy, evaluate, normalizePassword } from \"./core\";\nexport type * from \"./types\";\n","// Cores of common passwords: lowercase letters only, as `commonCore()` reduces them\n// (\"P@ssw0rd123!\" → \"password\"). Includes Turkish favourites. Digits-only and keyboard runs\n// are caught by the repetition and sequence checks instead.\nexport const COMMON =\n \"password passwort parola sifre sifrem sifreniz parolam qwerty qwertz azerty qwertyuiop asdfgh asdfghjkl \" +\n \"zxcvbn zxcvbnm qazwsx qwaszx wsxedc abc abcdef letmein welcome welcom admin administrator root user guest \" +\n \"login test tester demo default changeme secret private iloveyou loveyou lovely love monkey dragon master \" +\n \"shadow sunshine princess football baseball soccer basketball hockey superman batman spiderman starwars \" +\n \"pokemon minecraft trustno whatever freedom flower hello hellothere cookie cheese chocolate summer winter \" +\n \"autumn spring michael jessica jennifer charlie thomas jordan hunter ranger buster tigger ginger pepper \" +\n \"matrix computer internet google facebook instagram twitter yahoo hotmail gmail apple samsung iphone \" +\n \"android microsoft windows linux ubuntu killer access mustang harley ferrari porsche mercedes yankees \" +\n \"liverpool chelsea arsenal barcelona realmadrid juventus galatasaray fenerbahce besiktas trabzonspor \" +\n \"bursaspor cimbom istanbul ankara izmir antalya bursa turkiye turkey ataturk allah bismillah askim \" +\n \"sevgilim bitanem canim hayatim prenses kelebek melek gunes yildiz deniz mehmet ahmet mustafa ali \" +\n \"ayse fatma emine hatice zeynep elif omer murat emre burak huseyin hasan ibrahim yusuf merhaba \" +\n \"selam kardesim annem babam qwertyasdfgh correcthorsebatterystaple passphrase mypassword newpassword \" +\n \"temppassword passwordpassword nopassword sifreyok\";\n","import { COMMON } from \"./common\";\nimport type {\n EvaluateOptions,\n PasswordChecks,\n PasswordIssue,\n Policy,\n PolicyOptions,\n StrengthLevel,\n StrengthResult,\n StrengthScore,\n} from \"./types\";\n\nconst ZERO_WIDTH = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst LEET: Record<string, string> = { \"0\": \"o\", \"1\": \"i\", \"3\": \"e\", \"4\": \"a\", \"5\": \"s\", \"7\": \"t\", \"8\": \"b\", \"@\": \"a\", \"$\": \"s\", \"!\": \"i\", \"+\": \"t\" };\n// Rows and runs that read as a sequence when typed, forwards or backwards.\nconst SEQUENCES = [\n \"abcdefghijklmnopqrstuvwxyz\",\n \"0123456789\",\n \"qwertyuiopasdfghjklzxcvbnm\",\n \"qwertzuiopasdfghjklyxcvbnm\",\n \"azertyuiopqsdfghjklmwxcvbn\",\n // Turkish Q layout, folded: ı→i, ğ→g, ü→u, ş→s, ö→o, ç→c\n \"qwertyuiopguasdfghjklsizxcvbnmoc\",\n \"1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p\",\n];\nconst LEVELS: readonly StrengthLevel[] = [\"weak\", \"weak\", \"fair\", \"good\", \"strong\"];\n\nlet common: Set<string> | undefined;\n\n/** NFKC, so visually identical input from different keyboards compares and hashes the same. */\nexport const normalizePassword = (password: string): string => password.normalize(\"NFKC\");\n\nconst codePoints = (value: string): string[] => Array.from(value.replace(ZERO_WIDTH, \"\"));\n\n/** Lowercase without accents, for comparisons only: \"Şifre\" and \"sifre\" match, so do \"İ\" and \"i\". */\nconst fold = (value: string): string =>\n value.normalize(\"NFKD\").replace(/\\p{M}/gu, \"\").toLowerCase().replace(/\\u0131/g, \"i\").replace(ZERO_WIDTH, \"\");\n\nconst deLeet = (value: string): string => value.replace(/[0-9@$!+]/g, (c) => LEET[c] ?? c);\n\n/** Length of the shortest unit the string repeats, e.g. 3 for \"abcabcab\" (the last copy may be cut short). */\nconst period = (value: string[]): number => {\n const n = value.length;\n const lps = new Array<number>(n).fill(0);\n for (let i = 1, k = 0; i < n; i++) {\n while (k > 0 && value[i] !== value[k]) k = lps[k - 1];\n if (value[i] === value[k]) k++;\n lps[i] = k;\n }\n return n - (lps[n - 1] ?? 0);\n};\n\nconst unit = (value: string): string => {\n const chars = Array.from(value);\n return chars.slice(0, period(chars)).join(\"\");\n};\n\n/** \"P@ssw0rd2024!\" → \"password\"; \"summer2024summer\" → \"summer\". Digits inside are leetspeak or separators. */\nconst commonCores = (folded: string): string[] => {\n const trimmed = folded.replace(/^[^\\p{L}]+|[^\\p{L}]+$/gu, \"\");\n return [deLeet(trimmed), trimmed].map((core) => unit(core.replace(/[^\\p{L}]/gu, \"\")));\n};\n\nconst isSequence = (folded: string): boolean => {\n if (folded.length < 3) return false;\n return SEQUENCES.some((sequence) => {\n const forward = sequence.repeat(Math.ceil(folded.length / sequence.length) + 1);\n const backward = Array.from(forward).reverse().join(\"\");\n return forward.includes(folded) || backward.includes(folded);\n });\n};\n\nconst isRepetitive = (chars: string[], folded: string): boolean => {\n // Fewer than 4 distinct characters; short inputs need half of theirs.\n if (new Set(chars).size < Math.min(4, Math.ceil(chars.length / 2))) return true;\n if (period(chars) <= chars.length / 3) return true;\n return isSequence(folded);\n};\n\n/** Splits emails (local part only), names and usernames into lowercase parts. */\nexport const personalTokens = (forbidden: readonly unknown[], minTokenLength: number): string[] => {\n const tokens = new Set<string>();\n for (const value of forbidden) {\n if (typeof value !== \"string\") continue;\n const at = value.lastIndexOf(\"@\");\n const source = fold(at > 0 ? value.slice(0, at) : value).trim();\n const parts = [source, ...source.split(/[^\\p{L}\\p{N}]+|(?<=\\p{L})(?=\\p{N})|(?<=\\p{N})(?=\\p{L})/u)];\n for (const part of parts) {\n if (Array.from(part).length >= minTokenLength) tokens.add(part);\n }\n }\n return [...tokens];\n};\n\nconst containsAny = (folded: string, words: readonly string[]): boolean => {\n if (words.length === 0) return false;\n const variants = [folded, deLeet(folded), folded.replace(/[^\\p{L}\\p{N}]/gu, \"\")];\n return words.some((word) => variants.some((variant) => variant.includes(word)));\n};\n\nconst scoreFor = (chars: string[], issues: PasswordIssue[], minLength: number): StrengthScore => {\n if (chars.length === 0) return 0;\n if (issues.length > 0) {\n const severe = issues.some((issue) =>\n [\"password.common\", \"password.blocked\", \"password.repetitive\", \"password.containsPersonalInfo\"].includes(issue),\n );\n return severe || chars.length < minLength / 2 ? 0 : 1;\n }\n let pool = 0;\n const text = chars.join(\"\");\n if (/\\p{Ll}/u.test(text)) pool += 26;\n if (/\\p{Lu}/u.test(text)) pool += 26;\n if (/\\p{Nd}/u.test(text)) pool += 10;\n if (/[\\p{P}\\p{S}\\p{Zs}]/u.test(text)) pool += 33;\n if (/[^\\p{Ll}\\p{Lu}\\p{Nd}\\p{P}\\p{S}\\p{Zs}]/u.test(text)) pool += 100;\n // Repeated characters add little; count each distinct one at most twice.\n const effective = Math.min(chars.length, new Set(chars).size * 2);\n const bits = effective * Math.log2(Math.max(pool, 2));\n return bits >= 100 ? 4 : bits >= 80 ? 3 : 2;\n};\n\nconst evaluateWith = (policy: Policy, input: unknown, options: EvaluateOptions = {}): StrengthResult => {\n const password = typeof input === \"string\" ? normalizePassword(input) : \"\";\n const chars = codePoints(password);\n const folded = fold(password);\n const { require } = policy;\n const issues: PasswordIssue[] = [];\n const checks: PasswordChecks = {\n minLength: chars.length >= policy.minLength,\n maxLength: chars.length <= policy.maxLength,\n notCommon: true,\n notBlocked: true,\n notRepetitive: true,\n notPersonal: true,\n };\n\n if (!checks.minLength) issues.push(\"password.tooShort\");\n if (!checks.maxLength) issues.push(\"password.tooLong\");\n\n if (require.lowercase) checks.lowercase = /\\p{Ll}/u.test(password);\n if (require.uppercase) checks.uppercase = /\\p{Lu}/u.test(password);\n if (require.digit) checks.digit = /\\p{Nd}/u.test(password);\n if (require.symbol) checks.symbol = /[^\\p{L}\\p{N}]/u.test(password);\n if (checks.lowercase === false) issues.push(\"password.noLowercase\");\n if (checks.uppercase === false) issues.push(\"password.noUppercase\");\n if (checks.digit === false) issues.push(\"password.noDigit\");\n if (checks.symbol === false) issues.push(\"password.noSymbol\");\n\n // Far past the limit the answer is \"too long\" anyway; skip the pattern checks on huge input.\n const huge = password.length > policy.maxLength * 4;\n if (chars.length >= 4 && !huge) {\n if (policy.blockCommon) {\n const list = (common ??= new Set(COMMON.split(\" \")));\n checks.notCommon = !commonCores(folded).some((core) => list.has(core));\n }\n checks.notBlocked = !containsAny(folded, policy.blocklist);\n if (policy.blockRepetitive) checks.notRepetitive = !isRepetitive(Array.from(folded), folded);\n }\n if (chars.length > 0 && options.forbidden && !huge) {\n checks.notPersonal = !containsAny(folded, personalTokens(options.forbidden, policy.minTokenLength));\n }\n\n if (!checks.notCommon) issues.push(\"password.common\");\n if (!checks.notBlocked) issues.push(\"password.blocked\");\n if (!checks.notRepetitive) issues.push(\"password.repetitive\");\n if (!checks.notPersonal) issues.push(\"password.containsPersonalInfo\");\n\n const score = scoreFor(chars, issues, policy.minLength);\n return { score, level: LEVELS[score], length: chars.length, valid: issues.length === 0, checks, issues };\n};\n\nconst PRESETS = {\n nist: { minLength: 15, maxLength: 64, require: {} },\n legacy: { minLength: 8, maxLength: 64, require: { lowercase: true, uppercase: true, digit: true, symbol: true } },\n} as const;\n\nconst assertLength = (name: string, value: number) => {\n if (!Number.isInteger(value) || value < 1) throw new RangeError(`definePolicy: ${name} must be a positive integer, got ${value}`);\n};\n\n/** A password policy. Define it once in a shared module and use it in the form and on the server. */\nexport const definePolicy = (options: PolicyOptions = {}): Policy => {\n const preset = options.preset ?? \"nist\";\n const base = PRESETS[preset];\n if (!base) throw new RangeError(`definePolicy: unknown preset \"${String(preset)}\"`);\n const minLength = options.minLength ?? base.minLength;\n const maxLength = options.maxLength ?? base.maxLength;\n const minTokenLength = options.minTokenLength ?? 3;\n assertLength(\"minLength\", minLength);\n assertLength(\"maxLength\", maxLength);\n assertLength(\"minTokenLength\", minTokenLength);\n if (minLength > maxLength) throw new RangeError(`definePolicy: minLength (${minLength}) is greater than maxLength (${maxLength})`);\n const req = { ...base.require, ...options.require };\n const require = {\n lowercase: req.lowercase === true,\n uppercase: req.uppercase === true,\n digit: req.digit === true,\n symbol: req.symbol === true,\n };\n const classes = Object.values(require).filter(Boolean).length;\n if (classes > maxLength) throw new RangeError(`definePolicy: ${classes} required character classes do not fit in maxLength (${maxLength})`);\n\n const policy: Policy = Object.freeze({\n preset,\n minLength,\n maxLength,\n require: Object.freeze(require),\n blockCommon: options.blockCommon ?? true,\n blockRepetitive: options.blockRepetitive ?? true,\n blocklist: Object.freeze((options.blocklist ?? []).map((word) => fold(word).replace(/[^\\p{L}\\p{N}]/gu, \"\")).filter(Boolean)),\n minTokenLength,\n evaluate: (password: string, evaluateOptions?: EvaluateOptions) => evaluateWith(policy, password, evaluateOptions),\n });\n return policy;\n};\n\n/** Scores a password against a policy. Pure; no UI. */\nexport const evaluate = (password: string, policy: Policy, options?: EvaluateOptions): StrengthResult =>\n policy.evaluate(password, options);\n"],"mappings":"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","names":["require"]}
@@ -0,0 +1,77 @@
1
+ /** Error keys. Map them to messages in your own i18n. */
2
+ type PasswordIssue = "password.tooShort" | "password.tooLong" | "password.noLowercase" | "password.noUppercase" | "password.noDigit" | "password.noSymbol" | "password.common" | "password.blocked" | "password.repetitive" | "password.containsPersonalInfo" | "password.mismatch";
3
+ type StrengthScore = 0 | 1 | 2 | 3 | 4;
4
+ type StrengthLevel = "weak" | "fair" | "good" | "strong";
5
+ interface PasswordChecks {
6
+ minLength: boolean;
7
+ maxLength: boolean;
8
+ notCommon: boolean;
9
+ notBlocked: boolean;
10
+ notRepetitive: boolean;
11
+ notPersonal: boolean;
12
+ /** Only present when the policy requires the character class. */
13
+ lowercase?: boolean;
14
+ uppercase?: boolean;
15
+ digit?: boolean;
16
+ symbol?: boolean;
17
+ }
18
+ interface StrengthResult {
19
+ /** 0–1 fail the policy, 2–4 pass it. */
20
+ score: StrengthScore;
21
+ level: StrengthLevel;
22
+ /** Characters (Unicode code points) after NFKC normalization, without zero-width characters. */
23
+ length: number;
24
+ /** No issues. */
25
+ valid: boolean;
26
+ checks: PasswordChecks;
27
+ issues: PasswordIssue[];
28
+ }
29
+ interface EvaluateOptions {
30
+ /** Values the password must not contain, e.g. the user's email and name. Non-strings are ignored. */
31
+ forbidden?: readonly unknown[];
32
+ }
33
+ interface CharacterRequirements {
34
+ lowercase?: boolean;
35
+ uppercase?: boolean;
36
+ digit?: boolean;
37
+ symbol?: boolean;
38
+ }
39
+ type PolicyPreset = "nist" | "legacy";
40
+ interface PolicyOptions {
41
+ /** `nist` (default): 15–64 characters, no composition rules. `legacy`: 8–64 characters, all four classes. */
42
+ preset?: PolicyPreset;
43
+ minLength?: number;
44
+ maxLength?: number;
45
+ /** Composition rules. Not recommended by NIST SP 800-63B-4. */
46
+ require?: CharacterRequirements;
47
+ /** Reject common passwords from the built-in list. @default true */
48
+ blockCommon?: boolean;
49
+ /** Reject repeated characters and sequences like `aaaa…` or `12345…`. @default true */
50
+ blockRepetitive?: boolean;
51
+ /** Words the password must not contain, e.g. your product name. Case and accents are ignored. */
52
+ blocklist?: readonly string[];
53
+ /** Parts of `forbidden` values shorter than this are ignored. @default 3 */
54
+ minTokenLength?: number;
55
+ }
56
+ interface Policy {
57
+ readonly preset: PolicyPreset;
58
+ readonly minLength: number;
59
+ readonly maxLength: number;
60
+ readonly require: Readonly<Required<CharacterRequirements>>;
61
+ readonly blockCommon: boolean;
62
+ readonly blockRepetitive: boolean;
63
+ readonly blocklist: readonly string[];
64
+ readonly minTokenLength: number;
65
+ /** The engine travels with the policy, so the hook does not import it. */
66
+ readonly evaluate: (password: string, options?: EvaluateOptions) => StrengthResult;
67
+ }
68
+
69
+ /** NFKC, so visually identical input from different keyboards compares and hashes the same. */
70
+ declare const normalizePassword: (password: string) => string;
71
+ /** A password policy. Define it once in a shared module and use it in the form and on the server. */
72
+ declare const definePolicy: (options?: PolicyOptions) => Policy;
73
+ /** Scores a password against a policy. Pure; no UI. */
74
+ declare const evaluate: (password: string, policy: Policy, options?: EvaluateOptions) => StrengthResult;
75
+
76
+ export { definePolicy, evaluate, normalizePassword };
77
+ export type { CharacterRequirements, EvaluateOptions, PasswordChecks, PasswordIssue, Policy, PolicyOptions, PolicyPreset, StrengthLevel, StrengthResult, StrengthScore };
@@ -0,0 +1,77 @@
1
+ /** Error keys. Map them to messages in your own i18n. */
2
+ type PasswordIssue = "password.tooShort" | "password.tooLong" | "password.noLowercase" | "password.noUppercase" | "password.noDigit" | "password.noSymbol" | "password.common" | "password.blocked" | "password.repetitive" | "password.containsPersonalInfo" | "password.mismatch";
3
+ type StrengthScore = 0 | 1 | 2 | 3 | 4;
4
+ type StrengthLevel = "weak" | "fair" | "good" | "strong";
5
+ interface PasswordChecks {
6
+ minLength: boolean;
7
+ maxLength: boolean;
8
+ notCommon: boolean;
9
+ notBlocked: boolean;
10
+ notRepetitive: boolean;
11
+ notPersonal: boolean;
12
+ /** Only present when the policy requires the character class. */
13
+ lowercase?: boolean;
14
+ uppercase?: boolean;
15
+ digit?: boolean;
16
+ symbol?: boolean;
17
+ }
18
+ interface StrengthResult {
19
+ /** 0–1 fail the policy, 2–4 pass it. */
20
+ score: StrengthScore;
21
+ level: StrengthLevel;
22
+ /** Characters (Unicode code points) after NFKC normalization, without zero-width characters. */
23
+ length: number;
24
+ /** No issues. */
25
+ valid: boolean;
26
+ checks: PasswordChecks;
27
+ issues: PasswordIssue[];
28
+ }
29
+ interface EvaluateOptions {
30
+ /** Values the password must not contain, e.g. the user's email and name. Non-strings are ignored. */
31
+ forbidden?: readonly unknown[];
32
+ }
33
+ interface CharacterRequirements {
34
+ lowercase?: boolean;
35
+ uppercase?: boolean;
36
+ digit?: boolean;
37
+ symbol?: boolean;
38
+ }
39
+ type PolicyPreset = "nist" | "legacy";
40
+ interface PolicyOptions {
41
+ /** `nist` (default): 15–64 characters, no composition rules. `legacy`: 8–64 characters, all four classes. */
42
+ preset?: PolicyPreset;
43
+ minLength?: number;
44
+ maxLength?: number;
45
+ /** Composition rules. Not recommended by NIST SP 800-63B-4. */
46
+ require?: CharacterRequirements;
47
+ /** Reject common passwords from the built-in list. @default true */
48
+ blockCommon?: boolean;
49
+ /** Reject repeated characters and sequences like `aaaa…` or `12345…`. @default true */
50
+ blockRepetitive?: boolean;
51
+ /** Words the password must not contain, e.g. your product name. Case and accents are ignored. */
52
+ blocklist?: readonly string[];
53
+ /** Parts of `forbidden` values shorter than this are ignored. @default 3 */
54
+ minTokenLength?: number;
55
+ }
56
+ interface Policy {
57
+ readonly preset: PolicyPreset;
58
+ readonly minLength: number;
59
+ readonly maxLength: number;
60
+ readonly require: Readonly<Required<CharacterRequirements>>;
61
+ readonly blockCommon: boolean;
62
+ readonly blockRepetitive: boolean;
63
+ readonly blocklist: readonly string[];
64
+ readonly minTokenLength: number;
65
+ /** The engine travels with the policy, so the hook does not import it. */
66
+ readonly evaluate: (password: string, options?: EvaluateOptions) => StrengthResult;
67
+ }
68
+
69
+ /** NFKC, so visually identical input from different keyboards compares and hashes the same. */
70
+ declare const normalizePassword: (password: string) => string;
71
+ /** A password policy. Define it once in a shared module and use it in the form and on the server. */
72
+ declare const definePolicy: (options?: PolicyOptions) => Policy;
73
+ /** Scores a password against a policy. Pure; no UI. */
74
+ declare const evaluate: (password: string, policy: Policy, options?: EvaluateOptions) => StrengthResult;
75
+
76
+ export { definePolicy, evaluate, normalizePassword };
77
+ export type { CharacterRequirements, EvaluateOptions, PasswordChecks, PasswordIssue, Policy, PolicyOptions, PolicyPreset, StrengthLevel, StrengthResult, StrengthScore };
@@ -0,0 +1,180 @@
1
+ // src/strength/common.ts
2
+ var COMMON = "password passwort parola sifre sifrem sifreniz parolam qwerty qwertz azerty qwertyuiop asdfgh asdfghjkl zxcvbn zxcvbnm qazwsx qwaszx wsxedc abc abcdef letmein welcome welcom admin administrator root user guest login test tester demo default changeme secret private iloveyou loveyou lovely love monkey dragon master shadow sunshine princess football baseball soccer basketball hockey superman batman spiderman starwars pokemon minecraft trustno whatever freedom flower hello hellothere cookie cheese chocolate summer winter autumn spring michael jessica jennifer charlie thomas jordan hunter ranger buster tigger ginger pepper matrix computer internet google facebook instagram twitter yahoo hotmail gmail apple samsung iphone android microsoft windows linux ubuntu killer access mustang harley ferrari porsche mercedes yankees liverpool chelsea arsenal barcelona realmadrid juventus galatasaray fenerbahce besiktas trabzonspor bursaspor cimbom istanbul ankara izmir antalya bursa turkiye turkey ataturk allah bismillah askim sevgilim bitanem canim hayatim prenses kelebek melek gunes yildiz deniz mehmet ahmet mustafa ali ayse fatma emine hatice zeynep elif omer murat emre burak huseyin hasan ibrahim yusuf merhaba selam kardesim annem babam qwertyasdfgh correcthorsebatterystaple passphrase mypassword newpassword temppassword passwordpassword nopassword sifreyok";
3
+
4
+ // src/strength/core.ts
5
+ var ZERO_WIDTH = /[\u200B-\u200D\u2060\uFEFF]/g;
6
+ var LEET = { "0": "o", "1": "i", "3": "e", "4": "a", "5": "s", "7": "t", "8": "b", "@": "a", "$": "s", "!": "i", "+": "t" };
7
+ var SEQUENCES = [
8
+ "abcdefghijklmnopqrstuvwxyz",
9
+ "0123456789",
10
+ "qwertyuiopasdfghjklzxcvbnm",
11
+ "qwertzuiopasdfghjklyxcvbnm",
12
+ "azertyuiopqsdfghjklmwxcvbn",
13
+ // Turkish Q layout, folded: ı→i, ğ→g, ü→u, ş→s, ö→o, ç→c
14
+ "qwertyuiopguasdfghjklsizxcvbnmoc",
15
+ "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p"
16
+ ];
17
+ var LEVELS = ["weak", "weak", "fair", "good", "strong"];
18
+ var common;
19
+ var normalizePassword = (password) => password.normalize("NFKC");
20
+ var codePoints = (value) => Array.from(value.replace(ZERO_WIDTH, ""));
21
+ var fold = (value) => value.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/\u0131/g, "i").replace(ZERO_WIDTH, "");
22
+ var deLeet = (value) => value.replace(/[0-9@$!+]/g, (c) => LEET[c] ?? c);
23
+ var period = (value) => {
24
+ const n = value.length;
25
+ const lps = new Array(n).fill(0);
26
+ for (let i = 1, k = 0; i < n; i++) {
27
+ while (k > 0 && value[i] !== value[k]) k = lps[k - 1];
28
+ if (value[i] === value[k]) k++;
29
+ lps[i] = k;
30
+ }
31
+ return n - (lps[n - 1] ?? 0);
32
+ };
33
+ var unit = (value) => {
34
+ const chars = Array.from(value);
35
+ return chars.slice(0, period(chars)).join("");
36
+ };
37
+ var commonCores = (folded) => {
38
+ const trimmed = folded.replace(/^[^\p{L}]+|[^\p{L}]+$/gu, "");
39
+ return [deLeet(trimmed), trimmed].map((core) => unit(core.replace(/[^\p{L}]/gu, "")));
40
+ };
41
+ var isSequence = (folded) => {
42
+ if (folded.length < 3) return false;
43
+ return SEQUENCES.some((sequence) => {
44
+ const forward = sequence.repeat(Math.ceil(folded.length / sequence.length) + 1);
45
+ const backward = Array.from(forward).reverse().join("");
46
+ return forward.includes(folded) || backward.includes(folded);
47
+ });
48
+ };
49
+ var isRepetitive = (chars, folded) => {
50
+ if (new Set(chars).size < Math.min(4, Math.ceil(chars.length / 2))) return true;
51
+ if (period(chars) <= chars.length / 3) return true;
52
+ return isSequence(folded);
53
+ };
54
+ var personalTokens = (forbidden, minTokenLength) => {
55
+ const tokens = /* @__PURE__ */ new Set();
56
+ for (const value of forbidden) {
57
+ if (typeof value !== "string") continue;
58
+ const at = value.lastIndexOf("@");
59
+ const source = fold(at > 0 ? value.slice(0, at) : value).trim();
60
+ const parts = [source, ...source.split(/[^\p{L}\p{N}]+|(?<=\p{L})(?=\p{N})|(?<=\p{N})(?=\p{L})/u)];
61
+ for (const part of parts) {
62
+ if (Array.from(part).length >= minTokenLength) tokens.add(part);
63
+ }
64
+ }
65
+ return [...tokens];
66
+ };
67
+ var containsAny = (folded, words) => {
68
+ if (words.length === 0) return false;
69
+ const variants = [folded, deLeet(folded), folded.replace(/[^\p{L}\p{N}]/gu, "")];
70
+ return words.some((word) => variants.some((variant) => variant.includes(word)));
71
+ };
72
+ var scoreFor = (chars, issues, minLength) => {
73
+ if (chars.length === 0) return 0;
74
+ if (issues.length > 0) {
75
+ const severe = issues.some(
76
+ (issue) => ["password.common", "password.blocked", "password.repetitive", "password.containsPersonalInfo"].includes(issue)
77
+ );
78
+ return severe || chars.length < minLength / 2 ? 0 : 1;
79
+ }
80
+ let pool = 0;
81
+ const text = chars.join("");
82
+ if (/\p{Ll}/u.test(text)) pool += 26;
83
+ if (/\p{Lu}/u.test(text)) pool += 26;
84
+ if (/\p{Nd}/u.test(text)) pool += 10;
85
+ if (/[\p{P}\p{S}\p{Zs}]/u.test(text)) pool += 33;
86
+ if (/[^\p{Ll}\p{Lu}\p{Nd}\p{P}\p{S}\p{Zs}]/u.test(text)) pool += 100;
87
+ const effective = Math.min(chars.length, new Set(chars).size * 2);
88
+ const bits = effective * Math.log2(Math.max(pool, 2));
89
+ return bits >= 100 ? 4 : bits >= 80 ? 3 : 2;
90
+ };
91
+ var evaluateWith = (policy, input, options = {}) => {
92
+ const password = typeof input === "string" ? normalizePassword(input) : "";
93
+ const chars = codePoints(password);
94
+ const folded = fold(password);
95
+ const { require: require2 } = policy;
96
+ const issues = [];
97
+ const checks = {
98
+ minLength: chars.length >= policy.minLength,
99
+ maxLength: chars.length <= policy.maxLength,
100
+ notCommon: true,
101
+ notBlocked: true,
102
+ notRepetitive: true,
103
+ notPersonal: true
104
+ };
105
+ if (!checks.minLength) issues.push("password.tooShort");
106
+ if (!checks.maxLength) issues.push("password.tooLong");
107
+ if (require2.lowercase) checks.lowercase = /\p{Ll}/u.test(password);
108
+ if (require2.uppercase) checks.uppercase = /\p{Lu}/u.test(password);
109
+ if (require2.digit) checks.digit = /\p{Nd}/u.test(password);
110
+ if (require2.symbol) checks.symbol = /[^\p{L}\p{N}]/u.test(password);
111
+ if (checks.lowercase === false) issues.push("password.noLowercase");
112
+ if (checks.uppercase === false) issues.push("password.noUppercase");
113
+ if (checks.digit === false) issues.push("password.noDigit");
114
+ if (checks.symbol === false) issues.push("password.noSymbol");
115
+ const huge = password.length > policy.maxLength * 4;
116
+ if (chars.length >= 4 && !huge) {
117
+ if (policy.blockCommon) {
118
+ const list = common ??= new Set(COMMON.split(" "));
119
+ checks.notCommon = !commonCores(folded).some((core) => list.has(core));
120
+ }
121
+ checks.notBlocked = !containsAny(folded, policy.blocklist);
122
+ if (policy.blockRepetitive) checks.notRepetitive = !isRepetitive(Array.from(folded), folded);
123
+ }
124
+ if (chars.length > 0 && options.forbidden && !huge) {
125
+ checks.notPersonal = !containsAny(folded, personalTokens(options.forbidden, policy.minTokenLength));
126
+ }
127
+ if (!checks.notCommon) issues.push("password.common");
128
+ if (!checks.notBlocked) issues.push("password.blocked");
129
+ if (!checks.notRepetitive) issues.push("password.repetitive");
130
+ if (!checks.notPersonal) issues.push("password.containsPersonalInfo");
131
+ const score = scoreFor(chars, issues, policy.minLength);
132
+ return { score, level: LEVELS[score], length: chars.length, valid: issues.length === 0, checks, issues };
133
+ };
134
+ var PRESETS = {
135
+ nist: { minLength: 15, maxLength: 64, require: {} },
136
+ legacy: { minLength: 8, maxLength: 64, require: { lowercase: true, uppercase: true, digit: true, symbol: true } }
137
+ };
138
+ var assertLength = (name, value) => {
139
+ if (!Number.isInteger(value) || value < 1) throw new RangeError(`definePolicy: ${name} must be a positive integer, got ${value}`);
140
+ };
141
+ var definePolicy = (options = {}) => {
142
+ const preset = options.preset ?? "nist";
143
+ const base = PRESETS[preset];
144
+ if (!base) throw new RangeError(`definePolicy: unknown preset "${String(preset)}"`);
145
+ const minLength = options.minLength ?? base.minLength;
146
+ const maxLength = options.maxLength ?? base.maxLength;
147
+ const minTokenLength = options.minTokenLength ?? 3;
148
+ assertLength("minLength", minLength);
149
+ assertLength("maxLength", maxLength);
150
+ assertLength("minTokenLength", minTokenLength);
151
+ if (minLength > maxLength) throw new RangeError(`definePolicy: minLength (${minLength}) is greater than maxLength (${maxLength})`);
152
+ const req = { ...base.require, ...options.require };
153
+ const require2 = {
154
+ lowercase: req.lowercase === true,
155
+ uppercase: req.uppercase === true,
156
+ digit: req.digit === true,
157
+ symbol: req.symbol === true
158
+ };
159
+ const classes = Object.values(require2).filter(Boolean).length;
160
+ if (classes > maxLength) throw new RangeError(`definePolicy: ${classes} required character classes do not fit in maxLength (${maxLength})`);
161
+ const policy = Object.freeze({
162
+ preset,
163
+ minLength,
164
+ maxLength,
165
+ require: Object.freeze(require2),
166
+ blockCommon: options.blockCommon ?? true,
167
+ blockRepetitive: options.blockRepetitive ?? true,
168
+ blocklist: Object.freeze((options.blocklist ?? []).map((word) => fold(word).replace(/[^\p{L}\p{N}]/gu, "")).filter(Boolean)),
169
+ minTokenLength,
170
+ evaluate: (password, evaluateOptions) => evaluateWith(policy, password, evaluateOptions)
171
+ });
172
+ return policy;
173
+ };
174
+ var evaluate = (password, policy, options) => policy.evaluate(password, options);
175
+ export {
176
+ definePolicy,
177
+ evaluate,
178
+ normalizePassword
179
+ };
180
+ //# sourceMappingURL=strength.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strength/common.ts","../src/strength/core.ts"],"sourcesContent":["// Cores of common passwords: lowercase letters only, as `commonCore()` reduces them\n// (\"P@ssw0rd123!\" → \"password\"). Includes Turkish favourites. Digits-only and keyboard runs\n// are caught by the repetition and sequence checks instead.\nexport const COMMON =\n \"password passwort parola sifre sifrem sifreniz parolam qwerty qwertz azerty qwertyuiop asdfgh asdfghjkl \" +\n \"zxcvbn zxcvbnm qazwsx qwaszx wsxedc abc abcdef letmein welcome welcom admin administrator root user guest \" +\n \"login test tester demo default changeme secret private iloveyou loveyou lovely love monkey dragon master \" +\n \"shadow sunshine princess football baseball soccer basketball hockey superman batman spiderman starwars \" +\n \"pokemon minecraft trustno whatever freedom flower hello hellothere cookie cheese chocolate summer winter \" +\n \"autumn spring michael jessica jennifer charlie thomas jordan hunter ranger buster tigger ginger pepper \" +\n \"matrix computer internet google facebook instagram twitter yahoo hotmail gmail apple samsung iphone \" +\n \"android microsoft windows linux ubuntu killer access mustang harley ferrari porsche mercedes yankees \" +\n \"liverpool chelsea arsenal barcelona realmadrid juventus galatasaray fenerbahce besiktas trabzonspor \" +\n \"bursaspor cimbom istanbul ankara izmir antalya bursa turkiye turkey ataturk allah bismillah askim \" +\n \"sevgilim bitanem canim hayatim prenses kelebek melek gunes yildiz deniz mehmet ahmet mustafa ali \" +\n \"ayse fatma emine hatice zeynep elif omer murat emre burak huseyin hasan ibrahim yusuf merhaba \" +\n \"selam kardesim annem babam qwertyasdfgh correcthorsebatterystaple passphrase mypassword newpassword \" +\n \"temppassword passwordpassword nopassword sifreyok\";\n","import { COMMON } from \"./common\";\nimport type {\n EvaluateOptions,\n PasswordChecks,\n PasswordIssue,\n Policy,\n PolicyOptions,\n StrengthLevel,\n StrengthResult,\n StrengthScore,\n} from \"./types\";\n\nconst ZERO_WIDTH = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst LEET: Record<string, string> = { \"0\": \"o\", \"1\": \"i\", \"3\": \"e\", \"4\": \"a\", \"5\": \"s\", \"7\": \"t\", \"8\": \"b\", \"@\": \"a\", \"$\": \"s\", \"!\": \"i\", \"+\": \"t\" };\n// Rows and runs that read as a sequence when typed, forwards or backwards.\nconst SEQUENCES = [\n \"abcdefghijklmnopqrstuvwxyz\",\n \"0123456789\",\n \"qwertyuiopasdfghjklzxcvbnm\",\n \"qwertzuiopasdfghjklyxcvbnm\",\n \"azertyuiopqsdfghjklmwxcvbn\",\n // Turkish Q layout, folded: ı→i, ğ→g, ü→u, ş→s, ö→o, ç→c\n \"qwertyuiopguasdfghjklsizxcvbnmoc\",\n \"1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p\",\n];\nconst LEVELS: readonly StrengthLevel[] = [\"weak\", \"weak\", \"fair\", \"good\", \"strong\"];\n\nlet common: Set<string> | undefined;\n\n/** NFKC, so visually identical input from different keyboards compares and hashes the same. */\nexport const normalizePassword = (password: string): string => password.normalize(\"NFKC\");\n\nconst codePoints = (value: string): string[] => Array.from(value.replace(ZERO_WIDTH, \"\"));\n\n/** Lowercase without accents, for comparisons only: \"Şifre\" and \"sifre\" match, so do \"İ\" and \"i\". */\nconst fold = (value: string): string =>\n value.normalize(\"NFKD\").replace(/\\p{M}/gu, \"\").toLowerCase().replace(/\\u0131/g, \"i\").replace(ZERO_WIDTH, \"\");\n\nconst deLeet = (value: string): string => value.replace(/[0-9@$!+]/g, (c) => LEET[c] ?? c);\n\n/** Length of the shortest unit the string repeats, e.g. 3 for \"abcabcab\" (the last copy may be cut short). */\nconst period = (value: string[]): number => {\n const n = value.length;\n const lps = new Array<number>(n).fill(0);\n for (let i = 1, k = 0; i < n; i++) {\n while (k > 0 && value[i] !== value[k]) k = lps[k - 1];\n if (value[i] === value[k]) k++;\n lps[i] = k;\n }\n return n - (lps[n - 1] ?? 0);\n};\n\nconst unit = (value: string): string => {\n const chars = Array.from(value);\n return chars.slice(0, period(chars)).join(\"\");\n};\n\n/** \"P@ssw0rd2024!\" → \"password\"; \"summer2024summer\" → \"summer\". Digits inside are leetspeak or separators. */\nconst commonCores = (folded: string): string[] => {\n const trimmed = folded.replace(/^[^\\p{L}]+|[^\\p{L}]+$/gu, \"\");\n return [deLeet(trimmed), trimmed].map((core) => unit(core.replace(/[^\\p{L}]/gu, \"\")));\n};\n\nconst isSequence = (folded: string): boolean => {\n if (folded.length < 3) return false;\n return SEQUENCES.some((sequence) => {\n const forward = sequence.repeat(Math.ceil(folded.length / sequence.length) + 1);\n const backward = Array.from(forward).reverse().join(\"\");\n return forward.includes(folded) || backward.includes(folded);\n });\n};\n\nconst isRepetitive = (chars: string[], folded: string): boolean => {\n // Fewer than 4 distinct characters; short inputs need half of theirs.\n if (new Set(chars).size < Math.min(4, Math.ceil(chars.length / 2))) return true;\n if (period(chars) <= chars.length / 3) return true;\n return isSequence(folded);\n};\n\n/** Splits emails (local part only), names and usernames into lowercase parts. */\nexport const personalTokens = (forbidden: readonly unknown[], minTokenLength: number): string[] => {\n const tokens = new Set<string>();\n for (const value of forbidden) {\n if (typeof value !== \"string\") continue;\n const at = value.lastIndexOf(\"@\");\n const source = fold(at > 0 ? value.slice(0, at) : value).trim();\n const parts = [source, ...source.split(/[^\\p{L}\\p{N}]+|(?<=\\p{L})(?=\\p{N})|(?<=\\p{N})(?=\\p{L})/u)];\n for (const part of parts) {\n if (Array.from(part).length >= minTokenLength) tokens.add(part);\n }\n }\n return [...tokens];\n};\n\nconst containsAny = (folded: string, words: readonly string[]): boolean => {\n if (words.length === 0) return false;\n const variants = [folded, deLeet(folded), folded.replace(/[^\\p{L}\\p{N}]/gu, \"\")];\n return words.some((word) => variants.some((variant) => variant.includes(word)));\n};\n\nconst scoreFor = (chars: string[], issues: PasswordIssue[], minLength: number): StrengthScore => {\n if (chars.length === 0) return 0;\n if (issues.length > 0) {\n const severe = issues.some((issue) =>\n [\"password.common\", \"password.blocked\", \"password.repetitive\", \"password.containsPersonalInfo\"].includes(issue),\n );\n return severe || chars.length < minLength / 2 ? 0 : 1;\n }\n let pool = 0;\n const text = chars.join(\"\");\n if (/\\p{Ll}/u.test(text)) pool += 26;\n if (/\\p{Lu}/u.test(text)) pool += 26;\n if (/\\p{Nd}/u.test(text)) pool += 10;\n if (/[\\p{P}\\p{S}\\p{Zs}]/u.test(text)) pool += 33;\n if (/[^\\p{Ll}\\p{Lu}\\p{Nd}\\p{P}\\p{S}\\p{Zs}]/u.test(text)) pool += 100;\n // Repeated characters add little; count each distinct one at most twice.\n const effective = Math.min(chars.length, new Set(chars).size * 2);\n const bits = effective * Math.log2(Math.max(pool, 2));\n return bits >= 100 ? 4 : bits >= 80 ? 3 : 2;\n};\n\nconst evaluateWith = (policy: Policy, input: unknown, options: EvaluateOptions = {}): StrengthResult => {\n const password = typeof input === \"string\" ? normalizePassword(input) : \"\";\n const chars = codePoints(password);\n const folded = fold(password);\n const { require } = policy;\n const issues: PasswordIssue[] = [];\n const checks: PasswordChecks = {\n minLength: chars.length >= policy.minLength,\n maxLength: chars.length <= policy.maxLength,\n notCommon: true,\n notBlocked: true,\n notRepetitive: true,\n notPersonal: true,\n };\n\n if (!checks.minLength) issues.push(\"password.tooShort\");\n if (!checks.maxLength) issues.push(\"password.tooLong\");\n\n if (require.lowercase) checks.lowercase = /\\p{Ll}/u.test(password);\n if (require.uppercase) checks.uppercase = /\\p{Lu}/u.test(password);\n if (require.digit) checks.digit = /\\p{Nd}/u.test(password);\n if (require.symbol) checks.symbol = /[^\\p{L}\\p{N}]/u.test(password);\n if (checks.lowercase === false) issues.push(\"password.noLowercase\");\n if (checks.uppercase === false) issues.push(\"password.noUppercase\");\n if (checks.digit === false) issues.push(\"password.noDigit\");\n if (checks.symbol === false) issues.push(\"password.noSymbol\");\n\n // Far past the limit the answer is \"too long\" anyway; skip the pattern checks on huge input.\n const huge = password.length > policy.maxLength * 4;\n if (chars.length >= 4 && !huge) {\n if (policy.blockCommon) {\n const list = (common ??= new Set(COMMON.split(\" \")));\n checks.notCommon = !commonCores(folded).some((core) => list.has(core));\n }\n checks.notBlocked = !containsAny(folded, policy.blocklist);\n if (policy.blockRepetitive) checks.notRepetitive = !isRepetitive(Array.from(folded), folded);\n }\n if (chars.length > 0 && options.forbidden && !huge) {\n checks.notPersonal = !containsAny(folded, personalTokens(options.forbidden, policy.minTokenLength));\n }\n\n if (!checks.notCommon) issues.push(\"password.common\");\n if (!checks.notBlocked) issues.push(\"password.blocked\");\n if (!checks.notRepetitive) issues.push(\"password.repetitive\");\n if (!checks.notPersonal) issues.push(\"password.containsPersonalInfo\");\n\n const score = scoreFor(chars, issues, policy.minLength);\n return { score, level: LEVELS[score], length: chars.length, valid: issues.length === 0, checks, issues };\n};\n\nconst PRESETS = {\n nist: { minLength: 15, maxLength: 64, require: {} },\n legacy: { minLength: 8, maxLength: 64, require: { lowercase: true, uppercase: true, digit: true, symbol: true } },\n} as const;\n\nconst assertLength = (name: string, value: number) => {\n if (!Number.isInteger(value) || value < 1) throw new RangeError(`definePolicy: ${name} must be a positive integer, got ${value}`);\n};\n\n/** A password policy. Define it once in a shared module and use it in the form and on the server. */\nexport const definePolicy = (options: PolicyOptions = {}): Policy => {\n const preset = options.preset ?? \"nist\";\n const base = PRESETS[preset];\n if (!base) throw new RangeError(`definePolicy: unknown preset \"${String(preset)}\"`);\n const minLength = options.minLength ?? base.minLength;\n const maxLength = options.maxLength ?? base.maxLength;\n const minTokenLength = options.minTokenLength ?? 3;\n assertLength(\"minLength\", minLength);\n assertLength(\"maxLength\", maxLength);\n assertLength(\"minTokenLength\", minTokenLength);\n if (minLength > maxLength) throw new RangeError(`definePolicy: minLength (${minLength}) is greater than maxLength (${maxLength})`);\n const req = { ...base.require, ...options.require };\n const require = {\n lowercase: req.lowercase === true,\n uppercase: req.uppercase === true,\n digit: req.digit === true,\n symbol: req.symbol === true,\n };\n const classes = Object.values(require).filter(Boolean).length;\n if (classes > maxLength) throw new RangeError(`definePolicy: ${classes} required character classes do not fit in maxLength (${maxLength})`);\n\n const policy: Policy = Object.freeze({\n preset,\n minLength,\n maxLength,\n require: Object.freeze(require),\n blockCommon: options.blockCommon ?? true,\n blockRepetitive: options.blockRepetitive ?? true,\n blocklist: Object.freeze((options.blocklist ?? []).map((word) => fold(word).replace(/[^\\p{L}\\p{N}]/gu, \"\")).filter(Boolean)),\n minTokenLength,\n evaluate: (password: string, evaluateOptions?: EvaluateOptions) => evaluateWith(policy, password, evaluateOptions),\n });\n return policy;\n};\n\n/** Scores a password against a policy. Pure; no UI. */\nexport const evaluate = (password: string, policy: Policy, options?: EvaluateOptions): StrengthResult =>\n policy.evaluate(password, options);\n"],"mappings":"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","names":["require"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@virastack/password",
3
- "version": "1.0.2",
4
- "description": "Fully accessible and customizable password visibility hook for React.",
3
+ "version": "1.1.0",
4
+ "description": "Accessible password hook for React: visibility, NIST-style strength, confirmation, server validation and leaked password checks.",
5
5
  "author": {
6
6
  "name": "Ömer Gülçiçek (ViraStack)",
7
7
  "email": "iletisim@omergulcicek.com",
@@ -16,15 +16,52 @@
16
16
  "bugs": {
17
17
  "url": "https://github.com/virastack/password/issues"
18
18
  },
19
- "main": "./dist/index.js",
19
+ "type": "commonjs",
20
+ "main": "./dist/index.cjs",
20
21
  "module": "./dist/index.mjs",
21
- "types": "./dist/index.d.ts",
22
+ "types": "./dist/index.d.cts",
22
23
  "exports": {
23
24
  ".": {
24
- "types": "./dist/index.d.ts",
25
- "import": "./dist/index.mjs",
26
- "require": "./dist/index.js"
27
- }
25
+ "import": {
26
+ "types": "./dist/index.d.mts",
27
+ "default": "./dist/index.mjs"
28
+ },
29
+ "require": {
30
+ "types": "./dist/index.d.cts",
31
+ "default": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "./strength": {
35
+ "import": {
36
+ "types": "./dist/strength.d.mts",
37
+ "default": "./dist/strength.mjs"
38
+ },
39
+ "require": {
40
+ "types": "./dist/strength.d.cts",
41
+ "default": "./dist/strength.cjs"
42
+ }
43
+ },
44
+ "./server": {
45
+ "import": {
46
+ "types": "./dist/server.d.mts",
47
+ "default": "./dist/server.mjs"
48
+ },
49
+ "require": {
50
+ "types": "./dist/server.d.cts",
51
+ "default": "./dist/server.cjs"
52
+ }
53
+ },
54
+ "./pwned": {
55
+ "import": {
56
+ "types": "./dist/pwned.d.mts",
57
+ "default": "./dist/pwned.mjs"
58
+ },
59
+ "require": {
60
+ "types": "./dist/pwned.d.cts",
61
+ "default": "./dist/pwned.cjs"
62
+ }
63
+ },
64
+ "./package.json": "./package.json"
28
65
  },
29
66
  "files": [
30
67
  "dist"
@@ -37,10 +74,19 @@
37
74
  "node": ">=18"
38
75
  },
39
76
  "scripts": {
40
- "build": "tsup",
77
+ "build": "tsup && node scripts/cjs-types.mjs",
41
78
  "dev": "tsup --watch",
42
- "test": "vitest",
43
- "lint": "tsc"
79
+ "test": "vitest run --project unit",
80
+ "test:watch": "vitest --project unit",
81
+ "test:coverage": "vitest run --project unit --coverage",
82
+ "test:browser": "vitest run --project browser",
83
+ "typecheck": "tsc -p .",
84
+ "lint": "eslint .",
85
+ "size": "size-limit",
86
+ "check:package": "publint --strict && attw --pack . --profile node16 && node scripts/check-dist.mjs",
87
+ "check": "pnpm typecheck && pnpm lint && pnpm test:coverage && pnpm build && pnpm check:package && pnpm size",
88
+ "prepublishOnly": "pnpm check",
89
+ "e2e:next": "node e2e/nextjs/run.mjs"
44
90
  },
45
91
  "keywords": [
46
92
  "virastack",
@@ -48,9 +94,16 @@
48
94
  "hook",
49
95
  "password",
50
96
  "password-visibility",
97
+ "password-strength",
98
+ "password-confirmation",
99
+ "nist",
100
+ "have-i-been-pwned",
101
+ "react-hook-form",
102
+ "server-actions",
51
103
  "accessibility",
52
104
  "a11y",
53
105
  "form-validation",
106
+ "typescript",
54
107
  "dx"
55
108
  ],
56
109
  "peerDependencies": {
@@ -58,15 +111,78 @@
58
111
  "react-dom": ">=18.2.0"
59
112
  },
60
113
  "devDependencies": {
61
- "@testing-library/jest-dom": "^6.4.2",
62
- "@testing-library/react": "^14.2.1",
63
- "@types/react": "^18.2.64",
64
- "@types/react-dom": "^18.2.21",
65
- "jsdom": "^24.0.0",
66
- "react": "^18.2.0",
67
- "react-dom": "^18.2.0",
68
- "tsup": "^8.0.2",
69
- "typescript": "^5.3.3",
70
- "vitest": "^1.3.1"
114
+ "@arethetypeswrong/cli": "^0.18.5",
115
+ "@eslint/js": "^10.0.1",
116
+ "@hookform/resolvers": "^6.0.0",
117
+ "@size-limit/preset-small-lib": "^14.3.0",
118
+ "@testing-library/jest-dom": "^7.0.1",
119
+ "@testing-library/react": "^16.3.3",
120
+ "@testing-library/user-event": "^14.6.8",
121
+ "@types/node": "^22.20.5",
122
+ "@types/react": "^19.3.0",
123
+ "@types/react-dom": "^19.3.0",
124
+ "@vitest/browser-playwright": "5.0.5",
125
+ "@vitest/coverage-v8": "5.0.5",
126
+ "eslint": "^10.13.0",
127
+ "fast-check": "^4.10.2",
128
+ "jsdom": "^30.1.3",
129
+ "playwright": "^1.64.1",
130
+ "publint": "^0.3.25",
131
+ "react": "^19.3.1",
132
+ "react-dom": "^19.3.1",
133
+ "react-hook-form": "^7.91.0",
134
+ "size-limit": "^14.3.0",
135
+ "tsup": "^9.0.0",
136
+ "typescript": "^6.0.3",
137
+ "typescript-eslint": "^8.72.0",
138
+ "vite": "^8.3.5",
139
+ "vitest": "^5.0.5",
140
+ "vitest-browser-react": "^2.3.0",
141
+ "zod": "^4.6.7"
142
+ },
143
+ "size-limit": [
144
+ {
145
+ "name": "@virastack/password",
146
+ "path": "dist/index.mjs",
147
+ "import": "*",
148
+ "limit": "2.5 kB",
149
+ "ignore": [
150
+ "react",
151
+ "react-dom"
152
+ ]
153
+ },
154
+ {
155
+ "name": "@virastack/password (toggle only)",
156
+ "path": "dist/index.mjs",
157
+ "import": "{ useViraPassword }",
158
+ "limit": "2.5 kB",
159
+ "ignore": [
160
+ "react",
161
+ "react-dom"
162
+ ]
163
+ },
164
+ {
165
+ "name": "@virastack/password/strength",
166
+ "path": "dist/strength.mjs",
167
+ "import": "*",
168
+ "limit": "3 kB"
169
+ },
170
+ {
171
+ "name": "@virastack/password/server",
172
+ "path": "dist/server.mjs",
173
+ "import": "*",
174
+ "limit": "3 kB"
175
+ },
176
+ {
177
+ "name": "@virastack/password/pwned",
178
+ "path": "dist/pwned.mjs",
179
+ "import": "*",
180
+ "limit": "1 kB"
181
+ }
182
+ ],
183
+ "pnpm": {
184
+ "onlyBuiltDependencies": [
185
+ "esbuild"
186
+ ]
71
187
  }
72
188
  }