typesecure 0.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/dist/index.mjs ADDED
@@ -0,0 +1,662 @@
1
+ // src/utils/hash.ts
2
+ import CryptoJS from "crypto-js";
3
+
4
+ // src/types/index.ts
5
+ import { z } from "zod";
6
+ var PasswordStrengthSchema = z.object({
7
+ score: z.number().min(0).max(4),
8
+ feedback: z.object({
9
+ warning: z.string().optional(),
10
+ suggestions: z.array(z.string()).optional()
11
+ }),
12
+ isStrong: z.boolean()
13
+ });
14
+ var HashOptionsSchema = z.object({
15
+ algorithm: z.enum(["md5", "sha1", "sha256", "sha512", "sha3"]).default("sha256"),
16
+ encoding: z.enum(["hex", "base64"]).default("hex")
17
+ });
18
+ var PasswordHashOptionsSchema = z.object({
19
+ algorithm: z.enum(["pbkdf2", "argon2", "bcrypt"]).default("pbkdf2"),
20
+ iterations: z.number().min(1e3).default(1e4),
21
+ saltLength: z.number().min(16).default(32),
22
+ keyLength: z.number().min(16).default(64)
23
+ });
24
+ var TimingSafeOptionsSchema = z.object({
25
+ encoding: z.enum(["utf8", "hex", "base64"]).default("utf8")
26
+ });
27
+ var EncryptionOptionsSchema = z.object({
28
+ mode: z.enum(["aes-cbc", "aes-ctr", "aes-ecb", "aes-gcm"]).default("aes-cbc"),
29
+ padding: z.enum(["Pkcs7", "NoPadding", "ZeroPadding"]).default("Pkcs7"),
30
+ iv: z.string().optional(),
31
+ authTag: z.string().optional(),
32
+ aad: z.string().optional()
33
+ // Additional authenticated data for GCM mode
34
+ });
35
+ var ConfigSchema = z.object({
36
+ minimumPasswordLength: z.number().min(8).default(12),
37
+ requireNumbers: z.boolean().default(true),
38
+ requireSymbols: z.boolean().default(true),
39
+ requireUppercase: z.boolean().default(true),
40
+ requireLowercase: z.boolean().default(true)
41
+ });
42
+
43
+ // src/utils/hash.ts
44
+ function hash(input, options) {
45
+ const validatedOptions = HashOptionsSchema.parse({
46
+ algorithm: options?.algorithm || "sha256",
47
+ encoding: options?.encoding || "hex"
48
+ });
49
+ let hashedValue;
50
+ switch (validatedOptions.algorithm) {
51
+ case "md5":
52
+ hashedValue = CryptoJS.MD5(input);
53
+ break;
54
+ case "sha1":
55
+ hashedValue = CryptoJS.SHA1(input);
56
+ break;
57
+ case "sha256":
58
+ hashedValue = CryptoJS.SHA256(input);
59
+ break;
60
+ case "sha512":
61
+ hashedValue = CryptoJS.SHA512(input);
62
+ break;
63
+ case "sha3":
64
+ hashedValue = CryptoJS.SHA3(input);
65
+ break;
66
+ default:
67
+ hashedValue = CryptoJS.SHA256(input);
68
+ }
69
+ return validatedOptions.encoding === "base64" ? hashedValue.toString(CryptoJS.enc.Base64) : hashedValue.toString(CryptoJS.enc.Hex);
70
+ }
71
+ function verifyHash(input, hashedValue, options) {
72
+ const newHash = hash(input, options);
73
+ return newHash === hashedValue;
74
+ }
75
+ function hmac(input, key, options) {
76
+ const validatedOptions = HashOptionsSchema.parse({
77
+ algorithm: options?.algorithm || "sha256",
78
+ encoding: options?.encoding || "hex"
79
+ });
80
+ let hmacValue;
81
+ switch (validatedOptions.algorithm) {
82
+ case "md5":
83
+ hmacValue = CryptoJS.HmacMD5(input, key);
84
+ break;
85
+ case "sha1":
86
+ hmacValue = CryptoJS.HmacSHA1(input, key);
87
+ break;
88
+ case "sha256":
89
+ hmacValue = CryptoJS.HmacSHA256(input, key);
90
+ break;
91
+ case "sha512":
92
+ hmacValue = CryptoJS.HmacSHA512(input, key);
93
+ break;
94
+ case "sha3":
95
+ hmacValue = CryptoJS.HmacSHA3(input, key);
96
+ break;
97
+ default:
98
+ hmacValue = CryptoJS.HmacSHA256(input, key);
99
+ }
100
+ return validatedOptions.encoding === "base64" ? hmacValue.toString(CryptoJS.enc.Base64) : hmacValue.toString(CryptoJS.enc.Hex);
101
+ }
102
+ function hashPassword(password, options) {
103
+ const validatedOptions = PasswordHashOptionsSchema.parse({
104
+ algorithm: options?.algorithm || "pbkdf2",
105
+ iterations: options?.iterations || 1e4,
106
+ saltLength: options?.saltLength || 32,
107
+ keyLength: options?.keyLength || 64
108
+ });
109
+ const salt = CryptoJS.lib.WordArray.random(validatedOptions.saltLength / 2).toString(
110
+ CryptoJS.enc.Hex
111
+ );
112
+ let hashedPassword;
113
+ switch (validatedOptions.algorithm) {
114
+ case "pbkdf2":
115
+ hashedPassword = CryptoJS.PBKDF2(password, salt, {
116
+ keySize: validatedOptions.keyLength / 4,
117
+ // keySize is in 32-bit words
118
+ iterations: validatedOptions.iterations
119
+ }).toString(CryptoJS.enc.Hex);
120
+ break;
121
+ case "argon2":
122
+ hashedPassword = CryptoJS.PBKDF2(password, salt, {
123
+ keySize: validatedOptions.keyLength / 4,
124
+ iterations: validatedOptions.iterations * 2
125
+ // Higher iterations to simulate Argon2 costs
126
+ }).toString(CryptoJS.enc.Hex);
127
+ break;
128
+ case "bcrypt":
129
+ hashedPassword = CryptoJS.PBKDF2(password, salt, {
130
+ keySize: validatedOptions.keyLength / 4,
131
+ iterations: 1024 + validatedOptions.iterations % 1024
132
+ // bcrypt simulation
133
+ }).toString(CryptoJS.enc.Hex);
134
+ break;
135
+ default:
136
+ hashedPassword = CryptoJS.PBKDF2(password, salt, {
137
+ keySize: validatedOptions.keyLength / 4,
138
+ iterations: validatedOptions.iterations
139
+ }).toString(CryptoJS.enc.Hex);
140
+ }
141
+ return {
142
+ hash: hashedPassword,
143
+ salt,
144
+ params: validatedOptions
145
+ };
146
+ }
147
+ function verifyPassword(password, hash2, salt, options) {
148
+ const validatedOptions = PasswordHashOptionsSchema.parse({
149
+ algorithm: options?.algorithm || "pbkdf2",
150
+ iterations: options?.iterations || 1e4,
151
+ saltLength: options?.saltLength || 32,
152
+ keyLength: options?.keyLength || 64
153
+ });
154
+ const { hash: generatedHash } = hashPassword(password, validatedOptions);
155
+ return timingSafeEqual(generatedHash, hash2);
156
+ }
157
+ function timingSafeEqual(a, b, options) {
158
+ const validatedOptions = TimingSafeOptionsSchema.parse({
159
+ encoding: options?.encoding || "utf8"
160
+ });
161
+ if (a.length !== b.length) {
162
+ return false;
163
+ }
164
+ let bufferA = a;
165
+ let bufferB = b;
166
+ if (validatedOptions.encoding === "hex") {
167
+ try {
168
+ bufferA = CryptoJS.enc.Hex.parse(a).toString(CryptoJS.enc.Utf8);
169
+ bufferB = CryptoJS.enc.Hex.parse(b).toString(CryptoJS.enc.Utf8);
170
+ } catch {
171
+ return false;
172
+ }
173
+ } else if (validatedOptions.encoding === "base64") {
174
+ try {
175
+ bufferA = CryptoJS.enc.Base64.parse(a).toString(CryptoJS.enc.Utf8);
176
+ bufferB = CryptoJS.enc.Base64.parse(b).toString(CryptoJS.enc.Utf8);
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+ let result = 0;
182
+ for (let i = 0; i < bufferA.length; i++) {
183
+ result |= bufferA.charCodeAt(i) ^ bufferB.charCodeAt(i);
184
+ }
185
+ return result === 0;
186
+ }
187
+ function generateRandomBytes(length = 32, encoding = "hex") {
188
+ const randomBytes = CryptoJS.lib.WordArray.random(length);
189
+ return encoding === "base64" ? randomBytes.toString(CryptoJS.enc.Base64) : randomBytes.toString(CryptoJS.enc.Hex);
190
+ }
191
+
192
+ // src/utils/encryption.ts
193
+ import CryptoJS2 from "crypto-js";
194
+ var SecurityLevel = /* @__PURE__ */ ((SecurityLevel2) => {
195
+ SecurityLevel2["HIGH"] = "HIGH";
196
+ SecurityLevel2["MEDIUM"] = "MEDIUM";
197
+ SecurityLevel2["LOW"] = "LOW";
198
+ SecurityLevel2["INSECURE"] = "INSECURE";
199
+ return SecurityLevel2;
200
+ })(SecurityLevel || {});
201
+ function getSecurityLevel(options) {
202
+ if (options.mode === "aes-gcm") {
203
+ return "HIGH" /* HIGH */;
204
+ }
205
+ if (options.mode === "aes-cbc" || options.mode === "aes-ctr") {
206
+ return "HIGH" /* HIGH */;
207
+ }
208
+ if (options.mode === "aes-ecb") {
209
+ return "INSECURE" /* INSECURE */;
210
+ }
211
+ return "LOW" /* LOW */;
212
+ }
213
+ function logSecurityWarning(options) {
214
+ const securityLevel = getSecurityLevel(options);
215
+ switch (securityLevel) {
216
+ case "INSECURE" /* INSECURE */:
217
+ console.warn(
218
+ `\u26A0\uFE0F SECURITY WARNING: ${options.mode} mode is insecure and should not be used in production!`
219
+ );
220
+ break;
221
+ case "LOW" /* LOW */:
222
+ console.warn(
223
+ `\u26A0\uFE0F Security Warning: ${options.mode} with ${options.padding} padding provides low security.`
224
+ );
225
+ break;
226
+ case "MEDIUM" /* MEDIUM */:
227
+ console.info(
228
+ `\u2139\uFE0F Security Note: ${options.mode} with ${options.padding} padding provides adequate security for most use cases.`
229
+ );
230
+ break;
231
+ case "HIGH" /* HIGH */:
232
+ break;
233
+ }
234
+ }
235
+ function encrypt(text, key, options) {
236
+ const validatedOptions = EncryptionOptionsSchema.parse({
237
+ mode: options?.mode || "aes-cbc",
238
+ padding: options?.padding || "Pkcs7",
239
+ iv: options?.iv,
240
+ authTag: options?.authTag,
241
+ aad: options?.aad
242
+ });
243
+ logSecurityWarning(validatedOptions);
244
+ const keyWordArray = CryptoJS2.enc.Utf8.parse(key);
245
+ let encrypted;
246
+ const paddingOption = CryptoJS2.pad[validatedOptions.padding];
247
+ switch (validatedOptions.mode) {
248
+ case "aes-gcm": {
249
+ const iv = validatedOptions.iv ? CryptoJS2.enc.Utf8.parse(validatedOptions.iv) : CryptoJS2.lib.WordArray.random(12);
250
+ const aad = validatedOptions.aad ? CryptoJS2.enc.Utf8.parse(validatedOptions.aad) : void 0;
251
+ encrypted = CryptoJS2.AES.encrypt(text, keyWordArray, {
252
+ iv,
253
+ mode: CryptoJS2.mode.CTR,
254
+ // Use CTR as a base for GCM simulation
255
+ padding: paddingOption
256
+ });
257
+ const authData = aad ? aad.concat(encrypted.ciphertext) : encrypted.ciphertext;
258
+ const authTag = CryptoJS2.HmacSHA256(authData, keyWordArray).toString().substring(0, 32);
259
+ const ivHex = iv.toString(CryptoJS2.enc.Hex);
260
+ return `${ivHex}:${authTag}:${encrypted.toString()}`;
261
+ }
262
+ case "aes-cbc": {
263
+ const iv = validatedOptions.iv ? CryptoJS2.enc.Utf8.parse(validatedOptions.iv) : CryptoJS2.lib.WordArray.random(16);
264
+ encrypted = CryptoJS2.AES.encrypt(text, keyWordArray, {
265
+ iv,
266
+ padding: paddingOption,
267
+ mode: CryptoJS2.mode.CBC
268
+ });
269
+ if (!validatedOptions.iv) {
270
+ const ivHex = iv.toString(CryptoJS2.enc.Hex);
271
+ return `${ivHex}:${encrypted.toString()}`;
272
+ }
273
+ break;
274
+ }
275
+ case "aes-ctr": {
276
+ const iv = validatedOptions.iv ? CryptoJS2.enc.Utf8.parse(validatedOptions.iv) : CryptoJS2.lib.WordArray.random(16);
277
+ encrypted = CryptoJS2.AES.encrypt(text, keyWordArray, {
278
+ iv,
279
+ padding: paddingOption,
280
+ mode: CryptoJS2.mode.CTR
281
+ });
282
+ if (!validatedOptions.iv) {
283
+ const ivHex = iv.toString(CryptoJS2.enc.Hex);
284
+ return `${ivHex}:${encrypted.toString()}`;
285
+ }
286
+ break;
287
+ }
288
+ case "aes-ecb":
289
+ encrypted = CryptoJS2.AES.encrypt(text, keyWordArray, {
290
+ padding: paddingOption,
291
+ mode: CryptoJS2.mode.ECB
292
+ });
293
+ break;
294
+ default:
295
+ throw new Error(`Unsupported encryption mode: ${validatedOptions.mode}`);
296
+ }
297
+ return encrypted.toString();
298
+ }
299
+ function decrypt(encryptedText, key, options) {
300
+ const validatedOptions = EncryptionOptionsSchema.parse({
301
+ mode: options?.mode || "aes-cbc",
302
+ padding: options?.padding || "Pkcs7",
303
+ iv: options?.iv,
304
+ authTag: options?.authTag,
305
+ aad: options?.aad
306
+ });
307
+ const keyWordArray = CryptoJS2.enc.Utf8.parse(key);
308
+ let cipherText = encryptedText;
309
+ let iv;
310
+ let authTag;
311
+ if (validatedOptions.mode === "aes-gcm" && encryptedText.includes(":")) {
312
+ const parts = encryptedText.split(":");
313
+ if (parts.length >= 3) {
314
+ const ivHex = parts[0];
315
+ authTag = parts[1];
316
+ cipherText = parts[2];
317
+ iv = CryptoJS2.enc.Hex.parse(ivHex);
318
+ }
319
+ } else if (encryptedText.includes(":") && !validatedOptions.iv) {
320
+ const parts = encryptedText.split(":");
321
+ const ivHex = parts[0];
322
+ cipherText = parts[1];
323
+ iv = CryptoJS2.enc.Hex.parse(ivHex);
324
+ } else if (validatedOptions.iv) {
325
+ iv = CryptoJS2.enc.Utf8.parse(validatedOptions.iv);
326
+ }
327
+ const paddingOption = CryptoJS2.pad[validatedOptions.padding];
328
+ let decrypted;
329
+ switch (validatedOptions.mode) {
330
+ case "aes-gcm": {
331
+ if (!iv) {
332
+ throw new Error("IV is required for GCM mode decryption");
333
+ }
334
+ if (!authTag && !validatedOptions.authTag) {
335
+ throw new Error("Authentication tag is required for GCM mode decryption");
336
+ }
337
+ const actualAuthTag = authTag || validatedOptions.authTag;
338
+ const aad = validatedOptions.aad ? CryptoJS2.enc.Utf8.parse(validatedOptions.aad) : void 0;
339
+ decrypted = CryptoJS2.AES.decrypt(cipherText, keyWordArray, {
340
+ iv,
341
+ mode: CryptoJS2.mode.CTR,
342
+ padding: paddingOption
343
+ });
344
+ const cipherTextObj = CryptoJS2.enc.Base64.parse(cipherText);
345
+ const authData = aad ? aad.concat(cipherTextObj) : cipherTextObj;
346
+ const calculatedAuthTag = CryptoJS2.HmacSHA256(authData, keyWordArray).toString().substring(0, 32);
347
+ if (calculatedAuthTag !== actualAuthTag) {
348
+ throw new Error("Authentication failed: Invalid authentication tag");
349
+ }
350
+ break;
351
+ }
352
+ case "aes-cbc":
353
+ if (!iv && validatedOptions.mode === "aes-cbc") {
354
+ throw new Error("IV is required for CBC mode decryption");
355
+ }
356
+ decrypted = CryptoJS2.AES.decrypt(cipherText, keyWordArray, {
357
+ iv,
358
+ padding: paddingOption,
359
+ mode: CryptoJS2.mode.CBC
360
+ });
361
+ break;
362
+ case "aes-ctr":
363
+ if (!iv && validatedOptions.mode === "aes-ctr") {
364
+ throw new Error("IV is required for CTR mode decryption");
365
+ }
366
+ decrypted = CryptoJS2.AES.decrypt(cipherText, keyWordArray, {
367
+ iv,
368
+ padding: paddingOption,
369
+ mode: CryptoJS2.mode.CTR
370
+ });
371
+ break;
372
+ case "aes-ecb":
373
+ console.warn(
374
+ "\u26A0\uFE0F WARNING: ECB mode does not provide semantic security and should not be used in most applications"
375
+ );
376
+ decrypted = CryptoJS2.AES.decrypt(cipherText, keyWordArray, {
377
+ padding: paddingOption,
378
+ mode: CryptoJS2.mode.ECB
379
+ });
380
+ break;
381
+ default:
382
+ throw new Error(`Unsupported encryption mode: ${validatedOptions.mode}`);
383
+ }
384
+ try {
385
+ return decrypted.toString(CryptoJS2.enc.Utf8);
386
+ } catch {
387
+ throw new Error("Failed to decrypt: Invalid key or corrupted data");
388
+ }
389
+ }
390
+ function generateKey(length = 32) {
391
+ return CryptoJS2.lib.WordArray.random(length).toString(CryptoJS2.enc.Hex);
392
+ }
393
+
394
+ // src/utils/password.ts
395
+ function calculatePasswordEntropy(password) {
396
+ if (!password || password.length === 0) return 0;
397
+ let poolSize = 0;
398
+ const hasLowercase = /[a-z]/.test(password);
399
+ const hasUppercase = /[A-Z]/.test(password);
400
+ const hasDigits = /\d/.test(password);
401
+ const hasSymbols = /[^A-Za-z0-9]/.test(password);
402
+ if (hasLowercase) poolSize += 26;
403
+ if (hasUppercase) poolSize += 26;
404
+ if (hasDigits) poolSize += 10;
405
+ if (hasSymbols) poolSize += 33;
406
+ const entropy = password.length * Math.log2(poolSize);
407
+ return Math.round(entropy * 100) / 100;
408
+ }
409
+ function analyzePasswordPatterns(password) {
410
+ const patterns = [];
411
+ let entropyReduction = 0;
412
+ const keyboardPatterns = [
413
+ "qwerty",
414
+ "asdfgh",
415
+ "zxcvbn",
416
+ "qwertz",
417
+ "azerty",
418
+ "123456",
419
+ "654321"
420
+ ];
421
+ const sequentialCheck = (pwd) => {
422
+ for (let i = 0; i < pwd.length - 2; i++) {
423
+ const c1 = pwd.charCodeAt(i);
424
+ const c2 = pwd.charCodeAt(i + 1);
425
+ const c3 = pwd.charCodeAt(i + 2);
426
+ if (c1 + 1 === c2 && c2 + 1 === c3 || c1 - 1 === c2 && c2 - 1 === c3) {
427
+ return true;
428
+ }
429
+ }
430
+ return false;
431
+ };
432
+ const repeatedPatternCheck = (pwd) => {
433
+ if (pwd.length <= 2) return false;
434
+ for (let len = 2; len <= pwd.length / 2; len++) {
435
+ for (let i = 0; i <= pwd.length - len * 2; i++) {
436
+ const pattern = pwd.substring(i, i + len);
437
+ const nextPart = pwd.substring(i + len, i + len * 2);
438
+ if (pattern === nextPart) {
439
+ return true;
440
+ }
441
+ }
442
+ }
443
+ return false;
444
+ };
445
+ for (const pattern of keyboardPatterns) {
446
+ if (password.toLowerCase().includes(pattern)) {
447
+ patterns.push("Contains keyboard pattern");
448
+ entropyReduction += 0.2;
449
+ break;
450
+ }
451
+ }
452
+ if (sequentialCheck(password)) {
453
+ patterns.push("Contains sequential characters");
454
+ entropyReduction += 0.15;
455
+ }
456
+ if (repeatedPatternCheck(password)) {
457
+ patterns.push("Contains repeated patterns");
458
+ entropyReduction += 0.2;
459
+ }
460
+ const l33tMap = {
461
+ "4": "a",
462
+ "@": "a",
463
+ "8": "b",
464
+ "3": "e",
465
+ "6": "g",
466
+ "1": "i",
467
+ "!": "i",
468
+ "0": "o",
469
+ "9": "g",
470
+ $: "s",
471
+ "5": "s",
472
+ "+": "t",
473
+ "7": "t",
474
+ "%": "x",
475
+ "2": "z"
476
+ };
477
+ let transformedPassword = password.toLowerCase();
478
+ for (const [digit, letter] of Object.entries(l33tMap)) {
479
+ const safeDigit = digit.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
480
+ transformedPassword = transformedPassword.replace(
481
+ new RegExp(safeDigit, "g"),
482
+ letter
483
+ );
484
+ }
485
+ const commonWords = [
486
+ "password",
487
+ "admin",
488
+ "user",
489
+ "login",
490
+ "welcome",
491
+ "secure",
492
+ "secret"
493
+ ];
494
+ for (const word of commonWords) {
495
+ if (transformedPassword.includes(word)) {
496
+ patterns.push("Contains common word with substitutions");
497
+ entropyReduction += 0.25;
498
+ break;
499
+ }
500
+ }
501
+ if (/^[a-z]+$/.test(password) || /^[A-Z]+$/.test(password) || /^[0-9]+$/.test(password) || /^[^A-Za-z0-9]+$/.test(password)) {
502
+ patterns.push("Uses only one character type");
503
+ entropyReduction += 0.3;
504
+ }
505
+ entropyReduction = Math.min(entropyReduction, 0.9);
506
+ return { entropyReduction, patterns };
507
+ }
508
+ function checkPasswordStrength(password, config) {
509
+ const validatedConfig = ConfigSchema.parse({
510
+ minimumPasswordLength: config?.minimumPasswordLength || 12,
511
+ requireNumbers: config?.requireNumbers !== void 0 ? config.requireNumbers : true,
512
+ requireSymbols: config?.requireSymbols !== void 0 ? config.requireSymbols : true,
513
+ requireUppercase: config?.requireUppercase !== void 0 ? config.requireUppercase : true,
514
+ requireLowercase: config?.requireLowercase !== void 0 ? config.requireLowercase : true
515
+ });
516
+ const hasMinLength = password.length >= validatedConfig.minimumPasswordLength;
517
+ const hasNumbers = /\d/.test(password);
518
+ const hasSymbols = /[^A-Za-z0-9]/.test(password);
519
+ const hasUppercase = /[A-Z]/.test(password);
520
+ const hasLowercase = /[a-z]/.test(password);
521
+ const suggestions = [];
522
+ let warning = "";
523
+ let criteriaCount = 0;
524
+ if (hasMinLength) criteriaCount++;
525
+ if (hasNumbers && validatedConfig.requireNumbers) criteriaCount++;
526
+ if (hasSymbols && validatedConfig.requireSymbols) criteriaCount++;
527
+ if (hasUppercase && validatedConfig.requireUppercase) criteriaCount++;
528
+ if (hasLowercase && validatedConfig.requireLowercase) criteriaCount++;
529
+ if (!hasMinLength) {
530
+ warning = "Password is too short";
531
+ suggestions.push(
532
+ `Password should be at least ${validatedConfig.minimumPasswordLength} characters long`
533
+ );
534
+ }
535
+ if (validatedConfig.requireNumbers && !hasNumbers) {
536
+ suggestions.push("Add numbers to make your password stronger");
537
+ }
538
+ if (validatedConfig.requireSymbols && !hasSymbols) {
539
+ suggestions.push("Add symbols to make your password stronger");
540
+ }
541
+ if (validatedConfig.requireUppercase && !hasUppercase) {
542
+ suggestions.push("Add uppercase letters to make your password stronger");
543
+ }
544
+ if (validatedConfig.requireLowercase && !hasLowercase) {
545
+ suggestions.push("Add lowercase letters to make your password stronger");
546
+ }
547
+ const entropy = calculatePasswordEntropy(password);
548
+ const { entropyReduction, patterns } = analyzePasswordPatterns(password);
549
+ const effectiveEntropy = entropy * (1 - entropyReduction);
550
+ suggestions.push(
551
+ ...patterns.map((p) => `${p}: Consider a more random pattern`)
552
+ );
553
+ let score;
554
+ if (effectiveEntropy < 28) {
555
+ score = 0;
556
+ if (!warning) warning = "Very weak password";
557
+ } else if (effectiveEntropy < 36) {
558
+ score = 1;
559
+ if (!warning) warning = "Weak password";
560
+ } else if (effectiveEntropy < 60) {
561
+ score = 2;
562
+ } else if (effectiveEntropy < 80) {
563
+ score = 3;
564
+ } else {
565
+ score = 4;
566
+ }
567
+ const criteriaBoost = criteriaCount >= 5 ? 1 : 0;
568
+ score = Math.min(4, score + criteriaBoost);
569
+ if (!hasMinLength || validatedConfig.requireNumbers && !hasNumbers || validatedConfig.requireSymbols && !hasSymbols || validatedConfig.requireUppercase && !hasUppercase || validatedConfig.requireLowercase && !hasLowercase) {
570
+ score = Math.min(score, 2);
571
+ }
572
+ if (effectiveEntropy < 50) {
573
+ suggestions.push(
574
+ `Increase password entropy (currently ~${Math.round(
575
+ effectiveEntropy
576
+ )} bits)`
577
+ );
578
+ }
579
+ const isStrong = hasMinLength && (!validatedConfig.requireNumbers || hasNumbers) && (!validatedConfig.requireSymbols || hasSymbols) && (!validatedConfig.requireUppercase || hasUppercase) && (!validatedConfig.requireLowercase || hasLowercase) && score >= 3;
580
+ return PasswordStrengthSchema.parse({
581
+ score,
582
+ feedback: {
583
+ warning,
584
+ suggestions: suggestions.length > 0 ? suggestions : void 0
585
+ },
586
+ isStrong
587
+ });
588
+ }
589
+ function generateSecurePassword(config) {
590
+ const validatedConfig = ConfigSchema.parse({
591
+ minimumPasswordLength: config?.minimumPasswordLength || 12,
592
+ requireNumbers: config?.requireNumbers !== void 0 ? config.requireNumbers : true,
593
+ requireSymbols: config?.requireSymbols !== void 0 ? config.requireSymbols : true,
594
+ requireUppercase: config?.requireUppercase !== void 0 ? config.requireUppercase : true,
595
+ requireLowercase: config?.requireLowercase !== void 0 ? config.requireLowercase : true
596
+ });
597
+ const length = validatedConfig.minimumPasswordLength;
598
+ const lowercaseChars = "abcdefghijklmnopqrstuvwxyz";
599
+ const uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
600
+ const numbers = "0123456789";
601
+ const symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?";
602
+ let characters = "";
603
+ let password = "";
604
+ if (validatedConfig.requireLowercase) characters += lowercaseChars;
605
+ if (validatedConfig.requireUppercase) characters += uppercaseChars;
606
+ if (validatedConfig.requireNumbers) characters += numbers;
607
+ if (validatedConfig.requireSymbols) characters += symbols;
608
+ if (validatedConfig.requireLowercase) {
609
+ password += lowercaseChars.charAt(
610
+ Math.floor(Math.random() * lowercaseChars.length)
611
+ );
612
+ }
613
+ if (validatedConfig.requireUppercase) {
614
+ password += uppercaseChars.charAt(
615
+ Math.floor(Math.random() * uppercaseChars.length)
616
+ );
617
+ }
618
+ if (validatedConfig.requireNumbers) {
619
+ password += numbers.charAt(Math.floor(Math.random() * numbers.length));
620
+ }
621
+ if (validatedConfig.requireSymbols) {
622
+ password += symbols.charAt(Math.floor(Math.random() * symbols.length));
623
+ }
624
+ for (let i = password.length; i < length; i++) {
625
+ password += characters.charAt(
626
+ Math.floor(Math.random() * characters.length)
627
+ );
628
+ }
629
+ return shuffleString(password);
630
+ }
631
+ function shuffleString(str) {
632
+ const array = str.split("");
633
+ for (let i = array.length - 1; i > 0; i--) {
634
+ const j = Math.floor(Math.random() * (i + 1));
635
+ [array[i], array[j]] = [array[j], array[i]];
636
+ }
637
+ return array.join("");
638
+ }
639
+ export {
640
+ ConfigSchema,
641
+ EncryptionOptionsSchema,
642
+ HashOptionsSchema,
643
+ PasswordHashOptionsSchema,
644
+ PasswordStrengthSchema,
645
+ SecurityLevel,
646
+ TimingSafeOptionsSchema,
647
+ analyzePasswordPatterns,
648
+ calculatePasswordEntropy,
649
+ checkPasswordStrength,
650
+ decrypt,
651
+ encrypt,
652
+ generateKey,
653
+ generateRandomBytes,
654
+ generateSecurePassword,
655
+ getSecurityLevel,
656
+ hash,
657
+ hashPassword,
658
+ hmac,
659
+ timingSafeEqual,
660
+ verifyHash,
661
+ verifyPassword
662
+ };