@lenne.tech/nest-server 8.4.0 → 8.5.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/dist/core/common/decorators/restricted.decorator.d.ts +3 -2
- package/dist/core/common/decorators/restricted.decorator.js +29 -8
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +1 -0
- package/dist/core/common/helpers/service.helper.js +18 -2
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/interfaces/service-options.interface.d.ts +1 -1
- package/dist/core/common/services/module.service.js +14 -8
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/decorators/restricted.decorator.ts +50 -14
- package/src/core/common/helpers/service.helper.ts +17 -2
- package/src/core/common/interfaces/service-options.interface.ts +3 -3
- package/src/core/common/services/module.service.ts +17 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.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",
|
|
@@ -9,6 +9,10 @@ import { RequireAtLeastOne } from '../types/required-at-least-one.type';
|
|
|
9
9
|
* Restricted meta key
|
|
10
10
|
*/
|
|
11
11
|
const restrictedMetaKey = Symbol('restricted');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Restricted type
|
|
15
|
+
*/
|
|
12
16
|
export type RestrictedType = (
|
|
13
17
|
| string
|
|
14
18
|
| RequireAtLeastOne<
|
|
@@ -31,14 +35,17 @@ export type RestrictedType = (
|
|
|
31
35
|
* properties of the processed item, which is specified by the value of `memberOf`
|
|
32
36
|
* Via processType the restriction can be set for input or output only
|
|
33
37
|
*/
|
|
34
|
-
export const Restricted = (...rolesOrMember: RestrictedType): PropertyDecorator => {
|
|
38
|
+
export const Restricted = (...rolesOrMember: RestrictedType): ClassDecorator & PropertyDecorator => {
|
|
35
39
|
return Reflect.metadata(restrictedMetaKey, rolesOrMember);
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
|
-
* Get restricted
|
|
43
|
+
* Get restricted data for (property of) object
|
|
40
44
|
*/
|
|
41
|
-
export const getRestricted = (object: unknown, propertyKey
|
|
45
|
+
export const getRestricted = (object: unknown, propertyKey?: string): RestrictedType => {
|
|
46
|
+
if (!propertyKey) {
|
|
47
|
+
return Reflect.getMetadata(restrictedMetaKey, object);
|
|
48
|
+
}
|
|
42
49
|
return Reflect.getMetadata(restrictedMetaKey, object, propertyKey);
|
|
43
50
|
};
|
|
44
51
|
|
|
@@ -49,11 +56,18 @@ export const getRestricted = (object: unknown, propertyKey: string): RestrictedT
|
|
|
49
56
|
export const checkRestricted = (
|
|
50
57
|
data: any,
|
|
51
58
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
52
|
-
options: {
|
|
59
|
+
options: {
|
|
60
|
+
dbObject?: any;
|
|
61
|
+
ignoreUndefined?: boolean;
|
|
62
|
+
processType?: ProcessType;
|
|
63
|
+
removeUndefinedFromResultArray?: boolean;
|
|
64
|
+
throwError?: boolean;
|
|
65
|
+
} = {},
|
|
53
66
|
processedObjects: any[] = []
|
|
54
67
|
) => {
|
|
55
68
|
const config = {
|
|
56
69
|
ignoreUndefined: true,
|
|
70
|
+
removeUndefinedFromResultArray: true,
|
|
57
71
|
throwError: true,
|
|
58
72
|
...options,
|
|
59
73
|
};
|
|
@@ -72,18 +86,15 @@ export const checkRestricted = (
|
|
|
72
86
|
// Array
|
|
73
87
|
if (Array.isArray(data)) {
|
|
74
88
|
// Check array items
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Object
|
|
79
|
-
for (const propertyKey of Object.keys(data)) {
|
|
80
|
-
// Check undefined
|
|
81
|
-
if (data[propertyKey] === undefined && config.ignoreUndefined) {
|
|
82
|
-
continue;
|
|
89
|
+
let result = data.map((item) => checkRestricted(item, user, config, processedObjects));
|
|
90
|
+
if (!config.throwError && config.removeUndefinedFromResultArray) {
|
|
91
|
+
result = result.filter((item) => item !== undefined);
|
|
83
92
|
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
// Check function
|
|
97
|
+
const validateRestricted = (restricted) => {
|
|
87
98
|
let valid = true;
|
|
88
99
|
if (restricted?.length) {
|
|
89
100
|
valid = false;
|
|
@@ -110,6 +121,7 @@ export const checkRestricted = (
|
|
|
110
121
|
// Check roles
|
|
111
122
|
if (
|
|
112
123
|
user?.hasRole(roles) ||
|
|
124
|
+
(user?.id && roles.includes(RoleEnum.S_USER)) ||
|
|
113
125
|
(roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
|
|
114
126
|
) {
|
|
115
127
|
valid = true;
|
|
@@ -161,6 +173,30 @@ export const checkRestricted = (
|
|
|
161
173
|
}
|
|
162
174
|
}
|
|
163
175
|
}
|
|
176
|
+
return valid;
|
|
177
|
+
};
|
|
178
|
+
|
|
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);
|
|
164
200
|
|
|
165
201
|
// Check rights
|
|
166
202
|
if (valid) {
|
|
@@ -1,5 +1,6 @@
|
|
|
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';
|
|
5
6
|
|
|
@@ -54,6 +55,7 @@ export async function prepareInput<T = any>(
|
|
|
54
55
|
clone?: boolean;
|
|
55
56
|
getNewArray?: boolean;
|
|
56
57
|
removeUndefined?: boolean;
|
|
58
|
+
targetModel?: new (...args: any[]) => T;
|
|
57
59
|
} = {}
|
|
58
60
|
): Promise<T> {
|
|
59
61
|
// Configuration
|
|
@@ -86,6 +88,15 @@ export async function prepareInput<T = any>(
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
// Map input if target model exist
|
|
92
|
+
if (config.targetModel && !(input instanceof config.targetModel)) {
|
|
93
|
+
if ((config.targetModel as any)?.map) {
|
|
94
|
+
input = await (config.targetModel as any).map(input);
|
|
95
|
+
} else {
|
|
96
|
+
input = plainToInstance(config.targetModel, input);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
// Remove undefined properties to avoid unwanted overwrites
|
|
90
101
|
if (config.removeUndefined) {
|
|
91
102
|
Object.keys(input).forEach((key) => input[key] === undefined && delete input[key]);
|
|
@@ -171,8 +182,12 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
171
182
|
}
|
|
172
183
|
|
|
173
184
|
// Map output if target model exist
|
|
174
|
-
if (config.targetModel) {
|
|
175
|
-
|
|
185
|
+
if (config.targetModel && !(output instanceof config.targetModel)) {
|
|
186
|
+
if ((config.targetModel as any)?.map) {
|
|
187
|
+
output = await (config.targetModel as any).map(output);
|
|
188
|
+
} else {
|
|
189
|
+
output = plainToInstance(config.targetModel, output);
|
|
190
|
+
}
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
// Remove password if exists
|
|
@@ -26,6 +26,9 @@ export interface ServiceOptions {
|
|
|
26
26
|
// Overwrites type of input (array items)
|
|
27
27
|
inputType?: new (...params: any[]) => any;
|
|
28
28
|
|
|
29
|
+
// Overwrites type of output (array items)
|
|
30
|
+
outputType?: new (...params: any[]) => any;
|
|
31
|
+
|
|
29
32
|
// Process field selection
|
|
30
33
|
// If {} or not set, then the field selection runs with defaults
|
|
31
34
|
// If falsy, then the field selection will not be automatically executed
|
|
@@ -59,9 +62,6 @@ export interface ServiceOptions {
|
|
|
59
62
|
// Whether to publish action via GraphQL subscription
|
|
60
63
|
pubSub?: boolean;
|
|
61
64
|
|
|
62
|
-
// Overwrites type of result (array items)
|
|
63
|
-
resultType?: new (...params: any[]) => any;
|
|
64
|
-
|
|
65
65
|
// Roles (as string) to check
|
|
66
66
|
roles?: string | string[];
|
|
67
67
|
}
|
|
@@ -84,7 +84,11 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
84
84
|
|
|
85
85
|
// Prepare input
|
|
86
86
|
if (config.prepareInput && this.prepareInput) {
|
|
87
|
-
|
|
87
|
+
const opts = config.prepareInput;
|
|
88
|
+
if (!opts.targetModel && config.inputType) {
|
|
89
|
+
opts.targetModel = config.inputType;
|
|
90
|
+
}
|
|
91
|
+
await this.prepareInput(config.input, opts);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
// Get DB object
|
|
@@ -101,7 +105,7 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
101
105
|
if (config.input && config.checkRights && this.checkRights) {
|
|
102
106
|
const opts: any = { dbObject: config.dbObject, processType: ProcessType.INPUT, roles: config.roles };
|
|
103
107
|
if (config.inputType) {
|
|
104
|
-
opts.metatype = config.
|
|
108
|
+
opts.metatype = config.inputType;
|
|
105
109
|
}
|
|
106
110
|
config.input = await this.checkRights(config.input, config.currentUser as any, opts);
|
|
107
111
|
}
|
|
@@ -116,11 +120,11 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
116
120
|
|
|
117
121
|
// Prepare output
|
|
118
122
|
if (config.prepareOutput && this.prepareOutput) {
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
123
|
+
const opts = config.prepareOutput;
|
|
124
|
+
if (!opts.targetModel && config.outputType) {
|
|
125
|
+
opts.targetModel = config.outputType;
|
|
122
126
|
}
|
|
123
|
-
result = await this.prepareOutput(result,
|
|
127
|
+
result = await this.prepareOutput(result, opts);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
// Check output rights
|
|
@@ -131,8 +135,8 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
131
135
|
roles: config.roles,
|
|
132
136
|
throwError: false,
|
|
133
137
|
};
|
|
134
|
-
if (config.
|
|
135
|
-
opts.metatype = config.
|
|
138
|
+
if (config.outputType) {
|
|
139
|
+
opts.metatype = config.outputType;
|
|
136
140
|
}
|
|
137
141
|
result = await this.checkRights(result, config.currentUser as any, opts);
|
|
138
142
|
}
|
|
@@ -145,7 +149,11 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
145
149
|
* Prepare input before save
|
|
146
150
|
*/
|
|
147
151
|
async prepareInput(input: Record<string, any>, options: ServiceOptions = {}) {
|
|
148
|
-
|
|
152
|
+
const config = {
|
|
153
|
+
targetModel: this.mainModelConstructor,
|
|
154
|
+
...options?.prepareInput,
|
|
155
|
+
};
|
|
156
|
+
return prepareInput(input, options.currentUser, config);
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
/**
|