@carecard/validate 3.18.0 → 3.19.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/carecard-workspace-standards/SKILL.md +49 -20
- package/.agents/skills/github-pr-create-update/SKILL.md +22 -1
- package/.agents/skills/github-pr-merge-cleanup/SKILL.md +22 -1
- package/.agents/skills/logged-in-user-profile-page/SKILL.md +22 -1
- package/.agents/skills/pkg-publish/SKILL.md +22 -1
- package/.agents/skills/pkg-validate-coding-standards-and-best-practices/SKILL.md +47 -9
- package/.agents/skills/pkg-validate-validation-library/SKILL.md +45 -14
- package/.agents/skills/software-design-patterns-and-clean-code/SKILL.md +23 -3
- package/.codex/AGENTS.md +41 -10
- package/.github/workflows/auto-draft-pr.yml +173 -151
- package/.github/workflows/ci.yml +35 -32
- package/.husky/pre-commit +4 -4
- package/.prettierrc.js +10 -9
- package/AGENTS.md +19 -0
- package/eslint.config.mjs +60 -8
- package/index.d.ts +108 -107
- package/index.js +6 -6
- package/lib/validate.js +230 -157
- package/lib/validateNewUserRoleRequest.js +68 -60
- package/lib/validateProperties.js +326 -326
- package/lib/validateWhitelistProperties.js +182 -142
- package/lint-staged.config.mjs +36 -0
- package/package.json +66 -60
- package/readme.md +22 -1
- package/scripts/canonicalTestCommand.test.mjs +32 -0
- package/scripts/packageTaskRunner.audit.mjs +37 -0
- package/scripts/packageTaskRunner.test.mjs +50 -72
- package/scripts/runPackageTask.mjs +103 -58
- package/scripts/testOrder/randomizeTestOrder.cjs +35 -25
- package/scripts/testOrder/randomizeTestOrder.test.mjs +19 -19
- package/scripts/testOrder/testOrderPolicy.audit.mjs +54 -0
- package/scripts/testParallel/parallelTestPolicy.audit.mjs +63 -0
- package/scripts/testParallel/runIndexedMochaTests.cjs +45 -43
- package/scripts/testParallel/runIndexedMochaTests.test.mjs +13 -7
- package/scripts/testOrder/testOrderPolicy.test.mjs +0 -48
- package/scripts/testParallel/parallelTestPolicy.test.mjs +0 -43
package/lib/validate.js
CHANGED
|
@@ -1,279 +1,352 @@
|
|
|
1
1
|
const isImageUrl = imageUrl => {
|
|
2
|
-
|
|
2
|
+
if (
|
|
3
|
+
imageUrl === undefined ||
|
|
4
|
+
typeof imageUrl !== 'string' ||
|
|
5
|
+
imageUrl.length === 0 ||
|
|
6
|
+
imageUrl.length > 2048
|
|
7
|
+
) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
const imageUrlRegex = /^[a-zA-Z0-9-_./]+$/;
|
|
12
|
+
return imageUrlRegex.test(imageUrl);
|
|
6
13
|
};
|
|
7
14
|
|
|
8
15
|
const isInteger = number => {
|
|
9
|
-
|
|
16
|
+
return Number.isInteger(number);
|
|
10
17
|
};
|
|
11
18
|
|
|
19
|
+
// Pattern: Predicate - reports whether a bounded string contains a JSON object or array.
|
|
12
20
|
const isValidJsonString = str => {
|
|
13
|
-
|
|
21
|
+
if (str === undefined || typeof str !== 'string' || str.length === 0 || str.length > 10000) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
25
|
+
try {
|
|
26
|
+
const json = JSON.parse(str);
|
|
27
|
+
return typeof json === 'object' && json !== null;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
22
31
|
};
|
|
23
32
|
|
|
24
33
|
const isValidIntegerString = str => {
|
|
25
|
-
|
|
26
|
-
return
|
|
34
|
+
if (str === undefined || typeof str !== 'string' || str.length === 0 || str.length > 20) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return /^\d+$/.test(str);
|
|
27
38
|
};
|
|
28
39
|
|
|
29
40
|
const isValidUuidString = str => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
if (str === undefined || typeof str !== 'string' || str.length === 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
45
|
+
return uuidRegex.test(str);
|
|
33
46
|
};
|
|
34
47
|
|
|
35
48
|
const isCcIdString = str => {
|
|
36
|
-
|
|
37
|
-
return
|
|
49
|
+
if (typeof str !== 'string') {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return /^[a-z0-9]{8}$/.test(str);
|
|
38
53
|
};
|
|
39
54
|
|
|
40
55
|
const isCharactersString = str => {
|
|
41
|
-
|
|
42
|
-
return
|
|
56
|
+
if (str === undefined || typeof str !== 'string' || str.length === 0 || str.length > 1000) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return /^[\da-zA-Z _-]+$/.test(str);
|
|
43
60
|
};
|
|
44
61
|
|
|
45
62
|
const isStreetString = str => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
if (typeof str !== 'string' || str.trim().length === 0 || str.length > 1000) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const value = str.trim();
|
|
67
|
+
const streetRegex = /^(?![,_-])[0-9a-zA-Z\s,./#-]+$/;
|
|
68
|
+
return streetRegex.test(value);
|
|
52
69
|
};
|
|
53
70
|
|
|
54
71
|
const isNameString = str => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 1000) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return /^[A-Za-z][0-9a-zA-Z-_.,'() ]+$/.test(str.trim());
|
|
59
76
|
};
|
|
60
77
|
|
|
61
78
|
const isSafeSearchString = str => {
|
|
62
|
-
|
|
63
|
-
return
|
|
79
|
+
if (typeof str !== 'string' || str.length === 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return /^[A-Za-z][0-9a-zA-Z\-_.,'()@ ]{1,100}$/.test(str.trim());
|
|
64
83
|
};
|
|
65
84
|
|
|
66
85
|
const isEmailString = email => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
if (typeof email !== 'string' || email.length === 0 || email.length > 320) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
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);
|
|
74
93
|
};
|
|
75
94
|
|
|
76
95
|
const isJwtString = jwt => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
if (typeof jwt !== 'string' || jwt.length === 0 || jwt.length > 8192 || jwt.trim() === '') {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const jwtRegex = /^eyJ[a-zA-Z0-9-_.]+$/;
|
|
100
|
+
return jwtRegex.test(jwt);
|
|
80
101
|
};
|
|
81
102
|
|
|
82
103
|
const isPasswordString = password => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
if (typeof password !== 'string' || password.length === 0 || password.length > 128) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const regExPassword = /^(?=.*[a-zA-Z0-9])(?=.*[!@#$%^&*_-])[a-zA-Z0-9!@#$%^&*_-]{6,32}$/;
|
|
108
|
+
return regExPassword.test(String(password));
|
|
86
109
|
};
|
|
87
110
|
|
|
88
111
|
const isSimplePasswordString = password => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
if (typeof password !== 'string' || password.length === 0 || password.length > 128) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const regExPassword = /^[a-zA-Z0-9!@#$%^&*_-]{6,32}$/;
|
|
116
|
+
return regExPassword.test(String(password));
|
|
92
117
|
};
|
|
93
118
|
|
|
94
119
|
const isPasswordStringFailureMessage = password => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
120
|
+
if (!isPasswordString(password)) {
|
|
121
|
+
return 'Total 6 to 32 characters, numbers and one of !@#$%^&*_-';
|
|
122
|
+
} else {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
100
125
|
};
|
|
101
126
|
|
|
102
127
|
const isSimplePasswordStringFailureMessage = password => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
if (!isSimplePasswordString(password)) {
|
|
129
|
+
return 'Total 6 to 32 characters, numbers or !@#$%^&*_-';
|
|
130
|
+
} else {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
108
133
|
};
|
|
109
134
|
|
|
110
135
|
const isUsernameString = str => {
|
|
111
|
-
|
|
112
|
-
return
|
|
136
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 200) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return /^[0-9a-zA-Z]+$/.test(str);
|
|
113
140
|
};
|
|
114
141
|
|
|
115
142
|
function isPhoneNumber(str) {
|
|
116
|
-
|
|
117
|
-
return
|
|
143
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 20) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/.test(str);
|
|
118
147
|
}
|
|
119
148
|
|
|
120
149
|
const isUrlSafeString = inputString => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
if (
|
|
151
|
+
typeof inputString !== 'string' ||
|
|
152
|
+
inputString.length === 0 ||
|
|
153
|
+
inputString.length > 2048 ||
|
|
154
|
+
inputString.trim() === ''
|
|
155
|
+
) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const urlSafeRegex = /^[a-zA-Z0-9-_.]+$/;
|
|
159
|
+
return urlSafeRegex.test(inputString);
|
|
124
160
|
};
|
|
125
161
|
|
|
126
162
|
const isString6To24CharacterLong = password => {
|
|
127
|
-
|
|
128
|
-
return
|
|
163
|
+
if (typeof password !== 'string' || password.length === 0) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return 6 <= password.length && password.length <= 24;
|
|
129
167
|
};
|
|
130
168
|
|
|
131
169
|
const isString6To16CharacterLong = password => {
|
|
132
|
-
|
|
133
|
-
return
|
|
170
|
+
if (typeof password !== 'string' || password.length === 0) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return 6 <= password.length && password.length <= 16;
|
|
134
174
|
};
|
|
135
175
|
|
|
136
176
|
const isProvinceString = inputString => {
|
|
137
|
-
|
|
177
|
+
const provinces = ['on', 'qc'];
|
|
138
178
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
179
|
+
if (isNameString(inputString)) {
|
|
180
|
+
return provinces.includes(inputString.toLowerCase().trim());
|
|
181
|
+
}
|
|
142
182
|
|
|
143
|
-
|
|
183
|
+
return false;
|
|
144
184
|
};
|
|
145
185
|
|
|
146
186
|
const isBoolValue = inputValue => {
|
|
147
|
-
|
|
187
|
+
return typeof inputValue === 'boolean' || inputValue === 'true' || inputValue === 'false';
|
|
148
188
|
};
|
|
149
189
|
|
|
150
190
|
const isPostalCodeString = inputString => {
|
|
151
|
-
|
|
191
|
+
const postalCodeRegex = /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$/i;
|
|
152
192
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
if (isNameString(inputString)) {
|
|
194
|
+
return postalCodeRegex.test(inputString.trim());
|
|
195
|
+
}
|
|
156
196
|
|
|
157
|
-
|
|
197
|
+
return false;
|
|
158
198
|
};
|
|
159
199
|
|
|
160
200
|
const isSafeString = str => {
|
|
161
|
-
|
|
162
|
-
return
|
|
201
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 10000) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return /^[\da-zA-Z-_.,#*'()[\]: ]+$/.test(str);
|
|
163
205
|
};
|
|
164
206
|
|
|
165
207
|
const isTextString = str => {
|
|
166
|
-
|
|
208
|
+
return typeof str === 'string' && str.length > 0 && str.length <= 10000;
|
|
167
209
|
};
|
|
168
210
|
|
|
169
211
|
const isInStringArray = (StringArray, inputString) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
212
|
+
if (isNameString(inputString)) {
|
|
213
|
+
return StringArray.includes(inputString.toLowerCase().trim());
|
|
214
|
+
}
|
|
173
215
|
|
|
174
|
-
|
|
216
|
+
return false;
|
|
175
217
|
};
|
|
176
218
|
|
|
177
219
|
const isUserRoleRequestStatusString = inputString => {
|
|
178
|
-
|
|
179
|
-
|
|
220
|
+
const statuses = [
|
|
221
|
+
'pending',
|
|
222
|
+
'approved',
|
|
223
|
+
'rejected',
|
|
224
|
+
'cancelled',
|
|
225
|
+
'expired',
|
|
226
|
+
'hidden',
|
|
227
|
+
'on_hold',
|
|
228
|
+
'in_progress',
|
|
229
|
+
'info_needed',
|
|
230
|
+
];
|
|
231
|
+
return isInStringArray(statuses, inputString);
|
|
180
232
|
};
|
|
181
233
|
|
|
182
234
|
const isUserRoleRequestRoleString = inputString => {
|
|
183
|
-
|
|
184
|
-
|
|
235
|
+
const roles = ['student', 'intern', 'volunteer'];
|
|
236
|
+
return typeof inputString === 'string' && roles.includes(inputString.trim().toLowerCase());
|
|
185
237
|
};
|
|
186
238
|
|
|
187
239
|
const isCountryCodeString = str => {
|
|
188
|
-
|
|
240
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 4) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
189
243
|
|
|
190
|
-
|
|
244
|
+
const countryCodeRegex = /^\+[1-9][0-9]{0,2}$/;
|
|
191
245
|
|
|
192
|
-
|
|
246
|
+
return countryCodeRegex.test(str);
|
|
193
247
|
};
|
|
194
248
|
|
|
195
249
|
const isValidDomainName = domain => {
|
|
196
|
-
|
|
250
|
+
if (typeof domain !== 'string' || domain.length === 0 || domain.length > 253) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
197
253
|
|
|
198
|
-
|
|
199
|
-
|
|
254
|
+
const domainRegex =
|
|
255
|
+
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i;
|
|
256
|
+
return domainRegex.test(domain);
|
|
200
257
|
};
|
|
201
258
|
|
|
202
259
|
const isValidTimestampzString = str => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
260
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 64) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
// ISO 8601 for UTC or with Offset: 2023-10-27T10:00:00Z or 2023-10-27T10:00:00+02:00
|
|
264
|
+
const timestampzRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
265
|
+
return timestampzRegex.test(str) && !isNaN(Date.parse(str));
|
|
207
266
|
};
|
|
208
267
|
|
|
209
268
|
const isValidTimestampString = str => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
269
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 64) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
// ISO 8601 without time zone: 2023-10-27T10:00:00 or 2023-10-27T10:00:00.123
|
|
273
|
+
const timestampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/;
|
|
274
|
+
return timestampRegex.test(str) && !isNaN(Date.parse(str));
|
|
214
275
|
};
|
|
215
276
|
|
|
216
277
|
const isValidDateString = str => {
|
|
217
|
-
|
|
278
|
+
if (typeof str !== 'string' || str.length === 0 || str.length > 10) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
218
281
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
282
|
+
const dateRegex = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
283
|
+
const match = str.match(dateRegex);
|
|
284
|
+
if (!match) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
222
287
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
288
|
+
const year = Number(match[1]);
|
|
289
|
+
const month = Number(match[2]);
|
|
290
|
+
const day = Number(match[3]);
|
|
291
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
227
292
|
|
|
228
|
-
|
|
293
|
+
return (
|
|
294
|
+
date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day
|
|
295
|
+
);
|
|
229
296
|
};
|
|
230
297
|
|
|
231
298
|
const isValidUrl = url => {
|
|
232
|
-
|
|
233
|
-
|
|
299
|
+
if (typeof url !== 'string' || url.length === 0 || url.length > 2048) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (!URL.canParse(url)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
234
305
|
|
|
235
|
-
|
|
236
|
-
|
|
306
|
+
const parsedUrl = new URL(url);
|
|
307
|
+
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
|
|
237
308
|
};
|
|
238
309
|
const isValidArrayOfStrings = arr => {
|
|
239
|
-
|
|
240
|
-
return
|
|
310
|
+
if (!Array.isArray(arr)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return arr.every(isSafeString);
|
|
241
314
|
};
|
|
242
315
|
module.exports = {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
316
|
+
isImageUrl,
|
|
317
|
+
isInteger,
|
|
318
|
+
isValidJsonString,
|
|
319
|
+
isValidIntegerString,
|
|
320
|
+
isValidUuidString,
|
|
321
|
+
isCcIdString,
|
|
322
|
+
isCharactersString,
|
|
323
|
+
isStreetString,
|
|
324
|
+
isNameString,
|
|
325
|
+
isSafeSearchString,
|
|
326
|
+
isEmailString,
|
|
327
|
+
isJwtString,
|
|
328
|
+
isPasswordString,
|
|
329
|
+
isSimplePasswordString,
|
|
330
|
+
isPasswordStringFailureMessage,
|
|
331
|
+
isSimplePasswordStringFailureMessage,
|
|
332
|
+
isUsernameString,
|
|
333
|
+
isPhoneNumber,
|
|
334
|
+
isUrlSafeString,
|
|
335
|
+
isString6To24CharacterLong,
|
|
336
|
+
isString6To16CharacterLong,
|
|
337
|
+
isProvinceString,
|
|
338
|
+
isBoolValue,
|
|
339
|
+
isPostalCodeString,
|
|
340
|
+
isSafeString,
|
|
341
|
+
isTextString,
|
|
342
|
+
isInStringArray,
|
|
343
|
+
isUserRoleRequestStatusString,
|
|
344
|
+
isUserRoleRequestRoleString,
|
|
345
|
+
isCountryCodeString,
|
|
346
|
+
isValidDomainName,
|
|
347
|
+
isValidTimestampzString,
|
|
348
|
+
isValidTimestampString,
|
|
349
|
+
isValidDateString,
|
|
350
|
+
isValidUrl,
|
|
351
|
+
isValidArrayOfStrings,
|
|
279
352
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
error: { throwBadInputError },
|
|
5
5
|
} = require('@carecard/common-util');
|
|
6
6
|
const { isUserRoleRequestRoleString } = require('./validate');
|
|
7
7
|
|
|
@@ -9,79 +9,87 @@ const DEFAULT_USER_ROLE_REQUEST_ROLE = 'student';
|
|
|
9
9
|
const REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT = 'whenRoleOrScopePresent';
|
|
10
10
|
|
|
11
11
|
function validateNewUserRoleRequestObject(roleRequest = {}, options = {}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
const normalized = normalizeNewUserRoleRequestObject(roleRequest);
|
|
13
|
+
const defaultRole = Object.prototype.hasOwnProperty.call(options, 'defaultRole')
|
|
14
|
+
? options.defaultRole
|
|
15
|
+
: DEFAULT_USER_ROLE_REQUEST_ROLE;
|
|
16
|
+
const requireScope = Object.prototype.hasOwnProperty.call(options, 'requireScope')
|
|
17
|
+
? options.requireScope
|
|
18
|
+
: true;
|
|
19
|
+
|
|
20
|
+
if (normalized.role_name === undefined && defaultRole !== undefined) {
|
|
21
|
+
normalized.role_name = defaultRole;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (normalized.role_name !== undefined && !isUserRoleRequestRoleString(normalized.role_name)) {
|
|
25
|
+
throwBadInputError({
|
|
26
|
+
userMessage: 'Invalid property: role.role',
|
|
27
|
+
details: { role: 'Role requests are limited to student, intern, or volunteer' },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (shouldRequireScope(normalized, requireScope)) {
|
|
32
|
+
requireNewUserRoleRequestScope(normalized);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalized;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function normalizeNewUserRoleRequestObject(roleRequest) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
const normalized = { ...roleRequest };
|
|
40
|
+
|
|
41
|
+
assignAlias(normalized, 'role_name', 'roleName');
|
|
42
|
+
assignAlias(normalized, 'role_name', 'role');
|
|
43
|
+
assignAlias(normalized, 'institution_id', 'institutionId');
|
|
44
|
+
assignAlias(normalized, 'campus_id', 'campusId');
|
|
45
|
+
assignAlias(normalized, 'program_id', 'programId');
|
|
46
|
+
assignAlias(normalized, 'program_term_id', 'programTermId');
|
|
47
|
+
|
|
48
|
+
delete normalized.roleName;
|
|
49
|
+
delete normalized.role;
|
|
50
|
+
delete normalized.institutionId;
|
|
51
|
+
delete normalized.campusId;
|
|
52
|
+
delete normalized.programId;
|
|
53
|
+
delete normalized.programTermId;
|
|
54
|
+
|
|
55
|
+
return normalized;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
function assignAlias(target, canonicalKey, aliasKey) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
if (target[canonicalKey] === undefined && target[aliasKey] !== undefined) {
|
|
60
|
+
target[canonicalKey] = target[aliasKey];
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
function shouldRequireScope(roleRequest, requireScope) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
if (requireScope === true) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (requireScope !== REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
roleRequest.role_name !== undefined ||
|
|
74
|
+
roleRequest.institution_id !== undefined ||
|
|
75
|
+
roleRequest.campus_id !== undefined ||
|
|
76
|
+
roleRequest.program_id !== undefined ||
|
|
77
|
+
roleRequest.program_term_id !== undefined
|
|
78
|
+
);
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function requireNewUserRoleRequestScope(roleRequest) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
if (!roleRequest.institution_id) {
|
|
83
|
+
throwBadInputError({ userMessage: 'Missing property: role.institutionId' });
|
|
84
|
+
}
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
if (!roleRequest.campus_id) {
|
|
87
|
+
throwBadInputError({ userMessage: 'Missing property: role.campusId' });
|
|
88
|
+
}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
module.exports = {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
DEFAULT_USER_ROLE_REQUEST_ROLE,
|
|
93
|
+
REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT,
|
|
94
|
+
validateNewUserRoleRequestObject,
|
|
87
95
|
};
|