@carecard/validate 3.1.25 → 3.1.26
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/.husky/pre-commit +2 -4
- package/index.d.ts +115 -75
- package/lib/validateWhitelistProperties.js +6 -49
- package/package.json +1 -1
- package/readme.md +4 -3
package/.husky/pre-commit
CHANGED
package/index.d.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-compatible TypeScript declarations for the CommonJS
|
|
3
|
+
* `@carecard/validate` package.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ValidatePropertiesInput = Record<string, unknown> | null | undefined;
|
|
7
|
+
export type ValidatePropertiesResult = Record<string, unknown>;
|
|
8
|
+
export type BoolValidator = (input: unknown) => boolean;
|
|
9
|
+
export type FailureMessageValidator = (input: unknown) => string | null;
|
|
10
|
+
export type InStringArrayValidator = (stringArray: readonly string[], input: unknown) => boolean;
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* Utility function to validate multiple properties of an object at once.
|
|
3
14
|
*/
|
|
4
|
-
export function validateProperties(obj?:
|
|
15
|
+
export function validateProperties(obj?: ValidatePropertiesInput): ValidatePropertiesResult;
|
|
16
|
+
|
|
17
|
+
export type ValidateWhitelistFlattenKeyStyle = 'path' | 'leaf';
|
|
5
18
|
|
|
6
19
|
/**
|
|
7
20
|
* Options for {@link validateWhitelistProperties}.
|
|
8
21
|
*/
|
|
9
22
|
export interface ValidateWhitelistPropertiesOptions {
|
|
10
23
|
/** Properties allowed in the input but not required. */
|
|
11
|
-
optionalProperties?: string[];
|
|
24
|
+
optionalProperties?: readonly string[] | null;
|
|
12
25
|
/** When true, the returned object's keys are converted to snake_case. */
|
|
13
26
|
convertToSnakeCase?: boolean;
|
|
14
27
|
/**
|
|
@@ -24,46 +37,74 @@ export interface ValidateWhitelistPropertiesOptions {
|
|
|
24
37
|
*
|
|
25
38
|
* Defaults to `path`.
|
|
26
39
|
*/
|
|
27
|
-
flattenKeyStyle?:
|
|
40
|
+
flattenKeyStyle?: ValidateWhitelistFlattenKeyStyle;
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
): Promise<
|
|
43
|
+
export interface ValidateWhitelistPropertiesFunction {
|
|
44
|
+
/**
|
|
45
|
+
* Validates and transforms whitelisted properties from an input object.
|
|
46
|
+
*
|
|
47
|
+
* - Supports nested objects via dot-notation paths (e.g. `"address.city"`),
|
|
48
|
+
* up to 5 levels deep. The function checks that each path resolves to an
|
|
49
|
+
* existing leaf property and validates the leaf value by its leaf segment.
|
|
50
|
+
* - Extracts only the whitelisted (required + optional) leaf properties and
|
|
51
|
+
* rebuilds the same nested shape in the result.
|
|
52
|
+
* - Validates values via {@link validateProperties}.
|
|
53
|
+
* - Throws a "Bad_Input" error when any required property is missing/invalid,
|
|
54
|
+
* when a provided optional property has an invalid value, when a path
|
|
55
|
+
* exceeds 5 levels of nesting, or when the combined count of
|
|
56
|
+
* `requiredProperties` and `options.optionalProperties` exceeds 5000.
|
|
57
|
+
* - Array values are supported: if a leaf value is an array, the per-leaf
|
|
58
|
+
* validator is applied to each element. The leaf is accepted only when every
|
|
59
|
+
* element passes validation, and the returned value is an array of the
|
|
60
|
+
* validated elements.
|
|
61
|
+
* - Optionally converts the resulting keys, including nested keys, to snake_case.
|
|
62
|
+
* - Optionally flattens the result after snake_case conversion.
|
|
63
|
+
*
|
|
64
|
+
* @param inputObject The input object, for example `req.body` or `req.params`.
|
|
65
|
+
* @param requiredProperties Leaf paths that must be present and valid. Dot notation is supported.
|
|
66
|
+
* @param options Optional additional leaf paths plus output transformation flags.
|
|
67
|
+
*/
|
|
68
|
+
(
|
|
69
|
+
inputObject?: ValidatePropertiesInput,
|
|
70
|
+
requiredProperties?: readonly string[] | null,
|
|
71
|
+
options?: ValidateWhitelistPropertiesOptions | null,
|
|
72
|
+
): Promise<ValidatePropertiesResult>;
|
|
73
|
+
validateWhitelistProperties: ValidateWhitelistPropertiesFunction;
|
|
74
|
+
MAX_NESTING_DEPTH: 5;
|
|
75
|
+
MAX_KEYS_PER_CALL: 5000;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const validateWhitelistProperties: ValidateWhitelistPropertiesFunction;
|
|
79
|
+
|
|
80
|
+
export type UserRoleRequestRole = 'student' | 'intern' | 'volunteer';
|
|
81
|
+
export type UserRoleRequestScopeRequirement = boolean | typeof REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT;
|
|
60
82
|
|
|
61
83
|
export const DEFAULT_USER_ROLE_REQUEST_ROLE: 'student';
|
|
62
84
|
export const REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT: 'whenRoleOrScopePresent';
|
|
63
85
|
|
|
64
86
|
export interface ValidateNewUserRoleRequestOptions {
|
|
65
|
-
defaultRole?:
|
|
66
|
-
requireScope?:
|
|
87
|
+
defaultRole?: UserRoleRequestRole | undefined;
|
|
88
|
+
requireScope?: UserRoleRequestScopeRequirement;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ValidateNewUserRoleRequestInput extends Record<string, unknown> {
|
|
92
|
+
role_name?: unknown;
|
|
93
|
+
roleName?: unknown;
|
|
94
|
+
role?: unknown;
|
|
95
|
+
institution_id?: unknown;
|
|
96
|
+
institutionId?: unknown;
|
|
97
|
+
campus_id?: unknown;
|
|
98
|
+
campusId?: unknown;
|
|
99
|
+
program_id?: unknown;
|
|
100
|
+
programId?: unknown;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ValidateNewUserRoleRequestPayload extends Record<string, unknown> {
|
|
104
|
+
role_name?: string;
|
|
105
|
+
institution_id?: string;
|
|
106
|
+
campus_id?: string;
|
|
107
|
+
program_id?: string;
|
|
67
108
|
}
|
|
68
109
|
|
|
69
110
|
/**
|
|
@@ -72,79 +113,78 @@ export interface ValidateNewUserRoleRequestOptions {
|
|
|
72
113
|
* both institution_id and campus_id must be provided.
|
|
73
114
|
*/
|
|
74
115
|
export function validateNewUserRoleRequestObject(
|
|
75
|
-
roleRequest?:
|
|
76
|
-
options?: ValidateNewUserRoleRequestOptions,
|
|
77
|
-
):
|
|
116
|
+
roleRequest?: ValidateNewUserRoleRequestInput | null,
|
|
117
|
+
options?: ValidateNewUserRoleRequestOptions | null,
|
|
118
|
+
): ValidateNewUserRoleRequestPayload;
|
|
78
119
|
|
|
79
120
|
/** Checks if the string is a valid image URL format. */
|
|
80
|
-
export
|
|
121
|
+
export const isImageUrl: BoolValidator;
|
|
81
122
|
/** Checks if the value is an integer. */
|
|
82
|
-
export
|
|
123
|
+
export const isInteger: BoolValidator;
|
|
83
124
|
/** Checks if the string is a valid JSON string and can be parsed into an object. */
|
|
84
|
-
export
|
|
125
|
+
export const isValidJsonString: BoolValidator;
|
|
85
126
|
/** Checks if the string represents a valid integer. */
|
|
86
|
-
export
|
|
127
|
+
export const isValidIntegerString: BoolValidator;
|
|
87
128
|
/** Checks if the string is a valid UUID. */
|
|
88
|
-
export
|
|
129
|
+
export const isValidUuidString: BoolValidator;
|
|
89
130
|
/** Checks if the string contains only alphanumeric characters, spaces, underscores, or hyphens. */
|
|
90
|
-
export
|
|
131
|
+
export const isCharactersString: BoolValidator;
|
|
91
132
|
/** Checks if the string is a valid street address format. */
|
|
92
|
-
export
|
|
133
|
+
export const isStreetString: BoolValidator;
|
|
93
134
|
/** Checks if the string is a valid name format. */
|
|
94
|
-
export
|
|
135
|
+
export const isNameString: BoolValidator;
|
|
95
136
|
/** Checks if the string is safe for search queries. */
|
|
96
|
-
export
|
|
137
|
+
export const isSafeSearchString: BoolValidator;
|
|
97
138
|
/** Checks if the string is a valid email address. */
|
|
98
|
-
export
|
|
139
|
+
export const isEmailString: BoolValidator;
|
|
99
140
|
/** Checks if the string is a valid JWT format. */
|
|
100
|
-
export
|
|
141
|
+
export const isJwtString: BoolValidator;
|
|
101
142
|
/** Checks if the string is a valid strong password. */
|
|
102
|
-
export
|
|
143
|
+
export const isPasswordString: BoolValidator;
|
|
103
144
|
/** Checks if the string is a valid simple password. */
|
|
104
|
-
export
|
|
145
|
+
export const isSimplePasswordString: BoolValidator;
|
|
105
146
|
/** Returns a failure message if the password is not strong enough. */
|
|
106
|
-
export
|
|
147
|
+
export const isPasswordStringFailureMessage: FailureMessageValidator;
|
|
107
148
|
/** Returns a failure message if the password is not valid as a simple password. */
|
|
108
|
-
export
|
|
149
|
+
export const isSimplePasswordStringFailureMessage: FailureMessageValidator;
|
|
109
150
|
/** Checks if the string is a valid username. */
|
|
110
|
-
export
|
|
151
|
+
export const isUsernameString: BoolValidator;
|
|
111
152
|
/** Checks if the string is a valid phone number. */
|
|
112
|
-
export
|
|
153
|
+
export const isPhoneNumber: BoolValidator;
|
|
113
154
|
/** Checks if the string is URL-safe. */
|
|
114
|
-
export
|
|
155
|
+
export const isUrlSafeString: BoolValidator;
|
|
115
156
|
/** Checks if the string length is between 6 and 24 characters. */
|
|
116
|
-
export
|
|
157
|
+
export const isString6To24CharacterLong: BoolValidator;
|
|
117
158
|
/** Checks if the string length is between 6 and 16 characters. */
|
|
118
|
-
export
|
|
159
|
+
export const isString6To16CharacterLong: BoolValidator;
|
|
119
160
|
/** Checks if the string is a valid Canadian province abbreviation (ON, QC). */
|
|
120
|
-
export
|
|
161
|
+
export const isProvinceString: BoolValidator;
|
|
121
162
|
/** Checks if the value is a boolean. */
|
|
122
|
-
export
|
|
163
|
+
export const isBoolValue: BoolValidator;
|
|
123
164
|
/** Checks if the string is a valid Canadian postal code. */
|
|
124
|
-
export
|
|
165
|
+
export const isPostalCodeString: BoolValidator;
|
|
125
166
|
/** Checks if the string contains only allowed "safe" characters. */
|
|
126
|
-
export
|
|
167
|
+
export const isSafeString: BoolValidator;
|
|
127
168
|
/** Checks if the value is non-empty text up to the supported maximum length. */
|
|
128
|
-
export
|
|
129
|
-
/** Checks if a string exists within a given array of strings
|
|
130
|
-
export
|
|
169
|
+
export const isTextString: BoolValidator;
|
|
170
|
+
/** Checks if a string exists within a given array of strings, case-insensitive. */
|
|
171
|
+
export const isInStringArray: InStringArrayValidator;
|
|
131
172
|
/** Checks if the string is one of the supported user role request statuses. */
|
|
132
|
-
export
|
|
173
|
+
export const isUserRoleRequestStatusString: BoolValidator;
|
|
133
174
|
/** Checks if the string is a supported new user role request role. */
|
|
134
|
-
export
|
|
135
|
-
/** Checks if the string is a valid country code
|
|
136
|
-
export
|
|
175
|
+
export const isUserRoleRequestRoleString: BoolValidator;
|
|
176
|
+
/** Checks if the string is a valid country code, for example +1. */
|
|
177
|
+
export const isCountryCodeString: BoolValidator;
|
|
137
178
|
/** Checks if the string is a valid domain name. */
|
|
138
|
-
export
|
|
179
|
+
export const isValidDomainName: BoolValidator;
|
|
139
180
|
/** Checks if the string is a valid ISO 8601 timestamp with time zone. */
|
|
140
|
-
export
|
|
181
|
+
export const isValidTimestampzString: BoolValidator;
|
|
141
182
|
/** Checks if the string is a valid ISO 8601 timestamp without time zone. */
|
|
142
|
-
export
|
|
143
|
-
|
|
183
|
+
export const isValidTimestampString: BoolValidator;
|
|
144
184
|
/** Checks if the string is a valid URL. */
|
|
145
|
-
export
|
|
185
|
+
export const isValidUrl: BoolValidator;
|
|
146
186
|
/** Checks if the array contains only safe strings. */
|
|
147
|
-
export
|
|
187
|
+
export const isValidArrayOfStrings: BoolValidator;
|
|
148
188
|
|
|
149
189
|
/**
|
|
150
190
|
* Utility functions for validating various types of strings and values.
|
|
@@ -16,8 +16,6 @@ const MAX_NESTING_DEPTH = 5;
|
|
|
16
16
|
* adversarial inputs.
|
|
17
17
|
*/
|
|
18
18
|
const MAX_KEYS_PER_CALL = 5000;
|
|
19
|
-
const DEFAULT_FLATTEN_KEY_STYLE = 'path';
|
|
20
|
-
const VALID_FLATTEN_KEY_STYLES = new Set(['path', 'leaf']);
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* Returns true if the segment contains a mix of snake_case (underscore) and
|
|
@@ -163,35 +161,6 @@ function flattenObject(obj, prefix = '', out = {}) {
|
|
|
163
161
|
return out;
|
|
164
162
|
}
|
|
165
163
|
|
|
166
|
-
/**
|
|
167
|
-
* Recursively flattens a nested plain object using only each leaf property
|
|
168
|
-
* name as the output key.
|
|
169
|
-
*
|
|
170
|
-
* Example: `{ a: { b: { c: 1, d: 2 } } }` => `{ c: 1, d: 2 }`.
|
|
171
|
-
* If duplicate leaf keys exist at different nesting levels, the higher-level
|
|
172
|
-
* leaf wins. If duplicate leaf keys exist at the same depth, the first
|
|
173
|
-
* traversal wins.
|
|
174
|
-
*
|
|
175
|
-
* @param {Object} obj
|
|
176
|
-
* @param {Object} [out]
|
|
177
|
-
* @param {Object} [depthByKey]
|
|
178
|
-
* @param {number} [depth]
|
|
179
|
-
* @returns {Object}
|
|
180
|
-
*/
|
|
181
|
-
function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
|
|
182
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
183
|
-
if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
|
|
184
|
-
flattenObjectByLeafKey(value, out, depthByKey, depth + 1);
|
|
185
|
-
} else {
|
|
186
|
-
if (!Object.prototype.hasOwnProperty.call(out, key) || depth < depthByKey[key]) {
|
|
187
|
-
out[key] = value;
|
|
188
|
-
depthByKey[key] = depth;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return out;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
164
|
/**
|
|
196
165
|
* Validates and transforms whitelisted properties from an input object.
|
|
197
166
|
*
|
|
@@ -212,11 +181,8 @@ function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
|
|
|
212
181
|
* element passes validation, and the returned value is an array of the
|
|
213
182
|
* validated elements (in the same order).
|
|
214
183
|
* 5. Optionally converts all keys (including nested) to snake_case.
|
|
215
|
-
* 6. Optionally flattens the result
|
|
216
|
-
*
|
|
217
|
-
* names when requested (`flattenKeyStyle: 'leaf'`). For duplicate leaf
|
|
218
|
-
* keys in leaf mode, the shallower value wins; ties keep the first value
|
|
219
|
-
* encountered. Applied after snake_case conversion.
|
|
184
|
+
* 6. Optionally flattens the result so every leaf is a top-level key,
|
|
185
|
+
* joined by `.` (`flattenOutput`). Applied after snake_case conversion.
|
|
220
186
|
*
|
|
221
187
|
* @param {Object} inputObject - The input object (e.g., req.body / req.params).
|
|
222
188
|
* @param {Array<string>} [requiredProperties=[]] - Leaf paths that MUST be present and valid.
|
|
@@ -224,26 +190,17 @@ function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
|
|
|
224
190
|
* @param {Array<string>} [options.optionalProperties=[]] - Leaf paths allowed but not required.
|
|
225
191
|
* @param {boolean} [options.convertToSnakeCase=false] - Whether to convert keys to snake_case.
|
|
226
192
|
* @param {boolean} [options.flattenOutput=false] - Whether to flatten the result so that
|
|
227
|
-
* every leaf is a top-level key, with no nested objects in the output.
|
|
228
|
-
* @param {'path'|'leaf'} [options.flattenKeyStyle='path'] - Flattened key naming strategy
|
|
229
|
-
* when `flattenOutput` is true. `path` uses dot-joined paths; `leaf` uses leaf names.
|
|
193
|
+
* every leaf is a top-level key (joined by `.`), with no nested objects in the output.
|
|
230
194
|
* @returns {Promise<Object>} Resolves with the validated (and possibly transformed) object.
|
|
231
195
|
*/
|
|
232
196
|
function validateWhitelistProperties(
|
|
233
197
|
inputObject,
|
|
234
198
|
requiredProperties = [],
|
|
235
|
-
options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false
|
|
199
|
+
options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false },
|
|
236
200
|
) {
|
|
237
201
|
const optionalProperties = (options && options.optionalProperties) || [];
|
|
238
202
|
const convertToSnakeCase = !!(options && options.convertToSnakeCase);
|
|
239
203
|
const flattenOutput = !!(options && options.flattenOutput);
|
|
240
|
-
const flattenKeyStyle = options && options.flattenKeyStyle !== undefined ? options.flattenKeyStyle : DEFAULT_FLATTEN_KEY_STYLE;
|
|
241
|
-
|
|
242
|
-
if (!VALID_FLATTEN_KEY_STYLES.has(flattenKeyStyle)) {
|
|
243
|
-
throwBadInputError({
|
|
244
|
-
userMessage: `Invalid flattenKeyStyle: ${String(flattenKeyStyle)}. Expected "path" or "leaf"`,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
204
|
|
|
248
205
|
// Cap the total number of paths to validate per call.
|
|
249
206
|
const totalKeys = (requiredProperties ? requiredProperties.length : 0) + optionalProperties.length;
|
|
@@ -314,9 +271,9 @@ function validateWhitelistProperties(
|
|
|
314
271
|
validatedObject = keysToSnakeCase(validatedObject);
|
|
315
272
|
}
|
|
316
273
|
|
|
317
|
-
// 6. Optional flattening.
|
|
274
|
+
// 6. Optional flattening: produce a flat object with dot-joined keys.
|
|
318
275
|
if (flattenOutput) {
|
|
319
|
-
validatedObject =
|
|
276
|
+
validatedObject = flattenObject(validatedObject);
|
|
320
277
|
}
|
|
321
278
|
|
|
322
279
|
return Promise.resolve(validatedObject);
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -121,7 +121,7 @@ where the package supports both.
|
|
|
121
121
|
|
|
122
122
|
`validateWhitelistProperties` extracts only the required and optional property
|
|
123
123
|
paths you provide, validates each leaf through `validateProperties`, and returns
|
|
124
|
-
a `Promise<
|
|
124
|
+
a `Promise<ValidatePropertiesResult>` with the sanitized output.
|
|
125
125
|
|
|
126
126
|
```js
|
|
127
127
|
const body = {
|
|
@@ -333,10 +333,11 @@ await validateWhitelistProperties(input, ['name', 'user.name', 'user.email'], {
|
|
|
333
333
|
The package ships `index.d.ts` and declares types for the CommonJS exports.
|
|
334
334
|
|
|
335
335
|
```ts
|
|
336
|
-
import { validateWhitelistProperties, isEmailString } from '@carecard/validate';
|
|
336
|
+
import { validateWhitelistProperties, isEmailString, ValidatePropertiesResult } from '@carecard/validate';
|
|
337
337
|
|
|
338
338
|
const valid: boolean = isEmailString('jane@example.com');
|
|
339
|
-
const output:
|
|
339
|
+
const output: ValidatePropertiesResult = await validateWhitelistProperties({ first_name: 'Jane' }, ['first_name']);
|
|
340
|
+
const maxDepth: 5 = validateWhitelistProperties.MAX_NESTING_DEPTH;
|
|
340
341
|
```
|
|
341
342
|
|
|
342
343
|
## Project Layout
|