@lenne.tech/nest-server 8.2.0 → 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 -4
- package/dist/core/common/decorators/restricted.decorator.js +65 -21
- 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 +2 -2
- package/dist/core/common/enums/role.enum.js +2 -2
- package/dist/core/common/enums/role.enum.js.map +1 -1
- package/dist/core/common/helpers/db.helper.d.ts +5 -3
- package/dist/core/common/helpers/db.helper.js +36 -25
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +6 -3
- package/dist/core/common/helpers/input.helper.js +4 -12
- 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 -2
- package/dist/core/common/services/module.service.js +13 -14
- 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/modules/auth/guards/roles.guard.js +1 -2
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +1 -6
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- 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/avatar.controller.js +1 -1
- package/dist/server/modules/user/avatar.controller.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.resolver.js +6 -6
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.js +5 -1
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/core/common/decorators/restricted.decorator.ts +109 -42
- package/src/core/common/enums/process-type.enum.ts +7 -0
- package/src/core/common/enums/role.enum.ts +21 -6
- package/src/core/common/helpers/db.helper.ts +58 -51
- package/src/core/common/helpers/input.helper.ts +24 -13
- 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 +16 -15
- 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/modules/auth/guards/roles.guard.ts +4 -6
- package/src/core/modules/user/core-user.service.ts +1 -6
- package/src/core.module.ts +1 -19
- package/src/index.ts +3 -0
- package/src/server/modules/user/avatar.controller.ts +1 -1
- package/src/server/modules/user/user.model.ts +2 -2
- package/src/server/modules/user/user.resolver.ts +6 -6
- package/src/server/modules/user/user.service.ts +8 -1
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",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"@nestjs/mongoose": "9.0.3",
|
|
62
62
|
"@nestjs/passport": "8.2.1",
|
|
63
63
|
"@nestjs/platform-express": "8.4.4",
|
|
64
|
-
"apollo-server-core": "3.
|
|
65
|
-
"apollo-server-express": "3.
|
|
64
|
+
"apollo-server-core": "3.7.0",
|
|
65
|
+
"apollo-server-express": "3.7.0",
|
|
66
66
|
"bcrypt": "5.0.1",
|
|
67
67
|
"class-transformer": "0.5.1",
|
|
68
68
|
"class-validator": "0.13.2",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"mongodb": "4.5.0",
|
|
76
76
|
"mongoose": "6.3.2",
|
|
77
77
|
"multer": "1.4.4",
|
|
78
|
-
"node-mailjet": "3.
|
|
78
|
+
"node-mailjet": "3.4.1",
|
|
79
79
|
"nodemailer": "6.7.5",
|
|
80
80
|
"nodemon": "2.0.16",
|
|
81
81
|
"passport": "0.5.2",
|
|
@@ -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,42 +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.OWNER, ownerId(s) from current data (in DB) must be set in options.
|
|
47
|
+
* For special Roles and member of group checking the dbObject must be set in options
|
|
35
48
|
*/
|
|
36
49
|
export const checkRestricted = (
|
|
37
50
|
data: any,
|
|
38
51
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
39
|
-
options: { ignoreUndefined?: boolean;
|
|
52
|
+
options: { dbObject?: any; ignoreUndefined?: boolean; processType?: ProcessType; throwError?: boolean } = {},
|
|
40
53
|
processedObjects: any[] = []
|
|
41
54
|
) => {
|
|
42
55
|
const config = {
|
|
@@ -69,45 +82,99 @@ export const checkRestricted = (
|
|
|
69
82
|
continue;
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
//
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
// Check restricted
|
|
86
|
+
const restricted = getRestricted(data, propertyKey);
|
|
87
|
+
let valid = true;
|
|
88
|
+
if (restricted?.length) {
|
|
89
|
+
valid = false;
|
|
90
|
+
|
|
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
|
+
}
|
|
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[] }[];
|
|
130
|
+
|
|
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
|
+
}
|
|
94
148
|
}
|
|
95
|
-
throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
|
|
96
149
|
}
|
|
97
|
-
continue;
|
|
98
150
|
}
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
if (
|
|
102
|
-
|
|
151
|
+
|
|
152
|
+
// Check if user is a member
|
|
153
|
+
if (getIncludedIds(members, user)) {
|
|
154
|
+
valid = true;
|
|
103
155
|
}
|
|
104
|
-
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if there are no limitations
|
|
159
|
+
if (!roles.length && !groups.length) {
|
|
160
|
+
valid = true;
|
|
105
161
|
}
|
|
106
162
|
}
|
|
107
163
|
}
|
|
108
164
|
|
|
109
|
-
// Check
|
|
110
|
-
|
|
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
|
+
}
|
|
111
178
|
}
|
|
112
179
|
|
|
113
180
|
// Return processed data
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Enums for
|
|
2
|
+
* Enums for Resolver @Role and Model @Restricted decorator and for roles property in ServiceOptions
|
|
3
3
|
*/
|
|
4
4
|
export enum RoleEnum {
|
|
5
|
-
//
|
|
5
|
+
// ===================================================================================================================
|
|
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.
|
|
8
|
+
// ===================================================================================================================
|
|
9
|
+
|
|
10
|
+
// User must be an administrator (see roles of user)
|
|
6
11
|
ADMIN = 'admin',
|
|
7
12
|
|
|
8
|
-
//
|
|
9
|
-
|
|
13
|
+
// ===================================================================================================================
|
|
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!
|
|
16
|
+
// ===================================================================================================================
|
|
17
|
+
|
|
18
|
+
// User must be logged in (see context user, e.g. @GraphQLUser)
|
|
19
|
+
S_USER = 's_user',
|
|
20
|
+
|
|
21
|
+
// ===================================================================================================================
|
|
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!
|
|
24
|
+
// ===================================================================================================================
|
|
10
25
|
|
|
11
|
-
// User must be
|
|
12
|
-
|
|
26
|
+
// User must be the creator of the processed object(s) (see createdBy property of object(s))
|
|
27
|
+
S_CREATOR = 's_creator',
|
|
13
28
|
}
|
|
@@ -4,6 +4,7 @@ import { Document, Model, PopulateOptions, Query, SchemaType, Types } from 'mong
|
|
|
4
4
|
import { ResolveSelector } from '../interfaces/resolve-selector.interface';
|
|
5
5
|
import { CoreModel } from '../models/core-model.model';
|
|
6
6
|
import { FieldSelection } from '../types/field-selection.type';
|
|
7
|
+
import { IdsType } from '../types/ids.type';
|
|
7
8
|
import { StringOrObjectId } from '../types/string-or-object-id.type';
|
|
8
9
|
|
|
9
10
|
// =====================================================================================================================
|
|
@@ -78,10 +79,65 @@ export function addIds(
|
|
|
78
79
|
return result;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Checks if all IDs are equal
|
|
84
|
+
*/
|
|
85
|
+
export function equalIds(...ids: IdsType[]): boolean {
|
|
86
|
+
if (!ids) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const compare = getStringIds(ids[0]);
|
|
90
|
+
if (!compare) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return ids.every((id) => getStringIds(id) === compare);
|
|
94
|
+
}
|
|
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
|
+
|
|
81
137
|
/**
|
|
82
138
|
* Get indexes of IDs in an array
|
|
83
139
|
*/
|
|
84
|
-
export function getIndexesViaIds(ids:
|
|
140
|
+
export function getIndexesViaIds(ids: IdsType, array: IdsType): number[] {
|
|
85
141
|
// Check and prepare parameters
|
|
86
142
|
if (!ids) {
|
|
87
143
|
return [];
|
|
@@ -97,7 +153,7 @@ export function getIndexesViaIds(ids: any | any[], array: any[]): number[] {
|
|
|
97
153
|
const indexes: number[] = [];
|
|
98
154
|
ids.forEach((id) => {
|
|
99
155
|
array.forEach((element, index) => {
|
|
100
|
-
if (
|
|
156
|
+
if (equalIds(id, element)) {
|
|
101
157
|
indexes.push(index);
|
|
102
158
|
}
|
|
103
159
|
});
|
|
@@ -286,55 +342,6 @@ export function getJSONClone<T = any>(obj: T): Partial<T> {
|
|
|
286
342
|
return JSON.parse(JSON.stringify(obj));
|
|
287
343
|
}
|
|
288
344
|
|
|
289
|
-
/**
|
|
290
|
-
* Check if ID is included
|
|
291
|
-
* @param includes String, ObjectId or array with both, which should be checked if it contains the ID
|
|
292
|
-
* @param ids String, ObjectId or array with both, which should be included
|
|
293
|
-
* @param convert If set the result array will be converted to pure type String array or ObjectId array
|
|
294
|
-
* @return StringOrObjectId[] Array with IDs which are included or null if none is included
|
|
295
|
-
*/
|
|
296
|
-
export function includesIds(
|
|
297
|
-
includes: StringOrObjectId | StringOrObjectId[],
|
|
298
|
-
ids: StringOrObjectId | StringOrObjectId[],
|
|
299
|
-
convert?: 'string'
|
|
300
|
-
): string[];
|
|
301
|
-
export function includesIds(
|
|
302
|
-
includes: StringOrObjectId | StringOrObjectId[],
|
|
303
|
-
ids: StringOrObjectId | StringOrObjectId[],
|
|
304
|
-
convert?: 'object'
|
|
305
|
-
): Types.ObjectId[];
|
|
306
|
-
export function includesIds<T = StringOrObjectId>(
|
|
307
|
-
includes: StringOrObjectId | StringOrObjectId[] | any | any[],
|
|
308
|
-
ids: T | StringOrObjectId[],
|
|
309
|
-
convert?: 'string' | 'object'
|
|
310
|
-
): T[] | null {
|
|
311
|
-
if (!includes || !ids) {
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (!Array.isArray(includes)) {
|
|
316
|
-
includes = [includes];
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (!Array.isArray(ids)) {
|
|
320
|
-
ids = [ids] as any;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
let result = [];
|
|
324
|
-
const includesStrings = getStringIds(includes);
|
|
325
|
-
for (const id of ids as StringOrObjectId[]) {
|
|
326
|
-
if (includesStrings.includes(getStringIds(id))) {
|
|
327
|
-
result.push(id);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (convert) {
|
|
332
|
-
result = convert === 'string' ? getStringIds(result) : getObjectIds(result);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return result.length ? result : null;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
345
|
/**
|
|
339
346
|
* Convert all ObjectIds to strings
|
|
340
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 { 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,16 +208,15 @@ export async function check(
|
|
|
196
208
|
roles = [roles];
|
|
197
209
|
}
|
|
198
210
|
let valid = false;
|
|
199
|
-
if (
|
|
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
|
+
) {
|
|
200
219
|
valid = true;
|
|
201
|
-
} else if (user.hasRole(roles)) {
|
|
202
|
-
valid = true;
|
|
203
|
-
} else if (roles.includes(RoleEnum.OWNER) && user?.id && config.ownerIds) {
|
|
204
|
-
let ownerIds: string | string[] = getStringIds(config.ownerIds);
|
|
205
|
-
if (!Array.isArray(ownerIds)) {
|
|
206
|
-
ownerIds = [ownerIds];
|
|
207
|
-
}
|
|
208
|
-
valid = ownerIds.includes(getStringIds(user.id));
|
|
209
220
|
}
|
|
210
221
|
if (!valid) {
|
|
211
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,8 +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
|
+
dbObject?: any;
|
|
42
43
|
metatype?: any;
|
|
43
|
-
|
|
44
|
+
processType?: ProcessType;
|
|
44
45
|
roles?: string | string[];
|
|
45
46
|
throwError?: boolean;
|
|
46
47
|
}
|
|
@@ -86,24 +87,19 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
86
87
|
await this.prepareInput(config.input, config.prepareInput);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
// Get
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (typeof config.dbObject === 'string' || config.dbObject instanceof Types.ObjectId) {
|
|
96
|
-
ownerIds = (await this.get(getStringIds(config.dbObject)))?.ownerIds;
|
|
97
|
-
} else {
|
|
98
|
-
ownerIds = config.dbObject.ownerIds;
|
|
99
|
-
}
|
|
90
|
+
// Get DB object
|
|
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;
|
|
100
96
|
}
|
|
101
97
|
}
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
// Check rights for input
|
|
105
101
|
if (config.input && config.checkRights && this.checkRights) {
|
|
106
|
-
const opts: any = {
|
|
102
|
+
const opts: any = { dbObject: config.dbObject, processType: ProcessType.INPUT, roles: config.roles };
|
|
107
103
|
if (config.inputType) {
|
|
108
104
|
opts.metatype = config.resultType;
|
|
109
105
|
}
|
|
@@ -129,7 +125,12 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
129
125
|
|
|
130
126
|
// Check output rights
|
|
131
127
|
if (config.checkRights && this.checkRights) {
|
|
132
|
-
const opts: any = {
|
|
128
|
+
const opts: any = {
|
|
129
|
+
dbObject: config.dbObject,
|
|
130
|
+
processType: ProcessType.OUTPUT,
|
|
131
|
+
roles: config.roles,
|
|
132
|
+
throwError: false,
|
|
133
|
+
};
|
|
133
134
|
if (config.resultType) {
|
|
134
135
|
opts.metatype = config.resultType;
|
|
135
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];
|
|
@@ -8,7 +8,8 @@ import { AuthGuard } from './auth.guard';
|
|
|
8
8
|
* Role guard
|
|
9
9
|
*
|
|
10
10
|
* The RoleGuard is activated by the Role decorator. It checks whether the current user has at least one of the
|
|
11
|
-
* specified roles
|
|
11
|
+
* specified roles or is logged in when the S_USER role is set.
|
|
12
|
+
* If this is not the case, an UnauthorizedException is thrown.
|
|
12
13
|
*/
|
|
13
14
|
@Injectable()
|
|
14
15
|
export class RolesGuard extends AuthGuard('jwt') {
|
|
@@ -41,11 +42,8 @@ export class RolesGuard extends AuthGuard('jwt') {
|
|
|
41
42
|
// Get args
|
|
42
43
|
const args: any = GqlExecutionContext.create(context).getArgs();
|
|
43
44
|
|
|
44
|
-
// Check special role
|
|
45
|
-
if (
|
|
46
|
-
user &&
|
|
47
|
-
(roles.includes(RoleEnum.USER) || (roles.includes(RoleEnum.OWNER) && user.id.toString() === args.id))
|
|
48
|
-
) {
|
|
45
|
+
// Check special user role (user is logged in)
|
|
46
|
+
if (user && roles.includes(RoleEnum.S_USER)) {
|
|
49
47
|
return user;
|
|
50
48
|
}
|
|
51
49
|
|