@lenne.tech/nest-server 8.3.1 → 8.4.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 +11 -5
- package/dist/core/common/decorators/restricted.decorator.js +64 -23
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/enums/process-type.enum.d.ts +4 -0
- package/dist/core/common/enums/process-type.enum.js +9 -0
- package/dist/core/common/enums/process-type.enum.js.map +1 -0
- package/dist/core/common/enums/role.enum.d.ts +1 -2
- package/dist/core/common/enums/role.enum.js +0 -1
- package/dist/core/common/enums/role.enum.js.map +1 -1
- package/dist/core/common/helpers/db.helper.d.ts +3 -3
- package/dist/core/common/helpers/db.helper.js +25 -25
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +6 -5
- package/dist/core/common/helpers/input.helper.js +4 -15
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/interfaces/service-options.interface.d.ts +0 -2
- package/dist/core/common/models/core-persistence.model.d.ts +0 -1
- package/dist/core/common/models/core-persistence.model.js +0 -10
- package/dist/core/common/models/core-persistence.model.js.map +1 -1
- package/dist/core/common/services/module.service.d.ts +3 -3
- package/dist/core/common/services/module.service.js +13 -18
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/common/types/require-only-one.type.d.ts +3 -0
- package/dist/core/common/types/require-only-one.type.js +3 -0
- package/dist/core/common/types/require-only-one.type.js.map +1 -0
- package/dist/core/common/types/required-at-least-one.type.d.ts +3 -0
- package/dist/core/common/types/required-at-least-one.type.js +3 -0
- package/dist/core/common/types/required-at-least-one.type.js.map +1 -0
- package/dist/core.module.js +0 -5
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +1 -1
- package/dist/server/modules/user/user.model.js +1 -1
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.service.js +1 -1
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/core/common/decorators/restricted.decorator.ts +106 -45
- package/src/core/common/enums/process-type.enum.ts +7 -0
- package/src/core/common/enums/role.enum.ts +1 -4
- package/src/core/common/helpers/db.helper.ts +43 -51
- package/src/core/common/helpers/input.helper.ts +24 -15
- package/src/core/common/interfaces/service-options.interface.ts +2 -6
- package/src/core/common/models/core-persistence.model.ts +0 -11
- package/src/core/common/services/module.service.ts +15 -22
- package/src/core/common/types/require-only-one.type.ts +6 -0
- package/src/core/common/types/required-at-least-one.type.ts +6 -0
- package/src/core.module.ts +1 -19
- package/src/index.ts +3 -0
- package/src/server/modules/user/user.model.ts +2 -2
- package/src/server/modules/user/user.service.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.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.
|
|
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,7 +107,7 @@
|
|
|
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
|
|
110
|
+
"jest": "28.1.0",
|
|
111
111
|
"pm2": "5.2.0",
|
|
112
112
|
"prettier": "2.6.2",
|
|
113
113
|
"pretty-quick": "3.1.3",
|
|
@@ -1,43 +1,55 @@
|
|
|
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 {
|
|
5
|
-
import {
|
|
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');
|
|
12
|
+
export type RestrictedType = (
|
|
13
|
+
| string
|
|
14
|
+
| RequireAtLeastOne<
|
|
15
|
+
{ memberOf?: string | string[]; roles?: string | string[]; processType?: ProcessType },
|
|
16
|
+
'memberOf' | 'roles'
|
|
17
|
+
>
|
|
18
|
+
)[];
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* Decorator for restricted properties
|
|
14
22
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
23
|
+
* The restricted decorator can include roles as strings or object with property `roles`
|
|
24
|
+
* and memberships as objects with property `memberOf`.
|
|
17
25
|
*
|
|
18
|
-
*
|
|
26
|
+
* Roles:
|
|
27
|
+
* If one or more Role(Enum)s are set, the user must have at least one of it in his `role` property.
|
|
28
|
+
*
|
|
29
|
+
* Memberships:
|
|
30
|
+
* If one or more membership objects are set, the ID of the user must be included in one of the
|
|
31
|
+
* properties of the processed item, which is specified by the value of `memberOf`
|
|
32
|
+
* Via processType the restriction can be set for input or output only
|
|
19
33
|
*/
|
|
20
|
-
export const Restricted = (...
|
|
21
|
-
return Reflect.metadata(restrictedMetaKey,
|
|
34
|
+
export const Restricted = (...rolesOrMember: RestrictedType): PropertyDecorator => {
|
|
35
|
+
return Reflect.metadata(restrictedMetaKey, rolesOrMember);
|
|
22
36
|
};
|
|
23
37
|
|
|
24
38
|
/**
|
|
25
39
|
* Get restricted
|
|
26
40
|
*/
|
|
27
|
-
export const getRestricted = (object: unknown, propertyKey: string) => {
|
|
41
|
+
export const getRestricted = (object: unknown, propertyKey: string): RestrictedType => {
|
|
28
42
|
return Reflect.getMetadata(restrictedMetaKey, object, propertyKey);
|
|
29
43
|
};
|
|
30
44
|
|
|
31
45
|
/**
|
|
32
46
|
* 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
|
|
47
|
+
* For special Roles and member of group checking the dbObject must be set in options
|
|
36
48
|
*/
|
|
37
49
|
export const checkRestricted = (
|
|
38
50
|
data: any,
|
|
39
51
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
40
|
-
options: {
|
|
52
|
+
options: { dbObject?: any; ignoreUndefined?: boolean; processType?: ProcessType; throwError?: boolean } = {},
|
|
41
53
|
processedObjects: any[] = []
|
|
42
54
|
) => {
|
|
43
55
|
const config = {
|
|
@@ -70,50 +82,99 @@ export const checkRestricted = (
|
|
|
70
82
|
continue;
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
85
|
+
// Check restricted
|
|
86
|
+
const restricted = getRestricted(data, propertyKey);
|
|
87
|
+
let valid = true;
|
|
88
|
+
if (restricted?.length) {
|
|
89
|
+
valid = false;
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
// Get roles
|
|
92
|
+
const roles: string[] = [];
|
|
93
|
+
restricted.forEach((item) => {
|
|
94
|
+
if (typeof item === 'string') {
|
|
95
|
+
roles.push(item);
|
|
96
|
+
} else if (
|
|
97
|
+
item?.roles?.length &&
|
|
98
|
+
(config.processType && item.processType ? config.processType === item.processType : true)
|
|
99
|
+
) {
|
|
100
|
+
if (Array.isArray(item.roles)) {
|
|
101
|
+
roles.push(...item.roles);
|
|
102
|
+
} else {
|
|
103
|
+
roles.push(item.roles);
|
|
104
|
+
}
|
|
83
105
|
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Check roles
|
|
109
|
+
if (roles.length) {
|
|
110
|
+
// Check roles
|
|
111
|
+
if (
|
|
112
|
+
user?.hasRole(roles) ||
|
|
113
|
+
(roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
|
|
114
|
+
) {
|
|
115
|
+
valid = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!valid) {
|
|
120
|
+
// Get groups
|
|
121
|
+
const groups = restricted.filter((item) => {
|
|
122
|
+
return (
|
|
123
|
+
typeof item === 'object' &&
|
|
124
|
+
// Check if object is valid
|
|
125
|
+
item.memberOf.length &&
|
|
126
|
+
// Check if processType is specified and is valid for current process
|
|
127
|
+
(config.processType && item.processType ? config.processType === item.processType : true)
|
|
128
|
+
);
|
|
129
|
+
}) as { memberOf: string | string[] }[];
|
|
84
130
|
|
|
85
|
-
// Check
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
131
|
+
// Check groups
|
|
132
|
+
if (groups.length) {
|
|
133
|
+
// Get members from groups
|
|
134
|
+
const members = [];
|
|
135
|
+
for (const group of groups) {
|
|
136
|
+
let properties: string[] = group.memberOf as string[];
|
|
137
|
+
if (!Array.isArray(group.memberOf)) {
|
|
138
|
+
properties = [group.memberOf];
|
|
139
|
+
}
|
|
140
|
+
for (const property of properties) {
|
|
141
|
+
const items = config.dbObject?.[property];
|
|
142
|
+
if (items) {
|
|
143
|
+
if (Array.isArray(items)) {
|
|
144
|
+
members.concat(items);
|
|
145
|
+
} else {
|
|
146
|
+
members.push(items);
|
|
147
|
+
}
|
|
100
148
|
}
|
|
101
|
-
throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
|
|
102
149
|
}
|
|
103
|
-
continue;
|
|
104
150
|
}
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
if (
|
|
108
|
-
|
|
151
|
+
|
|
152
|
+
// Check if user is a member
|
|
153
|
+
if (getIncludedIds(members, user)) {
|
|
154
|
+
valid = true;
|
|
109
155
|
}
|
|
110
|
-
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if there are no limitations
|
|
159
|
+
if (!roles.length && !groups.length) {
|
|
160
|
+
valid = true;
|
|
111
161
|
}
|
|
112
162
|
}
|
|
113
163
|
}
|
|
114
164
|
|
|
115
|
-
// Check
|
|
116
|
-
|
|
165
|
+
// Check rights
|
|
166
|
+
if (valid) {
|
|
167
|
+
// Check deep
|
|
168
|
+
data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
|
|
169
|
+
} else {
|
|
170
|
+
// Throw error
|
|
171
|
+
if (config.throwError) {
|
|
172
|
+
throw new UnauthorizedException('The current user has no access rights for ' + propertyKey);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Remove property
|
|
176
|
+
delete data[propertyKey];
|
|
177
|
+
}
|
|
117
178
|
}
|
|
118
179
|
|
|
119
180
|
// Return processed data
|
|
@@ -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
|
|
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
|
|
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:
|
|
140
|
+
export function getIndexesViaIds(ids: IdsType, array: IdsType): number[] {
|
|
100
141
|
// Check and prepare parameters
|
|
101
142
|
if (!ids) {
|
|
102
143
|
return [];
|
|
@@ -301,55 +342,6 @@ export function getJSONClone<T = any>(obj: T): Partial<T> {
|
|
|
301
342
|
return JSON.parse(JSON.stringify(obj));
|
|
302
343
|
}
|
|
303
344
|
|
|
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
345
|
/**
|
|
354
346
|
* Convert all ObjectIds to strings
|
|
355
347
|
*/
|
|
@@ -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 {
|
|
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?: {
|
|
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?: {
|
|
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 (
|
|
200
|
-
|
|
201
|
-
|
|
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,6 +1,5 @@
|
|
|
1
|
-
import { Model
|
|
1
|
+
import { Model } from 'mongoose';
|
|
2
2
|
import { FieldSelection } from '../types/field-selection.type';
|
|
3
|
-
import { IdsType } from '../types/ids.type';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* General service options
|
|
@@ -14,7 +13,7 @@ export interface ServiceOptions {
|
|
|
14
13
|
// If truly (default): input data will be checked
|
|
15
14
|
checkRights?: boolean;
|
|
16
15
|
|
|
17
|
-
// Current user to set ownership, check
|
|
16
|
+
// Current user to set ownership, check rights and other things
|
|
18
17
|
currentUser?: {
|
|
19
18
|
[key: string]: any;
|
|
20
19
|
id: string;
|
|
@@ -27,9 +26,6 @@ export interface ServiceOptions {
|
|
|
27
26
|
// Overwrites type of input (array items)
|
|
28
27
|
inputType?: new (...params: any[]) => any;
|
|
29
28
|
|
|
30
|
-
// Owner IDs
|
|
31
|
-
ownerIds?: IdsType;
|
|
32
|
-
|
|
33
29
|
// Process field selection
|
|
34
30
|
// If {} or not set, then the field selection runs with defaults
|
|
35
31
|
// If falsy, then the field selection will not be automatically executed
|
|
@@ -59,16 +59,6 @@ export abstract class CorePersistenceModel extends CoreModel {
|
|
|
59
59
|
@Prop([String])
|
|
60
60
|
labels: string[] = undefined;
|
|
61
61
|
|
|
62
|
-
/**
|
|
63
|
-
* IDs of the Owners
|
|
64
|
-
*/
|
|
65
|
-
@Field((type) => [String], {
|
|
66
|
-
description: 'Users who own the object',
|
|
67
|
-
nullable: true,
|
|
68
|
-
})
|
|
69
|
-
@Prop([String])
|
|
70
|
-
ownerIds: string[] = undefined;
|
|
71
|
-
|
|
72
62
|
/**
|
|
73
63
|
* Tags for the object
|
|
74
64
|
*/
|
|
@@ -97,7 +87,6 @@ export abstract class CorePersistenceModel extends CoreModel {
|
|
|
97
87
|
super.init();
|
|
98
88
|
this.createdAt = this.createdAt === undefined ? new Date() : this.createdAt;
|
|
99
89
|
this.labels = this.labels === undefined ? [] : this.labels;
|
|
100
|
-
this.ownerIds = this.ownerIds === undefined ? [] : this.ownerIds;
|
|
101
90
|
this.tags = this.tags === undefined ? [] : this.tags;
|
|
102
91
|
this.updatedAt = this.tags === undefined ? this.createdAt : this.updatedAt;
|
|
103
92
|
return this;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Document, Model, Types } from 'mongoose';
|
|
2
|
+
import { ProcessType } from '../enums/process-type.enum';
|
|
2
3
|
import { getStringIds, popAndMap } from '../helpers/db.helper';
|
|
3
4
|
import { check } from '../helpers/input.helper';
|
|
4
5
|
import { prepareInput, prepareOutput } from '../helpers/service.helper';
|
|
5
6
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
6
7
|
import { CoreModel } from '../models/core-model.model';
|
|
7
8
|
import { FieldSelection } from '../types/field-selection.type';
|
|
8
|
-
import { IdsType } from '../types/ids.type';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Module service class to be extended by concrete module services
|
|
@@ -39,9 +39,9 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
39
39
|
input: any,
|
|
40
40
|
currentUser: { id: any; hasRole: (roles: string[]) => boolean },
|
|
41
41
|
options?: {
|
|
42
|
-
|
|
42
|
+
dbObject?: any;
|
|
43
43
|
metatype?: any;
|
|
44
|
-
|
|
44
|
+
processType?: ProcessType;
|
|
45
45
|
roles?: string | string[];
|
|
46
46
|
throwError?: boolean;
|
|
47
47
|
}
|
|
@@ -88,30 +88,18 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// Get DB object
|
|
91
|
-
|
|
92
|
-
if (config.dbObject) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
config.dbObject = dbObject;
|
|
97
|
-
}
|
|
91
|
+
if (config.dbObject && config.checkRights && this.checkRights) {
|
|
92
|
+
if (typeof config.dbObject === 'string' || config.dbObject instanceof Types.ObjectId) {
|
|
93
|
+
const dbObject = await this.get(getStringIds(config.dbObject));
|
|
94
|
+
if (dbObject) {
|
|
95
|
+
config.dbObject = dbObject;
|
|
98
96
|
}
|
|
99
97
|
}
|
|
100
|
-
return config.dbObject;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// Get owner IDs
|
|
104
|
-
let ownerIds = undefined;
|
|
105
|
-
if (config.checkRights && this.checkRights) {
|
|
106
|
-
ownerIds = getStringIds(config.ownerIds);
|
|
107
|
-
if (!ownerIds?.length) {
|
|
108
|
-
ownerIds = (await getDbObject())?.ownerIds;
|
|
109
|
-
}
|
|
110
98
|
}
|
|
111
99
|
|
|
112
100
|
// Check rights for input
|
|
113
101
|
if (config.input && config.checkRights && this.checkRights) {
|
|
114
|
-
const opts: any = {
|
|
102
|
+
const opts: any = { dbObject: config.dbObject, processType: ProcessType.INPUT, roles: config.roles };
|
|
115
103
|
if (config.inputType) {
|
|
116
104
|
opts.metatype = config.resultType;
|
|
117
105
|
}
|
|
@@ -137,7 +125,12 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
137
125
|
|
|
138
126
|
// Check output rights
|
|
139
127
|
if (config.checkRights && this.checkRights) {
|
|
140
|
-
const opts: any = {
|
|
128
|
+
const opts: any = {
|
|
129
|
+
dbObject: config.dbObject,
|
|
130
|
+
processType: ProcessType.OUTPUT,
|
|
131
|
+
roles: config.roles,
|
|
132
|
+
throwError: false,
|
|
133
|
+
};
|
|
141
134
|
if (config.resultType) {
|
|
142
135
|
opts.metatype = config.resultType;
|
|
143
136
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Require only one of the optional properties
|
|
3
|
+
* See https://stackoverflow.com/a/49725198
|
|
4
|
+
*/
|
|
5
|
+
export type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
|
|
6
|
+
{ [K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>> }[Keys];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Require at least on of optional properties
|
|
3
|
+
* See https://stackoverflow.com/a/49725198
|
|
4
|
+
*/
|
|
5
|
+
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
|
|
6
|
+
{ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];
|
package/src/core.module.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { DynamicModule, Global, Module, UnauthorizedException } from '@nestjs/common';
|
|
2
|
-
import {
|
|
2
|
+
import { APP_PIPE } from '@nestjs/core';
|
|
3
3
|
import { GraphQLModule } from '@nestjs/graphql';
|
|
4
4
|
import { merge } from './core/common/helpers/config.helper';
|
|
5
|
-
import { CheckResponseInterceptor } from './core/common/interceptors/check-response.interceptor';
|
|
6
5
|
import { IServerOptions } from './core/common/interfaces/server-options.interface';
|
|
7
6
|
import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
|
|
8
7
|
import { ConfigService } from './core/common/services/config.service';
|
|
@@ -19,8 +18,6 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
|
|
19
18
|
* - MongooseModule
|
|
20
19
|
* - GraphQL
|
|
21
20
|
* - ConfigService
|
|
22
|
-
* - CheckInput
|
|
23
|
-
* - CheckResponse
|
|
24
21
|
*
|
|
25
22
|
* and sets the following services as globals:
|
|
26
23
|
* - ConfigService
|
|
@@ -99,21 +96,6 @@ export class CoreModule {
|
|
|
99
96
|
useValue: new ConfigService(config),
|
|
100
97
|
},
|
|
101
98
|
|
|
102
|
-
// [Global] The CheckResponseInterceptor restricts the response to the properties
|
|
103
|
-
// that are permitted for the current user
|
|
104
|
-
{
|
|
105
|
-
provide: APP_INTERCEPTOR,
|
|
106
|
-
useClass: CheckResponseInterceptor,
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// [Global] The CheckInputPipe checks the permissibility of individual properties of inputs for the resolvers
|
|
110
|
-
// in relation to the current user, replaces MapAndValidatePipe
|
|
111
|
-
// Does not work yet, because context is missing: https://github.com/nestjs/graphql/issues/325
|
|
112
|
-
// {
|
|
113
|
-
// provide: APP_PIPE,
|
|
114
|
-
// useClass: CheckInputPipe,
|
|
115
|
-
// },
|
|
116
|
-
|
|
117
99
|
// [Global] Map plain objects to metatype and validate
|
|
118
100
|
{
|
|
119
101
|
provide: APP_PIPE,
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './core/common/decorators/restricted.decorator';
|
|
|
15
15
|
export * from './core/common/decorators/roles.decorator';
|
|
16
16
|
export * from './core/common/enums/comparison-operator.enum';
|
|
17
17
|
export * from './core/common/enums/logical-operator.enum';
|
|
18
|
+
export * from './core/common/enums/process-type.enum';
|
|
18
19
|
export * from './core/common/enums/role.enum';
|
|
19
20
|
export * from './core/common/enums/sort-order.emum';
|
|
20
21
|
export * from './core/common/helpers/config.helper';
|
|
@@ -54,6 +55,8 @@ export * from './core/common/types/core-model-constructor.type';
|
|
|
54
55
|
export * from './core/common/types/field-selection.type';
|
|
55
56
|
export * from './core/common/types/ids.type';
|
|
56
57
|
export * from './core/common/types/plain-input.type';
|
|
58
|
+
export * from './core/common/types/require-only-one.type';
|
|
59
|
+
export * from './core/common/types/required-at-least-one.type';
|
|
57
60
|
export * from './core/common/types/string-or-object-id.type';
|
|
58
61
|
|
|
59
62
|
// =====================================================================================================================
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import { Prop, Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
|
|
3
|
+
import { Document, Schema } from 'mongoose';
|
|
2
4
|
import { CoreUserModel } from '../../../core/modules/user/core-user.model';
|
|
3
5
|
import { PersistenceModel } from '../../common/models/persistence.model';
|
|
4
|
-
import { Prop, Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
|
|
5
|
-
import { Schema, Document } from 'mongoose';
|
|
6
6
|
|
|
7
7
|
export type UserDocument = User & Document;
|
|
8
8
|
|