@lenne.tech/nest-server 11.4.7 → 11.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 +2 -0
- package/dist/core/common/decorators/restricted.decorator.js +17 -16
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/enums/role.enum.d.ts +1 -0
- package/dist/core/common/enums/role.enum.js +1 -0
- package/dist/core/common/enums/role.enum.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +2 -0
- package/dist/core/common/helpers/input.helper.js +2 -1
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/plugins/mongoose-id.plugin.d.ts +1 -1
- package/dist/core/common/plugins/mongoose-id.plugin.js +4 -7
- package/dist/core/common/plugins/mongoose-id.plugin.js.map +1 -1
- package/dist/core.module.js +10 -3
- package/dist/core.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +26 -9
- package/src/core/common/decorators/restricted.decorator.ts +18 -17
- package/src/core/common/enums/role.enum.ts +5 -1
- package/src/core/common/helpers/input.helper.ts +4 -2
- package/src/core/common/plugins/mongoose-id.plugin.ts +14 -0
- package/src/core.module.ts +16 -7
- package/src/core/common/plugins/mongoose-id.plugin.js +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.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",
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
|
|
26
26
|
"format": "prettier --write 'src/**/*.ts'",
|
|
27
27
|
"format:staged": "pretty-quick --staged",
|
|
28
|
+
"jest": "npm run jest:e2e",
|
|
29
|
+
"jest:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
|
|
30
|
+
"jest:cov": "NODE_ENV=local jest --coverage",
|
|
31
|
+
"jest:debug": "NODE_ENV=local node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
32
|
+
"jest:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
|
|
33
|
+
"jest:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
|
|
34
|
+
"jest:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
|
|
35
|
+
"jest:watch": "NODE_ENV=local jest --watch",
|
|
28
36
|
"lint": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --cache",
|
|
29
37
|
"lint:fix": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --fix --cache",
|
|
30
38
|
"prestart:prod": "npm run build",
|
|
@@ -42,17 +50,18 @@
|
|
|
42
50
|
"start:dev:swc": "nest start -b swc -w --type-check",
|
|
43
51
|
"start:local": "NODE_ENV=local nodemon",
|
|
44
52
|
"start:local:swc": "NODE_ENV=local nest start -b swc -w --type-check",
|
|
45
|
-
"test": "npm run
|
|
46
|
-
"test:
|
|
47
|
-
"test:
|
|
48
|
-
"test:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
|
|
49
|
-
"test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
|
|
50
|
-
"test:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
|
|
51
|
-
"test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
|
|
52
|
-
"test:watch": "NODE_ENV=local jest --watch",
|
|
53
|
+
"test": "npm run vitest",
|
|
54
|
+
"test:ci": "npm run vitest:ci",
|
|
55
|
+
"test:e2e": "npm run vitest",
|
|
53
56
|
"prepack": "npm run prestart:prod",
|
|
54
57
|
"prepublishOnly": "npm run lint && npm run test:ci",
|
|
55
58
|
"preversion": "npm run lint",
|
|
59
|
+
"vitest": "NODE_ENV=local vitest run --config vitest-e2e.config.ts",
|
|
60
|
+
"vitest:ci": "NODE_ENV=ci vitest run --config vitest-e2e.config.ts",
|
|
61
|
+
"vitest:cov": "NODE_ENV=local vitest run --coverage --config vitest-e2e.config.ts",
|
|
62
|
+
"vitest:watch": "NODE_ENV=local vitest --config vitest-e2e.config.ts",
|
|
63
|
+
"vitest:unit": "vitest run --config vitest.config.ts",
|
|
64
|
+
"test:unit:watch": "vitest --config vitest.config.ts",
|
|
56
65
|
"watch": "npm-watch",
|
|
57
66
|
"link:eslint": "yalc add @lenne.tech/eslint-config-ts && yalc link @lenne.tech/eslint-config-ts && npm install",
|
|
58
67
|
"unlink:eslint": "yalc remove @lenne.tech/eslint-config-ts && npm install"
|
|
@@ -132,6 +141,9 @@
|
|
|
132
141
|
"@types/supertest": "6.0.3",
|
|
133
142
|
"@typescript-eslint/eslint-plugin": "8.46.3",
|
|
134
143
|
"@typescript-eslint/parser": "8.46.3",
|
|
144
|
+
"@vitest/coverage-v8": "4.0.9",
|
|
145
|
+
"@vitest/ui": "4.0.9",
|
|
146
|
+
"ansi-colors": "4.1.3",
|
|
135
147
|
"coffeescript": "2.7.0",
|
|
136
148
|
"eslint": "9.39.1",
|
|
137
149
|
"eslint-config-prettier": "10.1.8",
|
|
@@ -157,6 +169,11 @@
|
|
|
157
169
|
"ts-node": "10.9.2",
|
|
158
170
|
"tsconfig-paths": "4.2.0",
|
|
159
171
|
"typescript": "5.9.3",
|
|
172
|
+
"unplugin-swc": "1.5.8",
|
|
173
|
+
"vite": "7.2.2",
|
|
174
|
+
"vite-plugin-node": "7.0.0",
|
|
175
|
+
"vite-tsconfig-paths": "5.1.4",
|
|
176
|
+
"vitest": "4.0.9",
|
|
160
177
|
"yalc": "1.0.0-pre.53"
|
|
161
178
|
},
|
|
162
179
|
"overrides": {
|
|
@@ -61,7 +61,7 @@ export const getRestricted = (object: unknown, propertyKey?: string): Restricted
|
|
|
61
61
|
*/
|
|
62
62
|
export const checkRestricted = (
|
|
63
63
|
data: any,
|
|
64
|
-
user: { hasRole: (roles: string[]) => boolean; id: any },
|
|
64
|
+
user: { hasRole: (roles: string[]) => boolean; id: any; verified?: any; verifiedAt?: any },
|
|
65
65
|
options: {
|
|
66
66
|
allowCreatorOfParent?: boolean;
|
|
67
67
|
checkObjectItself?: boolean;
|
|
@@ -108,9 +108,9 @@ export const checkRestricted = (
|
|
|
108
108
|
// Array
|
|
109
109
|
if (Array.isArray(data)) {
|
|
110
110
|
// Check array items
|
|
111
|
-
let result = data.map(item => checkRestricted(item, user, config, processedObjects));
|
|
111
|
+
let result = data.map((item) => checkRestricted(item, user, config, processedObjects));
|
|
112
112
|
if (!config.throwError && config.removeUndefinedFromResultArray) {
|
|
113
|
-
result = result.filter(item => item !== undefined);
|
|
113
|
+
result = result.filter((item) => item !== undefined);
|
|
114
114
|
}
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
@@ -134,8 +134,8 @@ export const checkRestricted = (
|
|
|
134
134
|
if (typeof item === 'string') {
|
|
135
135
|
roles.push(item);
|
|
136
136
|
} else if (
|
|
137
|
-
item?.roles?.length
|
|
138
|
-
|
|
137
|
+
item?.roles?.length &&
|
|
138
|
+
(config.processType && item.processType ? config.processType === item.processType : true)
|
|
139
139
|
) {
|
|
140
140
|
if (Array.isArray(item.roles)) {
|
|
141
141
|
roles.push(...item.roles);
|
|
@@ -156,13 +156,14 @@ export const checkRestricted = (
|
|
|
156
156
|
|
|
157
157
|
// Check access rights
|
|
158
158
|
if (
|
|
159
|
-
roles.includes(RoleEnum.S_EVERYONE)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
roles.includes(RoleEnum.S_EVERYONE) ||
|
|
160
|
+
user?.hasRole?.(roles) ||
|
|
161
|
+
(user?.id && roles.includes(RoleEnum.S_USER)) ||
|
|
162
|
+
(roles.includes(RoleEnum.S_SELF) && equalIds(data, user)) ||
|
|
163
|
+
(roles.includes(RoleEnum.S_CREATOR) &&
|
|
164
|
+
(('createdBy' in data && equalIds(data.createdBy, user)) ||
|
|
165
|
+
(config.allowCreatorOfParent && !('createdBy' in data) && config.isCreatorOfParent))) ||
|
|
166
|
+
(roles.includes(RoleEnum.S_VERIFIED) && (user?.verified || user?.verifiedAt))
|
|
166
167
|
) {
|
|
167
168
|
valid = true;
|
|
168
169
|
}
|
|
@@ -172,11 +173,11 @@ export const checkRestricted = (
|
|
|
172
173
|
// Get groups
|
|
173
174
|
const groups = restricted.filter((item) => {
|
|
174
175
|
return (
|
|
175
|
-
typeof item === 'object'
|
|
176
|
+
typeof item === 'object' &&
|
|
176
177
|
// Check if object is valid
|
|
177
|
-
|
|
178
|
+
item.memberOf?.length &&
|
|
178
179
|
// Check if processType is specified and is valid for current process
|
|
179
|
-
|
|
180
|
+
(config.processType && item.processType ? config.processType === item.processType : true)
|
|
180
181
|
);
|
|
181
182
|
}) as { memberOf: string | string[] }[];
|
|
182
183
|
|
|
@@ -252,8 +253,8 @@ export const checkRestricted = (
|
|
|
252
253
|
// Check rights
|
|
253
254
|
if (valid) {
|
|
254
255
|
// Check if data is user or user is creator of data (for nested plain objects)
|
|
255
|
-
config.isCreatorOfParent
|
|
256
|
-
|
|
256
|
+
config.isCreatorOfParent =
|
|
257
|
+
equalIds(data, user) || ('createdBy' in data ? equalIds(data.createdBy, user) : config.isCreatorOfParent);
|
|
257
258
|
|
|
258
259
|
// Check deep
|
|
259
260
|
data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable perfectionist/sort-enums */
|
|
1
2
|
/**
|
|
2
3
|
* Enums for Resolver @Role and Model @Restricted decorator and for roles property in ServiceOptions
|
|
3
4
|
*
|
|
@@ -42,9 +43,12 @@ export enum RoleEnum {
|
|
|
42
43
|
// Everyone, including users who are not logged in, can access (see context user, e.g. @CurrentUser)
|
|
43
44
|
S_EVERYONE = 's_everyone',
|
|
44
45
|
|
|
45
|
-
// No one has access, not even administrators
|
|
46
|
+
// No one has access, not even administrators (regardless of which roles are still set, access will always be denied)
|
|
46
47
|
S_NO_ONE = 's_no_one',
|
|
47
48
|
|
|
49
|
+
// User must be verified (see verified or verifiedAt property of user)
|
|
50
|
+
S_VERIFIED = 's_verified',
|
|
51
|
+
|
|
48
52
|
// ===================================================================================================================
|
|
49
53
|
// Special system roles that check rights for DB objects and can be used via @Restricted for Models
|
|
50
54
|
// (classes and properties) and via ServiceOptions for Resolver methods. These roles should not be integrated in
|
|
@@ -209,7 +209,7 @@ export function assignPlain(target: Record<any, any>, ...args: Record<any, any>[
|
|
|
209
209
|
*/
|
|
210
210
|
export async function check(
|
|
211
211
|
value: any,
|
|
212
|
-
user: { hasRole: (roles: string[]) => boolean; id: any },
|
|
212
|
+
user: { hasRole: (roles: string[]) => boolean; id: any; verified?: any; verifiedAt?: any },
|
|
213
213
|
options?: {
|
|
214
214
|
allowCreatorOfParent?: boolean;
|
|
215
215
|
dbObject?: any;
|
|
@@ -262,7 +262,9 @@ export async function check(
|
|
|
262
262
|
(config.allowCreatorOfParent &&
|
|
263
263
|
config.dbObject &&
|
|
264
264
|
!('createdBy' in config.dbObject) &&
|
|
265
|
-
config.isCreatorOfParent)))
|
|
265
|
+
config.isCreatorOfParent))) ||
|
|
266
|
+
// check if the is verified
|
|
267
|
+
(roles.includes(RoleEnum.S_VERIFIED) && (user?.verified || user?.verifiedAt))
|
|
266
268
|
) {
|
|
267
269
|
valid = true;
|
|
268
270
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mongoose plugin to add a string 'id' field to documents based on the '_id' ObjectId.
|
|
3
|
+
* @param schema - The Mongoose schema to which the plugin will be applied.
|
|
4
|
+
*/
|
|
5
|
+
export function mongooseIdPlugin(schema) {
|
|
6
|
+
schema.post(/.*/, (docs) => {
|
|
7
|
+
const docsArray = Array.isArray(docs) ? docs : [docs];
|
|
8
|
+
for (const doc of docsArray) {
|
|
9
|
+
if (doc?._id && typeof doc._id.toHexString === 'function') {
|
|
10
|
+
doc.id = doc._id.toHexString();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
package/src/core.module.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { CheckSecurityInterceptor } from './core/common/interceptors/check-secur
|
|
|
13
13
|
import { IServerOptions } from './core/common/interfaces/server-options.interface';
|
|
14
14
|
import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
|
|
15
15
|
import { ComplexityPlugin } from './core/common/plugins/complexity.plugin';
|
|
16
|
+
import { mongooseIdPlugin } from './core/common/plugins/mongoose-id.plugin';
|
|
16
17
|
import { ConfigService } from './core/common/services/config.service';
|
|
17
18
|
import { EmailService } from './core/common/services/email.service';
|
|
18
19
|
import { MailjetService } from './core/common/services/mailjet.service';
|
|
@@ -98,8 +99,8 @@ export class CoreModule implements NestModule {
|
|
|
98
99
|
if (config.graphQl.enableSubscriptionAuth) {
|
|
99
100
|
// get authToken from authorization header
|
|
100
101
|
const headers = this.getHeaderFromArray(extra.request?.rawHeaders);
|
|
101
|
-
const authToken: string
|
|
102
|
-
|
|
102
|
+
const authToken: string =
|
|
103
|
+
connectionParams?.Authorization?.split(' ')[1] ?? headers.Authorization?.split(' ')[1];
|
|
103
104
|
if (authToken) {
|
|
104
105
|
// verify authToken/getJwtPayLoad
|
|
105
106
|
const payload = authService.decodeJwt(authToken);
|
|
@@ -148,7 +149,7 @@ export class CoreModule implements NestModule {
|
|
|
148
149
|
mongoose: {
|
|
149
150
|
options: {
|
|
150
151
|
connectionFactory: (connection) => {
|
|
151
|
-
connection.plugin(
|
|
152
|
+
connection.plugin(mongooseIdPlugin);
|
|
152
153
|
return connection;
|
|
153
154
|
},
|
|
154
155
|
},
|
|
@@ -177,11 +178,13 @@ export class CoreModule implements NestModule {
|
|
|
177
178
|
EmailService,
|
|
178
179
|
TemplateService,
|
|
179
180
|
MailjetService,
|
|
180
|
-
|
|
181
|
-
// Plugins
|
|
182
|
-
ComplexityPlugin,
|
|
183
181
|
];
|
|
184
182
|
|
|
183
|
+
// Add ComplexityPlugin only if not in Vitest (Vitest has dual GraphQL loading issue)
|
|
184
|
+
if (!process.env.VITEST) {
|
|
185
|
+
providers.push(ComplexityPlugin);
|
|
186
|
+
}
|
|
187
|
+
|
|
185
188
|
if (config.security?.checkResponseInterceptor ?? true) {
|
|
186
189
|
// Check restrictions for output (models and output objects)
|
|
187
190
|
providers.push({
|
|
@@ -225,9 +228,15 @@ export class CoreModule implements NestModule {
|
|
|
225
228
|
imports.push(CoreHealthCheckModule);
|
|
226
229
|
}
|
|
227
230
|
|
|
231
|
+
// Set exports
|
|
232
|
+
const exports: any[] = [ConfigService, EmailService, TemplateService, MailjetService];
|
|
233
|
+
if (!process.env.VITEST) {
|
|
234
|
+
exports.push(ComplexityPlugin);
|
|
235
|
+
}
|
|
236
|
+
|
|
228
237
|
// Return dynamic module
|
|
229
238
|
return {
|
|
230
|
-
exports
|
|
239
|
+
exports,
|
|
231
240
|
imports,
|
|
232
241
|
module: CoreModule,
|
|
233
242
|
providers,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function mongooseIdPlugin(schema) {
|
|
2
|
-
schema.post(['find', 'findOne', 'save', 'deleteOne'], (docs) => {
|
|
3
|
-
if (!Array.isArray(docs)) {
|
|
4
|
-
docs = [docs];
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
for (const doc of docs) {
|
|
8
|
-
if (doc !== null && doc._id) {
|
|
9
|
-
doc.id = doc._id.toHexString();
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
module.exports = mongooseIdPlugin;
|