@lenne.tech/nest-server 8.6.24 → 8.6.27

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.
Files changed (84) hide show
  1. package/dist/config.env.js +4 -1
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/decorators/restricted.decorator.d.ts +1 -0
  4. package/dist/core/common/decorators/restricted.decorator.js +68 -57
  5. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  6. package/dist/core/common/enums/role.enum.d.ts +2 -0
  7. package/dist/core/common/enums/role.enum.js +2 -0
  8. package/dist/core/common/enums/role.enum.js.map +1 -1
  9. package/dist/core/common/helpers/input.helper.d.ts +1 -0
  10. package/dist/core/common/helpers/input.helper.js +10 -2
  11. package/dist/core/common/helpers/input.helper.js.map +1 -1
  12. package/dist/core/common/helpers/model.helper.d.ts +6 -2
  13. package/dist/core/common/helpers/model.helper.js +14 -6
  14. package/dist/core/common/helpers/model.helper.js.map +1 -1
  15. package/dist/core/common/helpers/service.helper.d.ts +1 -0
  16. package/dist/core/common/helpers/service.helper.js +35 -9
  17. package/dist/core/common/helpers/service.helper.js.map +1 -1
  18. package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
  19. package/dist/core/common/services/core-cron-jobs.service.js +2 -2
  20. package/dist/core/common/services/core-cron-jobs.service.js.map +1 -1
  21. package/dist/core/common/services/module.service.js +3 -3
  22. package/dist/core/common/services/module.service.js.map +1 -1
  23. package/dist/core/common/types/plain-object.type.d.ts +3 -0
  24. package/dist/core/common/types/plain-object.type.js +3 -0
  25. package/dist/core/common/types/plain-object.type.js.map +1 -0
  26. package/dist/core/modules/auth/guards/roles.guard.js +4 -1
  27. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  28. package/dist/core/modules/auth/services/core-auth.service.js +3 -1
  29. package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
  30. package/dist/core/modules/file/core-file-info.model.d.ts +1 -1
  31. package/dist/core/modules/file/core-file-info.model.js +4 -0
  32. package/dist/core/modules/file/core-file-info.model.js.map +1 -1
  33. package/dist/core/modules/user/core-user.service.js +5 -0
  34. package/dist/core/modules/user/core-user.service.js.map +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/server/common/services/cron-jobs.service.js +1 -1
  39. package/dist/server/common/services/cron-jobs.service.js.map +1 -1
  40. package/dist/server/modules/auth/auth.service.js +21 -4
  41. package/dist/server/modules/auth/auth.service.js.map +1 -1
  42. package/dist/server/modules/file/file.controller.js +1 -1
  43. package/dist/server/modules/file/file.controller.js.map +1 -1
  44. package/dist/server/modules/file/file.resolver.js +1 -0
  45. package/dist/server/modules/file/file.resolver.js.map +1 -1
  46. package/dist/server/modules/user/user.model.d.ts +3 -3
  47. package/dist/server/modules/user/user.model.js +5 -7
  48. package/dist/server/modules/user/user.model.js.map +1 -1
  49. package/dist/server/modules/user/user.module.js +2 -0
  50. package/dist/server/modules/user/user.module.js.map +1 -1
  51. package/dist/server/modules/user/user.resolver.js +5 -0
  52. package/dist/server/modules/user/user.resolver.js.map +1 -1
  53. package/dist/server/modules/user/user.service.js +1 -1
  54. package/dist/server/modules/user/user.service.js.map +1 -1
  55. package/dist/test/test.helper.js +4 -4
  56. package/dist/test/test.helper.js.map +1 -1
  57. package/dist/tsconfig.build.tsbuildinfo +1 -1
  58. package/package.json +10 -9
  59. package/src/config.env.ts +4 -1
  60. package/src/core/common/decorators/restricted.decorator.ts +95 -75
  61. package/src/core/common/enums/role.enum.ts +34 -6
  62. package/src/core/common/helpers/context.helper.ts +2 -2
  63. package/src/core/common/helpers/input.helper.ts +17 -0
  64. package/src/core/common/helpers/model.helper.ts +20 -6
  65. package/src/core/common/helpers/service.helper.ts +44 -11
  66. package/src/core/common/interfaces/server-options.interface.ts +6 -0
  67. package/src/core/common/services/core-cron-jobs.service.ts +2 -2
  68. package/src/core/common/services/module.service.ts +5 -5
  69. package/src/core/common/types/plain-object.type.ts +5 -0
  70. package/src/core/common/types/remove-methods.type.ts +1 -0
  71. package/src/core/modules/auth/guards/roles.guard.ts +7 -2
  72. package/src/core/modules/auth/services/core-auth.service.ts +9 -1
  73. package/src/core/modules/file/core-file-info.model.ts +11 -1
  74. package/src/core/modules/user/core-user.service.ts +8 -0
  75. package/src/index.ts +1 -0
  76. package/src/server/common/services/cron-jobs.service.ts +1 -1
  77. package/src/server/modules/auth/auth.service.ts +13 -6
  78. package/src/server/modules/file/file.controller.ts +1 -1
  79. package/src/server/modules/file/file.resolver.ts +1 -0
  80. package/src/server/modules/user/user.model.ts +6 -5
  81. package/src/server/modules/user/user.module.ts +2 -0
  82. package/src/server/modules/user/user.resolver.ts +5 -0
  83. package/src/server/modules/user/user.service.ts +1 -1
  84. package/src/test/test.helper.ts +6 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "8.6.24",
3
+ "version": "8.6.27",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -21,7 +21,7 @@
21
21
  "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:boostrap",
22
22
  "format": "prettier --write 'src/**/*.ts'",
23
23
  "format:staged": "pretty-quick --staged",
24
- "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
24
+ "lint": "eslint \"{src,tests}/**/*.ts\" --fix",
25
25
  "prestart:prod": "npm run build",
26
26
  "reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
27
27
  "reinit:force": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --force && npm run test:e2e",
@@ -77,11 +77,12 @@
77
77
  "graphql": "16.5.0",
78
78
  "graphql-subscriptions": "2.0.0",
79
79
  "graphql-upload": "15.0.2",
80
+ "js-sha256": "0.9.0",
80
81
  "json-to-graphql-query": "2.2.4",
81
82
  "light-my-request": "5.0.0",
82
83
  "lodash": "4.17.21",
83
84
  "mongodb": "4.7.0",
84
- "mongoose": "6.4.1",
85
+ "mongoose": "6.4.2",
85
86
  "mongoose-gridfs": "1.3.0",
86
87
  "multer": "1.4.5-lts.1",
87
88
  "node-mailjet": "5.0.1",
@@ -100,14 +101,14 @@
100
101
  "@types/jest": "28.1.4",
101
102
  "@types/lodash": "4.14.182",
102
103
  "@types/multer": "1.4.7",
103
- "@types/node": "18.0.0",
104
+ "@types/node": "18.0.1",
104
105
  "@types/nodemailer": "6.4.4",
105
106
  "@types/passport": "1.0.9",
106
107
  "@types/supertest": "2.0.12",
107
- "@typescript-eslint/eslint-plugin": "5.30.0",
108
- "@typescript-eslint/parser": "5.30.0",
108
+ "@typescript-eslint/eslint-plugin": "5.30.4",
109
+ "@typescript-eslint/parser": "5.30.4",
109
110
  "coffeescript": "2.7.0",
110
- "eslint": "8.18.0",
111
+ "eslint": "8.19.0",
111
112
  "eslint-config-prettier": "8.5.0",
112
113
  "find-file-up": "2.0.1",
113
114
  "grunt": "1.5.3",
@@ -120,10 +121,10 @@
120
121
  "pm2": "5.2.0",
121
122
  "prettier": "2.7.1",
122
123
  "pretty-quick": "3.1.3",
123
- "supertest": "6.2.3",
124
+ "supertest": "6.2.4",
124
125
  "ts-jest": "28.0.5",
125
126
  "ts-morph": "15.1.0",
126
- "ts-node": "10.8.1",
127
+ "ts-node": "10.8.2",
127
128
  "tsconfig-paths": "4.0.0",
128
129
  "typescript": "4.7.4"
129
130
  },
package/src/config.env.ts CHANGED
@@ -55,6 +55,7 @@ const config: { [env: string]: IServerOptions } = {
55
55
  uri: 'mongodb://localhost/nest-server-dev',
56
56
  },
57
57
  port: 3000,
58
+ sha256: true,
58
59
  staticAssets: {
59
60
  path: join(__dirname, '..', 'public'),
60
61
  options: { prefix: '' },
@@ -105,6 +106,7 @@ const config: { [env: string]: IServerOptions } = {
105
106
  uri: 'mongodb://localhost/nest-server-dev',
106
107
  },
107
108
  port: 3000,
109
+ sha256: true,
108
110
  staticAssets: {
109
111
  path: join(__dirname, '..', 'public'),
110
112
  options: { prefix: '' },
@@ -155,6 +157,7 @@ const config: { [env: string]: IServerOptions } = {
155
157
  uri: 'mongodb://localhost/nest-server-prod',
156
158
  },
157
159
  port: 3000,
160
+ sha256: true,
158
161
  staticAssets: {
159
162
  path: join(__dirname, '..', 'public'),
160
163
  options: { prefix: '' },
@@ -173,7 +176,7 @@ const config: { [env: string]: IServerOptions } = {
173
176
  */
174
177
  const env = process.env['NODE' + '_ENV'] || 'development';
175
178
  const envConfig = config[env] || config.development;
176
- console.log('Configured for: ' + envConfig.env + (env !== envConfig.env ? ' (requested: ' + env + ')' : ''));
179
+ console.info('Configured for: ' + envConfig.env + (env !== envConfig.env ? ' (requested: ' + env + ')' : ''));
177
180
 
178
181
  /**
179
182
  * Export envConfig as default
@@ -4,6 +4,7 @@ import { ProcessType } from '../enums/process-type.enum';
4
4
  import { RoleEnum } from '../enums/role.enum';
5
5
  import { getIncludedIds } from '../helpers/db.helper';
6
6
  import { RequireAtLeastOne } from '../types/required-at-least-one.type';
7
+ import * as _ from 'lodash';
7
8
 
8
9
  /**
9
10
  * Restricted meta key
@@ -57,6 +58,7 @@ export const checkRestricted = (
57
58
  data: any,
58
59
  user: { id: any; hasRole: (roles: string[]) => boolean },
59
60
  options: {
61
+ checkObjectItself?: boolean;
60
62
  dbObject?: any;
61
63
  ignoreUndefined?: boolean;
62
64
  processType?: ProcessType;
@@ -66,6 +68,7 @@ export const checkRestricted = (
66
68
  processedObjects: any[] = []
67
69
  ) => {
68
70
  const config = {
71
+ checkObjectItself: false,
69
72
  ignoreUndefined: true,
70
73
  removeUndefinedFromResultArray: true,
71
74
  throwError: true,
@@ -95,95 +98,107 @@ export const checkRestricted = (
95
98
 
96
99
  // Check function
97
100
  const validateRestricted = (restricted) => {
98
- let valid = true;
99
- if (restricted?.length) {
100
- valid = false;
101
-
102
- // Get roles
103
- const roles: string[] = [];
104
- restricted.forEach((item) => {
105
- if (typeof item === 'string') {
106
- roles.push(item);
107
- } else if (
108
- item?.roles?.length &&
109
- (config.processType && item.processType ? config.processType === item.processType : true)
110
- ) {
111
- if (Array.isArray(item.roles)) {
112
- roles.push(...item.roles);
113
- } else {
114
- roles.push(item.roles);
115
- }
116
- }
117
- });
118
-
119
- // Check roles
120
- if (roles.length) {
121
- if (
122
- user?.hasRole?.(roles) ||
123
- (user?.id && roles.includes(RoleEnum.S_USER)) ||
124
- (roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
125
- ) {
126
- valid = true;
101
+ // Check restrictions
102
+ if (!restricted?.length) {
103
+ return true;
104
+ }
105
+
106
+ let valid = false;
107
+
108
+ // Get roles
109
+ const roles: string[] = [];
110
+ restricted.forEach((item) => {
111
+ if (typeof item === 'string') {
112
+ roles.push(item);
113
+ } else if (
114
+ item?.roles?.length &&
115
+ (config.processType && item.processType ? config.processType === item.processType : true)
116
+ ) {
117
+ if (Array.isArray(item.roles)) {
118
+ roles.push(...item.roles);
119
+ } else {
120
+ roles.push(item.roles);
127
121
  }
128
122
  }
123
+ });
129
124
 
130
- if (!valid) {
131
- // Get groups
132
- const groups = restricted.filter((item) => {
133
- return (
134
- typeof item === 'object' &&
135
- // Check if object is valid
136
- item.memberOf?.length &&
137
- // Check if processType is specified and is valid for current process
138
- (config.processType && item.processType ? config.processType === item.processType : true)
139
- );
140
- }) as { memberOf: string | string[] }[];
141
-
142
- // Check groups
143
- if (groups.length) {
144
- // Get members from groups
145
- const members = [];
146
- for (const group of groups) {
147
- let properties: string[] = group.memberOf as string[];
148
- if (!Array.isArray(group.memberOf)) {
149
- properties = [group.memberOf];
150
- }
151
- for (const property of properties) {
152
- const items = config.dbObject?.[property];
153
- if (items) {
154
- if (Array.isArray(items)) {
155
- members.concat(items);
156
- } else {
157
- members.push(items);
158
- }
125
+ // Check roles
126
+ if (roles.length) {
127
+ // Prevent access for everyone, including administrators
128
+ if (roles.includes(RoleEnum.S_NO_ONE)) {
129
+ return false;
130
+ }
131
+
132
+ // Check access rights
133
+ if (
134
+ roles.includes(RoleEnum.S_EVERYONE) ||
135
+ user?.hasRole?.(roles) ||
136
+ (user?.id && roles.includes(RoleEnum.S_USER)) ||
137
+ (roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
138
+ ) {
139
+ valid = true;
140
+ }
141
+ }
142
+
143
+ if (!valid) {
144
+ // Get groups
145
+ const groups = restricted.filter((item) => {
146
+ return (
147
+ typeof item === 'object' &&
148
+ // Check if object is valid
149
+ item.memberOf?.length &&
150
+ // Check if processType is specified and is valid for current process
151
+ (config.processType && item.processType ? config.processType === item.processType : true)
152
+ );
153
+ }) as { memberOf: string | string[] }[];
154
+
155
+ // Check groups
156
+ if (groups.length) {
157
+ // Get members from groups
158
+ const members = [];
159
+ for (const group of groups) {
160
+ let properties: string[] = group.memberOf as string[];
161
+ if (!Array.isArray(group.memberOf)) {
162
+ properties = [group.memberOf];
163
+ }
164
+ for (const property of properties) {
165
+ const items = config.dbObject?.[property];
166
+ if (items) {
167
+ if (Array.isArray(items)) {
168
+ members.concat(items);
169
+ } else {
170
+ members.push(items);
159
171
  }
160
172
  }
161
173
  }
162
-
163
- // Check if user is a member
164
- if (getIncludedIds(members, user)) {
165
- valid = true;
166
- }
167
174
  }
168
175
 
169
- // Check if there are no limitations
170
- if (!roles.length && !groups.length) {
176
+ // Check if user is a member
177
+ if (getIncludedIds(members, user)) {
171
178
  valid = true;
172
179
  }
173
180
  }
181
+
182
+ // Check if there are no limitations
183
+ if (!roles.length && !groups.length) {
184
+ valid = true;
185
+ }
174
186
  }
187
+
175
188
  return valid;
176
189
  };
177
190
 
178
191
  // Check object
179
- const objectRestrictions = getRestricted(data.constructor);
180
- const objectIsValid = validateRestricted(objectRestrictions);
181
- if (!objectIsValid) {
182
- // Throw error
183
- if (config.throwError) {
184
- throw new UnauthorizedException('The current user has no access rights for ' + data.constructor?.name);
192
+ const objectRestrictions = getRestricted(data.constructor) || [];
193
+ if (config.checkObjectItself) {
194
+ const objectIsValid = validateRestricted(objectRestrictions);
195
+ if (!objectIsValid) {
196
+ // Throw error
197
+ if (config.throwError) {
198
+ throw new UnauthorizedException('The current user has no access rights for ' + data.constructor?.name);
199
+ }
200
+ return null;
185
201
  }
186
- return null;
187
202
  }
188
203
 
189
204
  // Check properties of object
@@ -194,8 +209,9 @@ export const checkRestricted = (
194
209
  }
195
210
 
196
211
  // Check restricted
197
- const restricted = getRestricted(data, propertyKey);
198
- const valid = validateRestricted(restricted);
212
+ const restricted = getRestricted(data, propertyKey) || [];
213
+ const concatenatedRestrictions = _.uniq(objectRestrictions.concat(restricted));
214
+ const valid = validateRestricted(concatenatedRestrictions);
199
215
 
200
216
  // Check rights
201
217
  if (valid) {
@@ -204,7 +220,11 @@ export const checkRestricted = (
204
220
  } else {
205
221
  // Throw error
206
222
  if (config.throwError) {
207
- throw new UnauthorizedException('The current user has no access rights for ' + propertyKey);
223
+ throw new UnauthorizedException(
224
+ 'The current user has no access rights for ' +
225
+ propertyKey +
226
+ (data.constructor?.name ? ' of ' + data.constructor.name : '')
227
+ );
208
228
  }
209
229
 
210
230
  // Remove property
@@ -1,26 +1,54 @@
1
1
  /**
2
2
  * Enums for Resolver @Role and Model @Restricted decorator and for roles property in ServiceOptions
3
+ *
4
+ * There are two types of roles. The "normal" roles that can be defined as strings on the user in the `roles` property
5
+ * and there are special system roles (with the prefix `S_`) that are defined by the current context e.g.
6
+ * `S_USER` applies to all logged-in users or `S_CREATOR` applies to the creator of a specific object.
7
+ * The special roles can only be used under certain situations (see below). The "normal" roles can be used anywhere
8
+ * that involves checking the current user.
9
+ *
10
+ * Except for the role `S_NO_ONE` all roles extend the access. If for example the role `ADMIN` is specified for a class
11
+ * then the accesses to all methods / properties are limited to administrators. If then e.g. for a method of the class
12
+ * the role `S_USER` is specified, the method is accessible for all users (administrators & all users = all users). All
13
+ * other methods and properties of the class are still only accessible for administrators.
14
+ *
15
+ * The role `S_NO_ONE` is an exception to this behavior. If this role is specified, then no one can access the
16
+ * associated class or associated methods and properties no matter what other roles were specified for access.
17
+ * This role should be used thus only for classes, methods or characteristics, which are to be locked for a transition
18
+ * period but not deleted from the source code completely.
19
+ *
20
+ * The roles are divided into different scopes and can be used in `@Roles` or `@Restricted`. The scopes are specified
21
+ * and explained below.
22
+ *
3
23
  */
4
24
  export enum RoleEnum {
5
25
  // ===================================================================================================================
6
- // Real roles (integrated into user.roles), which can be used via @Restricted for Models (properties),
7
- // via @Roles for Resolvers (methods) and via ServiceOptions for Resolver methods.
26
+ // Real roles (integrated into user.roles), which can be used via @Restricted for Models (classes and properties),
27
+ // via @Roles for Resolvers (classes and methods) and via ServiceOptions for Resolver methods.
8
28
  // ===================================================================================================================
9
29
 
10
30
  // User must be an administrator (see roles of user)
11
31
  ADMIN = 'admin',
12
32
 
13
33
  // ===================================================================================================================
14
- // Special system roles, which can be used via @Restricted for Models (properties), via @Roles for Resolvers (methods)
15
- // and via ServiceOptions for Resolver methods. This roles should not be integrated into user.roles!
34
+ // Special system roles, which can be used via @Restricted for Models (classes and properties), via @Roles for
35
+ // Resolvers (classes and methods) and via ServiceOptions for Resolver methods. This roles should not be integrated
36
+ // into user.roles!
16
37
  // ===================================================================================================================
17
38
 
39
+ // Everyone, including users who are not logged in, can access (see context user, e.g. @GraphQLUser)
40
+ S_EVERYONE = 's_everyone',
41
+
42
+ // No one has access, not even administrators
43
+ S_NO_ONE = 's_no_one',
44
+
18
45
  // User must be logged in (see context user, e.g. @GraphQLUser)
19
46
  S_USER = 's_user',
20
47
 
21
48
  // ===================================================================================================================
22
- // Special system roles that check rights for DB objects and can be used via @Restricted for Models (properties)
23
- // and via ServiceOptions for Resolver methods. These roles should not be integrated in user.roles!
49
+ // Special system roles that check rights for DB objects and can be used via @Restricted for Models
50
+ // (classes and properties) and via ServiceOptions for Resolver methods. These roles should not be integrated in
51
+ // user.roles!
24
52
  // ===================================================================================================================
25
53
 
26
54
  // User must be the creator of the processed object(s) (see createdBy property of object(s))
@@ -30,14 +30,14 @@ export function getContextData(context: ExecutionContext): { currentUser: { [key
30
30
  try {
31
31
  ctx = GqlExecutionContext.create(context)?.getContext();
32
32
  } catch (e) {
33
- // console.log(e);
33
+ // console.info(e);
34
34
  }
35
35
 
36
36
  let args: any;
37
37
  try {
38
38
  args = GqlExecutionContext.create(context)?.getArgs();
39
39
  } catch (e) {
40
- // console.log(e);
40
+ // console.info(e);
41
41
  }
42
42
 
43
43
  // Get data
@@ -232,7 +232,16 @@ export async function check(
232
232
  roles = [roles];
233
233
  }
234
234
  let valid = false;
235
+
236
+ // Prevent access for everyone, including administrators
237
+ if (roles.includes(RoleEnum.S_NO_ONE)) {
238
+ throw new UnauthorizedException('No access');
239
+ }
240
+
241
+ // Check access
235
242
  if (
243
+ // check if any user, including users who are not logged in, can access
244
+ roles.includes(RoleEnum.S_EVERYONE) ||
236
245
  // check if user is logged in
237
246
  (roles.includes(RoleEnum.S_USER) && user?.id) ||
238
247
  // check if the user has at least one of the required roles
@@ -316,6 +325,14 @@ export function filterProperties<T = Record<string, any>>(
316
325
  .reduce((res, key) => Object.assign(res, { [key]: obj[key] }), {});
317
326
  }
318
327
 
328
+ /**
329
+ * Get plain copy of object
330
+ * @param element
331
+ */
332
+ export function getPlain(object: any) {
333
+ return JSON.parse(JSON.stringify(object));
334
+ }
335
+
319
336
  /**
320
337
  * Check if parameter is an array
321
338
  */
@@ -164,7 +164,8 @@ export function maps<T = Record<string, any>>(
164
164
  export function mapClasses<T = Record<string, any>>(
165
165
  input: Record<string, any>,
166
166
  mapping: Record<string, new (...args: any[]) => any>,
167
- target?: T
167
+ target?: T,
168
+ options?: { objectIdsToString?: boolean }
168
169
  ): T {
169
170
  // Check params
170
171
  if (!target) {
@@ -174,6 +175,12 @@ export function mapClasses<T = Record<string, any>>(
174
175
  return target;
175
176
  }
176
177
 
178
+ // Get config
179
+ const config = {
180
+ objectIdsToString: true,
181
+ ...options,
182
+ };
183
+
177
184
  // Process input
178
185
  for (const [prop, mapTarget] of Object.entries(mapping)) {
179
186
  if (prop in input) {
@@ -187,7 +194,7 @@ export function mapClasses<T = Record<string, any>>(
187
194
  if (value instanceof targetClass) {
188
195
  arr.push(value);
189
196
  } else if (value instanceof Types.ObjectId) {
190
- arr.push(value);
197
+ config.objectIdsToString ? arr.push(value.toHexString()) : arr.push(value);
191
198
  } else if (typeof value === 'object') {
192
199
  if (targetClass.map) {
193
200
  arr.push(targetClass.map(item));
@@ -203,7 +210,7 @@ export function mapClasses<T = Record<string, any>>(
203
210
 
204
211
  // Process ObjectId
205
212
  else if (value instanceof Types.ObjectId) {
206
- target[prop] = value as any;
213
+ target[prop] = config.objectIdsToString ? value.toHexString() : value;
207
214
  }
208
215
 
209
216
  // Process object
@@ -240,7 +247,8 @@ export function mapClasses<T = Record<string, any>>(
240
247
  export async function mapClassesAsync<T = Record<string, any>>(
241
248
  input: Record<string, any>,
242
249
  mapping: Record<string, new (...args: any[]) => any>,
243
- target?: T
250
+ target?: T,
251
+ options?: { objectIdsToString?: boolean }
244
252
  ): Promise<T> {
245
253
  // Check params
246
254
  if (!target) {
@@ -250,6 +258,12 @@ export async function mapClassesAsync<T = Record<string, any>>(
250
258
  return target;
251
259
  }
252
260
 
261
+ // Get config
262
+ const config = {
263
+ objectIdsToString: true,
264
+ ...options,
265
+ };
266
+
253
267
  // Process input
254
268
  for (const [prop, mapTarget] of Object.entries(mapping)) {
255
269
  if (prop in input) {
@@ -263,7 +277,7 @@ export async function mapClassesAsync<T = Record<string, any>>(
263
277
  if (value instanceof targetClass) {
264
278
  arr.push(value);
265
279
  } else if (value instanceof Types.ObjectId) {
266
- arr.push(value);
280
+ config.objectIdsToString ? arr.push(value.toHexString()) : arr.push(value);
267
281
  } else if (typeof value === 'object') {
268
282
  if (targetClass.map) {
269
283
  arr.push(await targetClass.map(item));
@@ -279,7 +293,7 @@ export async function mapClassesAsync<T = Record<string, any>>(
279
293
 
280
294
  // Process ObjectId
281
295
  else if (value instanceof Types.ObjectId) {
282
- target[prop] = value as any;
296
+ target[prop] = config.objectIdsToString ? value.toHexString() : value;
283
297
  }
284
298
 
285
299
  // Process object
@@ -1,7 +1,10 @@
1
1
  import { UnauthorizedException } from '@nestjs/common';
2
2
  import * as bcrypt from 'bcrypt';
3
3
  import { plainToInstance } from 'class-transformer';
4
+ import { sha256 } from 'js-sha256';
4
5
  import * as _ from 'lodash';
6
+ import { Types } from 'mongoose';
7
+ import envConfig from '../../../config.env';
5
8
  import { RoleEnum } from '../enums/role.enum';
6
9
  import { PrepareInputOptions } from '../interfaces/prepare-input-options.interface';
7
10
  import { PrepareOutputOptions } from '../interfaces/prepare-output-options.interface';
@@ -80,8 +83,14 @@ export async function prepareInput<T = any>(
80
83
 
81
84
  // Process array
82
85
  if (Array.isArray(input)) {
83
- const processedArray = input.map(async (item) => await prepareInput(item, currentUser, options)) as any;
84
- return config.getNewArray ? processedArray : input;
86
+ const processedArray = config.getNewArray ? ([] as T & any[]) : input;
87
+ for (let i = 0; i <= input.length - 1; i++) {
88
+ processedArray[i] = await prepareOutput(input[i], options);
89
+ if (processedArray[i] === undefined && config.removeUndefined) {
90
+ processedArray.splice(i, 1);
91
+ }
92
+ }
93
+ return processedArray;
85
94
  }
86
95
 
87
96
  // Clone input
@@ -103,8 +112,8 @@ export async function prepareInput<T = any>(
103
112
  }
104
113
 
105
114
  // Remove undefined properties to avoid unwanted overwrites
106
- if (config.removeUndefined) {
107
- Object.keys(input).forEach((key) => input[key] === undefined && delete input[key]);
115
+ for (const [key, value] of Object.entries(input)) {
116
+ value === undefined && delete input[key];
108
117
  }
109
118
 
110
119
  // Process roles
@@ -122,8 +131,15 @@ export async function prepareInput<T = any>(
122
131
  }
123
132
 
124
133
  // Hash password
125
- if ((input as Record<string, any>).password) {
126
- (input as Record<string, any>).password = await bcrypt.hash((input as any).password, 10);
134
+ if ((input as any).password) {
135
+ // Check if the password was transmitted encrypted
136
+ // If not, the password is encrypted to enable future encrypted and unencrypted transmissions
137
+ if (envConfig.sha256 && !/^[a-f0-9]{64}$/i.test((input as any).password)) {
138
+ (input as any).password = sha256((input as any).password);
139
+ }
140
+
141
+ // Hash password
142
+ (input as any).password = await bcrypt.hash((input as any).password, 10);
127
143
  }
128
144
 
129
145
  // Set creator
@@ -149,6 +165,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
149
165
  [key: string]: any;
150
166
  clone?: boolean;
151
167
  getNewArray?: boolean;
168
+ objectIdsToStrings?: boolean;
152
169
  removeSecrets?: boolean;
153
170
  removeUndefined?: boolean;
154
171
  targetModel?: new (...args: any[]) => T;
@@ -158,6 +175,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
158
175
  const config = {
159
176
  clone: false,
160
177
  getNewArray: false,
178
+ objectIdsToStrings: true,
161
179
  removeSecrets: true,
162
180
  removeUndefined: false,
163
181
  targetModel: undefined,
@@ -171,10 +189,14 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
171
189
 
172
190
  // Process array
173
191
  if (Array.isArray(output)) {
174
- const processedArray = output.map(async (item, index) => {
175
- output[index] = await prepareOutput(item, options);
176
- }) as any;
177
- return config.getNewArray ? processedArray : output;
192
+ const processedArray = config.getNewArray ? [] : output;
193
+ for (let i = 0; i <= output.length - 1; i++) {
194
+ processedArray[i] = await prepareOutput(output[i], options);
195
+ if (processedArray[i] === undefined && config.removeUndefined) {
196
+ processedArray.splice(i, 1);
197
+ }
198
+ }
199
+ return processedArray;
178
200
  }
179
201
 
180
202
  // Clone output
@@ -212,7 +234,18 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
212
234
 
213
235
  // Remove undefined properties to avoid unwanted overwrites
214
236
  if (config.removeUndefined) {
215
- Object.keys(output).forEach((key) => output[key] === undefined && delete output[key]);
237
+ for (const [key, value] of Object.entries(output)) {
238
+ value === undefined && delete output[key];
239
+ }
240
+ }
241
+
242
+ // Convert ObjectIds into strings
243
+ if (config.objectIdsToStrings) {
244
+ for (const [key, value] of Object.entries(output)) {
245
+ if (value instanceof Types.ObjectId) {
246
+ output[key] = value.toHexString();
247
+ }
248
+ }
216
249
  }
217
250
 
218
251
  // Return prepared output
@@ -156,6 +156,12 @@ export interface IServerOptions {
156
156
  path?: string;
157
157
  };
158
158
 
159
+ /**
160
+ * Whether to enable verification and automatic encryption for received passwords that are not in sha256 format
161
+ * default = false, sha256 format check: /^[a-f0-9]{64}$/i
162
+ */
163
+ sha256?: boolean;
164
+
159
165
  /**
160
166
  * Templates
161
167
  */
@@ -80,7 +80,7 @@ export abstract class CoreCronJobs {
80
80
  // check if cron job exists
81
81
  if (!this[name]) {
82
82
  if (this.config.log) {
83
- console.log('Missing cron job function ' + name);
83
+ console.info('Missing cron job function ' + name);
84
84
  }
85
85
  continue;
86
86
  }
@@ -133,7 +133,7 @@ export abstract class CoreCronJobs {
133
133
  );
134
134
  this.schedulerRegistry.addCronJob(name, job);
135
135
  if (this.config.log && this.schedulerRegistry.getCronJob(name)) {
136
- console.log(`CronJob ${name} initialized with "${config.cronTime}"`);
136
+ console.info(`CronJob ${name} initialized with "${config.cronTime}"`);
137
137
  }
138
138
  }
139
139
  }