@lenne.tech/nest-server 11.21.3 → 11.22.1
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/.claude/rules/architecture.md +79 -0
- package/.claude/rules/better-auth.md +262 -0
- package/.claude/rules/configurable-features.md +308 -0
- package/.claude/rules/core-modules.md +205 -0
- package/.claude/rules/framework-compatibility.md +79 -0
- package/.claude/rules/migration-guides.md +149 -0
- package/.claude/rules/module-deprecation.md +214 -0
- package/.claude/rules/module-inheritance.md +97 -0
- package/.claude/rules/package-management.md +112 -0
- package/.claude/rules/role-system.md +146 -0
- package/.claude/rules/testing.md +120 -0
- package/.claude/rules/versioning.md +53 -0
- package/CLAUDE.md +174 -0
- package/FRAMEWORK-API.md +231 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +10 -0
- package/dist/core/modules/error-code/error-code.module.js.map +1 -1
- package/dist/core.module.d.ts +3 -3
- package/dist/core.module.js +17 -4
- package/dist/core.module.js.map +1 -1
- package/dist/server/modules/file/file-info.model.d.ts +1 -5
- package/dist/server/modules/user/user.model.d.ts +1 -5
- package/dist/server/server.module.js +6 -6
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1256 -0
- package/docs/error-codes.md +446 -0
- package/migration-guides/11.10.x-to-11.11.x.md +266 -0
- package/migration-guides/11.11.x-to-11.12.x.md +323 -0
- package/migration-guides/11.12.x-to-11.13.0.md +612 -0
- package/migration-guides/11.13.x-to-11.14.0.md +348 -0
- package/migration-guides/11.14.x-to-11.15.0.md +262 -0
- package/migration-guides/11.15.0-to-11.15.3.md +118 -0
- package/migration-guides/11.15.x-to-11.16.0.md +497 -0
- package/migration-guides/11.16.x-to-11.17.0.md +130 -0
- package/migration-guides/11.17.x-to-11.18.0.md +393 -0
- package/migration-guides/11.18.x-to-11.19.0.md +151 -0
- package/migration-guides/11.19.x-to-11.20.0.md +170 -0
- package/migration-guides/11.20.x-to-11.21.0.md +216 -0
- package/migration-guides/11.21.0-to-11.21.1.md +194 -0
- package/migration-guides/11.21.1-to-11.21.2.md +114 -0
- package/migration-guides/11.21.2-to-11.21.3.md +175 -0
- package/migration-guides/11.21.x-to-11.22.0.md +224 -0
- package/migration-guides/11.22.0-to-11.22.1.md +105 -0
- package/migration-guides/11.3.x-to-11.4.x.md +233 -0
- package/migration-guides/11.6.x-to-11.7.x.md +394 -0
- package/migration-guides/11.7.x-to-11.8.x.md +318 -0
- package/migration-guides/11.8.x-to-11.9.x.md +322 -0
- package/migration-guides/11.9.x-to-11.10.x.md +571 -0
- package/migration-guides/TEMPLATE.md +113 -0
- package/package.json +25 -18
- package/src/core/common/interfaces/server-options.interface.ts +83 -16
- package/src/core/modules/better-auth/CUSTOMIZATION.md +24 -17
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +5 -5
- package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +42 -12
- package/src/core/modules/error-code/error-code.module.ts +4 -9
- package/src/core.module.ts +52 -10
- package/src/server/server.module.ts +7 -9
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Migration Guide: 11.16.x → 11.17.0
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None |
|
|
8
|
+
| **New Features** | Permissions Report Module, Configurable endpoint path |
|
|
9
|
+
| **Bugfixes** | None |
|
|
10
|
+
| **Migration Effort** | < 5 minutes (optional feature) |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Migration (No Breaking Changes)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Update package
|
|
18
|
+
npm install @lenne.tech/nest-server@11.17.0
|
|
19
|
+
|
|
20
|
+
# Verify build
|
|
21
|
+
npm run build
|
|
22
|
+
|
|
23
|
+
# Run tests
|
|
24
|
+
npm test
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## What's New in 11.17.0
|
|
30
|
+
|
|
31
|
+
### 1. Permissions Report Module
|
|
32
|
+
|
|
33
|
+
A development tool that scans your project for `@Roles`, `@Restricted`, and `securityCheck()` usage, generating an interactive HTML dashboard at runtime.
|
|
34
|
+
|
|
35
|
+
**Setup (optional):**
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// config.env.ts - local/development only
|
|
39
|
+
permissions: true,
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This adds the following endpoints to your server:
|
|
43
|
+
|
|
44
|
+
| Method | Path | Description |
|
|
45
|
+
|--------|------|-------------|
|
|
46
|
+
| `GET` | `/permissions` | Interactive HTML dashboard |
|
|
47
|
+
| `GET` | `/permissions/json` | JSON report with stats |
|
|
48
|
+
| `GET` | `/permissions/markdown` | Markdown report |
|
|
49
|
+
| `POST` | `/permissions/rescan` | Force rescan |
|
|
50
|
+
|
|
51
|
+
**Features:**
|
|
52
|
+
- Lazy scanning (only on first request)
|
|
53
|
+
- File watcher auto-invalidates cache on `.ts` file changes
|
|
54
|
+
- Coverage metrics (endpoint coverage, security coverage)
|
|
55
|
+
- Warning detection (missing `@Roles`, `@Restricted`, `securityCheck()`)
|
|
56
|
+
- Role-based access control (admin-only by default)
|
|
57
|
+
|
|
58
|
+
**Advanced configuration:**
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Custom role
|
|
62
|
+
permissions: { role: 'S_EVERYONE' },
|
|
63
|
+
|
|
64
|
+
// No authentication
|
|
65
|
+
permissions: { role: false },
|
|
66
|
+
|
|
67
|
+
// Custom endpoint path
|
|
68
|
+
permissions: { path: 'admin/permissions' },
|
|
69
|
+
|
|
70
|
+
// Explicitly disabled
|
|
71
|
+
permissions: { enabled: false },
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Standalone Permissions Scanner
|
|
75
|
+
|
|
76
|
+
The scanning logic has been extracted into `permissions-scanner.ts` as a standalone module with no NestJS dependencies. This enables:
|
|
77
|
+
|
|
78
|
+
- The `lt server permissions` CLI command to use the same scanner via dynamic import
|
|
79
|
+
- External tools to import `scanPermissions()` directly from `@lenne.tech/nest-server`
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { scanPermissions, findProjectRoot } from '@lenne.tech/nest-server';
|
|
83
|
+
|
|
84
|
+
const projectRoot = findProjectRoot();
|
|
85
|
+
const report = scanPermissions(projectRoot, {
|
|
86
|
+
log: (msg) => console.log(msg),
|
|
87
|
+
warn: (msg) => console.warn(msg),
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Detailed Migration Steps
|
|
94
|
+
|
|
95
|
+
### Step 1: Update Package
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install @lenne.tech/nest-server@11.17.0
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 2: Adopt New Features (Optional)
|
|
102
|
+
|
|
103
|
+
Add `permissions: true` to your `config.env.ts` in local/development environments. See the [Permissions README](../src/core/modules/permissions/README.md) for details.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Compatibility Notes
|
|
108
|
+
|
|
109
|
+
- The permissions module is entirely opt-in. Existing projects are unaffected.
|
|
110
|
+
- The `lt server permissions` CLI command now requires `@lenne.tech/nest-server >= 11.17.0` in the target project.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Module Documentation
|
|
115
|
+
|
|
116
|
+
### Permissions Report
|
|
117
|
+
|
|
118
|
+
- **README:** [src/core/modules/permissions/README.md](../src/core/modules/permissions/README.md)
|
|
119
|
+
- **Integration Checklist:** [src/core/modules/permissions/INTEGRATION-CHECKLIST.md](../src/core/modules/permissions/INTEGRATION-CHECKLIST.md)
|
|
120
|
+
- **Key Files:**
|
|
121
|
+
- `permissions-scanner.ts` - Standalone AST scanning (shared with CLI)
|
|
122
|
+
- `core-permissions.service.ts` - Caching, file watching, HTML generation
|
|
123
|
+
- `core-permissions.controller.ts` - REST endpoints
|
|
124
|
+
- `core-permissions.module.ts` - DynamicModule with forRoot()
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## References
|
|
129
|
+
|
|
130
|
+
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Migration Guide: 11.17.x → 11.18.0
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | None (NestJS patch update `11.1.13` → `11.1.16` requires matching versions) |
|
|
8
|
+
| **New Features** | Safety Net Architecture: automatic security without CrudService.process() |
|
|
9
|
+
| **Dependency Updates** | NestJS 11.1.16, apollo-server-core removed (−151 packages), 21 packages updated |
|
|
10
|
+
| **Migration Effort** | ~5 minutes (update NestJS packages + verify) |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
All Safety Net features are **enabled by default** and fully backward compatible. No code changes required.
|
|
17
|
+
|
|
18
|
+
However, 11.18.0 updates NestJS core packages to `11.1.16`. Projects **must** update their NestJS packages to match, otherwise pnpm will install duplicate copies with incompatible TypeScript types (e.g. `Types have separate declarations of a private property 'logger'`).
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Update nest-server
|
|
22
|
+
pnpm add @lenne.tech/nest-server@11.18.0
|
|
23
|
+
|
|
24
|
+
# 2. Update NestJS packages to match (required!)
|
|
25
|
+
pnpm add @nestjs/common@11.1.16 @nestjs/core@11.1.16 @nestjs/platform-express@11.1.16
|
|
26
|
+
pnpm add -D @nestjs/testing@11.1.16
|
|
27
|
+
|
|
28
|
+
# 3. Verify build
|
|
29
|
+
pnpm run build
|
|
30
|
+
|
|
31
|
+
# 4. Run tests
|
|
32
|
+
pnpm test
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> **Why?** `@lenne.tech/nest-server` declares NestJS packages in `dependencies` with exact versions. If your project uses a different patch version, pnpm creates duplicate installations which TypeScript treats as incompatible types.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What's New in 11.18.0
|
|
40
|
+
|
|
41
|
+
### Safety Net Architecture
|
|
42
|
+
|
|
43
|
+
Previously, security guarantees (password hashing, role protection, secret removal, field filtering) only worked when using `CrudService.process()`. Developers bypassing `process()` with direct Mongoose calls (`this.mainDbModel.findOne()`, `.aggregate()`, etc.) could accidentally expose sensitive data or store plaintext passwords.
|
|
44
|
+
|
|
45
|
+
**11.18.0 introduces automatic security at the framework level**, ensuring protection works regardless of how data is accessed or written.
|
|
46
|
+
|
|
47
|
+
### 1. Mongoose Password Hashing Plugin
|
|
48
|
+
|
|
49
|
+
Automatically hashes passwords on all write operations (`save`, `findOneAndUpdate`, `updateOne`, `updateMany`).
|
|
50
|
+
|
|
51
|
+
- Detects already-hashed values (BCrypt pattern) to prevent double-hashing
|
|
52
|
+
- Supports SHA256 pre-hashing (matching the existing `prepareInput` behavior)
|
|
53
|
+
- Configurable skip patterns for sentinel values (e.g. `!LOCKED:REQUIRES_PASSWORD_RESET`)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// config.env.ts - enabled by default, examples for customization:
|
|
57
|
+
|
|
58
|
+
// Default: enabled
|
|
59
|
+
security: {
|
|
60
|
+
mongoosePasswordPlugin: true,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// With skip patterns for sentinel values
|
|
64
|
+
security: {
|
|
65
|
+
mongoosePasswordPlugin: {
|
|
66
|
+
skipPatterns: ['^!LOCKED:'],
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Disable entirely
|
|
71
|
+
security: {
|
|
72
|
+
mongoosePasswordPlugin: false,
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Mongoose Role Guard Plugin
|
|
77
|
+
|
|
78
|
+
Prevents unauthorized users from escalating roles via direct database operations. Only users with ADMIN role (or configured allowed roles) can assign roles to other users.
|
|
79
|
+
|
|
80
|
+
- Uses `RequestContext` (AsyncLocalStorage) to access the current user
|
|
81
|
+
- System operations without request context (e.g. migrations, seeds) are allowed through
|
|
82
|
+
- No-user requests (e.g. `signUp`) are allowed through (`currentUser` is `undefined`)
|
|
83
|
+
- Configurable for custom role hierarchies
|
|
84
|
+
- Programmatic bypass via `RequestContext.runWithBypassRoleGuard()` for authorized service code
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// config.env.ts - enabled by default, examples for customization:
|
|
88
|
+
|
|
89
|
+
// Default: only ADMIN can assign roles
|
|
90
|
+
security: {
|
|
91
|
+
mongooseRoleGuardPlugin: true,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Allow additional roles to assign roles
|
|
95
|
+
security: {
|
|
96
|
+
mongooseRoleGuardPlugin: {
|
|
97
|
+
allowedRoles: ['ORGA', 'COMPANY_ADMIN'],
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Programmatic bypass for authorized service operations (e.g. HR_MANAGER creating users with roles):
|
|
102
|
+
import { RequestContext } from '@lenne.tech/nest-server';
|
|
103
|
+
|
|
104
|
+
await RequestContext.runWithBypassRoleGuard(async () => {
|
|
105
|
+
await this.mainDbModel.findByIdAndUpdate(userId, { roles: ['EMPLOYEE'] });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Also works with CrudService.process() via force: true (automatically bypasses the plugin)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Mongoose Audit Fields Plugin
|
|
112
|
+
|
|
113
|
+
Automatically sets `createdBy` and `updatedBy` fields on save/update operations using the current user from `RequestContext`.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// config.env.ts - enabled by default
|
|
117
|
+
|
|
118
|
+
// Disable if not needed
|
|
119
|
+
security: {
|
|
120
|
+
mongooseAuditFieldsPlugin: false,
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. ResponseModelInterceptor
|
|
125
|
+
|
|
126
|
+
Auto-converts plain objects and Mongoose documents into model instances with `securityCheck()` and correct constructor metadata. This ensures `@Restricted` field filtering and `securityCheck()` work even when `CrudService.process()` is bypassed.
|
|
127
|
+
|
|
128
|
+
**Model class resolution order:**
|
|
129
|
+
1. Explicit `@ResponseModel(ModelClass)` decorator (for REST)
|
|
130
|
+
2. GraphQL `TypeMetadataStorage` lookup (automatic for `@Query`/`@Mutation` return types)
|
|
131
|
+
3. Swagger `@ApiOkResponse` / `@ApiCreatedResponse` type (automatic for REST)
|
|
132
|
+
4. No conversion (existing interceptors work as before)
|
|
133
|
+
|
|
134
|
+
Results are cached per route handler for zero-cost subsequent lookups.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// config.env.ts - enabled by default
|
|
138
|
+
|
|
139
|
+
// Enable with debug warnings (logs when auto-conversion happens)
|
|
140
|
+
security: {
|
|
141
|
+
responseModelInterceptor: { debug: true },
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Disable entirely
|
|
145
|
+
security: {
|
|
146
|
+
responseModelInterceptor: false,
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**For REST controllers**, you can use the `@ResponseModel()` decorator to explicitly specify the model class:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { ResponseModel } from '@lenne.tech/nest-server';
|
|
154
|
+
|
|
155
|
+
@ResponseModel(User)
|
|
156
|
+
@Get(':id')
|
|
157
|
+
async getUser(@Param('id') id: string): Promise<User> {
|
|
158
|
+
// Even if this returns a plain object, the interceptor will convert it
|
|
159
|
+
return this.mainDbModel.findById(id).exec();
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 5. TranslateResponseInterceptor
|
|
164
|
+
|
|
165
|
+
Automatically applies `_translations` to response objects based on the `Accept-Language` header. Ensures translations work even when `CrudService.process()` is bypassed.
|
|
166
|
+
|
|
167
|
+
- Includes early bailout: skips recursive traversal when no `_translations` are present
|
|
168
|
+
- Zero overhead for non-translated responses
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// config.env.ts - enabled by default
|
|
172
|
+
|
|
173
|
+
// Disable entirely
|
|
174
|
+
security: {
|
|
175
|
+
translateResponseInterceptor: false,
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 6. Fallback Secret Removal
|
|
180
|
+
|
|
181
|
+
`CheckSecurityInterceptor` now includes a configurable fallback that removes known secret fields from all responses, regardless of whether the response is a model instance.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// config.env.ts - enabled by default
|
|
185
|
+
|
|
186
|
+
// Default secret fields
|
|
187
|
+
security: {
|
|
188
|
+
secretFields: ['password', 'verificationToken', 'passwordResetToken', 'refreshTokens', 'tempTokens'],
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Custom secret fields
|
|
192
|
+
security: {
|
|
193
|
+
secretFields: ['password', 'apiKey', 'internalToken'],
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 7. RequestContext Service
|
|
198
|
+
|
|
199
|
+
New `RequestContext` service using Node.js `AsyncLocalStorage` provides the current user and request language in any context — including Mongoose hooks — without dependency injection.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { RequestContext } from '@lenne.tech/nest-server';
|
|
203
|
+
|
|
204
|
+
// In any service, helper, or Mongoose hook:
|
|
205
|
+
const currentUser = RequestContext.getCurrentUser();
|
|
206
|
+
const language = RequestContext.getLanguage();
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 8. ModelRegistry Service
|
|
210
|
+
|
|
211
|
+
Central registry mapping Mongoose model names to CoreModel classes. Auto-populated when `ModuleService` is constructed. Used internally by `ResponseModelInterceptor`.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { ModelRegistry } from '@lenne.tech/nest-server';
|
|
215
|
+
|
|
216
|
+
// Lookup a model class by Mongoose model name
|
|
217
|
+
const UserClass = ModelRegistry.getModelClass('User');
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 9. ModuleService Helper Methods
|
|
221
|
+
|
|
222
|
+
Two new helper methods for services that use direct Mongoose queries:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// processResult(): Simplified post-processing (population + prepareOutput)
|
|
226
|
+
const doc = await this.mainDbModel.findById(id).exec();
|
|
227
|
+
return this.processResult(doc, serviceOptions);
|
|
228
|
+
|
|
229
|
+
// applyTranslationInput(): Translation-aware input processing
|
|
230
|
+
const oldDoc = await this.mainDbModel.findById(id).lean();
|
|
231
|
+
input = this.applyTranslationInput(input, oldDoc, serviceOptions.language);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Configuration Reference
|
|
237
|
+
|
|
238
|
+
All new features follow the "enabled by default, opt-out via `false`" pattern:
|
|
239
|
+
|
|
240
|
+
| Config Path | Type | Default | Description |
|
|
241
|
+
|-------------|------|---------|-------------|
|
|
242
|
+
| `security.mongoosePasswordPlugin` | `boolean \| { skipPatterns }` | `true` | Auto password hashing |
|
|
243
|
+
| `security.mongooseRoleGuardPlugin` | `boolean \| { allowedRoles }` | `true` | Role escalation prevention |
|
|
244
|
+
| `security.mongooseAuditFieldsPlugin` | `boolean` | `true` | Auto createdBy/updatedBy |
|
|
245
|
+
| `security.responseModelInterceptor` | `boolean \| { debug }` | `true` | Plain→Model auto-conversion |
|
|
246
|
+
| `security.translateResponseInterceptor` | `boolean` | `true` | Auto translation application |
|
|
247
|
+
| `security.secretFields` | `string[]` | `['password', ...]` | Global secret field removal |
|
|
248
|
+
| `security.checkSecurityInterceptor.removeSecretFields` | `boolean` | `true` | Fallback secret removal |
|
|
249
|
+
| `security.checkSecurityInterceptor.secretFields` | `string[]` | `['password', ...]` | Interceptor-specific secret fields |
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Compatibility Notes
|
|
254
|
+
|
|
255
|
+
### Existing CrudService Usage
|
|
256
|
+
|
|
257
|
+
**No changes required.** All existing code using `CrudService.process()` continues to work exactly as before. The Safety Net is purely additive — it catches cases that previously had no protection.
|
|
258
|
+
|
|
259
|
+
### Double-Hashing Prevention
|
|
260
|
+
|
|
261
|
+
The password plugin detects already-hashed values using BCrypt pattern matching (`/^\$2[aby]\$\d+\$/`). If `CrudService.process()` already hashes a password via `prepareInput()`, the plugin will skip it. No double-hashing occurs.
|
|
262
|
+
|
|
263
|
+
### Double-Mapping Prevention
|
|
264
|
+
|
|
265
|
+
The `ResponseModelInterceptor` checks `instanceof` and the `_objectAlreadyCheckedForRestrictions` flag. If `CrudService.process()` already mapped and checked an object, the interceptor skips it. No duplicate processing.
|
|
266
|
+
|
|
267
|
+
### Direct Mongoose Usage (Advanced)
|
|
268
|
+
|
|
269
|
+
Projects that intentionally use direct Mongoose calls now get automatic security for free. For best results:
|
|
270
|
+
|
|
271
|
+
- **Output:** Return plain documents — the `ResponseModelInterceptor` handles conversion
|
|
272
|
+
- **Input:** Passwords are hashed automatically, roles are protected automatically
|
|
273
|
+
- **Audit fields:** `createdBy`/`updatedBy` are set automatically when `RequestContext` is available
|
|
274
|
+
- **Translations:** Use `this.applyTranslationInput()` before writes, responses are translated automatically
|
|
275
|
+
|
|
276
|
+
### Performance
|
|
277
|
+
|
|
278
|
+
K6 benchmarks (10 VUs, 30s) show negligible overhead:
|
|
279
|
+
|
|
280
|
+
| Operation | Overhead vs Baseline |
|
|
281
|
+
|-----------|---------------------|
|
|
282
|
+
| signUp (bcrypt) | < 1% |
|
|
283
|
+
| signIn (bcrypt) | < 1% |
|
|
284
|
+
| getUser | ~2% (< 0.1ms absolute) |
|
|
285
|
+
| updateUser | ~7% (< 0.2ms absolute) |
|
|
286
|
+
| REST getUser | ~14% (~5ms absolute) |
|
|
287
|
+
| **Total throughput** | **-1.6%** |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Troubleshooting
|
|
292
|
+
|
|
293
|
+
### Sentinel password values are being hashed
|
|
294
|
+
|
|
295
|
+
If your application uses sentinel values for password locking (e.g. `!LOCKED:REQUIRES_PASSWORD_RESET`), configure skip patterns:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
security: {
|
|
299
|
+
mongoosePasswordPlugin: {
|
|
300
|
+
skipPatterns: ['^!LOCKED:'],
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Roles are empty after creating users in a service
|
|
306
|
+
|
|
307
|
+
The Role Guard Plugin blocks role assignments from non-admin users. This affects services where a logged-in non-admin user creates other users with roles (e.g. HR systems, team management).
|
|
308
|
+
|
|
309
|
+
**Option A: Programmatic bypass** (for specific service operations):
|
|
310
|
+
```typescript
|
|
311
|
+
import { RequestContext } from '@lenne.tech/nest-server';
|
|
312
|
+
|
|
313
|
+
// Wrap the operation — current user context is preserved
|
|
314
|
+
await RequestContext.runWithBypassRoleGuard(async () => {
|
|
315
|
+
await this.mainDbModel.create({ email, roles: ['EMPLOYEE'] });
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Option B: CrudService force mode** (bypasses role guard automatically):
|
|
320
|
+
```typescript
|
|
321
|
+
return this.process(
|
|
322
|
+
async () => this.mainDbModel.findByIdAndUpdate(id, { roles }),
|
|
323
|
+
{ serviceOptions, force: true },
|
|
324
|
+
);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Option C: Config-based** (permanently allow specific roles to assign roles):
|
|
328
|
+
```typescript
|
|
329
|
+
security: {
|
|
330
|
+
mongooseRoleGuardPlugin: {
|
|
331
|
+
allowedRoles: ['ORGA', 'HR_MANAGER'],
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
> **Note:** `signUp` without a logged-in user is NOT affected — when no `currentUser` exists, the plugin allows role changes.
|
|
337
|
+
|
|
338
|
+
### ResponseModelInterceptor not converting REST responses
|
|
339
|
+
|
|
340
|
+
For REST controllers, the interceptor resolves model types via Swagger metadata or the `@ResponseModel()` decorator. If neither is present, no auto-conversion occurs. Add the decorator explicitly:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
@ResponseModel(User)
|
|
344
|
+
@Get(':id')
|
|
345
|
+
async getUser(@Param('id') id: string): Promise<User> { ... }
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Debug: Which responses are being auto-converted?
|
|
349
|
+
|
|
350
|
+
Enable debug mode to see warnings when the interceptor converts plain objects:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
security: {
|
|
354
|
+
responseModelInterceptor: { debug: true },
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## New Exports
|
|
361
|
+
|
|
362
|
+
The following new exports are available from `@lenne.tech/nest-server`:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Decorators
|
|
366
|
+
export { ResponseModel } from '@lenne.tech/nest-server';
|
|
367
|
+
|
|
368
|
+
// Interceptors
|
|
369
|
+
export { ResponseModelInterceptor } from '@lenne.tech/nest-server';
|
|
370
|
+
export { TranslateResponseInterceptor } from '@lenne.tech/nest-server';
|
|
371
|
+
|
|
372
|
+
// Plugins
|
|
373
|
+
export { mongoosePasswordPlugin } from '@lenne.tech/nest-server';
|
|
374
|
+
export { mongooseRoleGuardPlugin } from '@lenne.tech/nest-server';
|
|
375
|
+
export { mongooseAuditFieldsPlugin } from '@lenne.tech/nest-server';
|
|
376
|
+
|
|
377
|
+
// Services
|
|
378
|
+
export { ModelRegistry } from '@lenne.tech/nest-server';
|
|
379
|
+
export { RequestContext } from '@lenne.tech/nest-server';
|
|
380
|
+
|
|
381
|
+
// Middleware
|
|
382
|
+
export { RequestContextMiddleware } from '@lenne.tech/nest-server';
|
|
383
|
+
|
|
384
|
+
// Helpers
|
|
385
|
+
export { resolveResponseModelClass } from '@lenne.tech/nest-server';
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## References
|
|
391
|
+
|
|
392
|
+
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
|
|
393
|
+
- [Configurable Features Pattern](../.claude/rules/configurable-features.md)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Migration Guide: 11.18.x → 11.19.0
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| **Breaking Changes** | REST controller path prefix `api/` removed from ErrorCode and SystemSetup endpoints |
|
|
8
|
+
| **New Features** | None |
|
|
9
|
+
| **Bugfixes** | None |
|
|
10
|
+
| **Migration Effort** | Minimal (~2 minutes) - update proxy config and/or hardcoded fetch URLs |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Update package
|
|
18
|
+
npm install @lenne.tech/nest-server@11.19.0
|
|
19
|
+
|
|
20
|
+
# Verify build
|
|
21
|
+
npm run build
|
|
22
|
+
|
|
23
|
+
# Run tests
|
|
24
|
+
npm test
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Breaking Changes
|
|
30
|
+
|
|
31
|
+
### 1. REST Endpoint Paths Changed (Removed `api/` Prefix)
|
|
32
|
+
|
|
33
|
+
The `api/` prefix has been removed from two controller route paths to avoid conflicts with the Nuxt frontend proxy, which uses `/api/...` to forward requests to the backend.
|
|
34
|
+
|
|
35
|
+
**Affected endpoints:**
|
|
36
|
+
|
|
37
|
+
| Before | After |
|
|
38
|
+
|--------|-------|
|
|
39
|
+
| `GET /api/i18n/errors/:locale` | `GET /i18n/errors/:locale` |
|
|
40
|
+
| `GET /api/i18n/errors/codes` | `GET /i18n/errors/codes` |
|
|
41
|
+
| `GET /api/system-setup/status` | `GET /system-setup/status` |
|
|
42
|
+
| `POST /api/system-setup/init` | `POST /system-setup/init` |
|
|
43
|
+
|
|
44
|
+
**Why:** The Nuxt frontend proxy intercepts all `/api/...` requests and forwards them to the backend. When backend controllers also use `/api/...` as a route prefix, the paths conflict and cause issues with CORS and cookies during local development.
|
|
45
|
+
|
|
46
|
+
All other controllers (`auth`, `iam`, `files`, `tus`, `users`, etc.) already used paths without the `api/` prefix and are unaffected.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Detailed Migration Steps
|
|
51
|
+
|
|
52
|
+
### Step 1: Update Package
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install @lenne.tech/nest-server@11.19.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 2: Update Custom Controllers (If Applicable)
|
|
59
|
+
|
|
60
|
+
If your project has a custom controller extending `CoreErrorCodeController` or `CoreSystemSetupController`, update the `@Controller()` decorator:
|
|
61
|
+
|
|
62
|
+
**ErrorCode Controller:**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Before
|
|
66
|
+
@Controller('api/i18n/errors')
|
|
67
|
+
export class ErrorCodeController { ... }
|
|
68
|
+
|
|
69
|
+
// After
|
|
70
|
+
@Controller('i18n/errors')
|
|
71
|
+
export class ErrorCodeController { ... }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**SystemSetup Controller:**
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Before
|
|
78
|
+
@Controller('api/system-setup')
|
|
79
|
+
export class SystemSetupController extends CoreSystemSetupController { ... }
|
|
80
|
+
|
|
81
|
+
// After
|
|
82
|
+
@Controller('system-setup')
|
|
83
|
+
export class SystemSetupController extends CoreSystemSetupController { ... }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Step 3: Update Frontend Fetch Calls
|
|
87
|
+
|
|
88
|
+
If your frontend makes direct fetch calls to these endpoints (without the Nuxt proxy), update the URLs:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Before
|
|
92
|
+
const status = await fetch('/api/system-setup/status');
|
|
93
|
+
const errors = await fetch('/api/i18n/errors/de');
|
|
94
|
+
|
|
95
|
+
// After (direct backend calls)
|
|
96
|
+
const status = await fetch('/system-setup/status');
|
|
97
|
+
const errors = await fetch('/i18n/errors/de');
|
|
98
|
+
|
|
99
|
+
// If using Nuxt proxy (no change needed - proxy maps /api/* automatically)
|
|
100
|
+
const status = await fetch('/api/system-setup/status'); // proxy strips /api/
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 4: Update Reverse Proxy / Nginx Config (If Applicable)
|
|
104
|
+
|
|
105
|
+
If your deployment uses a reverse proxy with explicit route rules for these endpoints, update accordingly.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Compatibility Notes
|
|
110
|
+
|
|
111
|
+
- **Nuxt proxy users:** If your Nuxt frontend uses the `/api/...` proxy (default in nuxt-extensions), no frontend changes are needed. The proxy strips the `/api/` prefix before forwarding to the backend.
|
|
112
|
+
- **Direct API consumers:** Any client calling the backend directly (e.g., mobile apps, scripts, Postman collections) must update the endpoint URLs.
|
|
113
|
+
- **Other endpoints unaffected:** `auth`, `iam`, `files`, `tus`, `users`, `avatar`, `health-check`, and `permissions` endpoints remain unchanged.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Troubleshooting
|
|
118
|
+
|
|
119
|
+
### Endpoints return 404 after update
|
|
120
|
+
|
|
121
|
+
**Cause:** Still using the old `/api/...` paths.
|
|
122
|
+
|
|
123
|
+
**Solution:** Remove the `api/` prefix from the endpoint URL:
|
|
124
|
+
- `/api/i18n/errors/de` → `/i18n/errors/de`
|
|
125
|
+
- `/api/system-setup/status` → `/system-setup/status`
|
|
126
|
+
|
|
127
|
+
### Custom ErrorCode controller not working
|
|
128
|
+
|
|
129
|
+
**Cause:** Custom controller still uses `@Controller('api/i18n/errors')`.
|
|
130
|
+
|
|
131
|
+
**Solution:** Update the decorator to `@Controller('i18n/errors')`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Module Documentation
|
|
136
|
+
|
|
137
|
+
### ErrorCode Module
|
|
138
|
+
|
|
139
|
+
- **README:** [src/core/modules/error-code/](../src/core/modules/error-code/)
|
|
140
|
+
- **Integration Checklist:** [INTEGRATION-CHECKLIST.md](../src/core/modules/error-code/INTEGRATION-CHECKLIST.md)
|
|
141
|
+
|
|
142
|
+
### System Setup Module
|
|
143
|
+
|
|
144
|
+
- **README:** [src/core/modules/system-setup/README.md](../src/core/modules/system-setup/README.md)
|
|
145
|
+
- **Integration Checklist:** [INTEGRATION-CHECKLIST.md](../src/core/modules/system-setup/INTEGRATION-CHECKLIST.md)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## References
|
|
150
|
+
|
|
151
|
+
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
|