@lenne.tech/nest-server 11.21.2 → 11.22.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/.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/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 +172 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +10 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +25 -25
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.js +8 -4
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/error-code/error-code.module.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.guard.d.ts +1 -0
- package/dist/core/modules/tenant/core-tenant.guard.js +59 -4
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.helpers.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/server.module.js +6 -6
- package/dist/server/server.module.js.map +1 -1
- package/dist/test/test.helper.d.ts +6 -2
- package/dist/test/test.helper.js +28 -6
- package/dist/test/test.helper.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.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 +8 -3
- 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/better-auth/core-better-auth-user.mapper.ts +29 -25
- package/src/core/modules/better-auth/core-better-auth.service.ts +13 -9
- 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/modules/tenant/INTEGRATION-CHECKLIST.md +13 -2
- package/src/core/modules/tenant/README.md +26 -1
- package/src/core/modules/tenant/core-tenant.guard.ts +142 -11
- package/src/core/modules/tenant/core-tenant.helpers.ts +6 -2
- package/src/core.module.ts +52 -10
- package/src/server/server.module.ts +7 -9
- package/src/test/README.md +47 -0
- package/src/test/test.helper.ts +55 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.22.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",
|
|
@@ -168,7 +168,11 @@
|
|
|
168
168
|
"files": [
|
|
169
169
|
"dist/**/*",
|
|
170
170
|
"src/**/*",
|
|
171
|
-
"bin/**/*"
|
|
171
|
+
"bin/**/*",
|
|
172
|
+
"CLAUDE.md",
|
|
173
|
+
".claude/rules/**/*",
|
|
174
|
+
"docs/**/*",
|
|
175
|
+
"migration-guides/**/*"
|
|
172
176
|
],
|
|
173
177
|
"watch": {
|
|
174
178
|
"build:dev": "src"
|
|
@@ -191,7 +195,8 @@
|
|
|
191
195
|
"picomatch@>=4.0.0 <4.0.4": "4.0.4",
|
|
192
196
|
"path-to-regexp@>=8.0.0 <8.4.0": "8.4.1",
|
|
193
197
|
"kysely@>=0.26.0 <0.28.15": "0.28.15",
|
|
194
|
-
"lodash@>=4.0.0 <4.18.0": "4.18.1"
|
|
198
|
+
"lodash@>=4.0.0 <4.18.0": "4.18.1",
|
|
199
|
+
"defu@<=6.1.4": "6.1.6"
|
|
195
200
|
},
|
|
196
201
|
"onlyBuiltDependencies": [
|
|
197
202
|
"bcrypt",
|
|
@@ -2117,16 +2117,12 @@ interface IBetterAuthBase {
|
|
|
2117
2117
|
* Custom controller class to use instead of the default CoreBetterAuthController.
|
|
2118
2118
|
* The class should extend CoreBetterAuthController.
|
|
2119
2119
|
*
|
|
2120
|
-
*
|
|
2121
|
-
* a separate module. Use this with CoreModule.forRoot(envConfig) (IAM-only mode).
|
|
2122
|
-
*
|
|
2123
|
-
* @example
|
|
2120
|
+
* @deprecated Since 11.22.0 — Use the `overrides` parameter on `CoreModule.forRoot()` instead:
|
|
2124
2121
|
* ```typescript
|
|
2125
|
-
*
|
|
2126
|
-
* betterAuth: {
|
|
2127
|
-
* controller: IamController,
|
|
2128
|
-
* }
|
|
2122
|
+
* CoreModule.forRoot(envConfig, { betterAuth: { controller: IamController } })
|
|
2129
2123
|
* ```
|
|
2124
|
+
* This separates class references from environment configuration. The config field
|
|
2125
|
+
* still works for backward compatibility but `overrides` takes precedence.
|
|
2130
2126
|
*
|
|
2131
2127
|
* @since 11.14.0
|
|
2132
2128
|
*/
|
|
@@ -2342,16 +2338,12 @@ interface IBetterAuthBase {
|
|
|
2342
2338
|
* Custom resolver class to use instead of the default DefaultBetterAuthResolver.
|
|
2343
2339
|
* The class should extend CoreBetterAuthResolver.
|
|
2344
2340
|
*
|
|
2345
|
-
*
|
|
2346
|
-
* a separate module. Use this with CoreModule.forRoot(envConfig) (IAM-only mode).
|
|
2347
|
-
*
|
|
2348
|
-
* @example
|
|
2341
|
+
* @deprecated Since 11.22.0 — Use the `overrides` parameter on `CoreModule.forRoot()` instead:
|
|
2349
2342
|
* ```typescript
|
|
2350
|
-
*
|
|
2351
|
-
* betterAuth: {
|
|
2352
|
-
* resolver: IamResolver,
|
|
2353
|
-
* }
|
|
2343
|
+
* CoreModule.forRoot(envConfig, { betterAuth: { resolver: IamResolver } })
|
|
2354
2344
|
* ```
|
|
2345
|
+
* This separates class references from environment configuration. The config field
|
|
2346
|
+
* still works for backward compatibility but `overrides` takes precedence.
|
|
2355
2347
|
*
|
|
2356
2348
|
* @since 11.14.0
|
|
2357
2349
|
*/
|
|
@@ -2663,3 +2655,78 @@ interface IBetterAuthWithPasskey extends IBetterAuthBase {
|
|
|
2663
2655
|
*/
|
|
2664
2656
|
trustedOrigins: string[];
|
|
2665
2657
|
}
|
|
2658
|
+
|
|
2659
|
+
/**
|
|
2660
|
+
* Override default implementations of core module components.
|
|
2661
|
+
*
|
|
2662
|
+
* Use this as the second parameter of `CoreModule.forRoot()` to replace
|
|
2663
|
+
* default controllers, resolvers, or services with project-specific implementations.
|
|
2664
|
+
*
|
|
2665
|
+
* This keeps class references (code) separate from environment configuration (strings/numbers)
|
|
2666
|
+
* and ensures each module's `forRoot()` is called exactly once — preventing duplicate
|
|
2667
|
+
* controller registration.
|
|
2668
|
+
*
|
|
2669
|
+
* Each core module that supports customization has a typed entry here.
|
|
2670
|
+
* Only specified components are overridden — unset fields use defaults.
|
|
2671
|
+
*
|
|
2672
|
+
* @example
|
|
2673
|
+
* ```typescript
|
|
2674
|
+
* // IAM-only mode with overrides
|
|
2675
|
+
* CoreModule.forRoot(envConfig, {
|
|
2676
|
+
* errorCode: { controller: ErrorCodeController, service: ErrorCodeService },
|
|
2677
|
+
* betterAuth: { resolver: BetterAuthResolver },
|
|
2678
|
+
* })
|
|
2679
|
+
*
|
|
2680
|
+
* // Legacy mode with overrides
|
|
2681
|
+
* CoreModule.forRoot(CoreAuthService, AuthModule.forRoot(envConfig.jwt), envConfig, {
|
|
2682
|
+
* errorCode: { controller: ErrorCodeController, service: ErrorCodeService },
|
|
2683
|
+
* betterAuth: { controller: BetterAuthController, resolver: BetterAuthResolver },
|
|
2684
|
+
* })
|
|
2685
|
+
* ```
|
|
2686
|
+
*
|
|
2687
|
+
* @since 11.22.0
|
|
2688
|
+
*/
|
|
2689
|
+
export interface ICoreModuleOverrides {
|
|
2690
|
+
/**
|
|
2691
|
+
* Override BetterAuth controller and/or resolver.
|
|
2692
|
+
*
|
|
2693
|
+
* The custom controller must extend `CoreBetterAuthController`.
|
|
2694
|
+
* The custom resolver must extend `CoreBetterAuthResolver` and re-declare
|
|
2695
|
+
* all GraphQL decorators (`@Mutation`, `@Query`, `@Roles`).
|
|
2696
|
+
*
|
|
2697
|
+
* @example
|
|
2698
|
+
* ```typescript
|
|
2699
|
+
* {
|
|
2700
|
+
* betterAuth: {
|
|
2701
|
+
* controller: BetterAuthController,
|
|
2702
|
+
* resolver: BetterAuthResolver,
|
|
2703
|
+
* },
|
|
2704
|
+
* }
|
|
2705
|
+
* ```
|
|
2706
|
+
*/
|
|
2707
|
+
betterAuth?: {
|
|
2708
|
+
controller?: Type<any>;
|
|
2709
|
+
resolver?: Type<any>;
|
|
2710
|
+
};
|
|
2711
|
+
|
|
2712
|
+
/**
|
|
2713
|
+
* Override ErrorCode controller and/or service.
|
|
2714
|
+
*
|
|
2715
|
+
* The custom controller can be standalone (recommended) or extend `CoreErrorCodeController`.
|
|
2716
|
+
* The custom service must extend `CoreErrorCodeService`.
|
|
2717
|
+
*
|
|
2718
|
+
* @example
|
|
2719
|
+
* ```typescript
|
|
2720
|
+
* {
|
|
2721
|
+
* errorCode: {
|
|
2722
|
+
* controller: ErrorCodeController,
|
|
2723
|
+
* service: ErrorCodeService,
|
|
2724
|
+
* },
|
|
2725
|
+
* }
|
|
2726
|
+
* ```
|
|
2727
|
+
*/
|
|
2728
|
+
errorCode?: {
|
|
2729
|
+
controller?: Type<any>;
|
|
2730
|
+
service?: Type<any>;
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
@@ -41,29 +41,23 @@ export class ServerModule {}
|
|
|
41
41
|
- Default `CoreBetterAuthController` and `DefaultBetterAuthResolver` are registered
|
|
42
42
|
- No additional configuration needed
|
|
43
43
|
|
|
44
|
-
### Pattern 2:
|
|
44
|
+
### Pattern 2: Overrides Parameter (Recommended for Customization)
|
|
45
45
|
|
|
46
46
|
**Use when:** Need custom Controller or Resolver, but don't need a separate module.
|
|
47
47
|
|
|
48
|
-
```typescript
|
|
49
|
-
// config.env.ts
|
|
50
|
-
import { IamController } from './server/modules/iam/iam.controller';
|
|
51
|
-
import { IamResolver } from './server/modules/iam/iam.resolver';
|
|
52
|
-
|
|
53
|
-
const config = {
|
|
54
|
-
betterAuth: {
|
|
55
|
-
controller: IamController, // Custom controller class
|
|
56
|
-
resolver: IamResolver, // Custom resolver class
|
|
57
|
-
// ... other betterAuth config
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
```
|
|
61
|
-
|
|
62
48
|
```typescript
|
|
63
49
|
// server.module.ts
|
|
50
|
+
import { IamController } from './modules/iam/iam.controller';
|
|
51
|
+
import { IamResolver } from './modules/iam/iam.resolver';
|
|
52
|
+
|
|
64
53
|
@Module({
|
|
65
54
|
imports: [
|
|
66
|
-
CoreModule.forRoot(envConfig
|
|
55
|
+
CoreModule.forRoot(envConfig, {
|
|
56
|
+
betterAuth: {
|
|
57
|
+
controller: IamController,
|
|
58
|
+
resolver: IamResolver,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
67
61
|
],
|
|
68
62
|
})
|
|
69
63
|
export class ServerModule {}
|
|
@@ -71,9 +65,22 @@ export class ServerModule {}
|
|
|
71
65
|
|
|
72
66
|
**What happens:**
|
|
73
67
|
|
|
74
|
-
- CoreModule passes
|
|
68
|
+
- CoreModule passes overrides to `CoreBetterAuthModule.forRoot()`
|
|
75
69
|
- Your custom classes are registered instead of defaults
|
|
76
70
|
- Single registration point, no duplicate imports
|
|
71
|
+
- Class references stay separate from environment config
|
|
72
|
+
|
|
73
|
+
**Alternative:** Set `controller`/`resolver` directly in config (backward compatible):
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// config.env.ts — still works but overrides parameter is preferred
|
|
77
|
+
const config = {
|
|
78
|
+
betterAuth: {
|
|
79
|
+
controller: IamController,
|
|
80
|
+
resolver: IamResolver,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
```
|
|
77
84
|
|
|
78
85
|
### Pattern 3: Separate Module (autoRegister: false)
|
|
79
86
|
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
|
|
24
24
|
### Registration Patterns (Quick Reference)
|
|
25
25
|
|
|
26
|
-
| Pattern
|
|
27
|
-
|
|
|
28
|
-
| **Zero-Config**
|
|
29
|
-
| **
|
|
30
|
-
| **Separate Module**
|
|
26
|
+
| Pattern | Use When | Configuration |
|
|
27
|
+
| --------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
28
|
+
| **Zero-Config** | No customization needed | Just use `CoreModule.forRoot(envConfig)` |
|
|
29
|
+
| **Overrides** (recommended) | Custom Controller/Resolver | Pass `overrides` to `CoreModule.forRoot(envConfig, { betterAuth: { controller, resolver } })` |
|
|
30
|
+
| **Separate Module** | Full control, additional providers | Set `autoRegister: false` in config |
|
|
31
31
|
|
|
32
32
|
**Details:** See [CUSTOMIZATION.md](./CUSTOMIZATION.md#module-registration-patterns)
|
|
33
33
|
|
|
@@ -1019,32 +1019,36 @@ export class CoreBetterAuthUserMapper {
|
|
|
1019
1019
|
const migrationPercentage = totalUsers > 0 ? Math.round((fullyMigratedUsers / totalUsers) * 100 * 100) / 100 : 0;
|
|
1020
1020
|
|
|
1021
1021
|
// Get emails of pending users (limit to 100)
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
from: 'account',
|
|
1029
|
-
localField: '_id',
|
|
1030
|
-
},
|
|
1031
|
-
},
|
|
1032
|
-
{
|
|
1033
|
-
$match: {
|
|
1034
|
-
$or: [
|
|
1035
|
-
{ iamId: { $exists: false } },
|
|
1036
|
-
{ iamId: null },
|
|
1037
|
-
{
|
|
1038
|
-
$and: [{ iamId: { $exists: true, $ne: null } }, { 'accounts.providerId': { $ne: 'credential' } }],
|
|
1039
|
-
},
|
|
1040
|
-
],
|
|
1041
|
-
},
|
|
1042
|
-
},
|
|
1043
|
-
{ $limit: 100 },
|
|
1044
|
-
{ $project: { email: 1 } },
|
|
1045
|
-
])
|
|
1022
|
+
// Two-phase approach: first get users without iamId (no $lookup needed),
|
|
1023
|
+
// then check users with iamId but missing credential account
|
|
1024
|
+
const usersWithoutIamId = await usersCollection
|
|
1025
|
+
.find({ $or: [{ iamId: { $exists: false } }, { iamId: null }] })
|
|
1026
|
+
.limit(100)
|
|
1027
|
+
.project({ email: 1 })
|
|
1046
1028
|
.toArray();
|
|
1047
|
-
|
|
1029
|
+
|
|
1030
|
+
const remaining = 100 - usersWithoutIamId.length;
|
|
1031
|
+
let usersWithIamButNoAccount: { email?: string }[] = [];
|
|
1032
|
+
if (remaining > 0) {
|
|
1033
|
+
usersWithIamButNoAccount = await usersCollection
|
|
1034
|
+
.aggregate([
|
|
1035
|
+
{ $match: { iamId: { $exists: true, $ne: null } } },
|
|
1036
|
+
{
|
|
1037
|
+
$lookup: {
|
|
1038
|
+
as: 'accounts',
|
|
1039
|
+
foreignField: 'userId',
|
|
1040
|
+
from: 'account',
|
|
1041
|
+
localField: '_id',
|
|
1042
|
+
},
|
|
1043
|
+
},
|
|
1044
|
+
{ $match: { 'accounts.providerId': { $ne: 'credential' } } },
|
|
1045
|
+
{ $limit: remaining },
|
|
1046
|
+
{ $project: { email: 1 } },
|
|
1047
|
+
])
|
|
1048
|
+
.toArray();
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const pendingUserEmails = [...usersWithoutIamId, ...usersWithIamButNoAccount].map((u) => u.email).filter(Boolean);
|
|
1048
1052
|
|
|
1049
1053
|
// Can disable legacy auth only if ALL users are fully migrated
|
|
1050
1054
|
const canDisableLegacyAuth = totalUsers > 0 && fullyMigratedUsers === totalUsers;
|
|
@@ -88,15 +88,19 @@ export class CoreBetterAuthService implements OnModuleInit {
|
|
|
88
88
|
try {
|
|
89
89
|
const db = this.connection.db;
|
|
90
90
|
|
|
91
|
-
//
|
|
92
|
-
await
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
// All indices are idempotent (no-op if already exists) and independent — run in parallel
|
|
92
|
+
await Promise.all([
|
|
93
|
+
// Session: token lookup (getSessionByToken) and user+expiry lookup (getActiveSessionForUser)
|
|
94
|
+
db.collection('session').createIndex({ token: 1 }),
|
|
95
|
+
db.collection('session').createIndex({ userId: 1, expiresAt: 1 }),
|
|
96
|
+
// Users: iamId lookup (mapSessionUser uses $or with email and iamId)
|
|
97
|
+
db.collection('users').createIndex({ iamId: 1 }, { sparse: true }),
|
|
98
|
+
// Account: userId lookup ($lookup in getMigrationStatus) and providerId filtering
|
|
99
|
+
db.collection('account').createIndex({ userId: 1 }),
|
|
100
|
+
db.collection('account').createIndex({ providerId: 1, userId: 1 }),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
this.logger.debug('Performance indices ensured on session, users, and account collections');
|
|
100
104
|
} catch (error) {
|
|
101
105
|
// Non-fatal: indices improve performance but are not required for correctness
|
|
102
106
|
this.logger.warn(`Could not create performance indices: ${error instanceof Error ? error.message : 'unknown'}`);
|
|
@@ -141,16 +141,15 @@ NestJS @Global() modules use "first wins" for provider registration. Without thi
|
|
|
141
141
|
|
|
142
142
|
**Update:** `src/server/server.module.ts`
|
|
143
143
|
|
|
144
|
+
Use the `overrides` parameter of `CoreModule.forRoot()` (recommended since v11.22.0):
|
|
145
|
+
|
|
144
146
|
```typescript
|
|
145
|
-
import { ErrorCodeModule as CoreErrorCodeModule } from '@lenne.tech/nest-server';
|
|
146
147
|
import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
147
148
|
|
|
148
149
|
@Module({
|
|
149
150
|
imports: [
|
|
150
|
-
CoreModule.forRoot(
|
|
151
|
-
|
|
152
|
-
CoreErrorCodeModule.forRoot({
|
|
153
|
-
service: ErrorCodeService,
|
|
151
|
+
CoreModule.forRoot(envConfig, {
|
|
152
|
+
errorCode: { service: ErrorCodeService },
|
|
154
153
|
}),
|
|
155
154
|
// ... other modules
|
|
156
155
|
],
|
|
@@ -158,6 +157,22 @@ import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
|
158
157
|
export class ServerModule {}
|
|
159
158
|
```
|
|
160
159
|
|
|
160
|
+
**Alternative** (for complex setups): disable auto-registration and import separately:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { ErrorCodeModule as CoreErrorCodeModule } from '@lenne.tech/nest-server';
|
|
164
|
+
import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
165
|
+
|
|
166
|
+
@Module({
|
|
167
|
+
imports: [
|
|
168
|
+
CoreModule.forRoot({ ...envConfig, errorCode: { autoRegister: false } }),
|
|
169
|
+
CoreErrorCodeModule.forRoot({ service: ErrorCodeService }),
|
|
170
|
+
// ... other modules
|
|
171
|
+
],
|
|
172
|
+
})
|
|
173
|
+
export class ServerModule {}
|
|
174
|
+
```
|
|
175
|
+
|
|
161
176
|
---
|
|
162
177
|
|
|
163
178
|
## Scenario C: Custom Controller (For Custom Routes)
|
|
@@ -168,7 +183,7 @@ Use this when you need:
|
|
|
168
183
|
- Different route paths
|
|
169
184
|
- Additional REST endpoints
|
|
170
185
|
|
|
171
|
-
**No custom module needed!** Use
|
|
186
|
+
**No custom module needed!** Use the `overrides` parameter of `CoreModule.forRoot()`.
|
|
172
187
|
|
|
173
188
|
### 1. Create Files
|
|
174
189
|
|
|
@@ -183,13 +198,29 @@ Files needed:
|
|
|
183
198
|
|
|
184
199
|
**No `error-code.module.ts` needed!**
|
|
185
200
|
|
|
186
|
-
### 2.
|
|
201
|
+
### 2. Register via CoreModule.forRoot() Overrides (Recommended)
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
**Update:** `src/server/server.module.ts`
|
|
189
204
|
|
|
190
|
-
|
|
205
|
+
```typescript
|
|
206
|
+
import { ErrorCodeController } from './modules/error-code/error-code.controller';
|
|
207
|
+
import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
191
208
|
|
|
192
|
-
|
|
209
|
+
@Module({
|
|
210
|
+
imports: [
|
|
211
|
+
CoreModule.forRoot(envConfig, {
|
|
212
|
+
errorCode: {
|
|
213
|
+
controller: ErrorCodeController,
|
|
214
|
+
service: ErrorCodeService,
|
|
215
|
+
},
|
|
216
|
+
}),
|
|
217
|
+
// ... other modules
|
|
218
|
+
],
|
|
219
|
+
})
|
|
220
|
+
export class ServerModule {}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Alternative** (for complex setups): disable auto-registration and import separately:
|
|
193
224
|
|
|
194
225
|
```typescript
|
|
195
226
|
import { ErrorCodeModule } from '@lenne.tech/nest-server';
|
|
@@ -198,8 +229,7 @@ import { ErrorCodeService } from './modules/error-code/error-code.service';
|
|
|
198
229
|
|
|
199
230
|
@Module({
|
|
200
231
|
imports: [
|
|
201
|
-
CoreModule.forRoot(...),
|
|
202
|
-
// Use Core ErrorCodeModule with custom service and controller
|
|
232
|
+
CoreModule.forRoot({ ...envConfig, errorCode: { autoRegister: false } }),
|
|
203
233
|
ErrorCodeModule.forRoot({
|
|
204
234
|
controller: ErrorCodeController,
|
|
205
235
|
service: ErrorCodeService,
|
|
@@ -12,8 +12,10 @@ import { IErrorCodeModuleConfig } from './interfaces/error-code.interfaces';
|
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* ```typescript
|
|
15
|
-
* // Basic usage (auto-register in CoreModule)
|
|
16
|
-
*
|
|
15
|
+
* // Basic usage (auto-register in CoreModule via overrides)
|
|
16
|
+
* CoreModule.forRoot(envConfig, {
|
|
17
|
+
* errorCode: { controller: ErrorCodeController, service: ErrorCodeService },
|
|
18
|
+
* })
|
|
17
19
|
*
|
|
18
20
|
* // Extended usage (with custom error registry - RECOMMENDED)
|
|
19
21
|
* const ProjectErrors = {
|
|
@@ -25,13 +27,6 @@ import { IErrorCodeModuleConfig } from './interfaces/error-code.interfaces';
|
|
|
25
27
|
* } as const satisfies IErrorRegistry;
|
|
26
28
|
*
|
|
27
29
|
* ErrorCodeModule.forRoot({ additionalErrorRegistry: ProjectErrors })
|
|
28
|
-
*
|
|
29
|
-
* // Extended usage (with custom controller and service)
|
|
30
|
-
* ErrorCodeModule.forRoot({
|
|
31
|
-
* additionalErrorRegistry: ProjectErrors,
|
|
32
|
-
* controller: ErrorCodeController,
|
|
33
|
-
* service: ErrorCodeService,
|
|
34
|
-
* })
|
|
35
30
|
* ```
|
|
36
31
|
*/
|
|
37
32
|
@Global()
|
|
@@ -121,7 +121,18 @@ Normal (non-hierarchy) roles also work:
|
|
|
121
121
|
async viewAuditLog(...) { ... }
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
### 8.
|
|
124
|
+
### 8. System Roles as OR Alternatives
|
|
125
|
+
|
|
126
|
+
System roles (`S_EVERYONE`, `S_USER`, `S_VERIFIED`) are checked as OR alternatives **before** real role checks in `CoreTenantGuard`. If a system role grants access, real roles in the same `@Roles()` are alternatives, not requirements.
|
|
127
|
+
|
|
128
|
+
When `X-Tenant-Id` header is present and a system role grants access:
|
|
129
|
+
|
|
130
|
+
- `S_EVERYONE`: public — membership is optionally enriched but never blocks
|
|
131
|
+
- `S_USER`/`S_VERIFIED`: membership is validated (403 if not a member; admin bypass applies)
|
|
132
|
+
|
|
133
|
+
`@SkipTenantCheck()` suppresses membership validation for `S_USER`/`S_VERIFIED` paths — authentication/verification is still enforced, but no membership check runs even with a header present.
|
|
134
|
+
|
|
135
|
+
### 9. Skip Tenant Check for Non-Tenant Endpoints
|
|
125
136
|
|
|
126
137
|
Use `@SkipTenantCheck()` for endpoints that intentionally work without tenant context:
|
|
127
138
|
|
|
@@ -133,7 +144,7 @@ import { SkipTenantCheck, Roles, RoleEnum } from '@lenne.tech/nest-server';
|
|
|
133
144
|
async listMyTenants() { ... }
|
|
134
145
|
```
|
|
135
146
|
|
|
136
|
-
###
|
|
147
|
+
### 10. BetterAuth (IAM) Coexistence
|
|
137
148
|
|
|
138
149
|
When both `multiTenancy` and `betterAuth` are active, IAM endpoints (sign-in, sign-up, session, etc.)
|
|
139
150
|
automatically skip tenant validation when no `X-Tenant-Id` header is sent (`betterAuth.skipTenantCheck: true`, default).
|
|
@@ -138,9 +138,33 @@ Roles not in `roleHierarchy` use exact match — no higher role can compensate:
|
|
|
138
138
|
async auditLog(@CurrentTenant() tenantId: string) { ... }
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
+
### System Roles as OR Alternatives
|
|
142
|
+
|
|
143
|
+
When `@Roles()` includes system roles (`S_EVERYONE`, `S_USER`, `S_VERIFIED`), `CoreTenantGuard` checks them **before** real roles in priority order. If a system role is satisfied, access is granted immediately — real roles in the same decorator are OR alternatives, not additional requirements.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
@Roles(RoleEnum.ADMIN) // Class: admin can always access all methods
|
|
147
|
+
@Controller('users')
|
|
148
|
+
class UserController {
|
|
149
|
+
@Roles(RoleEnum.S_USER) // Method: any authenticated user (extends class ADMIN via OR)
|
|
150
|
+
getProfile() { ... } // → admin OR authenticated user can access
|
|
151
|
+
|
|
152
|
+
@Roles(RoleEnum.S_EVERYONE) // Method: public endpoint (extends class ADMIN via OR)
|
|
153
|
+
getPublicInfo() { ... } // → anyone can access
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Method-level system roles take precedence** for the system role check. Class `@Roles(S_EVERYONE)` does not make a method `@Roles(S_USER)` endpoint public — the method's `S_USER` applies.
|
|
158
|
+
|
|
159
|
+
| System Role | No Header | Header Present |
|
|
160
|
+
| ----------- | --------------------- | -------------------------------------------------- |
|
|
161
|
+
| S_EVERYONE | Pass (public) | Pass + optional tenant context enrichment |
|
|
162
|
+
| S_USER | Pass if authenticated | Pass if authenticated member; admin bypass applies |
|
|
163
|
+
| S_VERIFIED | Pass if verified | Pass if verified member; admin bypass applies |
|
|
164
|
+
|
|
141
165
|
### Tenant Context Rule
|
|
142
166
|
|
|
143
|
-
**When a tenant header is present:**
|
|
167
|
+
**When a tenant header is present:** For real role checks, only `membership.role` is checked. `user.roles` is ignored (except ADMIN bypass). For system role checks (`S_USER`, `S_VERIFIED`), membership is validated to set tenant context (`tenantId`, `tenantRole`), but access depends on membership existence, not role level.
|
|
144
168
|
|
|
145
169
|
**When no tenant header:** `user.roles` is checked instead. Hierarchy roles use level comparison, normal roles use exact match.
|
|
146
170
|
|
|
@@ -161,6 +185,7 @@ async listMyTenants() { ... }
|
|
|
161
185
|
```
|
|
162
186
|
|
|
163
187
|
Note: `@SkipTenantCheck()` with hierarchy roles still checks `user.roles` (no tenant context).
|
|
188
|
+
It also suppresses tenant membership validation for `S_USER`/`S_VERIFIED` system role paths — when set, these roles still enforce authentication/verification but skip the membership check even when a tenant header is present.
|
|
164
189
|
|
|
165
190
|
### Admin Bypass
|
|
166
191
|
|