@carecard/validate 3.37.0 → 3.39.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 +27 -0
- package/index.d.ts +30 -3
- package/index.js +2 -0
- package/lib/inputValidation.js +164 -0
- package/lib/validate.js +55 -18
- package/lib/validateProperties.js +17 -6
- package/lib/validateWhitelistProperties.js +15 -6
- package/package.json +2 -2
- package/readme.md +80 -9
|
@@ -111,8 +111,35 @@ depend on those folders being present.
|
|
|
111
111
|
|
|
112
112
|
## Validation Behavior
|
|
113
113
|
|
|
114
|
+
- `isNameString`, `isFileNameString`, and `isTextString` share a character
|
|
115
|
+
allowlist: Unicode letters, marks, numbers, space separators, dashes, emoji,
|
|
116
|
+
script/emoji joiners, and `_ . , ' ’ ( ) @ &`. Require a visible letter,
|
|
117
|
+
number, or emoji. Check the complete raw input and its NFKC representation
|
|
118
|
+
before trimming; reject controls, malformed Unicode, private-use characters,
|
|
119
|
+
tags, paths, and syntax outside the allowlist. Preserve accepted display
|
|
120
|
+
names; only filename metadata is returned NFKC-normalized and trimmed.
|
|
121
|
+
Permit only the explicitly allowlisted compatibility punctuation forms and
|
|
122
|
+
check their normalized characters too; do not admit unrelated symbols merely
|
|
123
|
+
because NFKC converts them to letters.
|
|
124
|
+
- `isSafeString` keeps its narrower ASCII subset and `isSafeSearchString`
|
|
125
|
+
keeps its existing search grammar. Neither may hide invalid edge characters
|
|
126
|
+
by trimming before validation. Use format-specific validators for passwords,
|
|
127
|
+
emails, URLs, JSON, addresses, and phones. Role-request phone snapshots use
|
|
128
|
+
`isPhoneContactString`, including an optional international dialing prefix.
|
|
129
|
+
- `isReactionString` preserves allowlisted text and bounded ASCII named tokens
|
|
130
|
+
such as `:reviewed:`. Do not use reaction-token syntax to relax human text.
|
|
114
131
|
- General low-level validators should be deterministic predicate functions that
|
|
115
132
|
return `true` or `false`.
|
|
133
|
+
- Use the direct field-specific predicates from `lib/inputValidation.js` only
|
|
134
|
+
for the matching contract. Keep existing stricter character rules and
|
|
135
|
+
source-owned lengths, requiredness and nullability. Validate raw input before
|
|
136
|
+
trimming; multiline fields may accept LF and CRLF, never standalone CR.
|
|
137
|
+
- Use `isHttpUrlString`, `isMimeTypeString`, `isIdempotencyKeyString` and
|
|
138
|
+
`isNonNegativeSafeInteger` at their structured boundaries. Do not parse a
|
|
139
|
+
numeric prefix or rely on a URL parser to discard unsafe raw characters.
|
|
140
|
+
- Keep extensible metadata as safe JSON data through `isSafeJsonValue` or
|
|
141
|
+
`isSafeJsonString`. Reconstructed and flattened whitelist properties must be
|
|
142
|
+
own data, including reserved-looking keys; never follow inherited paths.
|
|
116
143
|
- `isEmailString` is the sole CareCard email-format decision. It accepts an
|
|
117
144
|
unmodified ASCII address no longer than 254 characters, with one practical
|
|
118
145
|
dot-atom local part no longer than 64 characters and a valid multi-label
|
package/index.d.ts
CHANGED
|
@@ -8,6 +8,27 @@ export type ValidatePropertiesResult = Record<string, unknown>;
|
|
|
8
8
|
export type BoolValidator = (input: unknown) => boolean;
|
|
9
9
|
export type InStringArrayValidator = (stringArray: readonly string[], input: unknown) => boolean;
|
|
10
10
|
|
|
11
|
+
/** Checks raw and NFKC single-line human text characters; callers own lengths and requiredness. */
|
|
12
|
+
export const isSingleLineTextString: BoolValidator;
|
|
13
|
+
/** Checks the human text character allowlist with LF or CRLF line endings. */
|
|
14
|
+
export const isMultilineTextString: BoolValidator;
|
|
15
|
+
/** Checks an unmodified HTTP(S) URL without whitespace, controls, backslashes or angle brackets. */
|
|
16
|
+
export const isHttpUrlString: BoolValidator;
|
|
17
|
+
/** Checks a concrete MIME type and optional token/quoted parameters. */
|
|
18
|
+
export const isMimeTypeString: BoolValidator;
|
|
19
|
+
/** Checks an ASCII idempotency key of 1 through 128 code units. */
|
|
20
|
+
export const isIdempotencyKeyString: BoolValidator;
|
|
21
|
+
/** Checks a nonnegative safe integer number or complete decimal integer string. */
|
|
22
|
+
export const isNonNegativeSafeInteger: BoolValidator;
|
|
23
|
+
/** Checks a version-four UUID with an RFC variant. */
|
|
24
|
+
export const isValidUuidV4String: BoolValidator;
|
|
25
|
+
/** Checks an extensible JSON object/array and nested text, with a 10000-code-unit serialized budget. */
|
|
26
|
+
export const isSafeJsonValue: BoolValidator;
|
|
27
|
+
/** Checks serialized JSON metadata without altering its representation or original length budget. */
|
|
28
|
+
export const isSafeJsonString: BoolValidator;
|
|
29
|
+
/** Checks single-line documentation search text, including printable code punctuation. */
|
|
30
|
+
export const isDocumentationSearchString: BoolValidator;
|
|
31
|
+
|
|
11
32
|
export type PasswordValidationFailureReason =
|
|
12
33
|
'invalid_type' | 'invalid_unicode' | 'too_short' | 'too_long' | 'blocked';
|
|
13
34
|
export type PasswordValidationResult =
|
|
@@ -144,9 +165,9 @@ export const isCcIdString: BoolValidator;
|
|
|
144
165
|
export const isCharactersString: BoolValidator;
|
|
145
166
|
/** Checks if the string is a valid street address format. */
|
|
146
167
|
export const isStreetString: BoolValidator;
|
|
147
|
-
/** Checks visible
|
|
168
|
+
/** Checks allowlisted visible Unicode names up to 1000 UTF-16 code units, including raw input edges. */
|
|
148
169
|
export const isNameString: BoolValidator;
|
|
149
|
-
/** Checks NFKC-trimmed filename metadata up to 255 code points
|
|
170
|
+
/** Checks the plain-text allowlist and NFKC-trimmed filename metadata up to 255 code points. */
|
|
150
171
|
export const isFileNameString: BoolValidator;
|
|
151
172
|
/** Checks if the string is safe for search queries. */
|
|
152
173
|
export const isSafeSearchString: BoolValidator;
|
|
@@ -158,6 +179,8 @@ export const isJwtString: BoolValidator;
|
|
|
158
179
|
export const isUsernameString: BoolValidator;
|
|
159
180
|
/** Checks if the string is a valid phone number. */
|
|
160
181
|
export const isPhoneNumber: BoolValidator;
|
|
182
|
+
/** Checks national or international phone contact snapshots with at most 15 international digits. */
|
|
183
|
+
export const isPhoneContactString: BoolValidator;
|
|
161
184
|
/** Checks if the string is URL-safe. */
|
|
162
185
|
export const isUrlSafeString: BoolValidator;
|
|
163
186
|
/** Checks if the string is a valid Canadian province abbreviation (ON, QC). */
|
|
@@ -168,8 +191,10 @@ export const isBoolValue: BoolValidator;
|
|
|
168
191
|
export const isPostalCodeString: BoolValidator;
|
|
169
192
|
/** Checks if the string contains only allowed "safe" characters. */
|
|
170
193
|
export const isSafeString: BoolValidator;
|
|
171
|
-
/** Checks
|
|
194
|
+
/** Checks allowlisted visible Unicode text up to 10000 UTF-16 code units. */
|
|
172
195
|
export const isTextString: BoolValidator;
|
|
196
|
+
/** Checks allowlisted text or ASCII named reaction tokens, at most 64 trimmed code units. */
|
|
197
|
+
export const isReactionString: BoolValidator;
|
|
173
198
|
/** Checks if a string exists within a given array of strings, case-insensitive. */
|
|
174
199
|
export const isInStringArray: InStringArrayValidator;
|
|
175
200
|
/** Checks if the string is one of the supported user role request statuses. */
|
|
@@ -211,12 +236,14 @@ export const validate: {
|
|
|
211
236
|
isJwtString: typeof isJwtString;
|
|
212
237
|
isUsernameString: typeof isUsernameString;
|
|
213
238
|
isPhoneNumber: typeof isPhoneNumber;
|
|
239
|
+
isPhoneContactString: typeof isPhoneContactString;
|
|
214
240
|
isUrlSafeString: typeof isUrlSafeString;
|
|
215
241
|
isProvinceString: typeof isProvinceString;
|
|
216
242
|
isBoolValue: typeof isBoolValue;
|
|
217
243
|
isPostalCodeString: typeof isPostalCodeString;
|
|
218
244
|
isSafeString: typeof isSafeString;
|
|
219
245
|
isTextString: typeof isTextString;
|
|
246
|
+
isReactionString: typeof isReactionString;
|
|
220
247
|
isInStringArray: typeof isInStringArray;
|
|
221
248
|
isUserRoleRequestStatusString: typeof isUserRoleRequestStatusString;
|
|
222
249
|
isUserRoleRequestRoleString: typeof isUserRoleRequestRoleString;
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const { validatePassword, ...legacyValidate } = validate;
|
|
|
3
3
|
const validateProperties = require('./lib/validateProperties');
|
|
4
4
|
const validateWhitelistProperties = require('./lib/validateWhitelistProperties');
|
|
5
5
|
const validateNewUserRoleRequest = require('./lib/validateNewUserRoleRequest');
|
|
6
|
+
const inputValidation = require('./lib/inputValidation');
|
|
6
7
|
|
|
7
8
|
module.exports = {
|
|
8
9
|
validate: legacyValidate,
|
|
@@ -12,4 +13,5 @@ module.exports = {
|
|
|
12
13
|
...validateNewUserRoleRequest,
|
|
13
14
|
...legacyValidate,
|
|
14
15
|
...validateProperties,
|
|
16
|
+
...inputValidation,
|
|
15
17
|
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isValidIntegerString, isValidJsonString } = require('./validate');
|
|
4
|
+
|
|
5
|
+
const SINGLE_LINE_TEXT =
|
|
6
|
+
/^[\p{L}\p{M}\p{N}\p{Zs}\p{Pd}\p{Sc}\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji_Modifier}_.,'’‘“”"():;!?/@&#+%=[\]{}*\u200c\u200d]*$/u;
|
|
7
|
+
const DOCUMENTATION_SEARCH =
|
|
8
|
+
/^[\x20-\x3b\x3d\x3f-\x7e\p{L}\p{M}\p{N}\p{Zs}\p{Pd}\p{Sc}\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji_Modifier}\u200c\u200d]*$/u;
|
|
9
|
+
const HTTP_URL_CHARACTERS =
|
|
10
|
+
/^[-\p{L}\p{M}\p{N}\p{Pd}\p{Sc}\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji_Modifier}._~:/?#[\]@!$&'()*+,;=%\u200c\u200d]+$/u;
|
|
11
|
+
const MIME_NAME = '[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}';
|
|
12
|
+
const MIME_TOKEN = "[!#$%&'*+.^_`|~0-9A-Za-z-]+";
|
|
13
|
+
const MIME_QUOTED_VALUE =
|
|
14
|
+
'"(?:[\\x20\\x21\\x23-\\x3b\\x3d\\x3f-\\x5b\\x5d-\\x7e]|\\\\[\\x20-\\x3b\\x3d\\x3f-\\x7e])*"';
|
|
15
|
+
const MIME_TYPE = new RegExp(
|
|
16
|
+
`^${MIME_NAME}/${MIME_NAME}(?: *; *${MIME_NAME}=(?:${MIME_TOKEN}|${MIME_QUOTED_VALUE}))*$`,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
function hasRawAndNormalizedCharacters(value, allowlist) {
|
|
20
|
+
if (typeof value !== 'string' || !value.isWellFormed()) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const rawCharacters = value.replace(
|
|
24
|
+
/[\u2024-\u2026\u207d\u207e\u208d\u208e\ufe10-\ufe19\ufe30-\ufe36\ufe4d-\ufe52\ufe54-\ufe5e\ufe60-\ufe61\ufe68-\ufe6b\uff01-\uff02\uff03-\uff0b\uff0c-\uff0f\uff1a-\uff1b\uff1d\uff1f-\uff20\uff3b\uff3d\uff3f\uff5b\uff5d]/gu,
|
|
25
|
+
'',
|
|
26
|
+
);
|
|
27
|
+
return allowlist.test(rawCharacters) && allowlist.test(value.normalize('NFKC'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Character validation only; the owning field defines length and requiredness. */
|
|
31
|
+
function isSingleLineTextString(value) {
|
|
32
|
+
return hasRawAndNormalizedCharacters(value, SINGLE_LINE_TEXT);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Allows LF and CRLF line endings, while rejecting standalone CR. */
|
|
36
|
+
function isMultilineTextString(value) {
|
|
37
|
+
return (
|
|
38
|
+
typeof value === 'string' &&
|
|
39
|
+
value.isWellFormed() &&
|
|
40
|
+
isSingleLineTextString(value.replace(/\r\n/g, '\n').replace(/\n/g, ''))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isDocumentationSearchString(value) {
|
|
45
|
+
return hasRawAndNormalizedCharacters(value, DOCUMENTATION_SEARCH);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isHttpUrlString(value) {
|
|
49
|
+
if (!hasRawAndNormalizedCharacters(value, HTTP_URL_CHARACTERS)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const url = new URL(value);
|
|
54
|
+
return url.protocol === 'https:' || url.protocol === 'http:';
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isMimeTypeString(value) {
|
|
61
|
+
return (
|
|
62
|
+
typeof value === 'string' && /^[\x20-\x3b\x3d\x3f-\x7e]+$/.test(value) && MIME_TYPE.test(value)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isIdempotencyKeyString(value) {
|
|
67
|
+
return typeof value === 'string' && value.length <= 128 && /^[A-Za-z0-9_.:-]+$/.test(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isNonNegativeSafeInteger(value) {
|
|
71
|
+
if (typeof value === 'number') {
|
|
72
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
73
|
+
}
|
|
74
|
+
return isValidIntegerString(value) && Number.isSafeInteger(Number(value));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isValidUuidV4String(value) {
|
|
78
|
+
return (
|
|
79
|
+
typeof value === 'string' &&
|
|
80
|
+
value.length === 36 &&
|
|
81
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isJsonContainer(value) {
|
|
86
|
+
return (
|
|
87
|
+
value !== null &&
|
|
88
|
+
typeof value === 'object' &&
|
|
89
|
+
(Array.isArray(value) ||
|
|
90
|
+
Object.getPrototypeOf(value) === Object.prototype ||
|
|
91
|
+
Object.getPrototypeOf(value) === null)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isSafeJsonScalar(value) {
|
|
96
|
+
return (
|
|
97
|
+
value === null ||
|
|
98
|
+
typeof value === 'boolean' ||
|
|
99
|
+
(typeof value === 'number' && Number.isFinite(value)) ||
|
|
100
|
+
(typeof value === 'string' && isMultilineTextString(value))
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function appendSafeJsonProperties(value, pending) {
|
|
105
|
+
for (const [key, property] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
|
|
106
|
+
if (!property.enumerable) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!isSingleLineTextString(key) || !Object.hasOwn(property, 'value')) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
pending.push({ value: property.value, finished: false });
|
|
113
|
+
}
|
|
114
|
+
return Object.getOwnPropertySymbols(value).length === 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hasSafeJsonTree(root) {
|
|
118
|
+
const ancestors = new Set();
|
|
119
|
+
const pending = [{ value: root, finished: false }];
|
|
120
|
+
while (pending.length > 0) {
|
|
121
|
+
const { value, finished } = pending.pop();
|
|
122
|
+
if (finished) {
|
|
123
|
+
ancestors.delete(value);
|
|
124
|
+
} else if (!isSafeJsonScalar(value)) {
|
|
125
|
+
if (!isJsonContainer(value) || ancestors.has(value)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
ancestors.add(value);
|
|
129
|
+
pending.push({ value, finished: true });
|
|
130
|
+
if (!appendSafeJsonProperties(value, pending)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Extensible JSON object/array, with the existing 10000-code-unit metadata budget. */
|
|
139
|
+
function isSafeJsonValue(value) {
|
|
140
|
+
try {
|
|
141
|
+
return (
|
|
142
|
+
isJsonContainer(value) && hasSafeJsonTree(value) && JSON.stringify(value).length <= 10000
|
|
143
|
+
);
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isSafeJsonString(value) {
|
|
150
|
+
return isValidJsonString(value) && isSafeJsonValue(JSON.parse(value));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
isSingleLineTextString,
|
|
155
|
+
isMultilineTextString,
|
|
156
|
+
isHttpUrlString,
|
|
157
|
+
isMimeTypeString,
|
|
158
|
+
isIdempotencyKeyString,
|
|
159
|
+
isNonNegativeSafeInteger,
|
|
160
|
+
isValidUuidV4String,
|
|
161
|
+
isSafeJsonValue,
|
|
162
|
+
isSafeJsonString,
|
|
163
|
+
isDocumentationSearchString,
|
|
164
|
+
};
|
package/lib/validate.js
CHANGED
|
@@ -68,21 +68,34 @@ const isStreetString = str => {
|
|
|
68
68
|
return streetRegex.test(value);
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
// Pattern: Predicate -
|
|
72
|
-
const
|
|
71
|
+
// Pattern: Predicate - checks every character against one Unicode plain-text allowlist.
|
|
72
|
+
const hasAllowlistedVisibleText = value => {
|
|
73
|
+
if (!value.isWellFormed()) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const normalizedValue = value.normalize('NFKC');
|
|
77
|
+
const nonAllowlistedCharacterPattern =
|
|
78
|
+
/[^\u200c\u200d\p{L}\p{M}\p{N}\p{Zs}\p{Pd}\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji_Modifier}_.,'’()@&]/u;
|
|
79
|
+
// These known punctuation forms are allowlisted; their NFKC output is checked below.
|
|
80
|
+
const originalCharactersToCheck = value.replace(
|
|
81
|
+
/[\u2024-\u2026\u207d\u207e\u208d\u208e\ufe10\ufe19\ufe30\ufe33-\ufe36\ufe4d-\ufe50\ufe52\ufe59\ufe5a\ufe60\ufe6b\uff06-\uff09\uff0c\uff0e\uff20\uff3f]/gu,
|
|
82
|
+
'',
|
|
83
|
+
);
|
|
73
84
|
return (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/[\p{L}\p{N}\p{
|
|
85
|
+
!nonAllowlistedCharacterPattern.test(originalCharactersToCheck) &&
|
|
86
|
+
!nonAllowlistedCharacterPattern.test(normalizedValue) &&
|
|
87
|
+
/[\p{L}\p{N}\p{Extended_Pictographic}\p{Emoji_Presentation}]/u.test(
|
|
88
|
+
normalizedValue.replace(/\p{Default_Ignorable_Code_Point}/gu, ''),
|
|
89
|
+
)
|
|
77
90
|
);
|
|
78
91
|
};
|
|
79
92
|
|
|
80
|
-
// Pattern: Predicate -
|
|
93
|
+
// Pattern: Predicate - validates the original name before callers trim accepted spaces.
|
|
81
94
|
const isNameString = str => {
|
|
82
95
|
if (typeof str !== 'string' || str.length === 0 || str.length > 1000) {
|
|
83
96
|
return false;
|
|
84
97
|
}
|
|
85
|
-
return
|
|
98
|
+
return hasAllowlistedVisibleText(str);
|
|
86
99
|
};
|
|
87
100
|
|
|
88
101
|
// Pattern: Predicate - validates canonical display metadata, never a filesystem path.
|
|
@@ -90,21 +103,17 @@ const isFileNameString = str => {
|
|
|
90
103
|
if (typeof str !== 'string') {
|
|
91
104
|
return false;
|
|
92
105
|
}
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
[...fileName].length <= 255 &&
|
|
96
|
-
hasSafeVisibleName(fileName) &&
|
|
97
|
-
fileName !== '.' &&
|
|
98
|
-
fileName !== '..' &&
|
|
99
|
-
!/[/\\]/u.test(fileName)
|
|
100
|
-
);
|
|
106
|
+
return hasAllowlistedVisibleText(str) && [...str.normalize('NFKC').trim()].length <= 255;
|
|
101
107
|
};
|
|
102
108
|
|
|
109
|
+
// Pattern: Predicate - combines the text allowlist with the existing ASCII search grammar.
|
|
103
110
|
const isSafeSearchString = str => {
|
|
104
111
|
if (typeof str !== 'string' || str.length === 0) {
|
|
105
112
|
return false;
|
|
106
113
|
}
|
|
107
|
-
return
|
|
114
|
+
return (
|
|
115
|
+
hasAllowlistedVisibleText(str) && /^[A-Za-z][0-9a-zA-Z\-_.,'()@ ]{1,100}$/.test(str.trim())
|
|
116
|
+
);
|
|
108
117
|
};
|
|
109
118
|
|
|
110
119
|
const EMAIL_LOCAL_PART_PATTERN =
|
|
@@ -182,6 +191,14 @@ function isPhoneNumber(str) {
|
|
|
182
191
|
return /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/.test(str);
|
|
183
192
|
}
|
|
184
193
|
|
|
194
|
+
// Pattern: Predicate - validates national or international contact snapshots as phone syntax.
|
|
195
|
+
const isPhoneContactString = str => {
|
|
196
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 32 || /[^+0-9(). -]/.test(str)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return isPhoneNumber(str) || /^\+[1-9](?:[ .-]?[0-9]){6,14}$/.test(str);
|
|
200
|
+
};
|
|
201
|
+
|
|
185
202
|
const isUrlSafeString = inputString => {
|
|
186
203
|
if (
|
|
187
204
|
typeof inputString !== 'string' ||
|
|
@@ -219,15 +236,33 @@ const isPostalCodeString = inputString => {
|
|
|
219
236
|
return false;
|
|
220
237
|
};
|
|
221
238
|
|
|
239
|
+
// Pattern: Predicate - retains the stricter ASCII subset of the shared text allowlist.
|
|
222
240
|
const isSafeString = str => {
|
|
223
241
|
if (typeof str !== 'string' || str.length === 0 || str.length > 10000) {
|
|
224
242
|
return false;
|
|
225
243
|
}
|
|
226
|
-
return /^[\da-zA-Z
|
|
244
|
+
return hasAllowlistedVisibleText(str) && /^[\da-zA-Z\-_.,'() ]+$/.test(str);
|
|
227
245
|
};
|
|
228
246
|
|
|
247
|
+
// Pattern: Predicate - applies the shared allowlist within the existing text size budget.
|
|
229
248
|
const isTextString = str => {
|
|
230
|
-
return
|
|
249
|
+
return (
|
|
250
|
+
typeof str === 'string' &&
|
|
251
|
+
str.length > 0 &&
|
|
252
|
+
str.length <= 10000 &&
|
|
253
|
+
hasAllowlistedVisibleText(str)
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Pattern: Predicate - preserves bounded named reaction tokens alongside allowlisted text.
|
|
258
|
+
const isReactionString = str => {
|
|
259
|
+
if (typeof str !== 'string' || str.trim().length > 64) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return (
|
|
263
|
+
isTextString(str) ||
|
|
264
|
+
(!/[^\p{Zs}:a-zA-Z0-9_-]/u.test(str) && /^:[a-zA-Z0-9_-]+:$/.test(str.trim()))
|
|
265
|
+
);
|
|
231
266
|
};
|
|
232
267
|
|
|
233
268
|
const isInStringArray = (StringArray, inputString) => {
|
|
@@ -382,12 +417,14 @@ module.exports = {
|
|
|
382
417
|
validatePassword,
|
|
383
418
|
isUsernameString,
|
|
384
419
|
isPhoneNumber,
|
|
420
|
+
isPhoneContactString,
|
|
385
421
|
isUrlSafeString,
|
|
386
422
|
isProvinceString,
|
|
387
423
|
isBoolValue,
|
|
388
424
|
isPostalCodeString,
|
|
389
425
|
isSafeString,
|
|
390
426
|
isTextString,
|
|
427
|
+
isReactionString,
|
|
391
428
|
isInStringArray,
|
|
392
429
|
isUserRoleRequestStatusString,
|
|
393
430
|
isUserRoleRequestRoleString,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const {
|
|
3
3
|
isEmailString,
|
|
4
4
|
isPhoneNumber,
|
|
5
|
+
isPhoneContactString,
|
|
5
6
|
isUrlSafeString,
|
|
6
7
|
isValidUuidString,
|
|
7
8
|
isCcIdString,
|
|
@@ -10,7 +11,6 @@ const {
|
|
|
10
11
|
isFileNameString,
|
|
11
12
|
isCharactersString,
|
|
12
13
|
isValidIntegerString,
|
|
13
|
-
isValidJsonString,
|
|
14
14
|
isImageUrl,
|
|
15
15
|
isValidDomainName,
|
|
16
16
|
isValidTimestampzString,
|
|
@@ -25,7 +25,14 @@ const {
|
|
|
25
25
|
isUserRoleRequestRoleString,
|
|
26
26
|
isCountryCodeString,
|
|
27
27
|
} = require('./validate');
|
|
28
|
+
const {
|
|
29
|
+
isSingleLineTextString,
|
|
30
|
+
isHttpUrlString,
|
|
31
|
+
isSafeJsonValue,
|
|
32
|
+
isSafeJsonString,
|
|
33
|
+
} = require('./inputValidation');
|
|
28
34
|
|
|
35
|
+
// Pattern: Boundary Validation - preserves field-specific formats while omitting invalid properties.
|
|
29
36
|
function validateProperties(obj = {}) {
|
|
30
37
|
const returnObj = {};
|
|
31
38
|
|
|
@@ -99,7 +106,7 @@ function validateProperties(obj = {}) {
|
|
|
99
106
|
}
|
|
100
107
|
break;
|
|
101
108
|
case 'street':
|
|
102
|
-
if (isStreetString(value)) {
|
|
109
|
+
if (isStreetString(value) && isSingleLineTextString(value)) {
|
|
103
110
|
returnObj[key] = value;
|
|
104
111
|
}
|
|
105
112
|
break;
|
|
@@ -226,11 +233,15 @@ function validateProperties(obj = {}) {
|
|
|
226
233
|
break;
|
|
227
234
|
case 'document_description':
|
|
228
235
|
case 'documentDescription':
|
|
236
|
+
if (isTextString(value)) {
|
|
237
|
+
returnObj[key] = value;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
229
240
|
case 'requested_by_phone':
|
|
230
241
|
case 'requestedByPhone':
|
|
231
242
|
case 'approved_by_phone':
|
|
232
243
|
case 'approvedByPhone':
|
|
233
|
-
if (
|
|
244
|
+
if (isPhoneContactString(value)) {
|
|
234
245
|
returnObj[key] = value;
|
|
235
246
|
}
|
|
236
247
|
break;
|
|
@@ -265,7 +276,7 @@ function validateProperties(obj = {}) {
|
|
|
265
276
|
}
|
|
266
277
|
break;
|
|
267
278
|
case 'about':
|
|
268
|
-
if (
|
|
279
|
+
if (isSafeJsonString(value)) {
|
|
269
280
|
returnObj[key] = value;
|
|
270
281
|
}
|
|
271
282
|
break;
|
|
@@ -277,7 +288,7 @@ function validateProperties(obj = {}) {
|
|
|
277
288
|
case 'meta_data':
|
|
278
289
|
case 'metaData':
|
|
279
290
|
case 'metadata':
|
|
280
|
-
if (
|
|
291
|
+
if (isSafeJsonValue(value)) {
|
|
281
292
|
returnObj[key] = value;
|
|
282
293
|
}
|
|
283
294
|
break;
|
|
@@ -291,7 +302,7 @@ function validateProperties(obj = {}) {
|
|
|
291
302
|
case 'website':
|
|
292
303
|
case 'file_url':
|
|
293
304
|
case 'fileUrl':
|
|
294
|
-
if (isImageUrl(value) || isValidUrl(value)) {
|
|
305
|
+
if (isImageUrl(value) || (isValidUrl(value) && isHttpUrlString(value))) {
|
|
295
306
|
returnObj[key] = value;
|
|
296
307
|
}
|
|
297
308
|
break;
|
|
@@ -142,16 +142,25 @@ function readLeaf(obj, segments) {
|
|
|
142
142
|
* @param {string[]} segments
|
|
143
143
|
* @param {*} value
|
|
144
144
|
*/
|
|
145
|
+
function defineWhitelistDataProperty(target, key, value) {
|
|
146
|
+
Object.defineProperty(target, key, {
|
|
147
|
+
value,
|
|
148
|
+
enumerable: true,
|
|
149
|
+
writable: true,
|
|
150
|
+
configurable: true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
145
154
|
function writeLeaf(target, segments, value) {
|
|
146
155
|
let current = target;
|
|
147
156
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
148
157
|
const key = segments[i];
|
|
149
|
-
if (current[key] === null || typeof current[key] !== 'object') {
|
|
150
|
-
current
|
|
158
|
+
if (!Object.hasOwn(current, key) || current[key] === null || typeof current[key] !== 'object') {
|
|
159
|
+
defineWhitelistDataProperty(current, key, {});
|
|
151
160
|
}
|
|
152
161
|
current = current[key];
|
|
153
162
|
}
|
|
154
|
-
current
|
|
163
|
+
defineWhitelistDataProperty(current, segments[segments.length - 1], value);
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
/**
|
|
@@ -176,7 +185,7 @@ function flattenObject(obj, prefix = '', out = {}) {
|
|
|
176
185
|
) {
|
|
177
186
|
flattenObject(value, path, out);
|
|
178
187
|
} else {
|
|
179
|
-
out
|
|
188
|
+
defineWhitelistDataProperty(out, path, value);
|
|
180
189
|
}
|
|
181
190
|
}
|
|
182
191
|
return out;
|
|
@@ -208,8 +217,8 @@ function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
|
|
|
208
217
|
flattenObjectByLeafKey(value, out, depthByKey, depth + 1);
|
|
209
218
|
} else {
|
|
210
219
|
if (!Object.prototype.hasOwnProperty.call(out, key) || depth < depthByKey[key]) {
|
|
211
|
-
out
|
|
212
|
-
depthByKey
|
|
220
|
+
defineWhitelistDataProperty(out, key, value);
|
|
221
|
+
defineWhitelistDataProperty(depthByKey, key, depth);
|
|
213
222
|
}
|
|
214
223
|
}
|
|
215
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carecard/validate",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.39.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/CareCard-ca/pkg-validate.git"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"typescript": "6.0.3"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@carecard/common-util": "3.
|
|
50
|
+
"@carecard/common-util": "3.39.0"
|
|
51
51
|
},
|
|
52
52
|
"nyc": {
|
|
53
53
|
"all": true,
|
package/readme.md
CHANGED
|
@@ -64,9 +64,9 @@ const {
|
|
|
64
64
|
} = require('@carecard/validate');
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
deprecated `validate` namespace.
|
|
69
|
-
|
|
67
|
+
The original validators are available both as top-level exports and under the
|
|
68
|
+
deprecated `validate` namespace. New field-specific validators and
|
|
69
|
+
`validatePassword` use direct exports.
|
|
70
70
|
|
|
71
71
|
The package root is safe to import from browser bundles. Its whitelist and role
|
|
72
72
|
request validators use the focused `@carecard/common-util/case-converter` and
|
|
@@ -91,18 +91,21 @@ General direct validators return `true` or `false`.
|
|
|
91
91
|
| `isValidUuidString(value)` | Canonical UUID string in `8-4-4-4-12` format, case-insensitive. |
|
|
92
92
|
| `isCharactersString(value)` | 1 to 1000 chars containing letters, numbers, spaces, `_`, or `-`. |
|
|
93
93
|
| `isStreetString(value)` | Non-empty street-like string up to 1000 chars using letters, numbers, spaces, `,`, `.`, `/`, `#`, or `-`, and not starting with `,`, `_`, or `-`. |
|
|
94
|
-
| `isNameString(value)` |
|
|
95
|
-
| `
|
|
94
|
+
| `isNameString(value)` | Visible Unicode plain text from the allowlist below; at most 1000 original UTF-16 code units. Checks raw input and NFKC without rewriting the name. |
|
|
95
|
+
| `isFileNameString(value)` | Same allowlist, with a 255-code-point limit after NFKC and trimming. Rejects paths and punctuation-only names; preserves `.hidden` and `a..b.pdf`. Never use display metadata as a storage path. |
|
|
96
|
+
| `isSafeSearchString(value)` | Checks raw input before trimming; retains the ASCII search grammar (first letter, then letters, numbers, spaces, `_`, `-`, `.`, `,`, `'`, `(`, `)`, or `@`). |
|
|
96
97
|
| `isEmailString(value)` | Unmodified ASCII email up to 254 chars with one dot-atom local part (max 64 chars) and a multi-label DNS/punycode domain. Quoted local parts, domain literals, Unicode EAI, whitespace, empty labels, invalid DNS labels, numeric final labels, and single-label domains are rejected. |
|
|
97
98
|
| `isJwtString(value)` | Non-blank JWT-like string up to 8192 chars that starts with `eyJ` and contains only letters, numbers, `-`, `_`, and `.`. |
|
|
98
99
|
| `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. |
|
|
99
100
|
| `isUsernameString(value)` | 1 to 200 alphanumeric chars. |
|
|
100
101
|
| `isPhoneNumber(value)` | North American 10-digit phone number with optional parentheses around the area code and optional space, `-`, or `.` separators. |
|
|
102
|
+
| `isPhoneContactString(value)` | National phone syntax or an international number starting with `+`, containing 7 to 15 digits and optional single space, `-`, or `.` separators. |
|
|
101
103
|
| `isUrlSafeString(value)` | Non-blank string up to 2048 chars using letters, numbers, `-`, `_`, and `.`. |
|
|
102
104
|
| `isProvinceString(value)` | `ON` or `QC`, case-insensitive. |
|
|
103
105
|
| `isBoolValue(value)` | Boolean `true`/`false` or strings `"true"`/`"false"`. |
|
|
104
106
|
| `isPostalCodeString(value)` | Canadian postal code format, case-insensitive, with optional middle space. |
|
|
105
|
-
| `isSafeString(value)` |
|
|
107
|
+
| `isSafeString(value)` | Visible ASCII plain text, at most 10000 units, using letters, numbers, spaces, `-`, `_`, `.`, `,`, `'`, `(`, or `)`. |
|
|
108
|
+
| `isTextString(value)` | Same Unicode plain-text allowlist as names, at most 10000 original UTF-16 code units. |
|
|
106
109
|
| `isInStringArray(array, value)` | `value`, after lowercase/trim validation as a name string, is included in the supplied array. |
|
|
107
110
|
| `isCountryCodeString(value)` | Country dialing code in `+1` to `+999` format. |
|
|
108
111
|
| `isValidDomainName(value)` | Domain name with at least one dot, valid DNS-like labels, and max total length 253. |
|
|
@@ -112,6 +115,71 @@ General direct validators return `true` or `false`.
|
|
|
112
115
|
| `isValidUrl(value)` | Absolute `http://` or `https://` URL up to 2048 chars. |
|
|
113
116
|
| `isValidArrayOfStrings(value)` | Array where every element passes `isSafeString`. |
|
|
114
117
|
|
|
118
|
+
The plain-text allowlist permits Unicode letters, combining marks, numbers,
|
|
119
|
+
space separators, dashes, emoji, script/emoji joiners, and the punctuation
|
|
120
|
+
`_ . , ' ’ ( ) @ &`. A value must contain a visible letter, number, or emoji.
|
|
121
|
+
Validate raw input before trimming so control characters at either edge cannot
|
|
122
|
+
be hidden. Compatibility normalization is checked without changing accepted
|
|
123
|
+
names; filename output is normalized as documented above. Explicitly allowed
|
|
124
|
+
compatibility punctuation includes fullwidth forms such as `A&B` and the
|
|
125
|
+
ellipsis in `Review…`. Unrelated symbols such as `℡`, `㏂`, and `㍿` are rejected
|
|
126
|
+
even when normalization would turn them into letters.
|
|
127
|
+
|
|
128
|
+
`isReactionString(value)` accepts allowlisted text or an ASCII named reaction
|
|
129
|
+
token such as `:reviewed:` or `:route-covered:`, up to 64 characters after
|
|
130
|
+
trimming ordinary spaces. Named tokens contain only letters, digits, `_` and
|
|
131
|
+
`-` between their two colons. Raw controls and unsupported syntax are rejected.
|
|
132
|
+
|
|
133
|
+
| Accepted examples | Rejected examples |
|
|
134
|
+
| --------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
135
|
+
| `José`, `李`, `محمد`, `123`, `O’Neil`, `A&B`, `👩🏽⚕️` | `<Research>`, `<script>alert(1)</script>`, `$(id)`, backticks |
|
|
136
|
+
| `A_B (Research), Inc.`, `Peter @ Singh`, `Jean–Luc` | `javascript:alert(1)`, `C++`, `Report #1.pdf`, SQL punctuation containing `;` |
|
|
137
|
+
| `A&B.pdf`, `.hidden`, `a..b.pdf`, `李.pdf` | `../secret`, `A/B`, `A\B`, `.`, `..`, controls, private-use glyphs |
|
|
138
|
+
|
|
139
|
+
Passwords, emails, URLs, JSON, addresses, phones and reaction tokens keep their own structured
|
|
140
|
+
format contracts. This allowlist does not replace output encoding, parameterized
|
|
141
|
+
queries, upload-content validation, or storage-path generation.
|
|
142
|
+
|
|
143
|
+
### Field-specific input characters
|
|
144
|
+
|
|
145
|
+
Use `isSingleLineTextString` for previously unrestricted human text and
|
|
146
|
+
`isMultilineTextString` for comments that support line breaks. They check raw
|
|
147
|
+
and NFKC-normalized characters before any trimming. They allow Unicode letters,
|
|
148
|
+
marks, numbers, spaces, dashes, currency symbols, emoji and script/emoji joiners,
|
|
149
|
+
plus `_ . , ' ’ ‘ “ ” " ( ) : ; ! ? / @ & # + % = [ ] { } *` and their
|
|
150
|
+
explicitly supported compatibility forms. Both reject angle brackets,
|
|
151
|
+
malformed Unicode, private-use characters, control and unwanted format
|
|
152
|
+
characters. Multiline text permits LF and CRLF; standalone CR is rejected.
|
|
153
|
+
Empty strings and ordinary spaces pass the lexical check so the owning field
|
|
154
|
+
can retain its requiredness, nullability and length contract. Do not replace an
|
|
155
|
+
existing stricter character rule with these predicates.
|
|
156
|
+
|
|
157
|
+
`isHttpUrlString` checks an unmodified HTTP(S) URL, including its raw
|
|
158
|
+
characters. It rejects whitespace, controls, backslashes and angle brackets
|
|
159
|
+
instead of allowing a URL parser to discard or escape them. Keep the owning
|
|
160
|
+
field's existing length limit. `isMimeTypeString` checks concrete registered
|
|
161
|
+
type/subtype syntax and optional token or quoted parameters; see
|
|
162
|
+
[RFC 6838](https://www.rfc-editor.org/rfc/rfc6838.html#section-4.2) and
|
|
163
|
+
[RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#section-8.3.1).
|
|
164
|
+
|
|
165
|
+
`isIdempotencyKeyString` permits only ASCII letters, digits, `_ . : -`, at most
|
|
166
|
+
128 code units. `isNonNegativeSafeInteger` accepts a safe integer number or a
|
|
167
|
+
complete decimal integer string, with no signs, suffixes, hexadecimal or
|
|
168
|
+
scientific notation. `isValidUuidV4String` retains version-four identifiers
|
|
169
|
+
where the service contract requires them. `isDocumentationSearchString`
|
|
170
|
+
permits single-line documentation queries with printable code punctuation,
|
|
171
|
+
excluding angle brackets and unsafe Unicode/control characters.
|
|
172
|
+
|
|
173
|
+
`isSafeJsonValue` preserves extensible JSON object/array metadata, including
|
|
174
|
+
reserved-looking keys as data. It validates nested keys as single-line text,
|
|
175
|
+
string values as multiline text, finite numbers, booleans, nulls, objects and
|
|
176
|
+
arrays. It rejects cycles and values that JSON would silently discard, and
|
|
177
|
+
retains the 10000-code-unit serialized budget. `isSafeJsonString` checks the
|
|
178
|
+
same contract in an existing serialized JSON field without rewriting it.
|
|
179
|
+
Whitelist output creates own data properties at every path and during
|
|
180
|
+
flattening. Existing property rules additionally require raw single-line
|
|
181
|
+
streets, safe HTTP(S) URL characters and safe nested JSON.
|
|
182
|
+
|
|
115
183
|
## `validateProperties(obj)`
|
|
116
184
|
|
|
117
185
|
`validateProperties` accepts an object and returns a new object that contains
|
|
@@ -141,11 +209,13 @@ where the package supports both.
|
|
|
141
209
|
|
|
142
210
|
| Validator | Keys |
|
|
143
211
|
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
-
| `isNameString` | `first_name`, `firstName`, `last_name`, `lastName`, `
|
|
212
|
+
| `isNameString` | `first_name`, `firstName`, `last_name`, `lastName`, `middle_name`, `middleName`, `nick_name`, `nickName`, `requested_by_name`, `requestedByName`, `approved_by_name`, `approvedByName`, `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` |
|
|
145
213
|
| `isStreetString` | `street` |
|
|
146
214
|
| `isCharactersString` | `postal_code`, `postalCode`, `period` |
|
|
147
215
|
| `isBoolValue` | `is_primary`, `isPrimary`, `active`, `document_optional`, `documentOptional` |
|
|
148
|
-
| `
|
|
216
|
+
| `isNameString` with a trimmed 101-unit limit | `search_string`, `searchString` |
|
|
217
|
+
| `isFileNameString` with NFKC/trim output | `file_name`, `fileName`, `filename` |
|
|
218
|
+
| Existing username creation grammar | `username`: starts with an ASCII letter, followed by the previously supported ASCII letters, digits, spaces and punctuation; up to 1000 UTF-16 units. Login-specific rules stay separate. |
|
|
149
219
|
| `validatePassword` | `password`, `new_password`, `newPassword` |
|
|
150
220
|
| `isEmailString` | `email`, `requested_by_email`, `requestedByEmail`, `approved_by_email`, `approvedByEmail` |
|
|
151
221
|
| `isPhoneNumber` | `phone_number`, `phoneNumber` |
|
|
@@ -156,7 +226,8 @@ where the package supports both.
|
|
|
156
226
|
| `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
|
|
157
227
|
| `isValidJsonString` on the raw value | `about` |
|
|
158
228
|
| `isValidJsonString(JSON.stringify(value))` | `weight`, `dimensions`, `permission`, `scope_data`, `scopeData`, `meta_data`, `metaData`, `metadata` |
|
|
159
|
-
| `isTextString` | `document_description`, `documentDescription
|
|
229
|
+
| `isTextString` | `document_description`, `documentDescription` |
|
|
230
|
+
| `isPhoneContactString` | `requested_by_phone`, `requestedByPhone`, `approved_by_phone`, `approvedByPhone` |
|
|
160
231
|
| `isValidArrayOfStrings` | `aliases` |
|
|
161
232
|
| `isImageUrl` or `isValidUrl` | `image_url`, `imageUrl`, `website`, `file_url`, `fileUrl` |
|
|
162
233
|
| `isValidDomainName` | `domain_name`, `domainName`, `domain`, `email_domain`, `emailDomain`, `email_domain_name`, `emailDomainName` |
|