@carecard/validate 3.24.0 → 3.26.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.
@@ -110,6 +110,13 @@ depend on those folders being present.
110
110
 
111
111
  - General low-level validators should be deterministic predicate functions that
112
112
  return `true` or `false`.
113
+ - `isEmailString` is the sole CareCard email-format decision. It accepts an
114
+ unmodified ASCII address no longer than 254 characters, with one practical
115
+ dot-atom local part no longer than 64 characters and a valid multi-label
116
+ DNS/punycode domain. It rejects quoted local parts, domain literals, Unicode
117
+ EAI, whitespace, invalid or empty DNS labels, numeric final labels, and
118
+ single-label domains. Callers may map its boolean to domain errors but must
119
+ not apply another email-syntax predicate.
113
120
  - `validatePassword` is the sole password-policy function and sole direct
114
121
  password export. It returns the NFC-normalized accepted value or a stable
115
122
  failure reason; do not add password predicates, aliases, failure-message
package/lib/validate.js CHANGED
@@ -82,14 +82,35 @@ const isSafeSearchString = str => {
82
82
  return /^[A-Za-z][0-9a-zA-Z\-_.,'()@ ]{1,100}$/.test(str.trim());
83
83
  };
84
84
 
85
+ const EMAIL_LOCAL_PART_PATTERN =
86
+ /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
87
+ const EMAIL_DOMAIN_LABEL_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;
88
+
89
+ const isEmailDomain = domain => {
90
+ if (domain.length === 0 || domain.length > 253) {
91
+ return false;
92
+ }
93
+ const labels = domain.split('.');
94
+ return (
95
+ labels.length >= 2 &&
96
+ labels.every(label => EMAIL_DOMAIN_LABEL_PATTERN.test(label)) &&
97
+ !/^\d+$/.test(labels.at(-1))
98
+ );
99
+ };
100
+
85
101
  const isEmailString = email => {
86
- if (typeof email !== 'string' || email.length === 0 || email.length > 320) {
102
+ if (typeof email !== 'string' || email.length === 0 || email.length > 254) {
103
+ return false;
104
+ }
105
+ const separatorIndex = email.indexOf('@');
106
+ if (separatorIndex <= 0 || separatorIndex !== email.lastIndexOf('@')) {
87
107
  return false;
88
108
  }
89
- // Simplified safe email regex
90
- const regExpEmail =
91
- /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-zA-Z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/;
92
- return regExpEmail.test(email);
109
+ const localPart = email.slice(0, separatorIndex);
110
+ const domain = email.slice(separatorIndex + 1);
111
+ return (
112
+ localPart.length <= 64 && EMAIL_LOCAL_PART_PATTERN.test(localPart) && isEmailDomain(domain)
113
+ );
93
114
  };
94
115
 
95
116
  const isJwtString = jwt => {
@@ -100,6 +121,7 @@ const isJwtString = jwt => {
100
121
  return jwtRegex.test(jwt);
101
122
  };
102
123
 
124
+ // Pattern: Policy Function - owns normalization, length, and blocklist decisions for passwords.
103
125
  const validatePassword = password => {
104
126
  if (typeof password !== 'string') {
105
127
  return { isValid: false, reason: 'invalid_type' };
@@ -116,6 +116,10 @@ function validateProperties(obj = {}) {
116
116
  }
117
117
  break;
118
118
  case 'email':
119
+ case 'requested_by_email':
120
+ case 'requestedByEmail':
121
+ case 'approved_by_email':
122
+ case 'approvedByEmail':
119
123
  if (isEmailString(value)) {
120
124
  returnObj[key] = value;
121
125
  }
@@ -207,14 +211,10 @@ function validateProperties(obj = {}) {
207
211
  case 'documentDescription':
208
212
  case 'nick_name':
209
213
  case 'nickName':
210
- case 'requested_by_email':
211
- case 'requestedByEmail':
212
214
  case 'requested_by_phone':
213
215
  case 'requestedByPhone':
214
216
  case 'approved_by_name':
215
217
  case 'approvedByName':
216
- case 'approved_by_email':
217
- case 'approvedByEmail':
218
218
  case 'approved_by_phone':
219
219
  case 'approvedByPhone':
220
220
  if (isTextString(value)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/validate",
3
- "version": "3.24.0",
3
+ "version": "3.26.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.24.0"
50
+ "@carecard/common-util": "3.26.0"
51
51
  },
52
52
  "nyc": {
53
53
  "all": true,
package/readme.md CHANGED
@@ -74,7 +74,7 @@ General direct validators return `true` or `false`.
74
74
  | `isStreetString(value)` | Non-empty street-like string up to 1000 chars using letters, numbers, spaces, `,`, `.`, `/`, `#`, or `-`, and not starting with `,`, `_`, or `-`. |
75
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
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. |
77
+ | `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. |
78
78
  | `isJwtString(value)` | Non-blank JWT-like string up to 8192 chars that starts with `eyJ` and contains only letters, numbers, `-`, `_`, and `.`. |
79
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
80
  | `isUsernameString(value)` | 1 to 200 alphanumeric chars. |
@@ -128,7 +128,7 @@ where the package supports both.
128
128
  | `isBoolValue` | `is_primary`, `isPrimary`, `active`, `document_optional`, `documentOptional` |
129
129
  | `isSafeSearchString` | `search_string`, `searchString` |
130
130
  | `validatePassword` | `password`, `new_password`, `newPassword` |
131
- | `isEmailString` | `email` |
131
+ | `isEmailString` | `email`, `requested_by_email`, `requestedByEmail`, `approved_by_email`, `approvedByEmail` |
132
132
  | `isPhoneNumber` | `phone_number`, `phoneNumber` |
133
133
  | `isCountryCodeString` | `country_code`, `countryCode` |
134
134
  | `isUrlSafeString` | `token`, `email_verification_token`, `emailVerificationToken`, `verification_token`, `verificationToken` |
@@ -137,7 +137,7 @@ where the package supports both.
137
137
  | `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
138
138
  | `isValidJsonString` on the raw value | `about` |
139
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` |
140
+ | `isTextString` | `document_description`, `documentDescription`, `nick_name`, `nickName`, `requested_by_name`, `requestedByName`, `requested_by_phone`, `requestedByPhone`, `approved_by_name`, `approvedByName`, `approved_by_phone`, `approvedByPhone` |
141
141
  | `isValidArrayOfStrings` | `aliases` |
142
142
  | `isImageUrl` or `isValidUrl` | `image_url`, `imageUrl`, `website`, `file_url`, `fileUrl` |
143
143
  | `isValidDomainName` | `domain_name`, `domainName`, `domain`, `email_domain`, `emailDomain`, `email_domain_name`, `emailDomainName` |