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