@lenne.tech/nest-server 11.4.8 → 11.6.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/graphql-populate.decorator.d.ts +2 -2
- 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/modules/auth/services/core-auth.service.d.ts +10 -5
- package/dist/core/modules/auth/services/core-auth.service.js +20 -12
- package/dist/core/modules/auth/services/core-auth.service.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 +49 -39
- package/src/core/common/plugins/mongoose-id.plugin.ts +14 -0
- package/src/core/modules/auth/services/core-auth.service.ts +43 -19
- 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.6.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"
|
|
@@ -68,24 +77,22 @@
|
|
|
68
77
|
"node": ">= 20"
|
|
69
78
|
},
|
|
70
79
|
"dependencies": {
|
|
71
|
-
"@apollo/gateway": "2.12.0",
|
|
72
80
|
"@getbrevo/brevo": "3.0.1",
|
|
73
81
|
"@nestjs/apollo": "13.1.0",
|
|
74
|
-
"@nestjs/common": "11.1.
|
|
75
|
-
"@nestjs/core": "11.1.
|
|
76
|
-
"@nestjs/graphql": "13.
|
|
77
|
-
"@nestjs/jwt": "11.0.
|
|
78
|
-
"@nestjs/mongoose": "11.0.
|
|
82
|
+
"@nestjs/common": "11.1.9",
|
|
83
|
+
"@nestjs/core": "11.1.9",
|
|
84
|
+
"@nestjs/graphql": "13.2.0",
|
|
85
|
+
"@nestjs/jwt": "11.0.2",
|
|
86
|
+
"@nestjs/mongoose": "11.0.4",
|
|
79
87
|
"@nestjs/passport": "11.0.5",
|
|
80
|
-
"@nestjs/platform-express": "11.1.
|
|
81
|
-
"@nestjs/schedule": "6.0
|
|
82
|
-
"@nestjs/swagger": "11.2.
|
|
88
|
+
"@nestjs/platform-express": "11.1.9",
|
|
89
|
+
"@nestjs/schedule": "6.1.0",
|
|
90
|
+
"@nestjs/swagger": "11.2.3",
|
|
83
91
|
"@nestjs/terminus": "11.0.0",
|
|
84
92
|
"apollo-server-core": "3.13.0",
|
|
85
|
-
"apollo-server-express": "3.13.0",
|
|
86
93
|
"bcrypt": "6.0.0",
|
|
87
94
|
"class-transformer": "0.5.1",
|
|
88
|
-
"class-validator": "0.14.
|
|
95
|
+
"class-validator": "0.14.3",
|
|
89
96
|
"compression": "1.8.1",
|
|
90
97
|
"cookie-parser": "1.4.7",
|
|
91
98
|
"dotenv": "17.2.3",
|
|
@@ -101,38 +108,38 @@
|
|
|
101
108
|
"mongoose": "8.19.3",
|
|
102
109
|
"multer": "2.0.2",
|
|
103
110
|
"node-mailjet": "6.0.11",
|
|
104
|
-
"nodemailer": "7.0.
|
|
111
|
+
"nodemailer": "7.0.11",
|
|
105
112
|
"passport": "0.7.0",
|
|
106
113
|
"passport-jwt": "4.0.1",
|
|
107
114
|
"reflect-metadata": "0.2.2",
|
|
108
115
|
"rfdc": "1.4.1",
|
|
109
116
|
"rxjs": "7.8.2",
|
|
117
|
+
"ts-jest": "29.4.6",
|
|
110
118
|
"yuml-diagram": "1.2.0"
|
|
111
119
|
},
|
|
112
120
|
"devDependencies": {
|
|
113
|
-
"@babel/plugin-proposal-private-methods": "7.18.6",
|
|
114
121
|
"@compodoc/compodoc": "1.1.32",
|
|
115
122
|
"@lenne.tech/eslint-config-ts": "2.1.4",
|
|
116
|
-
"@nestjs/cli": "11.0.
|
|
123
|
+
"@nestjs/cli": "11.0.14",
|
|
117
124
|
"@nestjs/schematics": "11.0.9",
|
|
118
|
-
"@nestjs/testing": "11.1.
|
|
125
|
+
"@nestjs/testing": "11.1.9",
|
|
119
126
|
"@swc/cli": "0.7.9",
|
|
120
|
-
"@swc/core": "1.15.
|
|
121
|
-
"@swc/jest": "0.2.39",
|
|
127
|
+
"@swc/core": "1.15.3",
|
|
122
128
|
"@types/compression": "1.8.1",
|
|
123
129
|
"@types/cookie-parser": "1.4.10",
|
|
124
130
|
"@types/ejs": "3.1.5",
|
|
125
131
|
"@types/express": "4.17.21",
|
|
126
|
-
"@types/
|
|
127
|
-
"@types/lodash": "4.17.20",
|
|
132
|
+
"@types/lodash": "4.17.21",
|
|
128
133
|
"@types/multer": "2.0.0",
|
|
129
|
-
"@types/node": "
|
|
130
|
-
"@types/nodemailer": "7.0.
|
|
134
|
+
"@types/node": "25.0.1",
|
|
135
|
+
"@types/nodemailer": "7.0.4",
|
|
131
136
|
"@types/passport": "1.0.17",
|
|
132
137
|
"@types/supertest": "6.0.3",
|
|
133
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
134
|
-
"@typescript-eslint/parser": "8.
|
|
135
|
-
"
|
|
138
|
+
"@typescript-eslint/eslint-plugin": "8.49.0",
|
|
139
|
+
"@typescript-eslint/parser": "8.49.0",
|
|
140
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
141
|
+
"@vitest/ui": "4.0.15",
|
|
142
|
+
"ansi-colors": "4.1.3",
|
|
136
143
|
"eslint": "9.39.1",
|
|
137
144
|
"eslint-config-prettier": "10.1.8",
|
|
138
145
|
"eslint-plugin-unused-imports": "4.3.0",
|
|
@@ -143,20 +150,23 @@
|
|
|
143
150
|
"grunt-contrib-watch": "1.1.0",
|
|
144
151
|
"grunt-sync": "0.8.2",
|
|
145
152
|
"husky": "9.1.7",
|
|
146
|
-
"
|
|
147
|
-
"nodemon": "3.1.10",
|
|
153
|
+
"nodemon": "3.1.11",
|
|
148
154
|
"npm-watch": "0.13.0",
|
|
149
|
-
"pm2": "6.0.
|
|
150
|
-
"prettier": "3.
|
|
155
|
+
"pm2": "6.0.14",
|
|
156
|
+
"prettier": "3.7.4",
|
|
151
157
|
"pretty-quick": "4.2.2",
|
|
152
|
-
"rimraf": "6.1.
|
|
158
|
+
"rimraf": "6.1.2",
|
|
153
159
|
"supertest": "7.1.4",
|
|
154
|
-
"ts-jest": "29.4.5",
|
|
155
160
|
"ts-loader": "9.5.4",
|
|
156
161
|
"ts-morph": "27.0.2",
|
|
157
162
|
"ts-node": "10.9.2",
|
|
158
163
|
"tsconfig-paths": "4.2.0",
|
|
159
164
|
"typescript": "5.9.3",
|
|
165
|
+
"unplugin-swc": "1.5.9",
|
|
166
|
+
"vite": "7.2.7",
|
|
167
|
+
"vite-plugin-node": "7.0.0",
|
|
168
|
+
"vite-tsconfig-paths": "5.1.4",
|
|
169
|
+
"vitest": "4.0.15",
|
|
160
170
|
"yalc": "1.0.0-pre.53"
|
|
161
171
|
},
|
|
162
172
|
"overrides": {
|
|
@@ -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
|
+
}
|
|
@@ -15,6 +15,18 @@ import { ICoreAuthUser } from '../interfaces/core-auth-user.interface';
|
|
|
15
15
|
import { JwtPayload } from '../interfaces/jwt-payload.interface';
|
|
16
16
|
import { CoreAuthUserService } from './core-auth-user.service';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Options for getResult method
|
|
20
|
+
*/
|
|
21
|
+
export interface GetResultOptions {
|
|
22
|
+
/** Current refresh token (for renewal) */
|
|
23
|
+
currentRefreshToken?: string;
|
|
24
|
+
/** Additional data (deviceId, deviceDescription, etc.) */
|
|
25
|
+
data?: { [key: string]: any; deviceId?: string };
|
|
26
|
+
/** Service options including currentUser for securityCheck */
|
|
27
|
+
serviceOptions?: ServiceOptions;
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* CoreAuthService to handle user authentication
|
|
20
32
|
*/
|
|
@@ -71,16 +83,17 @@ export class CoreAuthService {
|
|
|
71
83
|
/**
|
|
72
84
|
* Refresh tokens
|
|
73
85
|
*/
|
|
74
|
-
async refreshTokens(user: ICoreAuthUser, currentRefreshToken: string) {
|
|
86
|
+
async refreshTokens(user: ICoreAuthUser, currentRefreshToken: string, serviceOptions?: ServiceOptions) {
|
|
75
87
|
// Create new tokens
|
|
76
88
|
const { deviceDescription, deviceId } = this.decodeJwt(currentRefreshToken);
|
|
77
89
|
const tokens = await this.createTokens(user.id, { deviceDescription, deviceId });
|
|
78
90
|
tokens.refreshToken = await this.updateRefreshToken(user, currentRefreshToken, tokens.refreshToken);
|
|
79
91
|
|
|
80
|
-
// Return
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
// Return with currentUser set so securityCheck knows user is requesting own data
|
|
93
|
+
return this.getResult(user, {
|
|
94
|
+
currentRefreshToken,
|
|
95
|
+
data: { deviceDescription, deviceId },
|
|
96
|
+
serviceOptions: { ...serviceOptions, currentUser: user },
|
|
84
97
|
});
|
|
85
98
|
}
|
|
86
99
|
|
|
@@ -114,8 +127,11 @@ export class CoreAuthService {
|
|
|
114
127
|
throw new UnauthorizedException('Wrong password');
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
// Return tokens and user
|
|
118
|
-
return this.getResult(user, {
|
|
130
|
+
// Return tokens and user with currentUser set so securityCheck knows user is requesting own data
|
|
131
|
+
return this.getResult(user, {
|
|
132
|
+
data: { deviceDescription, deviceId },
|
|
133
|
+
serviceOptions: { ...serviceOptions, currentUser: user },
|
|
134
|
+
});
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
/**
|
|
@@ -138,8 +154,11 @@ export class CoreAuthService {
|
|
|
138
154
|
// Set device ID
|
|
139
155
|
const { deviceDescription, deviceId } = input;
|
|
140
156
|
|
|
141
|
-
// Return tokens and user
|
|
142
|
-
return this.getResult(user, {
|
|
157
|
+
// Return tokens and user with currentUser set so securityCheck knows user is requesting own data
|
|
158
|
+
return this.getResult(user, {
|
|
159
|
+
data: { deviceDescription, deviceId },
|
|
160
|
+
serviceOptions: { ...serviceOptions, currentUser: user },
|
|
161
|
+
});
|
|
143
162
|
} catch (err) {
|
|
144
163
|
if (err?.message === 'Unprocessable Entity') {
|
|
145
164
|
throw new BadRequestException('Email address already in use');
|
|
@@ -171,12 +190,16 @@ export class CoreAuthService {
|
|
|
171
190
|
|
|
172
191
|
/**
|
|
173
192
|
* Rest result with user and tokens
|
|
193
|
+
*
|
|
194
|
+
* @param user - The authenticated user
|
|
195
|
+
* @param options - Optional configuration for result generation
|
|
196
|
+
* @param options.data - Additional data (deviceId, deviceDescription, etc.)
|
|
197
|
+
* @param options.currentRefreshToken - Current refresh token (for renewal)
|
|
198
|
+
* @param options.serviceOptions - Service options including currentUser for securityCheck
|
|
174
199
|
*/
|
|
175
|
-
protected async getResult(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
currentRefreshToken?: string,
|
|
179
|
-
) {
|
|
200
|
+
protected async getResult(user: ICoreAuthUser, options?: GetResultOptions) {
|
|
201
|
+
const { currentRefreshToken, data, serviceOptions } = options || {};
|
|
202
|
+
|
|
180
203
|
// Create new tokens
|
|
181
204
|
const tokens = await this.createTokens(user.id, data);
|
|
182
205
|
|
|
@@ -184,9 +207,10 @@ export class CoreAuthService {
|
|
|
184
207
|
tokens.refreshToken = await this.updateRefreshToken(user, currentRefreshToken, tokens.refreshToken, data);
|
|
185
208
|
|
|
186
209
|
// Return tokens and user
|
|
210
|
+
// Pass serviceOptions to prepareOutput so currentUser is available for securityCheck
|
|
187
211
|
return CoreAuthModel.map({
|
|
188
212
|
...tokens,
|
|
189
|
-
user: await this.userService.prepareOutput(user),
|
|
213
|
+
user: await this.userService.prepareOutput(user, serviceOptions),
|
|
190
214
|
});
|
|
191
215
|
}
|
|
192
216
|
|
|
@@ -199,10 +223,10 @@ export class CoreAuthService {
|
|
|
199
223
|
path += '.refresh';
|
|
200
224
|
}
|
|
201
225
|
return (
|
|
202
|
-
this.configService.getFastButReadOnly(`${path}.signInOptions.secret`)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
226
|
+
this.configService.getFastButReadOnly(`${path}.signInOptions.secret`) ||
|
|
227
|
+
this.configService.getFastButReadOnly(`${path}.signInOptions.secretOrPrivateKey`) ||
|
|
228
|
+
this.configService.getFastButReadOnly(`${path}.secret`) ||
|
|
229
|
+
this.configService.getFastButReadOnly(`${path}.secretOrPrivateKey`)
|
|
206
230
|
);
|
|
207
231
|
}
|
|
208
232
|
|
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;
|