Skip to content

Commit 565e81e

Browse files
authored
Merge pull request #457 from lenneTech/develop
Release 11.6.0
2 parents 300cfc2 + 0e556dc commit 565e81e

File tree

14 files changed

+3937
-2150
lines changed

14 files changed

+3937
-2150
lines changed

package-lock.json

Lines changed: 3699 additions & 2028 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lenne.tech/nest-server",
3-
"version": "11.4.8",
3+
"version": "11.6.0",
44
"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).",
55
"keywords": [
66
"node",
@@ -25,6 +25,14 @@
2525
"docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
2626
"format": "prettier --write 'src/**/*.ts'",
2727
"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",
2836
"lint": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --cache",
2937
"lint:fix": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --fix --cache",
3038
"prestart:prod": "npm run build",
@@ -42,17 +50,18 @@
4250
"start:dev:swc": "nest start -b swc -w --type-check",
4351
"start:local": "NODE_ENV=local nodemon",
4452
"start:local:swc": "NODE_ENV=local nest start -b swc -w --type-check",
45-
"test": "npm run test:e2e",
46-
"test:cov": "NODE_ENV=local jest --coverage",
47-
"test:debug": "NODE_ENV=local node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
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",
5356
"prepack": "npm run prestart:prod",
5457
"prepublishOnly": "npm run lint && npm run test:ci",
5558
"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",
5665
"watch": "npm-watch",
5766
"link:eslint": "yalc add @lenne.tech/eslint-config-ts && yalc link @lenne.tech/eslint-config-ts && npm install",
5867
"unlink:eslint": "yalc remove @lenne.tech/eslint-config-ts && npm install"
@@ -68,24 +77,22 @@
6877
"node": ">= 20"
6978
},
7079
"dependencies": {
71-
"@apollo/gateway": "2.12.0",
7280
"@getbrevo/brevo": "3.0.1",
7381
"@nestjs/apollo": "13.1.0",
74-
"@nestjs/common": "11.1.8",
75-
"@nestjs/core": "11.1.8",
76-
"@nestjs/graphql": "13.1.0",
77-
"@nestjs/jwt": "11.0.1",
78-
"@nestjs/mongoose": "11.0.3",
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",
7987
"@nestjs/passport": "11.0.5",
80-
"@nestjs/platform-express": "11.1.8",
81-
"@nestjs/schedule": "6.0.1",
82-
"@nestjs/swagger": "11.2.1",
88+
"@nestjs/platform-express": "11.1.9",
89+
"@nestjs/schedule": "6.1.0",
90+
"@nestjs/swagger": "11.2.3",
8391
"@nestjs/terminus": "11.0.0",
8492
"apollo-server-core": "3.13.0",
85-
"apollo-server-express": "3.13.0",
8693
"bcrypt": "6.0.0",
8794
"class-transformer": "0.5.1",
88-
"class-validator": "0.14.2",
95+
"class-validator": "0.14.3",
8996
"compression": "1.8.1",
9097
"cookie-parser": "1.4.7",
9198
"dotenv": "17.2.3",
@@ -101,38 +108,38 @@
101108
"mongoose": "8.19.3",
102109
"multer": "2.0.2",
103110
"node-mailjet": "6.0.11",
104-
"nodemailer": "7.0.10",
111+
"nodemailer": "7.0.11",
105112
"passport": "0.7.0",
106113
"passport-jwt": "4.0.1",
107114
"reflect-metadata": "0.2.2",
108115
"rfdc": "1.4.1",
109116
"rxjs": "7.8.2",
117+
"ts-jest": "29.4.6",
110118
"yuml-diagram": "1.2.0"
111119
},
112120
"devDependencies": {
113-
"@babel/plugin-proposal-private-methods": "7.18.6",
114121
"@compodoc/compodoc": "1.1.32",
115122
"@lenne.tech/eslint-config-ts": "2.1.4",
116-
"@nestjs/cli": "11.0.10",
123+
"@nestjs/cli": "11.0.14",
117124
"@nestjs/schematics": "11.0.9",
118-
"@nestjs/testing": "11.1.8",
125+
"@nestjs/testing": "11.1.9",
119126
"@swc/cli": "0.7.9",
120-
"@swc/core": "1.15.0",
121-
"@swc/jest": "0.2.39",
127+
"@swc/core": "1.15.3",
122128
"@types/compression": "1.8.1",
123129
"@types/cookie-parser": "1.4.10",
124130
"@types/ejs": "3.1.5",
125131
"@types/express": "4.17.21",
126-
"@types/jest": "30.0.0",
127-
"@types/lodash": "4.17.20",
132+
"@types/lodash": "4.17.21",
128133
"@types/multer": "2.0.0",
129-
"@types/node": "24.10.0",
130-
"@types/nodemailer": "7.0.3",
134+
"@types/node": "25.0.1",
135+
"@types/nodemailer": "7.0.4",
131136
"@types/passport": "1.0.17",
132137
"@types/supertest": "6.0.3",
133-
"@typescript-eslint/eslint-plugin": "8.46.3",
134-
"@typescript-eslint/parser": "8.46.3",
135-
"coffeescript": "2.7.0",
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",
136143
"eslint": "9.39.1",
137144
"eslint-config-prettier": "10.1.8",
138145
"eslint-plugin-unused-imports": "4.3.0",
@@ -143,20 +150,23 @@
143150
"grunt-contrib-watch": "1.1.0",
144151
"grunt-sync": "0.8.2",
145152
"husky": "9.1.7",
146-
"jest": "30.2.0",
147-
"nodemon": "3.1.10",
153+
"nodemon": "3.1.11",
148154
"npm-watch": "0.13.0",
149-
"pm2": "6.0.13",
150-
"prettier": "3.6.2",
155+
"pm2": "6.0.14",
156+
"prettier": "3.7.4",
151157
"pretty-quick": "4.2.2",
152-
"rimraf": "6.1.0",
158+
"rimraf": "6.1.2",
153159
"supertest": "7.1.4",
154-
"ts-jest": "29.4.5",
155160
"ts-loader": "9.5.4",
156161
"ts-morph": "27.0.2",
157162
"ts-node": "10.9.2",
158163
"tsconfig-paths": "4.2.0",
159164
"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",
160170
"yalc": "1.0.0-pre.53"
161171
},
162172
"overrides": {

spectaql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ servers:
1111
info:
1212
title: lT Nest Server
1313
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).
14-
version: 11.4.8
14+
version: 11.5.0
1515
contact:
1616
name: lenne.Tech GmbH
1717
url: https://lenne.tech

src/core.module.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { CheckSecurityInterceptor } from './core/common/interceptors/check-secur
1313
import { IServerOptions } from './core/common/interfaces/server-options.interface';
1414
import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
1515
import { ComplexityPlugin } from './core/common/plugins/complexity.plugin';
16+
import { mongooseIdPlugin } from './core/common/plugins/mongoose-id.plugin';
1617
import { ConfigService } from './core/common/services/config.service';
1718
import { EmailService } from './core/common/services/email.service';
1819
import { MailjetService } from './core/common/services/mailjet.service';
@@ -98,8 +99,8 @@ export class CoreModule implements NestModule {
9899
if (config.graphQl.enableSubscriptionAuth) {
99100
// get authToken from authorization header
100101
const headers = this.getHeaderFromArray(extra.request?.rawHeaders);
101-
const authToken: string
102-
= connectionParams?.Authorization?.split(' ')[1] ?? headers.Authorization?.split(' ')[1];
102+
const authToken: string =
103+
connectionParams?.Authorization?.split(' ')[1] ?? headers.Authorization?.split(' ')[1];
103104
if (authToken) {
104105
// verify authToken/getJwtPayLoad
105106
const payload = authService.decodeJwt(authToken);
@@ -148,7 +149,7 @@ export class CoreModule implements NestModule {
148149
mongoose: {
149150
options: {
150151
connectionFactory: (connection) => {
151-
connection.plugin(require('./core/common/plugins/mongoose-id.plugin'));
152+
connection.plugin(mongooseIdPlugin);
152153
return connection;
153154
},
154155
},
@@ -177,11 +178,13 @@ export class CoreModule implements NestModule {
177178
EmailService,
178179
TemplateService,
179180
MailjetService,
180-
181-
// Plugins
182-
ComplexityPlugin,
183181
];
184182

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+
185188
if (config.security?.checkResponseInterceptor ?? true) {
186189
// Check restrictions for output (models and output objects)
187190
providers.push({
@@ -225,9 +228,15 @@ export class CoreModule implements NestModule {
225228
imports.push(CoreHealthCheckModule);
226229
}
227230

231+
// Set exports
232+
const exports: any[] = [ConfigService, EmailService, TemplateService, MailjetService];
233+
if (!process.env.VITEST) {
234+
exports.push(ComplexityPlugin);
235+
}
236+
228237
// Return dynamic module
229238
return {
230-
exports: [ConfigService, EmailService, TemplateService, MailjetService, ComplexityPlugin],
239+
exports,
231240
imports,
232241
module: CoreModule,
233242
providers,

src/core/common/plugins/mongoose-id.plugin.js

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

src/core/modules/auth/services/core-auth.service.ts

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ import { ICoreAuthUser } from '../interfaces/core-auth-user.interface';
1515
import { JwtPayload } from '../interfaces/jwt-payload.interface';
1616
import { CoreAuthUserService } from './core-auth-user.service';
1717

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+
1830
/**
1931
* CoreAuthService to handle user authentication
2032
*/
@@ -71,16 +83,17 @@ export class CoreAuthService {
7183
/**
7284
* Refresh tokens
7385
*/
74-
async refreshTokens(user: ICoreAuthUser, currentRefreshToken: string) {
86+
async refreshTokens(user: ICoreAuthUser, currentRefreshToken: string, serviceOptions?: ServiceOptions) {
7587
// Create new tokens
7688
const { deviceDescription, deviceId } = this.decodeJwt(currentRefreshToken);
7789
const tokens = await this.createTokens(user.id, { deviceDescription, deviceId });
7890
tokens.refreshToken = await this.updateRefreshToken(user, currentRefreshToken, tokens.refreshToken);
7991

80-
// Return
81-
return CoreAuthModel.map({
82-
...tokens,
83-
user: await this.userService.prepareOutput(user),
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 },
8497
});
8598
}
8699

@@ -114,8 +127,11 @@ export class CoreAuthService {
114127
throw new UnauthorizedException('Wrong password');
115128
}
116129

117-
// Return tokens and user
118-
return this.getResult(user, { deviceDescription, deviceId });
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+
});
119135
}
120136

121137
/**
@@ -138,8 +154,11 @@ export class CoreAuthService {
138154
// Set device ID
139155
const { deviceDescription, deviceId } = input;
140156

141-
// Return tokens and user
142-
return this.getResult(user, { deviceDescription, deviceId });
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+
});
143162
} catch (err) {
144163
if (err?.message === 'Unprocessable Entity') {
145164
throw new BadRequestException('Email address already in use');
@@ -171,22 +190,27 @@ export class CoreAuthService {
171190

172191
/**
173192
* 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
174199
*/
175-
protected async getResult(
176-
user: ICoreAuthUser,
177-
data?: { [key: string]: any; deviceId?: string },
178-
currentRefreshToken?: string,
179-
) {
200+
protected async getResult(user: ICoreAuthUser, options?: GetResultOptions) {
201+
const { currentRefreshToken, data, serviceOptions } = options || {};
202+
180203
// Create new tokens
181204
const tokens = await this.createTokens(user.id, data);
182205

183206
// Set refresh token
184207
tokens.refreshToken = await this.updateRefreshToken(user, currentRefreshToken, tokens.refreshToken, data);
185208

186209
// Return tokens and user
210+
// Pass serviceOptions to prepareOutput so currentUser is available for securityCheck
187211
return CoreAuthModel.map({
188212
...tokens,
189-
user: await this.userService.prepareOutput(user),
213+
user: await this.userService.prepareOutput(user, serviceOptions),
190214
});
191215
}
192216

@@ -199,10 +223,10 @@ export class CoreAuthService {
199223
path += '.refresh';
200224
}
201225
return (
202-
this.configService.getFastButReadOnly(`${path}.signInOptions.secret`)
203-
|| this.configService.getFastButReadOnly(`${path}.signInOptions.secretOrPrivateKey`)
204-
|| this.configService.getFastButReadOnly(`${path}.secret`)
205-
|| this.configService.getFastButReadOnly(`${path}.secretOrPrivateKey`)
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`)
206230
);
207231
}
208232

0 commit comments

Comments
 (0)