@carecard/validate 3.23.0 → 3.25.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/.agents/skills/pkg-validate-validation-library/SKILL.md +16 -14
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/publish.yml +56 -56
- package/.nvmrc +1 -0
- package/THIRD_PARTY_NOTICES +31 -0
- package/data/commonPasswords.json +10006 -0
- package/index.d.ts +8 -19
- package/index.js +4 -2
- package/lib/validate.js +21 -46
- package/lib/validateProperties.js +6 -11
- package/package.json +5 -2
- package/readme.md +124 -103
package/readme.md
CHANGED
|
@@ -8,10 +8,14 @@ Non-negotiable root-cause solution rule: Always identify and solve the verified
|
|
|
8
8
|
services. It exposes individual value validators, a bulk property sanitizer, and
|
|
9
9
|
a whitelist validator for request-like payloads.
|
|
10
10
|
|
|
11
|
-
The package returns booleans from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
The package returns booleans from general low-level validators, returns a
|
|
12
|
+
structured result from the single `validatePassword` policy boundary, strips
|
|
13
|
+
unknown or invalid values from `validateProperties`, and throws CareCard
|
|
14
|
+
`BAD_INPUT` errors from `validateWhitelistProperties` when required or provided
|
|
15
|
+
whitelist values do not pass validation.
|
|
16
|
+
|
|
17
|
+
Use Node.js 24.20.0. The package accepts compatible Node.js releases from
|
|
18
|
+
24.20.0 up to, but not including, Node.js 25.
|
|
15
19
|
|
|
16
20
|
## Development Rule
|
|
17
21
|
|
|
@@ -36,11 +40,19 @@ npm install @carecard/validate
|
|
|
36
40
|
## Importing
|
|
37
41
|
|
|
38
42
|
```js
|
|
39
|
-
const {
|
|
43
|
+
const {
|
|
44
|
+
validate,
|
|
45
|
+
validatePassword,
|
|
46
|
+
validateProperties,
|
|
47
|
+
validateWhitelistProperties,
|
|
48
|
+
isEmailString,
|
|
49
|
+
isValidUuidString,
|
|
50
|
+
} = require('@carecard/validate');
|
|
40
51
|
```
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
`validate` namespace.
|
|
53
|
+
General validators are available both as top-level exports and under the
|
|
54
|
+
deprecated `validate` namespace. `validatePassword` is intentionally available
|
|
55
|
+
only as one direct export.
|
|
44
56
|
|
|
45
57
|
```js
|
|
46
58
|
isEmailString('jane@example.com'); // true
|
|
@@ -49,43 +61,37 @@ validate.isEmailString('jane@example.com'); // true
|
|
|
49
61
|
|
|
50
62
|
## Direct Validators
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `isValidDomainName(value)` | Domain name with at least one dot, valid DNS-like labels, and max total length 253. |
|
|
84
|
-
| `isValidTimestampzString(value)` | ISO 8601 timestamp with `Z` or `+/-HH:MM` timezone offset. |
|
|
85
|
-
| `isValidTimestampString(value)` | ISO 8601 timestamp without timezone offset. |
|
|
86
|
-
| `isValidDateString(value)` | ISO date in `YYYY-MM-DD` format. |
|
|
87
|
-
| `isValidUrl(value)` | Absolute `http://` or `https://` URL up to 2048 chars. |
|
|
88
|
-
| `isValidArrayOfStrings(value)` | Array where every element passes `isSafeString`. |
|
|
64
|
+
General direct validators return `true` or `false`.
|
|
65
|
+
|
|
66
|
+
| Function | Accepted value |
|
|
67
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
68
|
+
| `isImageUrl(value)` | Non-empty string up to 2048 chars using letters, numbers, `-`, `_`, `.`, and `/`. Intended for safe image/file paths. |
|
|
69
|
+
| `isInteger(value)` | JavaScript integer number. String numbers are rejected. |
|
|
70
|
+
| `isValidJsonString(value)` | Non-empty string up to 10000 chars that parses to a non-null JSON object or array. JSON primitives are rejected. |
|
|
71
|
+
| `isValidIntegerString(value)` | Digit-only string, 1 to 20 chars. No signs or decimals. |
|
|
72
|
+
| `isValidUuidString(value)` | Canonical UUID string in `8-4-4-4-12` format, case-insensitive. |
|
|
73
|
+
| `isCharactersString(value)` | 1 to 1000 chars containing letters, numbers, spaces, `_`, or `-`. |
|
|
74
|
+
| `isStreetString(value)` | Non-empty street-like string up to 1000 chars using letters, numbers, spaces, `,`, `.`, `/`, `#`, or `-`, and not starting with `,`, `_`, or `-`. |
|
|
75
|
+
| `isNameString(value)` | 1 to 1000 char string that starts with a letter and uses letters, numbers, spaces, `_`, `-`, `.`, `,`, `'`, `(`, or `)`. Leading/trailing spaces are trimmed before pattern validation. |
|
|
76
|
+
| `isSafeSearchString(value)` | Trimmed string that starts with a letter and then uses letters, numbers, spaces, `_`, `-`, `.`, `,`, `'`, `(`, `)`, or `@`. |
|
|
77
|
+
| `isEmailString(value)` | Email-like string up to 320 chars using the package email regex. |
|
|
78
|
+
| `isJwtString(value)` | Non-blank JWT-like string up to 8192 chars that starts with `eyJ` and contains only letters, numbers, `-`, `_`, and `.`. |
|
|
79
|
+
| `validatePassword(value)` | A well-formed Unicode password from 15 to 128 code points after NFC normalization that is not an exact case-sensitive blocklist match. Returns a discriminated result with the normalized value or a stable failure reason. All Unicode characters, including spaces and emoji, are permitted without composition rules. |
|
|
80
|
+
| `isUsernameString(value)` | 1 to 200 alphanumeric chars. |
|
|
81
|
+
| `isPhoneNumber(value)` | North American 10-digit phone number with optional parentheses around the area code and optional space, `-`, or `.` separators. |
|
|
82
|
+
| `isUrlSafeString(value)` | Non-blank string up to 2048 chars using letters, numbers, `-`, `_`, and `.`. |
|
|
83
|
+
| `isProvinceString(value)` | `ON` or `QC`, case-insensitive. |
|
|
84
|
+
| `isBoolValue(value)` | Boolean `true`/`false` or strings `"true"`/`"false"`. |
|
|
85
|
+
| `isPostalCodeString(value)` | Canadian postal code format, case-insensitive, with optional middle space. |
|
|
86
|
+
| `isSafeString(value)` | 1 to 10000 chars using letters, numbers, spaces, `-`, `_`, `.`, `,`, `#`, `*`, `'`, `(`, `)`, `[`, `]`, or `:`. |
|
|
87
|
+
| `isInStringArray(array, value)` | `value`, after lowercase/trim validation as a name string, is included in the supplied array. |
|
|
88
|
+
| `isCountryCodeString(value)` | Country dialing code in `+1` to `+999` format. |
|
|
89
|
+
| `isValidDomainName(value)` | Domain name with at least one dot, valid DNS-like labels, and max total length 253. |
|
|
90
|
+
| `isValidTimestampzString(value)` | ISO 8601 timestamp with `Z` or `+/-HH:MM` timezone offset. |
|
|
91
|
+
| `isValidTimestampString(value)` | ISO 8601 timestamp without timezone offset. |
|
|
92
|
+
| `isValidDateString(value)` | ISO date in `YYYY-MM-DD` format. |
|
|
93
|
+
| `isValidUrl(value)` | Absolute `http://` or `https://` URL up to 2048 chars. |
|
|
94
|
+
| `isValidArrayOfStrings(value)` | Array where every element passes `isSafeString`. |
|
|
89
95
|
|
|
90
96
|
## `validateProperties(obj)`
|
|
91
97
|
|
|
@@ -96,10 +102,10 @@ argument returns `{}`.
|
|
|
96
102
|
|
|
97
103
|
```js
|
|
98
104
|
const input = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
first_name: 'Jane',
|
|
106
|
+
email: 'jane@example.com',
|
|
107
|
+
phone_number: '123',
|
|
108
|
+
unknown_key: 'ignored',
|
|
103
109
|
};
|
|
104
110
|
|
|
105
111
|
validateProperties(input);
|
|
@@ -114,30 +120,29 @@ validateProperties(input);
|
|
|
114
120
|
Keys are matched exactly. Both snake_case and camelCase variants are listed
|
|
115
121
|
where the package supports both.
|
|
116
122
|
|
|
117
|
-
| Validator
|
|
118
|
-
|
|
|
119
|
-
| `isNameString`
|
|
120
|
-
| `isStreetString`
|
|
121
|
-
| `isCharactersString`
|
|
122
|
-
| `isBoolValue`
|
|
123
|
-
| `isSafeSearchString`
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `isValidJsonString`
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `isValidDateString` | `effective_start_date`, `effectiveStartDate`, `effective_end_date`, `effectiveEndDate`, `valid_until_date`, `validUntilDate`, `renew_date`, `renewDate` |
|
|
123
|
+
| Validator | Keys |
|
|
124
|
+
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| `isNameString` | `first_name`, `firstName`, `last_name`, `lastName`, `username`, `new_status`, `newStatus`, `description`, `comment`, `status`, `name`, `title`, `brand`, `short_description`, `shortDescription`, `college_name`, `collegeName`, `campus_name`, `campusName`, `role`, `role_id`, `roleId`, `campus`, `institution_name`, `institutionName`, `program_name`, `programName`, `role_name`, `roleName`, `document_name`, `documentName`, `document_required_for_role_name`, `documentRequiredForRoleName`, `reason`, `entity_type`, `entityType`, `action_type`, `actionType`, `city`, `state`, `country`, `type` |
|
|
126
|
+
| `isStreetString` | `street` |
|
|
127
|
+
| `isCharactersString` | `postal_code`, `postalCode`, `period` |
|
|
128
|
+
| `isBoolValue` | `is_primary`, `isPrimary`, `active`, `document_optional`, `documentOptional` |
|
|
129
|
+
| `isSafeSearchString` | `search_string`, `searchString` |
|
|
130
|
+
| `validatePassword` | `password`, `new_password`, `newPassword` |
|
|
131
|
+
| `isEmailString` | `email` |
|
|
132
|
+
| `isPhoneNumber` | `phone_number`, `phoneNumber` |
|
|
133
|
+
| `isCountryCodeString` | `country_code`, `countryCode` |
|
|
134
|
+
| `isUrlSafeString` | `token`, `email_verification_token`, `emailVerificationToken`, `verification_token`, `verificationToken` |
|
|
135
|
+
| `isValidUuidString` | `uuid`, `item_id`, `itemId`, `user_id`, `userId`, `address_id`, `addressId`, `order_id`, `orderId`, `category_id`, `categoryId`, `parent_id`, `parentId`, `college_id`, `collegeId`, `campus_id`, `campusId`, `program_id`, `programId`, `program_term_id`, `programTermId`, `template_id`, `templateId`, `program_template_id`, `programTemplateId`, `user_item_id`, `userItemId`, `user_item_status_id`, `userItemStatusId`, `requirement_item_id`, `requirementItemId`, `program_document_id`, `programDocumentId`, `id`, `institution_id`, `institutionId`, `role_assignment_id`, `roleAssignmentId`, `user_role_id`, `userRoleId`, `phone_number_id`, `phoneNumberId`, `entity_id`, `entityId`, `changed_by`, `changedBy`, `request_id`, `requestId` |
|
|
136
|
+
| `isCcIdString` | `cc_id`, `ccId` |
|
|
137
|
+
| `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
|
|
138
|
+
| `isValidJsonString` on the raw value | `about` |
|
|
139
|
+
| `isValidJsonString(JSON.stringify(value))` | `weight`, `dimensions`, `permission`, `scope_data`, `scopeData`, `meta_data`, `metaData`, `metadata` |
|
|
140
|
+
| `isTextString` | `document_description`, `documentDescription`, `nick_name`, `nickName`, `requested_by_name`, `requestedByName`, `requested_by_email`, `requestedByEmail`, `requested_by_phone`, `requestedByPhone`, `approved_by_name`, `approvedByName`, `approved_by_email`, `approvedByEmail`, `approved_by_phone`, `approvedByPhone` |
|
|
141
|
+
| `isValidArrayOfStrings` | `aliases` |
|
|
142
|
+
| `isImageUrl` or `isValidUrl` | `image_url`, `imageUrl`, `website`, `file_url`, `fileUrl` |
|
|
143
|
+
| `isValidDomainName` | `domain_name`, `domainName`, `domain`, `email_domain`, `emailDomain`, `email_domain_name`, `emailDomainName` |
|
|
144
|
+
| `isValidTimestampzString` or `isValidTimestampString` | `expires_at`, `expiresAt`, `start_time`, `startTime`, `end_time`, `endTime` |
|
|
145
|
+
| `isValidDateString` | `effective_start_date`, `effectiveStartDate`, `effective_end_date`, `effectiveEndDate`, `valid_until_date`, `validUntilDate`, `renew_date`, `renewDate` |
|
|
141
146
|
|
|
142
147
|
## `validateWhitelistProperties(inputObject, requiredProperties, options)`
|
|
143
148
|
|
|
@@ -147,14 +152,14 @@ a `Promise<ValidatePropertiesResult>` with the sanitized output.
|
|
|
147
152
|
|
|
148
153
|
```js
|
|
149
154
|
const body = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
first_name: 'Jane',
|
|
156
|
+
email: 'jane@example.com',
|
|
157
|
+
role: 'Admin',
|
|
158
|
+
extra: '<script>',
|
|
154
159
|
};
|
|
155
160
|
|
|
156
161
|
const out = await validateWhitelistProperties(body, ['first_name', 'email'], {
|
|
157
|
-
|
|
162
|
+
optionalProperties: ['role'],
|
|
158
163
|
});
|
|
159
164
|
|
|
160
165
|
// {
|
|
@@ -182,10 +187,10 @@ The default output preserves the nested shape described by whitelisted dot paths
|
|
|
182
187
|
|
|
183
188
|
```js
|
|
184
189
|
const input = {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
user: {
|
|
191
|
+
first_name: 'Jane',
|
|
192
|
+
contact: { email: 'jane@example.com' },
|
|
193
|
+
},
|
|
189
194
|
};
|
|
190
195
|
|
|
191
196
|
await validateWhitelistProperties(input, ['user.first_name', 'user.contact.email']);
|
|
@@ -209,16 +214,22 @@ await validateWhitelistProperties(input, ['user.first_name', 'user.contact.email
|
|
|
209
214
|
Example with only the default options:
|
|
210
215
|
|
|
211
216
|
```js
|
|
212
|
-
await validateWhitelistProperties({ first_name: 'Jane', email: 'jane@example.com', ignored: 'x' }, [
|
|
217
|
+
await validateWhitelistProperties({ first_name: 'Jane', email: 'jane@example.com', ignored: 'x' }, [
|
|
218
|
+
'first_name',
|
|
219
|
+
]);
|
|
213
220
|
// { first_name: 'Jane' }
|
|
214
221
|
```
|
|
215
222
|
|
|
216
223
|
Example with optional properties:
|
|
217
224
|
|
|
218
225
|
```js
|
|
219
|
-
await validateWhitelistProperties(
|
|
226
|
+
await validateWhitelistProperties(
|
|
227
|
+
{ first_name: 'Jane', phone_number: '4165551234' },
|
|
228
|
+
['first_name'],
|
|
229
|
+
{
|
|
220
230
|
optionalProperties: ['phone_number'],
|
|
221
|
-
}
|
|
231
|
+
},
|
|
232
|
+
);
|
|
222
233
|
// { first_name: 'Jane', phone_number: '4165551234' }
|
|
223
234
|
```
|
|
224
235
|
|
|
@@ -241,7 +252,7 @@ Optional paths are ignored when absent, but invalid when present.
|
|
|
241
252
|
|
|
242
253
|
```js
|
|
243
254
|
await validateWhitelistProperties({ first_name: 'Jane', email: 'bad' }, ['first_name'], {
|
|
244
|
-
|
|
255
|
+
optionalProperties: ['email'],
|
|
245
256
|
});
|
|
246
257
|
// userMessage: 'Invalid property value: email'
|
|
247
258
|
```
|
|
@@ -253,13 +264,13 @@ used.
|
|
|
253
264
|
|
|
254
265
|
```js
|
|
255
266
|
const out = await validateWhitelistProperties(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
},
|
|
267
|
+
{
|
|
268
|
+
user: {
|
|
269
|
+
first_name: 'Jane',
|
|
270
|
+
contact: { email: 'jane@example.com', ignored: 'x' },
|
|
261
271
|
},
|
|
262
|
-
|
|
272
|
+
},
|
|
273
|
+
['user.first_name', 'user.contact.email'],
|
|
263
274
|
);
|
|
264
275
|
|
|
265
276
|
// {
|
|
@@ -290,11 +301,15 @@ This array behavior is intended for repeated scalar fields such as `email` or
|
|
|
290
301
|
### Case Conversion And Flattening
|
|
291
302
|
|
|
292
303
|
```js
|
|
293
|
-
const out = await validateWhitelistProperties(
|
|
304
|
+
const out = await validateWhitelistProperties(
|
|
305
|
+
{ userInfo: { firstName: 'Jane', phoneNumber: '4165551234' } },
|
|
306
|
+
['userInfo.firstName'],
|
|
307
|
+
{
|
|
294
308
|
optionalProperties: ['userInfo.phoneNumber'],
|
|
295
309
|
convertToSnakeCase: true,
|
|
296
310
|
flattenOutput: true,
|
|
297
|
-
}
|
|
311
|
+
},
|
|
312
|
+
);
|
|
298
313
|
|
|
299
314
|
// {
|
|
300
315
|
// 'user_info.first_name': 'Jane',
|
|
@@ -316,7 +331,7 @@ With `flattenOutput: true`, keys are full dot paths by default:
|
|
|
316
331
|
```js
|
|
317
332
|
const input = { a: { b: { c: { d: { email: 'jane@example.com', name: 'Jane' } } } } };
|
|
318
333
|
await validateWhitelistProperties(input, ['a.b.c.d.email', 'a.b.c.d.name'], {
|
|
319
|
-
|
|
334
|
+
flattenOutput: true,
|
|
320
335
|
});
|
|
321
336
|
// { 'a.b.c.d.email': 'jane@example.com', 'a.b.c.d.name': 'Jane' }
|
|
322
337
|
```
|
|
@@ -326,8 +341,8 @@ Use `flattenKeyStyle: 'leaf'` to return top-level leaf keys instead:
|
|
|
326
341
|
```js
|
|
327
342
|
const input = { a: { b: { c: { d: { email: 'jane@example.com', name: 'Jane' } } } } };
|
|
328
343
|
await validateWhitelistProperties(input, ['a.b.c.d.email', 'a.b.c.d.name'], {
|
|
329
|
-
|
|
330
|
-
|
|
344
|
+
flattenOutput: true,
|
|
345
|
+
flattenKeyStyle: 'leaf',
|
|
331
346
|
});
|
|
332
347
|
// { email: 'jane@example.com', name: 'Jane' }
|
|
333
348
|
```
|
|
@@ -339,13 +354,13 @@ encountered:
|
|
|
339
354
|
|
|
340
355
|
```js
|
|
341
356
|
const input = {
|
|
342
|
-
|
|
343
|
-
|
|
357
|
+
name: 'Top Level Name',
|
|
358
|
+
user: { name: 'Nested Name', email: 'jane@example.com' },
|
|
344
359
|
};
|
|
345
360
|
|
|
346
361
|
await validateWhitelistProperties(input, ['name', 'user.name', 'user.email'], {
|
|
347
|
-
|
|
348
|
-
|
|
362
|
+
flattenOutput: true,
|
|
363
|
+
flattenKeyStyle: 'leaf',
|
|
349
364
|
});
|
|
350
365
|
// { name: 'Top Level Name', email: 'jane@example.com' }
|
|
351
366
|
```
|
|
@@ -355,10 +370,16 @@ await validateWhitelistProperties(input, ['name', 'user.name', 'user.email'], {
|
|
|
355
370
|
The package ships `index.d.ts` and declares types for the CommonJS exports.
|
|
356
371
|
|
|
357
372
|
```ts
|
|
358
|
-
import {
|
|
373
|
+
import {
|
|
374
|
+
validateWhitelistProperties,
|
|
375
|
+
isEmailString,
|
|
376
|
+
ValidatePropertiesResult,
|
|
377
|
+
} from '@carecard/validate';
|
|
359
378
|
|
|
360
379
|
const valid: boolean = isEmailString('jane@example.com');
|
|
361
|
-
const output: ValidatePropertiesResult = await validateWhitelistProperties({ first_name: 'Jane' }, [
|
|
380
|
+
const output: ValidatePropertiesResult = await validateWhitelistProperties({ first_name: 'Jane' }, [
|
|
381
|
+
'first_name',
|
|
382
|
+
]);
|
|
362
383
|
const maxDepth: 5 = validateWhitelistProperties.MAX_NESTING_DEPTH;
|
|
363
384
|
```
|
|
364
385
|
|
|
@@ -385,7 +406,7 @@ npm run lint
|
|
|
385
406
|
npm run format:check
|
|
386
407
|
```
|
|
387
408
|
|
|
388
|
-
CI runs on Node.js
|
|
409
|
+
CI runs on Node.js 24.20.0 and executes `npm run test:All`. Publishing to npm happens
|
|
389
410
|
from `main` through the `Publish to npm` GitHub workflow.
|
|
390
411
|
|
|
391
412
|
## Auth Boundary
|