snap-validate 0.3.0 โ†’ 0.3.1

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.
Files changed (3) hide show
  1. package/README.md +88 -2
  2. package/package.json +1 -1
  3. package/src/index.js +262 -42
package/README.md CHANGED
@@ -3,9 +3,11 @@
3
3
  [![npm version](https://img.shields.io/npm/v/snap-validate.svg?style=flat-square)](https://www.npmjs.com/package/snap-validate)
4
4
  [![Build Status](https://github.com/aniru-dh21/snap-validate/workflows/CI%2FCD%20Pipeline/badge.svg)](https://github.com/aniru-dh21/snap-validate/actions)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![install size](https://img.shields.io/badge/dynamic/json?url=https://packagephobia.com/v2/api.json?p=snap-validate&query=$.install.pretty&label=install%20size&style=flat-square)](https://packagephobia.now.sh/result?p=snap-validate)
7
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/snap-validate?style=flat-square)](https://bundlephobia.com/package/snap-validate@latest)
6
8
  [![npm downloads](https://img.shields.io/npm/dm/snap-validate.svg?style=flat-square)](https://npm-stat.com/charts.html?package=snap-validate)
7
9
 
8
- A lightning-fast, lightweight validation library for common patterns without heavy dependencies. Perfect for client-side and server-side validation with zero external dependencies.
10
+ A lightning-fast, lightweight validation library for common patterns without heavy dependencies. Perfect for client-side and server-side validation with zero external dependencies and built-in protection against ReDoS (Regular Expression Denial of Service) attacks.
9
11
 
10
12
  ## Features
11
13
 
@@ -17,6 +19,8 @@ A lightning-fast, lightweight validation library for common patterns without hea
17
19
  - ๐Ÿ”„ **Async Support**: Full async validation support for database checks and API calls
18
20
  - ๐ŸŽฏ **Conditional**: Advanced conditional validation with `when()` and `optional()`
19
21
  - ๐Ÿ› ๏ธ **Custom Validators**: Add your own sync and async validation logic
22
+ - ๐Ÿ”’ **Security First**: Built-in protection against ReDoS attacks and unsafe regex patterns
23
+ - ๐Ÿ›ก๏ธ **Timeout Protection**: Configurable timeout for regex operations to prevent DoS attacks
20
24
  - ๐Ÿงช **Well Tested**: Comprehensive test suite with high coverage
21
25
  - ๐Ÿ“ฆ **Easy Integration**: Works in Node.js and browsers
22
26
  - ๐Ÿ”— **Chainable API**: Intuitive fluent interface
@@ -53,6 +57,30 @@ const result = validate(schema, data);
53
57
  console.log(result.isValid); // true
54
58
  ```
55
59
 
60
+ ## Security Features
61
+
62
+ ### ReDoS Protection
63
+
64
+ Snap Validate includes built-in protection against Regular Expression Denial of Service (ReDoS) attacks:
65
+
66
+ - **Regex Safety Detection**: Automatically detects and prevents potentially dangerous regex patterns
67
+ - **Input Length Limits**: Protects against extremely long input strings (10,000 character limit)
68
+ - **Timeout Protection**: Configurable timeout for regex operations (default: 1 second)
69
+ - **Safe Defaults**: All predefined validators use safe, optimized regex patterns
70
+
71
+ ```javascript
72
+ // Set custom timeout for regex operations
73
+ const validator = new BaseValidator(value)
74
+ .setRegexTimeout(2000) // 2 second timeout
75
+ .pattern(/your-pattern/, 'Error message');
76
+
77
+ // Use async pattern validation for complex patterns with timeout protection
78
+ const validator = new BaseValidator(value)
79
+ .patternAsync(/complex-pattern/, 'Error message');
80
+
81
+ const result = await validator.validateAsync();
82
+ ```
83
+
56
84
  ## Available Validators
57
85
 
58
86
  ### Email Validation
@@ -212,6 +240,39 @@ const asyncSchema = {
212
240
  const asyncResult = await validate.async(asyncSchema, userData);
213
241
  ```
214
242
 
243
+ ## Security and Pattern Validation
244
+
245
+ ### Safe Pattern Validation
246
+
247
+ ```javascript
248
+ const { BaseValidator } = require('snap-validate');
249
+
250
+ // Synchronous pattern validation with built-in safety checks
251
+ const validator = new BaseValidator(value)
252
+ .pattern(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed');
253
+
254
+ // Asynchronous pattern validation with timeout protection
255
+ const asyncValidator = new BaseValidator(value)
256
+ .patternAsync(/^[a-zA-Z0-9]+$/, 'Only alphanumeric characters allowed')
257
+ .setRegexTimeout(5000); // 5 second timeout
258
+
259
+ const result = await asyncValidator.validateAsync();
260
+ ```
261
+
262
+ ### Configurable Security Settings
263
+
264
+ ```javascript
265
+ const validator = new BaseValidator(value)
266
+ .setRegexTimeout(3000) // Set custom timeout (3 seconds)
267
+ .pattern(/your-pattern/, 'Error message');
268
+
269
+ // The library automatically:
270
+ // - Detects unsafe regex patterns
271
+ // - Limits input length to prevent ReDoS
272
+ // - Applies timeout protection for complex patterns
273
+ // - Provides clear error messages for security violations
274
+ ```
275
+
215
276
  ## Custom Validation
216
277
 
217
278
  ### Using BaseValidator
@@ -282,6 +343,13 @@ try {
282
343
  } catch (error) {
283
344
  console.log('Validation exception:', error.message);
284
345
  }
346
+
347
+ // Security-related errors
348
+ const unsafeResult = validator.pattern(/potentially-dangerous-pattern/, 'Error').validate();
349
+ if (!unsafeResult.isValid) {
350
+ console.log('Security errors:', unsafeResult.errors);
351
+ // Output: ['Potentially unsafe regex pattern detected']
352
+ }
285
353
  ```
286
354
 
287
355
  ## Browser Usage
@@ -308,7 +376,9 @@ try {
308
376
  - `required(message?)` - Field is required
309
377
  - `min(length, message?)` - Minimum length validation
310
378
  - `max(length, message?)` - Maximum length validation
311
- - `pattern(regex, message?)` - Pattern matching validation
379
+ - `pattern(regex, message?)` - Pattern matching validation with safety checks
380
+ - `patternAsync(regex, message?)` - Async pattern validation with timeout protection
381
+ - `setRegexTimeout(timeoutMs)` - Set custom timeout for regex operations
312
382
  - `when(condition, validator)` - Conditional validation
313
383
  - `optional()` - Skip validation if empty/null/undefined
314
384
  - `custom(fn, message?)` - Custom synchronous validation
@@ -332,6 +402,19 @@ try {
332
402
  - `validate(schema, data)` - Synchronous schema validation
333
403
  - `validate.async(schema, data)` - Asynchronous schema validation
334
404
 
405
+ ### Security Functions
406
+
407
+ - `isRegexSafe(regex)` - Check if a regex pattern is safe to use
408
+ - `safeRegexText(regex, str, timeoutMs)` - Execute regex with timeout protection
409
+
410
+ ## Security Best Practices
411
+
412
+ 1. **Use Built-in Validators**: The predefined validators are optimized for security and performance
413
+ 2. **Validate Input Length**: Large inputs are automatically limited to prevent ReDoS attacks
414
+ 3. **Set Appropriate Timeouts**: Configure regex timeouts based on your application's needs
415
+ 4. **Test Custom Patterns**: Use `isRegexSafe()` to check custom regex patterns before deployment
416
+ 5. **Handle Async Errors**: Always use try-catch blocks with async validation
417
+
335
418
  ## Contributing
336
419
 
337
420
  We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
@@ -362,6 +445,9 @@ npm run lint
362
445
 
363
446
  # Format code
364
447
  npm run format
448
+
449
+ # Security audit
450
+ npm audit
365
451
  ```
366
452
 
367
453
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snap-validate",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Lightweight validation library for common patterns without heavy dependencies",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
package/src/index.js CHANGED
@@ -1,8 +1,77 @@
1
1
  /**
2
2
  * Snap Validate - Enhanced Lightweight validator library
3
- * @version 0.3.0
3
+ * @version 0.3.1 - Security Fixes
4
4
  */
5
5
 
6
+ // Utility function to safely test regex with timeout protection
7
+ // Utility function to safely test regex with timeout protection
8
+ const safeRegexTest = (regex, str, timeoutMs = 1000) => {
9
+ return new Promise((resolve, reject) => {
10
+ // SECURITY FIX: Add input length validation before regex test
11
+ if (str.length > 10000) {
12
+ reject(new Error('Input too long for regex validation'));
13
+ return;
14
+ }
15
+
16
+ // SECURITY FIX: Add regex safety check before execution
17
+ if (!isRegexSafe(regex)) {
18
+ reject(new Error('Unsafe regex pattern detected'));
19
+ return;
20
+ }
21
+
22
+ const timeout = setTimeout(() => {
23
+ reject(new Error('Regex execution timeout - potential ReDoS attack'));
24
+ }, timeoutMs);
25
+
26
+ try {
27
+ const result = regex.test(str);
28
+ clearTimeout(timeout);
29
+ resolve(result);
30
+ } catch (error) {
31
+ clearTimeout(timeout);
32
+ reject(error);
33
+ }
34
+ });
35
+ };
36
+
37
+ // Synchronous safe regex test with input length protection
38
+ const safeRegexTestSync = (regex, str, maxLength = 10000) => {
39
+ // Limit input length to prevent ReDoS
40
+ if (str.length > maxLength) {
41
+ throw new Error('Input too long for pattern validation');
42
+ }
43
+
44
+ // For additional safety, we could add a timeout using a worker thread or
45
+ // other mechanism, but for now we rely on input length limiting
46
+ return regex.test(str);
47
+ };
48
+
49
+ // Function to detect potentially dangerous regex patterns
50
+ const isRegexSafe = (regex) => {
51
+ const regexStr = regex.toString();
52
+
53
+ // Check for common ReDoS patterns - more precise detection
54
+ const dangerousPatterns = [
55
+ // Nested quantifiers like (a+)+ or (a*)* or (a?)?
56
+ /\([^)]*[+*?][^)]*\)[+*?]/,
57
+ // Alternation with overlapping and quantifiers like (a|a)*
58
+ /\([^)]*\|[^)]*\)[+*]/,
59
+ // Catastrophic backtracking with greedy quantifiers
60
+ /\([^)]*\.\*[^)]*\)\*/,
61
+ // Multiple consecutive quantifiers (not separated by characters)
62
+ /[+*?]{2,}/,
63
+ // Exponential alternation patterns
64
+ /\([^)]*\|[^)]*\)\+.*\([^)]*\|[^)]*\)\+/
65
+ ];
66
+
67
+ // Check if the pattern has obvious ReDoS vulnerabilities
68
+ const isDangerous = dangerousPatterns.some((pattern) =>
69
+ pattern.test(regexStr)
70
+ );
71
+
72
+ return !isDangerous;
73
+ };
74
+
6
75
  // Core validation class
7
76
  class ValidationResult {
8
77
  constructor(isValid, errors = []) {
@@ -24,16 +93,24 @@ class BaseValidator {
24
93
  this.rules = [];
25
94
  this.asyncRules = [];
26
95
  this.isOptional = false;
96
+ this.regexTimeout = 1000; // Default timeout for regex operations
27
97
  }
28
98
 
29
99
  required(message = 'This field is required') {
30
100
  this.rules.push(() => {
31
101
  // Skip validation if optional and empty
32
- if (this.isOptional && (this.value === null || this.value === undefined || this.value === '')) {
102
+ if (
103
+ this.isOptional &&
104
+ (this.value === null || this.value === undefined || this.value === '')
105
+ ) {
33
106
  return new ValidationResult(true);
34
107
  }
35
108
 
36
- if (this.value === null || this.value === undefined || this.value === '') {
109
+ if (
110
+ this.value === null ||
111
+ this.value === undefined ||
112
+ this.value === ''
113
+ ) {
37
114
  return new ValidationResult(false, [message]);
38
115
  }
39
116
  return new ValidationResult(true);
@@ -46,10 +123,18 @@ class BaseValidator {
46
123
  return this;
47
124
  }
48
125
 
126
+ setRegexTimeout(timeoutMs) {
127
+ this.regexTimeout = timeoutMs;
128
+ return this;
129
+ }
130
+
49
131
  min(length, message = `Minimum length is ${length}`) {
50
132
  this.rules.push(() => {
51
133
  // Skip validation if optional and empty
52
- if (this.isOptional && (this.value === null || this.value === undefined || this.value === '')) {
134
+ if (
135
+ this.isOptional &&
136
+ (this.value === null || this.value === undefined || this.value === '')
137
+ ) {
53
138
  return new ValidationResult(true);
54
139
  }
55
140
 
@@ -66,7 +151,9 @@ class BaseValidator {
66
151
  return new ValidationResult(false, [message]);
67
152
  }
68
153
  } else {
69
- return new ValidationResult(false, ['Value must be a string, array, or number']);
154
+ return new ValidationResult(false, [
155
+ 'Value must be a string, array, or number'
156
+ ]);
70
157
  }
71
158
  }
72
159
  return new ValidationResult(true);
@@ -77,7 +164,10 @@ class BaseValidator {
77
164
  max(length, message = `Maximum length is ${length}`) {
78
165
  this.rules.push(() => {
79
166
  // Skip validation if optional and empty
80
- if (this.isOptional && (this.value === null || this.value === undefined || this.value === '')) {
167
+ if (
168
+ this.isOptional &&
169
+ (this.value === null || this.value === undefined || this.value === '')
170
+ ) {
81
171
  return new ValidationResult(true);
82
172
  }
83
173
 
@@ -94,7 +184,9 @@ class BaseValidator {
94
184
  return new ValidationResult(false, [message]);
95
185
  }
96
186
  } else {
97
- return new ValidationResult(false, ['Value must be a string, array or number']);
187
+ return new ValidationResult(false, [
188
+ 'Value must be a string, array or number'
189
+ ]);
98
190
  }
99
191
  }
100
192
  return new ValidationResult(true);
@@ -103,9 +195,61 @@ class BaseValidator {
103
195
  }
104
196
 
105
197
  pattern(regex, message = 'Invalid format') {
198
+ // SECURITY FIX: Add regex safety check
199
+ if (!isRegexSafe(regex)) {
200
+ throw new Error(
201
+ 'Potentially unsafe regex pattern detected. Please use a simple pattern.'
202
+ );
203
+ }
204
+
106
205
  this.rules.push(() => {
107
206
  // Skip validation if optional and empty
108
- if (this.isOptional && (this.value === null || this.value === undefined || this.value === '')) {
207
+ if (
208
+ this.isOptional &&
209
+ (this.value === null || this.value === undefined || this.value === '')
210
+ ) {
211
+ return new ValidationResult(true);
212
+ }
213
+
214
+ // Only test pattern if value exists and is not empty
215
+ if (this.value != null && this.value !== '') {
216
+ // Ensure value is a string before testing regex
217
+ const stringValue = String(this.value);
218
+
219
+ try {
220
+ // SECURITY FIX: Use safe regex test with input length protection
221
+ if (!safeRegexTestSync(regex, stringValue)) {
222
+ return new ValidationResult(false, [message]);
223
+ }
224
+ } catch (error) {
225
+ if (error.message.includes('Input too long')) {
226
+ return new ValidationResult(false, [
227
+ 'Input too long for pattern validation'
228
+ ]);
229
+ }
230
+ return new ValidationResult(false, ['Pattern validation failed']);
231
+ }
232
+ }
233
+ return new ValidationResult(true);
234
+ });
235
+ return this;
236
+ }
237
+
238
+ // New method for async pattern validation with timeout protection
239
+ patternAsync(regex, message = 'Invalid format') {
240
+ // Security Fix: Add regex safety check
241
+ if (!isRegexSafe(regex)) {
242
+ throw new Error(
243
+ 'Potentially unsafe regex pattern detected. Please use a simple pattern.'
244
+ );
245
+ }
246
+
247
+ this.asyncRules.push(async () => {
248
+ // Skip validation if optional and empty
249
+ if (
250
+ this.isOptional &&
251
+ (this.value === null || this.value === undefined || this.value === '')
252
+ ) {
109
253
  return new ValidationResult(true);
110
254
  }
111
255
 
@@ -113,8 +257,31 @@ class BaseValidator {
113
257
  if (this.value != null && this.value !== '') {
114
258
  // Ensure value is a string before testing regex
115
259
  const stringValue = String(this.value);
116
- if (!regex.test(stringValue)) {
117
- return new ValidationResult(false, [message]);
260
+
261
+ // Security Fix: Limit input length to prevent ReDoS
262
+ if (stringValue.length > 10000) {
263
+ return new ValidationResult(false, [
264
+ 'Input too long for pattern validation'
265
+ ]);
266
+ }
267
+
268
+ try {
269
+ // Security Fix: Use timeout protection for regex execution
270
+ const result = await safeRegexTest(
271
+ regex,
272
+ stringValue,
273
+ this.regexTimeout
274
+ );
275
+ if (!result) {
276
+ return new ValidationResult(false, [message]);
277
+ }
278
+ } catch (error) {
279
+ if (error.message.includes('timeout')) {
280
+ return new ValidationResult(false, [
281
+ 'Pattern validation timeout - pattern too complex'
282
+ ]);
283
+ }
284
+ return new ValidationResult(false, ['Pattern validation failed']);
118
285
  }
119
286
  }
120
287
  return new ValidationResult(true);
@@ -125,7 +292,8 @@ class BaseValidator {
125
292
  when(condition, validator) {
126
293
  this.rules.push(() => {
127
294
  // Evaluate condition
128
- const shouldValidate = typeof condition === 'function' ? condition(this.value) : condition;
295
+ const shouldValidate =
296
+ typeof condition === 'function' ? condition(this.value) : condition;
129
297
 
130
298
  if (shouldValidate) {
131
299
  // Apply the conditional validator
@@ -146,7 +314,10 @@ class BaseValidator {
146
314
  custom(validatorFn, message = 'Custom validation failed') {
147
315
  this.rules.push(() => {
148
316
  // Skip validation if optional and empty
149
- if (this.isOptional && (this.value === null || this.value === undefined || this.value === '')) {
317
+ if (
318
+ this.isOptional &&
319
+ (this.value === null || this.value === undefined || this.value === '')
320
+ ) {
150
321
  return new ValidationResult(true);
151
322
  }
152
323
 
@@ -155,7 +326,9 @@ class BaseValidator {
155
326
 
156
327
  // Handle boolean result
157
328
  if (typeof result === 'boolean') {
158
- return result ? new ValidationResult(true) : new ValidationResult(false, [message]);
329
+ return result
330
+ ? new ValidationResult(true)
331
+ : new ValidationResult(false, [message]);
159
332
  }
160
333
 
161
334
  // Handle ValidationResult object
@@ -171,7 +344,9 @@ class BaseValidator {
171
344
  // Default to true if no clear result
172
345
  return new ValidationResult(true);
173
346
  } catch (error) {
174
- return new ValidationResult(false, [`Custom validation error: ${error.message}`]);
347
+ return new ValidationResult(false, [
348
+ `Custom validation error: ${error.message}`
349
+ ]);
175
350
  }
176
351
  });
177
352
  return this;
@@ -180,7 +355,10 @@ class BaseValidator {
180
355
  customAsync(validatorFn, message = 'Async validation failed') {
181
356
  this.asyncRules.push(async () => {
182
357
  // Skip validation if optional and empty
183
- if (this.isOptional && (this.value === null || this.value === undefined || this.value === '')) {
358
+ if (
359
+ this.isOptional &&
360
+ (this.value === null || this.value === undefined || this.value === '')
361
+ ) {
184
362
  return new ValidationResult(true);
185
363
  }
186
364
 
@@ -189,7 +367,9 @@ class BaseValidator {
189
367
 
190
368
  // Handle boolean result
191
369
  if (typeof result === 'boolean') {
192
- return result ? new ValidationResult(true) : new ValidationResult(false, [message]);
370
+ return result
371
+ ? new ValidationResult(true)
372
+ : new ValidationResult(false, [message]);
193
373
  }
194
374
 
195
375
  // Handle ValidationResult object
@@ -205,7 +385,9 @@ class BaseValidator {
205
385
  // Default to true if no clear result
206
386
  return new ValidationResult(true);
207
387
  } catch (error) {
208
- return new ValidationResult(false, [`Async validation error: ${error.message}`]);
388
+ return new ValidationResult(false, [
389
+ `Async validation error: ${error.message}`
390
+ ]);
209
391
  }
210
392
  });
211
393
  return this;
@@ -261,6 +443,7 @@ class BaseValidator {
261
443
  }
262
444
 
263
445
  // Predefined validators
446
+ // Security Fix: Updated predefined validators with safer regex patterns
264
447
  const validators = {
265
448
  email: (value) => {
266
449
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -270,15 +453,25 @@ const validators = {
270
453
  },
271
454
 
272
455
  phone: (value, format = 'us') => {
456
+ // FIXED: Much simpler phone regex patterns to avoid ReDoS detection
273
457
  const phoneRegex = {
274
- us: /^\+?1?[-.\s]?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})$/,
275
- international: /^\+[1-9]\d{1,14}$/,
276
- simple: /^\d{10,15}$/
458
+ us: /^[+]?[1]?[0-9]{10}$/,
459
+ international: /^[+][1-9][0-9]{7,14}$/,
460
+ simple: /^[0-9]{10,15}$/
277
461
  };
278
462
 
279
463
  return new BaseValidator(value)
280
464
  .required('Phone number is required')
281
- .pattern(phoneRegex[format] || phoneRegex.simple, 'Invalid phone number format');
465
+ .custom((val) => {
466
+ // Remove all non-digit characters except +
467
+ const cleaned = String(val).replace(/[^+0-9]/g, '');
468
+ const regex = phoneRegex[format] || phoneRegex.simple;
469
+
470
+ if (!safeRegexTestSync(regex, cleaned)) {
471
+ return 'Invalid phone number format';
472
+ }
473
+ return true;
474
+ });
282
475
  },
283
476
 
284
477
  creditCard: (value) => {
@@ -305,22 +498,30 @@ const validators = {
305
498
  return sum % 10 === 0;
306
499
  };
307
500
 
308
- const validator = new BaseValidator(value)
309
- .required('Credit card number is required');
501
+ const validator = new BaseValidator(value).required(
502
+ 'Credit card number is required'
503
+ );
310
504
 
311
505
  // Add custom validation for credit card format and Luhn check
312
506
  validator.rules.push(() => {
313
507
  // Skip validation if optional and empty
314
- if (validator.isOptional && (validator.value === null || validator.value === undefined || validator.value === '')) {
508
+ if (
509
+ validator.isOptional &&
510
+ (validator.value === null ||
511
+ validator.value === undefined ||
512
+ validator.value === '')
513
+ ) {
315
514
  return new ValidationResult(true);
316
515
  }
317
516
 
318
517
  if (validator.value) {
319
518
  const cleanValue = String(validator.value).replace(/\s/g, '');
320
519
 
321
- // Check length (13-19 digits)
322
- if (!/^\d{13,19}$/.test(cleanValue)) {
323
- return new ValidationResult(false, ['Credit card must be 13-19 digits']);
520
+ // Check length (13-19 digits) using safe regex
521
+ if (!safeRegexTestSync(/^\d{13,19}$/, cleanValue)) {
522
+ return new ValidationResult(false, [
523
+ 'Credit card must be 13-19 digits'
524
+ ]);
324
525
  }
325
526
 
326
527
  // Check Luhn algorithm
@@ -355,16 +556,25 @@ const validators = {
355
556
  .min(minLength, `Password must be at least ${minLength} characters`);
356
557
 
357
558
  if (requireUppercase) {
358
- validator.pattern(/[A-Z]/, 'Password must contain at least one uppercase letter');
559
+ validator.pattern(
560
+ /[A-Z]/,
561
+ 'Password must contain at least one uppercase letter'
562
+ );
359
563
  }
360
564
  if (requireLowercase) {
361
- validator.pattern(/[a-z]/, 'Password must contain at least one lowercase letter');
565
+ validator.pattern(
566
+ /[a-z]/,
567
+ 'Password must contain at least one lowercase letter'
568
+ );
362
569
  }
363
570
  if (requireNumbers) {
364
571
  validator.pattern(/\d/, 'Password must contain at least one number');
365
572
  }
366
573
  if (requireSpecialChars) {
367
- validator.pattern(/[!@#$%^&*(),.?":{}|<>]/, 'Password must contain at least one special character');
574
+ validator.pattern(
575
+ /[!@#$%^&*(),.?":{}|<>]/,
576
+ 'Password must contain at least one special character'
577
+ );
368
578
  }
369
579
 
370
580
  return validator;
@@ -412,9 +622,10 @@ const validate = (schema, data) => {
412
622
  for (const [field, validator] of Object.entries(schema)) {
413
623
  try {
414
624
  const fieldValue = data[field];
415
- const result = typeof validator === 'function'
416
- ? validator(fieldValue).validate()
417
- : validator.validate();
625
+ const result =
626
+ typeof validator === 'function'
627
+ ? validator(fieldValue).validate()
628
+ : validator.validate();
418
629
 
419
630
  results[field] = result;
420
631
  if (!result.isValid) {
@@ -422,7 +633,9 @@ const validate = (schema, data) => {
422
633
  }
423
634
  } catch (error) {
424
635
  // Handle validation setup errors
425
- results[field] = new ValidationResult(false, [`Validation setup error: ${error.message}`]);
636
+ results[field] = new ValidationResult(false, [
637
+ `Validation setup error: ${error.message}`
638
+ ]);
426
639
  isValid = false;
427
640
  }
428
641
  }
@@ -463,13 +676,15 @@ const validateAsync = async (schema, data) => {
463
676
  let result;
464
677
  if (typeof validator === 'function') {
465
678
  const validatorInstance = validator(fieldValue);
466
- result = validatorInstance.asyncRules.length > 0
467
- ? await validatorInstance.validateAsync()
468
- : validatorInstance.validate();
679
+ result =
680
+ validatorInstance.asyncRules.length > 0
681
+ ? await validatorInstance.validateAsync()
682
+ : validatorInstance.validate();
469
683
  } else {
470
- result = validator.asyncRules && validator.asyncRules.length > 0
471
- ? await validator.validateAsync()
472
- : validator.validate();
684
+ result =
685
+ validator.asyncRules && validator.asyncRules.length > 0
686
+ ? await validator.validateAsync()
687
+ : validator.validate();
473
688
  }
474
689
 
475
690
  results[field] = result;
@@ -478,7 +693,9 @@ const validateAsync = async (schema, data) => {
478
693
  }
479
694
  } catch (error) {
480
695
  // Handle validation setup errors
481
- results[field] = new ValidationResult(false, [`Validation setup error: ${error.message}`]);
696
+ results[field] = new ValidationResult(false, [
697
+ `Validation setup error: ${error.message}`
698
+ ]);
482
699
  isValid = false;
483
700
  }
484
701
  }
@@ -503,5 +720,8 @@ module.exports = {
503
720
  ValidationResult,
504
721
  validators,
505
722
  validate,
506
- validateAsync
723
+ validateAsync,
724
+ safeRegexTest,
725
+ safeRegexTestSync,
726
+ isRegexSafe
507
727
  };