@carecard/validate 3.1.23 → 3.1.25

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.
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ error: { throwBadInputError },
5
+ } = require('@carecard/common-util');
6
+ const { isUserRoleRequestRoleString } = require('./validate');
7
+
8
+ const DEFAULT_USER_ROLE_REQUEST_ROLE = 'student';
9
+ const REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT = 'whenRoleOrScopePresent';
10
+
11
+ function validateNewUserRoleRequestObject(roleRequest = {}, options = {}) {
12
+ const normalized = normalizeNewUserRoleRequestObject(roleRequest);
13
+ const defaultRole = Object.prototype.hasOwnProperty.call(options, 'defaultRole') ? options.defaultRole : DEFAULT_USER_ROLE_REQUEST_ROLE;
14
+ const requireScope = Object.prototype.hasOwnProperty.call(options, 'requireScope') ? options.requireScope : true;
15
+
16
+ if (normalized.role_name === undefined && defaultRole !== undefined) {
17
+ normalized.role_name = defaultRole;
18
+ }
19
+
20
+ if (normalized.role_name !== undefined && !isUserRoleRequestRoleString(normalized.role_name)) {
21
+ throwBadInputError({
22
+ userMessage: 'Invalid property: role.role',
23
+ details: { role: 'Role requests are limited to student, intern, or volunteer' },
24
+ });
25
+ }
26
+
27
+ if (shouldRequireScope(normalized, requireScope)) {
28
+ requireNewUserRoleRequestScope(normalized);
29
+ }
30
+
31
+ return normalized;
32
+ }
33
+
34
+ function normalizeNewUserRoleRequestObject(roleRequest) {
35
+ const normalized = { ...roleRequest };
36
+
37
+ assignAlias(normalized, 'role_name', 'roleName');
38
+ assignAlias(normalized, 'role_name', 'role');
39
+ assignAlias(normalized, 'institution_id', 'institutionId');
40
+ assignAlias(normalized, 'campus_id', 'campusId');
41
+ assignAlias(normalized, 'program_id', 'programId');
42
+
43
+ delete normalized.roleName;
44
+ delete normalized.role;
45
+ delete normalized.institutionId;
46
+ delete normalized.campusId;
47
+ delete normalized.programId;
48
+
49
+ return normalized;
50
+ }
51
+
52
+ function assignAlias(target, canonicalKey, aliasKey) {
53
+ if (target[canonicalKey] === undefined && target[aliasKey] !== undefined) {
54
+ target[canonicalKey] = target[aliasKey];
55
+ }
56
+ }
57
+
58
+ function shouldRequireScope(roleRequest, requireScope) {
59
+ if (requireScope === true) return true;
60
+ if (requireScope !== REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT) return false;
61
+
62
+ return roleRequest.role_name !== undefined || roleRequest.institution_id !== undefined || roleRequest.campus_id !== undefined;
63
+ }
64
+
65
+ function requireNewUserRoleRequestScope(roleRequest) {
66
+ if (!roleRequest.institution_id) {
67
+ throwBadInputError({ userMessage: 'Missing property: role.institutionId' });
68
+ }
69
+
70
+ if (!roleRequest.campus_id) {
71
+ throwBadInputError({ userMessage: 'Missing property: role.campusId' });
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ DEFAULT_USER_ROLE_REQUEST_ROLE,
77
+ REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT,
78
+ validateNewUserRoleRequestObject,
79
+ };
@@ -22,6 +22,8 @@ const {
22
22
  isStreetString,
23
23
  isTextString,
24
24
  isUserRoleRequestStatusString,
25
+ isUserRoleRequestRoleString,
26
+ isCountryCodeString,
25
27
  } = require('./validate');
26
28
 
27
29
  function validateProperties(obj = {}) {
@@ -123,6 +125,12 @@ function validateProperties(obj = {}) {
123
125
  returnObj[key] = value;
124
126
  }
125
127
  break;
128
+ case 'country_code':
129
+ case 'countryCode':
130
+ if (isCountryCodeString(value)) {
131
+ returnObj[key] = value;
132
+ }
133
+ break;
126
134
  case 'token':
127
135
  case 'email_confirm_token':
128
136
  case 'emailConfirmToken':
@@ -196,6 +204,12 @@ function validateProperties(obj = {}) {
196
204
  returnObj[key] = value;
197
205
  }
198
206
  break;
207
+ case 'user_role_request_role':
208
+ case 'userRoleRequestRole':
209
+ if (isUserRoleRequestRoleString(value)) {
210
+ returnObj[key] = value;
211
+ }
212
+ break;
199
213
  case 'period':
200
214
  if (isCharactersString(value)) {
201
215
  returnObj[key] = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/validate",
3
- "version": "3.1.23",
3
+ "version": "3.1.25",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/CareCard-ca/pkg-validate.git"
@@ -12,7 +12,7 @@
12
12
  "test": "mocha --recursive",
13
13
  "test:types": "tsc --noEmit && echo \"\\n ✔ Type tests passed: tsc --noEmit reported 0 errors across index.d.ts and test/**/*.ts\\n\"",
14
14
  "test:coverage": "tsc --noEmit && export NODE_ENV=test && nyc mocha --recursive",
15
- "test:All": "npm run test && npm run test:types",
15
+ "test:All": "npm run test:coverage && npm run test:types",
16
16
  "format": "prettier --write .",
17
17
  "format:check": "prettier --check .",
18
18
  "prepare": "husky",
@@ -27,17 +27,29 @@
27
27
  "license": "ISC",
28
28
  "devDependencies": {
29
29
  "@types/mocha": "10.0.10",
30
- "@types/node": "25.6.2",
30
+ "@types/node": "25.9.1",
31
31
  "eslint": "9.39.4",
32
32
  "husky": "9.1.7",
33
- "lint-staged": "17.0.3",
34
- "mocha": "11.7.5",
33
+ "lint-staged": "17.0.5",
34
+ "mocha": "11.7.6",
35
35
  "nyc": "18.0.0",
36
36
  "prettier": "3.8.3",
37
37
  "ts-node": "10.9.2",
38
- "typescript": "5.9.3"
38
+ "typescript": "6.0.3"
39
39
  },
40
40
  "dependencies": {
41
- "@carecard/common-util": "^3.1.13"
41
+ "@carecard/common-util": "3.1.15"
42
+ },
43
+ "nyc": {
44
+ "all": true,
45
+ "include": [
46
+ "index.js",
47
+ "lib/**/*.js"
48
+ ],
49
+ "check-coverage": true,
50
+ "branches": 100,
51
+ "functions": 100,
52
+ "lines": 100,
53
+ "statements": 100
42
54
  }
43
55
  }
package/readme.md CHANGED
@@ -106,6 +106,7 @@ where the package supports both.
106
106
  | `isString6To16CharacterLong` and `isPasswordString` | `strong_password`, `strongPassword` |
107
107
  | `isEmailString` | `email` |
108
108
  | `isPhoneNumber` | `phone_number`, `phoneNumber` |
109
+ | `isCountryCodeString` | `country_code`, `countryCode` |
109
110
  | `isUrlSafeString` | `token`, `email_confirm_token`, `emailConfirmToken`, `verification_token`, `verificationToken` |
110
111
  | `isValidUuidString` | `uuid`, `item_id`, `itemId`, `user_id`, `userId`, `address_id`, `addressId`, `image_id`, `imageId`, `order_id`, `orderId`, `category_id`, `categoryId`, `parent_id`, `parentId`, `college_id`, `collegeId`, `campus_id`, `campusId`, `program_id`, `programId`, `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` |
111
112
  | `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
@@ -363,3 +364,16 @@ npm run format:check
363
364
 
364
365
  CI runs on Node.js 25 and executes `npm run test:All`. Publishing to npm happens
365
366
  from `main` through the `Publish to npm` GitHub workflow.
367
+
368
+ ## Auth Boundary
369
+
370
+ Validation protects request shape, not authorization. `ms-auth` owns its
371
+ auth-table RLS contract: normal users are self-row only, JWT `roles: ["ad"]`
372
+ is the auth super-admin signal, and public auth flows use narrow system
373
+ contexts. Do not use validators as a replacement for service RLS or database
374
+ context checks.
375
+
376
+ Docs that mention `ms-auth` controller internals should use concise action
377
+ names such as `loginUser`, `registerUser`, `getUserDetail`, and `renewJwt`.
378
+ Access level is conveyed by route middleware and endpoint placement, not by
379
+ `public`/`protected`/`admin`/`Handler` suffixes.