@lenne.tech/nest-server 8.3.1 → 8.6.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.
Files changed (99) hide show
  1. package/dist/core/common/decorators/restricted.decorator.d.ts +12 -5
  2. package/dist/core/common/decorators/restricted.decorator.js +91 -29
  3. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  4. package/dist/core/common/enums/process-type.enum.d.ts +4 -0
  5. package/dist/core/common/enums/process-type.enum.js +9 -0
  6. package/dist/core/common/enums/process-type.enum.js.map +1 -0
  7. package/dist/core/common/enums/role.enum.d.ts +1 -2
  8. package/dist/core/common/enums/role.enum.js +0 -1
  9. package/dist/core/common/enums/role.enum.js.map +1 -1
  10. package/dist/core/common/helpers/db.helper.d.ts +4 -4
  11. package/dist/core/common/helpers/db.helper.js +54 -30
  12. package/dist/core/common/helpers/db.helper.js.map +1 -1
  13. package/dist/core/common/helpers/input.helper.d.ts +6 -5
  14. package/dist/core/common/helpers/input.helper.js +4 -15
  15. package/dist/core/common/helpers/input.helper.js.map +1 -1
  16. package/dist/core/common/helpers/service.helper.d.ts +12 -0
  17. package/dist/core/common/helpers/service.helper.js +42 -3
  18. package/dist/core/common/helpers/service.helper.js.map +1 -1
  19. package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +8 -0
  20. package/dist/core/common/interfaces/prepare-input-options.interface.js +3 -0
  21. package/dist/core/common/interfaces/prepare-input-options.interface.js.map +1 -0
  22. package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +7 -0
  23. package/dist/core/common/interfaces/prepare-output-options.interface.js +3 -0
  24. package/dist/core/common/interfaces/prepare-output-options.interface.js.map +1 -0
  25. package/dist/core/common/interfaces/service-options.interface.d.ts +5 -17
  26. package/dist/core/common/models/core-persistence.model.d.ts +0 -1
  27. package/dist/core/common/models/core-persistence.model.js +0 -10
  28. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  29. package/dist/core/common/services/module.service.d.ts +4 -4
  30. package/dist/core/common/services/module.service.js +27 -26
  31. package/dist/core/common/services/module.service.js.map +1 -1
  32. package/dist/core/common/types/require-only-one.type.d.ts +3 -0
  33. package/dist/core/common/types/require-only-one.type.js +3 -0
  34. package/dist/core/common/types/require-only-one.type.js.map +1 -0
  35. package/dist/core/common/types/required-at-least-one.type.d.ts +3 -0
  36. package/dist/core/common/types/required-at-least-one.type.js +3 -0
  37. package/dist/core/common/types/required-at-least-one.type.js.map +1 -0
  38. package/dist/core/modules/auth/inputs/core-auth-sign-in.input.d.ts +5 -0
  39. package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js +34 -0
  40. package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js.map +1 -0
  41. package/dist/core/modules/auth/inputs/core-auth-sign-up.input.d.ts +5 -0
  42. package/dist/core/modules/auth/inputs/core-auth-sign-up.input.js +34 -0
  43. package/dist/core/modules/auth/inputs/core-auth-sign-up.input.js.map +1 -0
  44. package/dist/core.module.js +0 -5
  45. package/dist/core.module.js.map +1 -1
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.js +5 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/server/modules/auth/auth.model.js +2 -2
  50. package/dist/server/modules/auth/auth.model.js.map +1 -1
  51. package/dist/server/modules/auth/auth.module.js +7 -2
  52. package/dist/server/modules/auth/auth.module.js.map +1 -1
  53. package/dist/server/modules/auth/auth.resolver.d.ts +8 -3
  54. package/dist/server/modules/auth/auth.resolver.js +33 -10
  55. package/dist/server/modules/auth/auth.resolver.js.map +1 -1
  56. package/dist/server/modules/auth/auth.service.d.ts +15 -0
  57. package/dist/server/modules/auth/auth.service.js +71 -0
  58. package/dist/server/modules/auth/auth.service.js.map +1 -0
  59. package/dist/server/modules/auth/inputs/auth-sign-in.input.d.ts +3 -0
  60. package/dist/server/modules/auth/inputs/auth-sign-in.input.js +18 -0
  61. package/dist/server/modules/auth/inputs/auth-sign-in.input.js.map +1 -0
  62. package/dist/server/modules/auth/inputs/auth-sign-up.input.d.ts +5 -0
  63. package/dist/server/modules/auth/inputs/auth-sign-up.input.js +34 -0
  64. package/dist/server/modules/auth/inputs/auth-sign-up.input.js.map +1 -0
  65. package/dist/server/modules/user/user.model.d.ts +1 -1
  66. package/dist/server/modules/user/user.model.js +1 -1
  67. package/dist/server/modules/user/user.model.js.map +1 -1
  68. package/dist/server/modules/user/user.resolver.js +12 -11
  69. package/dist/server/modules/user/user.resolver.js.map +1 -1
  70. package/dist/server/modules/user/user.service.js +1 -5
  71. package/dist/server/modules/user/user.service.js.map +1 -1
  72. package/dist/tsconfig.build.tsbuildinfo +1 -1
  73. package/package.json +4 -4
  74. package/src/core/common/decorators/restricted.decorator.ts +150 -53
  75. package/src/core/common/enums/process-type.enum.ts +7 -0
  76. package/src/core/common/enums/role.enum.ts +1 -4
  77. package/src/core/common/helpers/db.helper.ts +70 -56
  78. package/src/core/common/helpers/input.helper.ts +24 -15
  79. package/src/core/common/helpers/service.helper.ts +72 -2
  80. package/src/core/common/interfaces/prepare-input-options.interface.ts +11 -0
  81. package/src/core/common/interfaces/prepare-output-options.interface.ts +10 -0
  82. package/src/core/common/interfaces/service-options.interface.ts +8 -22
  83. package/src/core/common/models/core-persistence.model.ts +0 -11
  84. package/src/core/common/services/module.service.ts +32 -31
  85. package/src/core/common/types/require-only-one.type.ts +6 -0
  86. package/src/core/common/types/required-at-least-one.type.ts +6 -0
  87. package/src/core/modules/auth/inputs/core-auth-sign-in.input.ts +18 -0
  88. package/src/core/modules/auth/inputs/core-auth-sign-up.input.ts +18 -0
  89. package/src/core.module.ts +1 -19
  90. package/src/index.ts +5 -0
  91. package/src/server/modules/auth/auth.model.ts +5 -5
  92. package/src/server/modules/auth/auth.module.ts +13 -2
  93. package/src/server/modules/auth/auth.resolver.ts +30 -12
  94. package/src/server/modules/auth/auth.service.ts +84 -0
  95. package/src/server/modules/auth/inputs/auth-sign-in.input.ts +10 -0
  96. package/src/server/modules/auth/inputs/auth-sign-up.input.ts +18 -0
  97. package/src/server/modules/user/user.model.ts +2 -2
  98. package/src/server/modules/user/user.resolver.ts +12 -11
  99. package/src/server/modules/user/user.service.ts +3 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "8.3.1",
3
+ "version": "8.6.0",
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",
@@ -98,7 +98,7 @@
98
98
  "@typescript-eslint/eslint-plugin": "5.22.0",
99
99
  "@typescript-eslint/parser": "5.22.0",
100
100
  "coffeescript": "2.7.0",
101
- "eslint": "8.14.0",
101
+ "eslint": "8.15.0",
102
102
  "eslint-config-prettier": "8.5.0",
103
103
  "find-file-up": "2.0.1",
104
104
  "grunt": "1.5.2",
@@ -107,12 +107,12 @@
107
107
  "grunt-contrib-watch": "1.1.0",
108
108
  "grunt-sync": "0.8.2",
109
109
  "husky": "7.0.4",
110
- "jest": "28.0.3",
110
+ "jest": "28.1.0",
111
111
  "pm2": "5.2.0",
112
112
  "prettier": "2.6.2",
113
113
  "pretty-quick": "3.1.3",
114
114
  "supertest": "6.2.3",
115
- "ts-jest": "28.0.1",
115
+ "ts-jest": "28.0.2",
116
116
  "ts-morph": "14.0.0",
117
117
  "ts-node": "10.7.0",
118
118
  "tsconfig-paths": "4.0.0",
@@ -1,47 +1,73 @@
1
1
  import 'reflect-metadata';
2
2
  import { UnauthorizedException } from '@nestjs/common';
3
+ import { ProcessType } from '../enums/process-type.enum';
3
4
  import { RoleEnum } from '../enums/role.enum';
4
- import { equalIds, getStringIds } from '../helpers/db.helper';
5
- import { IdsType } from '../types/ids.type';
5
+ import { getIncludedIds } from '../helpers/db.helper';
6
+ import { RequireAtLeastOne } from '../types/required-at-least-one.type';
6
7
 
7
8
  /**
8
9
  * Restricted meta key
9
10
  */
10
11
  const restrictedMetaKey = Symbol('restricted');
11
12
 
13
+ /**
14
+ * Restricted type
15
+ */
16
+ export type RestrictedType = (
17
+ | string
18
+ | RequireAtLeastOne<
19
+ { memberOf?: string | string[]; roles?: string | string[]; processType?: ProcessType },
20
+ 'memberOf' | 'roles'
21
+ >
22
+ )[];
23
+
12
24
  /**
13
25
  * Decorator for restricted properties
14
26
  *
15
- * If the decorator is used it will be checked if the current user has one of the included roles.
16
- * If this is not the case, the property is removed from the return object.
27
+ * The restricted decorator can include roles as strings or object with property `roles`
28
+ * and memberships as objects with property `memberOf`.
17
29
  *
18
- * Activation of the CheckResponseInterceptor is necessary for use.
30
+ * Roles:
31
+ * If one or more Role(Enum)s are set, the user must have at least one of it in his `role` property.
32
+ *
33
+ * Memberships:
34
+ * If one or more membership objects are set, the ID of the user must be included in one of the
35
+ * properties of the processed item, which is specified by the value of `memberOf`
36
+ * Via processType the restriction can be set for input or output only
19
37
  */
20
- export const Restricted = (...roles: string[]): PropertyDecorator => {
21
- return Reflect.metadata(restrictedMetaKey, roles);
38
+ export const Restricted = (...rolesOrMember: RestrictedType): ClassDecorator & PropertyDecorator => {
39
+ return Reflect.metadata(restrictedMetaKey, rolesOrMember);
22
40
  };
23
41
 
24
42
  /**
25
- * Get restricted
43
+ * Get restricted data for (property of) object
26
44
  */
27
- export const getRestricted = (object: unknown, propertyKey: string) => {
45
+ export const getRestricted = (object: unknown, propertyKey?: string): RestrictedType => {
46
+ if (!propertyKey) {
47
+ return Reflect.getMetadata(restrictedMetaKey, object);
48
+ }
28
49
  return Reflect.getMetadata(restrictedMetaKey, object, propertyKey);
29
50
  };
30
51
 
31
52
  /**
32
53
  * Check data for restricted properties (properties with `Restricted` decorator)
33
- *
34
- * If restricted roles includes RoleEnum.S_CREATOR, `creator` (createdBy) from current (DB) data must be set in options
35
- * If restricted roles includes RoleEnum.S_OWNER, `ownerIds` from current (DB) data must be set in options
54
+ * For special Roles and member of group checking the dbObject must be set in options
36
55
  */
37
56
  export const checkRestricted = (
38
57
  data: any,
39
58
  user: { id: any; hasRole: (roles: string[]) => boolean },
40
- options: { creator?: IdsType; ignoreUndefined?: boolean; ownerIds?: IdsType; throwError?: boolean } = {},
59
+ options: {
60
+ dbObject?: any;
61
+ ignoreUndefined?: boolean;
62
+ processType?: ProcessType;
63
+ removeUndefinedFromResultArray?: boolean;
64
+ throwError?: boolean;
65
+ } = {},
41
66
  processedObjects: any[] = []
42
67
  ) => {
43
68
  const config = {
44
69
  ignoreUndefined: true,
70
+ removeUndefinedFromResultArray: true,
45
71
  throwError: true,
46
72
  ...options,
47
73
  };
@@ -60,60 +86,131 @@ export const checkRestricted = (
60
86
  // Array
61
87
  if (Array.isArray(data)) {
62
88
  // Check array items
63
- return data.map((item) => checkRestricted(item, user, config, processedObjects));
89
+ let result = data.map((item) => checkRestricted(item, user, config, processedObjects));
90
+ if (!config.throwError && config.removeUndefinedFromResultArray) {
91
+ result = result.filter((item) => item !== undefined);
92
+ }
93
+ return result;
64
94
  }
65
95
 
66
- // Object
67
- for (const propertyKey of Object.keys(data)) {
68
- // Check undefined
69
- if (data[propertyKey] === undefined && config.ignoreUndefined) {
70
- continue;
71
- }
96
+ // Check function
97
+ const validateRestricted = (restricted) => {
98
+ let valid = true;
99
+ if (restricted?.length) {
100
+ valid = false;
72
101
 
73
- // Get roles
74
- const roles = getRestricted(data, propertyKey);
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
+ });
75
118
 
76
- // If roles are specified
77
- if (roles && roles.some((value) => !!value)) {
78
- // Check user and user roles
79
- if (!user || !user.hasRole(roles)) {
80
- // Check special creator role
81
- if (user?.id && roles.includes(RoleEnum.S_CREATOR) && equalIds(user.id, config.creator)) {
82
- continue;
119
+ // Check roles
120
+ if (roles.length) {
121
+ // Check roles
122
+ if (
123
+ user?.hasRole(roles) ||
124
+ (user?.id && roles.includes(RoleEnum.S_USER)) ||
125
+ (roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
126
+ ) {
127
+ valid = true;
83
128
  }
129
+ }
84
130
 
85
- // Check special owner role
86
- else if (user && roles.includes(RoleEnum.S_OWNER)) {
87
- const userId = getStringIds(user);
88
- const ownerIds = config.ownerIds ? getStringIds(config.ownerIds) : null;
89
-
90
- if (
91
- // No owner IDs
92
- !ownerIds ||
93
- // User is not the owner
94
- !(ownerIds === userId || (Array.isArray(ownerIds) && ownerIds.includes(userId)))
95
- ) {
96
- // The user does not have the required rights and is not the owner
97
- if (config.throwError) {
98
- if (!config.ownerIds) {
99
- throw new UnauthorizedException('Lack of ownerIds to verify ownership of ' + propertyKey);
131
+ if (!valid) {
132
+ // Get groups
133
+ const groups = restricted.filter((item) => {
134
+ return (
135
+ typeof item === 'object' &&
136
+ // Check if object is valid
137
+ item.memberOf.length &&
138
+ // Check if processType is specified and is valid for current process
139
+ (config.processType && item.processType ? config.processType === item.processType : true)
140
+ );
141
+ }) as { memberOf: string | string[] }[];
142
+
143
+ // Check groups
144
+ if (groups.length) {
145
+ // Get members from groups
146
+ const members = [];
147
+ for (const group of groups) {
148
+ let properties: string[] = group.memberOf as string[];
149
+ if (!Array.isArray(group.memberOf)) {
150
+ properties = [group.memberOf];
151
+ }
152
+ for (const property of properties) {
153
+ const items = config.dbObject?.[property];
154
+ if (items) {
155
+ if (Array.isArray(items)) {
156
+ members.concat(items);
157
+ } else {
158
+ members.push(items);
159
+ }
100
160
  }
101
- throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
102
161
  }
103
- continue;
104
162
  }
105
- } else {
106
- // The user does not have the required rights
107
- if (config.throwError) {
108
- throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
163
+
164
+ // Check if user is a member
165
+ if (getIncludedIds(members, user)) {
166
+ valid = true;
109
167
  }
110
- continue;
168
+ }
169
+
170
+ // Check if there are no limitations
171
+ if (!roles.length && !groups.length) {
172
+ valid = true;
111
173
  }
112
174
  }
113
175
  }
176
+ return valid;
177
+ };
114
178
 
115
- // Check property data
116
- data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
179
+ // Check object
180
+ const objectRestrictions = getRestricted(data.constructor);
181
+ const objectIsValid = validateRestricted(objectRestrictions);
182
+ if (!objectIsValid) {
183
+ // Throw error
184
+ if (config.throwError) {
185
+ throw new UnauthorizedException('The current user has no access rights for ' + data.constructor?.name);
186
+ }
187
+ return null;
188
+ }
189
+
190
+ // Check properties of object
191
+ for (const propertyKey of Object.keys(data)) {
192
+ // Check undefined
193
+ if (data[propertyKey] === undefined && config.ignoreUndefined) {
194
+ continue;
195
+ }
196
+
197
+ // Check restricted
198
+ const restricted = getRestricted(data, propertyKey);
199
+ const valid = validateRestricted(restricted);
200
+
201
+ // Check rights
202
+ if (valid) {
203
+ // Check deep
204
+ data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
205
+ } else {
206
+ // Throw error
207
+ if (config.throwError) {
208
+ throw new UnauthorizedException('The current user has no access rights for ' + propertyKey);
209
+ }
210
+
211
+ // Remove property
212
+ delete data[propertyKey];
213
+ }
117
214
  }
118
215
 
119
216
  // Return processed data
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Current process type for data input or data output
3
+ */
4
+ export enum ProcessType {
5
+ INPUT = 'input',
6
+ OUTPUT = 'output',
7
+ }
@@ -15,7 +15,7 @@ export enum RoleEnum {
15
15
  // and via ServiceOptions for Resolver methods. This roles should not be integrated into user.roles!
16
16
  // ===================================================================================================================
17
17
 
18
- // User must be signed in (see context user, e.g. @GraphQLUser)
18
+ // User must be logged in (see context user, e.g. @GraphQLUser)
19
19
  S_USER = 's_user',
20
20
 
21
21
  // ===================================================================================================================
@@ -25,7 +25,4 @@ export enum RoleEnum {
25
25
 
26
26
  // User must be the creator of the processed object(s) (see createdBy property of object(s))
27
27
  S_CREATOR = 's_creator',
28
-
29
- // User must be an owner of the processed object(s) (see owners property of object(s))
30
- S_OWNER = 's_owner',
31
28
  }
@@ -84,7 +84,7 @@ export function addIds(
84
84
  */
85
85
  export function equalIds(...ids: IdsType[]): boolean {
86
86
  if (!ids) {
87
- return true;
87
+ return false;
88
88
  }
89
89
  const compare = getStringIds(ids[0]);
90
90
  if (!compare) {
@@ -93,10 +93,51 @@ export function equalIds(...ids: IdsType[]): boolean {
93
93
  return ids.every((id) => getStringIds(id) === compare);
94
94
  }
95
95
 
96
+ /**
97
+ * Get included ids
98
+ * @param includes IdsType, which should be checked if it contains the ID
99
+ * @param ids IdsType, which should be included
100
+ * @param convert If set the result array will be converted to pure type String array or ObjectId array
101
+ * @return IdsType with IDs which are included, undefined if includes or ids are missing or null if none is included
102
+ */
103
+ export function getIncludedIds(includes: IdsType, ids: IdsType, convert?: 'string'): string[];
104
+ export function getIncludedIds(includes: IdsType, ids: IdsType, convert?: 'object'): Types.ObjectId[];
105
+ export function getIncludedIds<T = IdsType>(
106
+ includes: IdsType,
107
+ ids: T | IdsType,
108
+ convert?: 'string' | 'object'
109
+ ): T[] | null | undefined {
110
+ if (!includes || !ids) {
111
+ return undefined;
112
+ }
113
+
114
+ if (!Array.isArray(includes)) {
115
+ includes = [includes];
116
+ }
117
+
118
+ if (!Array.isArray(ids)) {
119
+ ids = [ids];
120
+ }
121
+
122
+ let result = [];
123
+ const includesStrings = getStringIds(includes);
124
+ for (const id of ids) {
125
+ if (includesStrings.includes(getStringIds(id))) {
126
+ result.push(id);
127
+ }
128
+ }
129
+
130
+ if (convert) {
131
+ result = convert === 'string' ? getStringIds(result) : getObjectIds(result);
132
+ }
133
+
134
+ return result.length ? result : null;
135
+ }
136
+
96
137
  /**
97
138
  * Get indexes of IDs in an array
98
139
  */
99
- export function getIndexesViaIds(ids: any | any[], array: any[]): number[] {
140
+ export function getIndexesViaIds(ids: IdsType, array: IdsType): number[] {
100
141
  // Check and prepare parameters
101
142
  if (!ids) {
102
143
  return [];
@@ -234,19 +275,41 @@ export function getElementsViaIds<T = any>(
234
275
  /**
235
276
  * Get populate options from GraphQL resolve info
236
277
  */
237
- export function getPopulateOptions(info: GraphQLResolveInfo, select?: string): PopulateOptions[] {
278
+ export function getPopulateOptions(info: GraphQLResolveInfo, dotSeparatedSelect?: string): PopulateOptions[] {
238
279
  const result = [];
239
280
 
240
281
  if (!info?.fieldNodes?.length) {
241
282
  return result;
242
283
  }
243
284
 
244
- for (const fieldNode of info.fieldNodes) {
245
- if ((select || fieldNode?.name?.value === select) && fieldNode?.selectionSet?.selections?.length) {
246
- return getPopulatOptionsFromSelections(fieldNode.selectionSet.selections);
285
+ if (dotSeparatedSelect?.length) {
286
+ for (const fieldNode of info.fieldNodes) {
287
+ const selects = dotSeparatedSelect.split('.');
288
+ const fieldNodeName = selects.shift();
289
+ if (fieldNode?.name?.value === fieldNodeName && fieldNode?.selectionSet?.selections?.length) {
290
+ if (!selects.length) {
291
+ return getPopulatOptionsFromSelections(fieldNode.selectionSet.selections);
292
+ } else {
293
+ let selections = fieldNode.selectionSet.selections;
294
+ do {
295
+ if (!selections?.length) {
296
+ break;
297
+ }
298
+ const select = selects.shift();
299
+ for (const selection of selections) {
300
+ if ((selection as FieldNode)?.name?.value === select) {
301
+ selections = (selection as FieldNode)?.selectionSet?.selections;
302
+ if (!selects.length) {
303
+ return getPopulatOptionsFromSelections(selections);
304
+ }
305
+ break;
306
+ }
307
+ }
308
+ } while (selects.length);
309
+ }
310
+ }
247
311
  }
248
312
  }
249
-
250
313
  return result;
251
314
  }
252
315
 
@@ -301,55 +364,6 @@ export function getJSONClone<T = any>(obj: T): Partial<T> {
301
364
  return JSON.parse(JSON.stringify(obj));
302
365
  }
303
366
 
304
- /**
305
- * Check if ID is included
306
- * @param includes String, ObjectId or array with both, which should be checked if it contains the ID
307
- * @param ids String, ObjectId or array with both, which should be included
308
- * @param convert If set the result array will be converted to pure type String array or ObjectId array
309
- * @return StringOrObjectId[] Array with IDs which are included or null if none is included
310
- */
311
- export function includesIds(
312
- includes: StringOrObjectId | StringOrObjectId[],
313
- ids: StringOrObjectId | StringOrObjectId[],
314
- convert?: 'string'
315
- ): string[];
316
- export function includesIds(
317
- includes: StringOrObjectId | StringOrObjectId[],
318
- ids: StringOrObjectId | StringOrObjectId[],
319
- convert?: 'object'
320
- ): Types.ObjectId[];
321
- export function includesIds<T = StringOrObjectId>(
322
- includes: StringOrObjectId | StringOrObjectId[] | any | any[],
323
- ids: T | StringOrObjectId[],
324
- convert?: 'string' | 'object'
325
- ): T[] | null {
326
- if (!includes || !ids) {
327
- return null;
328
- }
329
-
330
- if (!Array.isArray(includes)) {
331
- includes = [includes];
332
- }
333
-
334
- if (!Array.isArray(ids)) {
335
- ids = [ids] as any;
336
- }
337
-
338
- let result = [];
339
- const includesStrings = getStringIds(includes);
340
- for (const id of ids as StringOrObjectId[]) {
341
- if (includesStrings.includes(getStringIds(id))) {
342
- result.push(id);
343
- }
344
- }
345
-
346
- if (convert) {
347
- result = convert === 'string' ? getStringIds(result) : getObjectIds(result);
348
- }
349
-
350
- return result.length ? result : null;
351
- }
352
-
353
367
  /**
354
368
  * Convert all ObjectIds to strings
355
369
  */
@@ -3,9 +3,9 @@ import { plainToInstance } from 'class-transformer';
3
3
  import { validate } from 'class-validator';
4
4
  import * as _ from 'lodash';
5
5
  import { checkRestricted } from '../decorators/restricted.decorator';
6
+ import { ProcessType } from '../enums/process-type.enum';
6
7
  import { RoleEnum } from '../enums/role.enum';
7
- import { IdsType } from '../types/ids.type';
8
- import { equalIds, getStringIds } from './db.helper';
8
+ import { equalIds } from './db.helper';
9
9
 
10
10
  /**
11
11
  * Helper class for inputs
@@ -18,7 +18,13 @@ export default class InputHelper {
18
18
  public static async check(
19
19
  value: any,
20
20
  user: { id: any; hasRole: (roles: string[]) => boolean },
21
- options?: { creator?: IdsType; metatype?: any; ownerIds?: IdsType; roles?: string | string[] }
21
+ options?: {
22
+ dbObject?: any;
23
+ metatype?: any;
24
+ processType?: ProcessType;
25
+ roles?: string | string[];
26
+ throwError?: boolean;
27
+ }
22
28
  ): Promise<any> {
23
29
  return check(value, user, options);
24
30
  }
@@ -182,7 +188,13 @@ export default class InputHelper {
182
188
  export async function check(
183
189
  value: any,
184
190
  user: { id: any; hasRole: (roles: string[]) => boolean },
185
- options?: { creator?: IdsType; metatype?: any; ownerIds?: IdsType; roles?: string | string[]; throwError?: boolean }
191
+ options?: {
192
+ dbObject?: any;
193
+ metatype?: any;
194
+ processType?: ProcessType;
195
+ roles?: string | string[];
196
+ throwError?: boolean;
197
+ }
186
198
  ): Promise<any> {
187
199
  const config = {
188
200
  throwError: true,
@@ -196,18 +208,15 @@ export async function check(
196
208
  roles = [roles];
197
209
  }
198
210
  let valid = false;
199
- if (roles.includes(RoleEnum.S_USER) && user?.id) {
200
- valid = true;
201
- } else if (user.hasRole(roles)) {
211
+ if (
212
+ // check if user is logged in
213
+ (roles.includes(RoleEnum.S_USER) && user?.id) ||
214
+ // check if the user has at least one of the required roles
215
+ user.hasRole(roles) ||
216
+ // check if the user is the creator
217
+ (roles.includes(RoleEnum.S_CREATOR) && equalIds(config.dbObject?.createdBy, user))
218
+ ) {
202
219
  valid = true;
203
- } else if (roles.includes(RoleEnum.S_CREATOR) && user?.id && equalIds(user.id, config.creator)) {
204
- valid = true;
205
- } else if (roles.includes(RoleEnum.S_OWNER) && user?.id && config.ownerIds) {
206
- let ownerIds: string | string[] = getStringIds(config.ownerIds);
207
- if (!Array.isArray(ownerIds)) {
208
- ownerIds = [ownerIds];
209
- }
210
- valid = ownerIds.includes(getStringIds(user.id));
211
220
  }
212
221
  if (!valid) {
213
222
  throw new UnauthorizedException('Missing rights');
@@ -1,7 +1,12 @@
1
1
  import { UnauthorizedException } from '@nestjs/common';
2
2
  import * as bcrypt from 'bcrypt';
3
+ import { plainToInstance } from 'class-transformer';
3
4
  import * as _ from 'lodash';
4
5
  import { RoleEnum } from '../enums/role.enum';
6
+ import { PrepareInputOptions } from '../interfaces/prepare-input-options.interface';
7
+ import { PrepareOutputOptions } from '../interfaces/prepare-output-options.interface';
8
+ import { ResolveSelector } from '../interfaces/resolve-selector.interface';
9
+ import { ServiceOptions } from '../interfaces/service-options.interface';
5
10
 
6
11
  /**
7
12
  * Helper class for services
@@ -54,6 +59,7 @@ export async function prepareInput<T = any>(
54
59
  clone?: boolean;
55
60
  getNewArray?: boolean;
56
61
  removeUndefined?: boolean;
62
+ targetModel?: new (...args: any[]) => T;
57
63
  } = {}
58
64
  ): Promise<T> {
59
65
  // Configuration
@@ -86,6 +92,15 @@ export async function prepareInput<T = any>(
86
92
  }
87
93
  }
88
94
 
95
+ // Map input if target model exist
96
+ if (config.targetModel && !(input instanceof config.targetModel)) {
97
+ if ((config.targetModel as any)?.map) {
98
+ input = await (config.targetModel as any).map(input);
99
+ } else {
100
+ input = plainToInstance(config.targetModel, input);
101
+ }
102
+ }
103
+
89
104
  // Remove undefined properties to avoid unwanted overwrites
90
105
  if (config.removeUndefined) {
91
106
  Object.keys(input).forEach((key) => input[key] === undefined && delete input[key]);
@@ -171,8 +186,12 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
171
186
  }
172
187
 
173
188
  // Map output if target model exist
174
- if (config.targetModel) {
175
- output = await (config.targetModel as any).map(output);
189
+ if (config.targetModel && !(output instanceof config.targetModel)) {
190
+ if ((config.targetModel as any)?.map) {
191
+ output = await (config.targetModel as any).map(output);
192
+ } else {
193
+ output = plainToInstance(config.targetModel, output);
194
+ }
176
195
  }
177
196
 
178
197
  // Remove password if exists
@@ -198,3 +217,54 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
198
217
  // Return prepared output
199
218
  return output;
200
219
  }
220
+
221
+ /**
222
+ * Prepare service options
223
+ */
224
+ export function prepareServiceOptions(
225
+ serviceOptions: ServiceOptions,
226
+ options?: {
227
+ clone?: boolean;
228
+ inputType?: any;
229
+ outputType?: any;
230
+ subFieldSelection?: string;
231
+ prepareInput?: PrepareInputOptions;
232
+ prepareOutput?: PrepareOutputOptions;
233
+ }
234
+ ): ServiceOptions {
235
+ // Set default values
236
+ const config = {
237
+ clone: true,
238
+ ...options,
239
+ };
240
+
241
+ // Clone
242
+ if (serviceOptions && config.clone) {
243
+ serviceOptions = _.cloneDeep(serviceOptions);
244
+ }
245
+
246
+ // Init if not exists
247
+ serviceOptions = serviceOptions || {};
248
+
249
+ // Set properties
250
+ serviceOptions.inputType = serviceOptions.inputType || options?.inputType;
251
+ serviceOptions.outputType = serviceOptions.outputType || options?.outputType;
252
+
253
+ // Set properties which can deactivate handling when falsy
254
+ if (!serviceOptions.prepareInput && 'prepareInput' in config) {
255
+ serviceOptions.prepareInput = config.prepareInput;
256
+ }
257
+ if (!serviceOptions.prepareOutput && 'prepareOutput' in config) {
258
+ serviceOptions.prepareOutput = config.prepareOutput;
259
+ }
260
+
261
+ // Set subfield selection
262
+ if (config.subFieldSelection) {
263
+ if ((serviceOptions.fieldSelection as ResolveSelector)?.select) {
264
+ (serviceOptions.fieldSelection as ResolveSelector).select += '.' + config.subFieldSelection;
265
+ }
266
+ }
267
+
268
+ // Return (cloned and) prepared service options
269
+ return serviceOptions;
270
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Interface for prepare input options
3
+ */
4
+ export interface PrepareInputOptions {
5
+ [key: string]: any;
6
+ create?: boolean;
7
+ clone?: boolean;
8
+ getNewArray?: boolean;
9
+ removeUndefined?: boolean;
10
+ targetModel?: new (...args: any[]) => any;
11
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Interface for prepare output options
3
+ */
4
+ export interface PrepareOutputOptions {
5
+ [key: string]: any;
6
+ clone?: boolean;
7
+ getNewArray?: boolean;
8
+ removeUndefined?: boolean;
9
+ targetModel?: new (...args: any[]) => any;
10
+ }