@flusys/nestjs-auth 0.1.0-beta.3 → 1.0.0-rc
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/README.md +274 -301
- package/cjs/config/auth.constants.js +22 -4
- package/cjs/controllers/authentication.controller.js +222 -38
- package/cjs/controllers/branch.controller.js +46 -4
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +46 -4
- package/cjs/controllers/user-permission.controller.js +40 -37
- package/cjs/controllers/user.controller.js +222 -23
- package/cjs/dtos/authentication.dto.js +119 -13
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/guards/auth-throttler.guard.js +53 -0
- package/cjs/interceptors/clear-token.interceptor.js +3 -6
- package/cjs/interceptors/set-token.interceptor.js +38 -31
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/authentication.interface.js +0 -3
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +8 -20
- package/cjs/services/auth-config.service.js +42 -47
- package/cjs/services/auth-datasource.provider.js +18 -34
- package/cjs/services/auth-email.service.js +471 -0
- package/cjs/services/authentication.service.js +240 -153
- package/cjs/services/branch.service.js +15 -48
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/company.service.js +7 -36
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +9 -29
- package/cjs/services/user.service.js +228 -107
- package/cjs/strategies/jwt.strategy.js +14 -1
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +5 -0
- package/controllers/authentication.controller.d.ts +13 -9
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user.controller.d.ts +33 -0
- package/dtos/authentication.dto.d.ts +21 -3
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -2
- package/entities/index.d.ts +7 -3
- package/entities/pending-registration.entity.d.ts +17 -0
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +11 -8
- package/fesm/controllers/authentication.controller.js +224 -40
- package/fesm/controllers/branch.controller.js +49 -7
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +49 -7
- package/fesm/controllers/user-permission.controller.js +41 -38
- package/fesm/controllers/user.controller.js +225 -26
- package/fesm/dtos/authentication.dto.js +123 -111
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/guards/auth-throttler.guard.js +43 -0
- package/fesm/interceptors/clear-token.interceptor.js +2 -5
- package/fesm/interceptors/set-token.interceptor.js +37 -30
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -7
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +10 -22
- package/fesm/services/auth-config.service.js +42 -47
- package/fesm/services/auth-datasource.provider.js +19 -35
- package/fesm/services/auth-email.service.js +420 -0
- package/fesm/services/authentication.service.js +238 -151
- package/fesm/services/branch.service.js +16 -49
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/company.service.js +8 -37
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +10 -30
- package/fesm/services/user.service.js +229 -108
- package/fesm/strategies/jwt.strategy.js +14 -1
- package/fesm/validators/password.validator.js +48 -0
- package/guards/auth-throttler.guard.d.ts +7 -0
- package/interceptors/set-token.interceptor.d.ts +1 -0
- package/interfaces/auth-email-provider.interface.d.ts +6 -0
- package/interfaces/auth-module-options.interface.d.ts +4 -1
- package/interfaces/authentication.interface.d.ts +14 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/user-enricher.interface.d.ts +64 -0
- package/interfaces/user.interface.d.ts +1 -0
- package/modules/auth.module.d.ts +0 -1
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +5 -1
- package/services/auth-email.service.d.ts +51 -0
- package/services/authentication.service.d.ts +21 -12
- package/services/branch.service.d.ts +2 -5
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/company.service.d.ts +1 -4
- package/services/index.d.ts +1 -0
- package/services/user-permission.service.d.ts +0 -1
- package/services/user.service.d.ts +20 -6
- package/validators/password.validator.d.ts +10 -0
package/README.md
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
> **Package:** `@flusys/nestjs-auth`
|
|
4
4
|
> **Type:** Authentication system with JWT, multi-tenant, and company/branch support
|
|
5
5
|
|
|
6
|
-
This comprehensive guide covers the authentication package - flexible auth system for NestJS applications.
|
|
7
|
-
|
|
8
6
|
## Table of Contents
|
|
9
7
|
|
|
10
8
|
- [Installation](#installation)
|
|
11
9
|
- [Quick Start](#quick-start)
|
|
12
10
|
- [Configuration](#configuration)
|
|
11
|
+
- [Constants](#constants)
|
|
13
12
|
- [API Endpoints](#api-endpoints)
|
|
14
13
|
- [Architecture](#architecture)
|
|
15
14
|
- [Login Flows](#login-flows)
|
|
@@ -17,7 +16,6 @@ This comprehensive guide covers the authentication package - flexible auth syste
|
|
|
17
16
|
- [Environment Variables](#environment-variables)
|
|
18
17
|
- [Migrations](#migrations)
|
|
19
18
|
- [Configuration Modes](#configuration-modes)
|
|
20
|
-
- [IAM Integration](#iam-integration)
|
|
21
19
|
|
|
22
20
|
---
|
|
23
21
|
|
|
@@ -40,7 +38,6 @@ import { AuthModule } from '@flusys/nestjs-auth';
|
|
|
40
38
|
AuthModule.forRoot({
|
|
41
39
|
global: true,
|
|
42
40
|
includeController: true,
|
|
43
|
-
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
44
41
|
bootstrapAppConfig: {
|
|
45
42
|
databaseMode: 'single',
|
|
46
43
|
enableCompanyFeature: false,
|
|
@@ -69,10 +66,9 @@ export class AppModule {}
|
|
|
69
66
|
AuthModule.forRoot({
|
|
70
67
|
global: true,
|
|
71
68
|
includeController: true,
|
|
72
|
-
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
73
69
|
bootstrapAppConfig: {
|
|
74
70
|
databaseMode: 'single',
|
|
75
|
-
enableCompanyFeature: true,
|
|
71
|
+
enableCompanyFeature: true,
|
|
76
72
|
},
|
|
77
73
|
config: {
|
|
78
74
|
defaultDatabaseConfig: {
|
|
@@ -97,7 +93,6 @@ AuthModule.forRoot({
|
|
|
97
93
|
AuthModule.forRoot({
|
|
98
94
|
global: true,
|
|
99
95
|
includeController: true,
|
|
100
|
-
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
101
96
|
bootstrapAppConfig: {
|
|
102
97
|
databaseMode: 'multi-tenant',
|
|
103
98
|
enableCompanyFeature: true,
|
|
@@ -125,13 +120,12 @@ AuthModule.forRoot({
|
|
|
125
120
|
AuthModule.forRootAsync({
|
|
126
121
|
global: true,
|
|
127
122
|
includeController: true,
|
|
128
|
-
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
129
123
|
bootstrapAppConfig: {
|
|
130
124
|
databaseMode: 'single',
|
|
131
125
|
enableCompanyFeature: false,
|
|
132
126
|
},
|
|
133
127
|
imports: [HttpModule],
|
|
134
|
-
useClass: AuthConfigFactory,
|
|
128
|
+
useClass: AuthConfigFactory,
|
|
135
129
|
})
|
|
136
130
|
```
|
|
137
131
|
|
|
@@ -143,29 +137,45 @@ AuthModule.forRootAsync({
|
|
|
143
137
|
interface AuthModuleOptions {
|
|
144
138
|
global?: boolean; // Make module global
|
|
145
139
|
includeController?: boolean; // Include default controllers
|
|
146
|
-
|
|
147
|
-
bootstrapAppConfig: {
|
|
140
|
+
bootstrapAppConfig?: {
|
|
148
141
|
databaseMode: 'single' | 'multi-tenant';
|
|
149
|
-
enableCompanyFeature: boolean;
|
|
142
|
+
enableCompanyFeature: boolean;
|
|
150
143
|
};
|
|
151
|
-
config
|
|
144
|
+
config?: {
|
|
152
145
|
defaultDatabaseConfig?: IDatabaseConfig;
|
|
153
146
|
tenantDefaultDatabaseConfig?: IDatabaseConfig;
|
|
154
147
|
tenants?: ITenantDatabaseConfig[];
|
|
155
|
-
jwtSecret
|
|
148
|
+
jwtSecret?: string;
|
|
156
149
|
jwtExpiration?: string;
|
|
157
150
|
refreshTokenSecret?: string;
|
|
158
151
|
refreshTokenExpiration?: string;
|
|
159
152
|
refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
|
|
153
|
+
frontendUrl?: string; // For email links
|
|
160
154
|
};
|
|
155
|
+
providers?: Provider[]; // Additional providers (e.g., AUTH_EMAIL_PROVIDER)
|
|
161
156
|
}
|
|
162
157
|
```
|
|
163
158
|
|
|
164
|
-
|
|
159
|
+
## Constants
|
|
160
|
+
|
|
161
|
+
Centralized constants in `auth.constants.ts`:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Injection Tokens
|
|
165
|
+
export const AUTH_MODULE_OPTIONS = 'AUTH_MODULE_OPTIONS';
|
|
166
|
+
export const USER_ENRICHER = 'USER_ENRICHER';
|
|
167
|
+
|
|
168
|
+
// Security
|
|
169
|
+
export const BCRYPT_SALT_ROUNDS = 12;
|
|
165
170
|
|
|
166
|
-
|
|
171
|
+
// Cookie
|
|
172
|
+
export const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
173
|
+
export const MAX_COOKIE_BYTES = 3500;
|
|
167
174
|
|
|
168
|
-
|
|
175
|
+
// Cache Key Prefixes
|
|
176
|
+
export const SESSION_CACHE_PREFIX = 'auth:session';
|
|
177
|
+
export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
|
|
178
|
+
```
|
|
169
179
|
|
|
170
180
|
## API Endpoints
|
|
171
181
|
|
|
@@ -180,13 +190,20 @@ interface AuthModuleOptions {
|
|
|
180
190
|
| `/auth/change-password` | POST | Change user password |
|
|
181
191
|
| `/auth/me` | GET | Get current user info |
|
|
182
192
|
|
|
183
|
-
|
|
193
|
+
### Email Authentication Endpoints
|
|
194
|
+
|
|
195
|
+
| Endpoint | Method | Description |
|
|
196
|
+
|----------|--------|-------------|
|
|
197
|
+
| `/auth/forgot-password` | POST | Request password reset email |
|
|
198
|
+
| `/auth/reset-password` | POST | Reset password using token |
|
|
199
|
+
| `/auth/verify-email` | POST | Verify email using token |
|
|
200
|
+
| `/auth/resend-verification` | POST | Resend email verification link |
|
|
184
201
|
|
|
185
|
-
**
|
|
202
|
+
> **Note:** Email endpoints require `AuthEmailService` to be configured via the `providers` option.
|
|
186
203
|
|
|
187
|
-
|
|
204
|
+
#### GET /auth/me - Get Current User Info
|
|
188
205
|
|
|
189
|
-
**
|
|
206
|
+
**Authentication:** Requires valid access token (Bearer token)
|
|
190
207
|
|
|
191
208
|
When `enableCompanyFeature: false`:
|
|
192
209
|
```json
|
|
@@ -203,12 +220,7 @@ When `enableCompanyFeature: false`:
|
|
|
203
220
|
When `enableCompanyFeature: true`:
|
|
204
221
|
```json
|
|
205
222
|
{
|
|
206
|
-
"user": {
|
|
207
|
-
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
208
|
-
"email": "john@example.com",
|
|
209
|
-
"name": "John Doe",
|
|
210
|
-
"profilePictureId": null
|
|
211
|
-
},
|
|
223
|
+
"user": { ... },
|
|
212
224
|
"company": {
|
|
213
225
|
"id": "550e8400-e29b-41d4-a716-446655440001",
|
|
214
226
|
"name": "Acme Corporation",
|
|
@@ -224,14 +236,6 @@ When `enableCompanyFeature: true`:
|
|
|
224
236
|
}
|
|
225
237
|
```
|
|
226
238
|
|
|
227
|
-
**Key Characteristics:**
|
|
228
|
-
- Uses HTTP GET (not POST like other auth endpoints)
|
|
229
|
-
- Returns only user context data (no tokens)
|
|
230
|
-
- `company` and `branch` fields are automatically excluded from Swagger docs when company feature is disabled
|
|
231
|
-
- Used for session restoration on page refresh (frontend calls this after token refresh)
|
|
232
|
-
|
|
233
|
-
**Related Service:** `AuthenticationService.getUserInfo()` in `authentication.service.ts`
|
|
234
|
-
|
|
235
239
|
### Company Selection Endpoints (when company feature enabled)
|
|
236
240
|
|
|
237
241
|
| Endpoint | Method | Description |
|
|
@@ -241,17 +245,11 @@ When `enableCompanyFeature: true`:
|
|
|
241
245
|
| `/auth/companies` | GET | Get user's available companies |
|
|
242
246
|
| `/auth/companies/:companyId/branches` | GET | Get branches for a company |
|
|
243
247
|
|
|
244
|
-
> **Note:** These endpoints are automatically hidden from Swagger when `enableCompanyFeature: false`
|
|
245
|
-
|
|
246
248
|
### Swagger Schema Behavior
|
|
247
249
|
|
|
248
250
|
When `enableCompanyFeature: false`, the following are automatically hidden from Swagger:
|
|
249
251
|
|
|
250
|
-
**Excluded Tags:**
|
|
251
|
-
- Companies
|
|
252
|
-
- Branches
|
|
253
|
-
- User Permissions (NEW - v4.1.3+)
|
|
254
|
-
- Company Selection
|
|
252
|
+
**Excluded Tags:** Companies, Branches, User Permissions, Company Selection
|
|
255
253
|
|
|
256
254
|
**Excluded DTO Properties:**
|
|
257
255
|
|
|
@@ -262,23 +260,23 @@ When `enableCompanyFeature: false`, the following are automatically hidden from
|
|
|
262
260
|
| `LoginResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
|
|
263
261
|
| `MeResponseDto` | `company`, `branch` |
|
|
264
262
|
|
|
265
|
-
### User Management Endpoints
|
|
263
|
+
### User Management Endpoints
|
|
266
264
|
|
|
267
265
|
| Endpoint | Method | Description |
|
|
268
266
|
|----------|--------|-------------|
|
|
269
|
-
| `/users/insert` | POST | Create user |
|
|
270
|
-
| `/users/insert-many` | POST | Bulk create users |
|
|
271
|
-
| `/users/get/:id` | POST | Get user by ID |
|
|
272
|
-
| `/users/get-all` | POST | Get paginated user list |
|
|
273
|
-
| `/users/update` | POST | Update user |
|
|
274
|
-
| `/users/update-many` | POST | Bulk update users |
|
|
275
|
-
| `/users/delete` | POST | Delete/restore/permanent delete |
|
|
276
|
-
| `/users/profile` | POST | Update own profile |
|
|
277
|
-
| `/users/:id/verify-email` | POST | Mark email as verified |
|
|
278
|
-
| `/users/:id/verify-phone` | POST | Mark phone as verified |
|
|
279
|
-
| `/users/:id/status` | PUT | Update user active status |
|
|
280
|
-
|
|
281
|
-
### User Permission Endpoints (
|
|
267
|
+
| `/administration/users/insert` | POST | Create user |
|
|
268
|
+
| `/administration/users/insert-many` | POST | Bulk create users |
|
|
269
|
+
| `/administration/users/get/:id` | POST | Get user by ID |
|
|
270
|
+
| `/administration/users/get-all` | POST | Get paginated user list |
|
|
271
|
+
| `/administration/users/update` | POST | Update user |
|
|
272
|
+
| `/administration/users/update-many` | POST | Bulk update users |
|
|
273
|
+
| `/administration/users/delete` | POST | Delete/restore/permanent delete |
|
|
274
|
+
| `/administration/users/profile` | POST | Update own profile |
|
|
275
|
+
| `/administration/users/:id/verify-email` | POST | Mark email as verified |
|
|
276
|
+
| `/administration/users/:id/verify-phone` | POST | Mark phone as verified |
|
|
277
|
+
| `/administration/users/:id/status` | PUT | Update user active status |
|
|
278
|
+
|
|
279
|
+
### User Permission Endpoints (when company feature enabled)
|
|
282
280
|
|
|
283
281
|
| Endpoint | Method | Description |
|
|
284
282
|
|----------|--------|-------------|
|
|
@@ -299,42 +297,12 @@ POST /administration/permissions/user-company/assign
|
|
|
299
297
|
}
|
|
300
298
|
```
|
|
301
299
|
|
|
302
|
-
|
|
303
|
-
```json
|
|
304
|
-
POST /administration/permissions/user-branch/assign
|
|
305
|
-
{
|
|
306
|
-
"userId": "user-123",
|
|
307
|
-
"items": [
|
|
308
|
-
{ "targetId": "branch-5", "isAdd": false }
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
> **Note:** These endpoints are only available when `enableCompanyFeature: true`
|
|
300
|
+
### Company/Branch Management Endpoints (when company feature enabled)
|
|
314
301
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
|
318
|
-
|
|
319
|
-
| `/company/insert` | POST | Create company |
|
|
320
|
-
| `/company/insert-many` | POST | Bulk create companies |
|
|
321
|
-
| `/company/get/:id` | POST | Get company by ID |
|
|
322
|
-
| `/company/get-all` | POST | Get paginated company list |
|
|
323
|
-
| `/company/update` | POST | Update company |
|
|
324
|
-
| `/company/update-many` | POST | Bulk update companies |
|
|
325
|
-
| `/company/delete` | POST | Delete/restore company |
|
|
326
|
-
|
|
327
|
-
### Branch Management Endpoints (`/administration/branch`) - When `enableCompanyFeature: true`
|
|
328
|
-
|
|
329
|
-
| Endpoint | Method | Description |
|
|
330
|
-
|----------|--------|-------------|
|
|
331
|
-
| `/branch/insert` | POST | Create branch |
|
|
332
|
-
| `/branch/insert-many` | POST | Bulk create branches |
|
|
333
|
-
| `/branch/get/:id` | POST | Get branch by ID |
|
|
334
|
-
| `/branch/get-all` | POST | Get paginated branch list (auto-filtered by company) |
|
|
335
|
-
| `/branch/update` | POST | Update branch |
|
|
336
|
-
| `/branch/update-many` | POST | Bulk update branches |
|
|
337
|
-
| `/branch/delete` | POST | Delete/restore branch |
|
|
302
|
+
| Resource | Endpoints |
|
|
303
|
+
|----------|-----------|
|
|
304
|
+
| Companies | `/administration/company` - CRUD operations |
|
|
305
|
+
| Branches | `/administration/branch` - CRUD operations |
|
|
338
306
|
|
|
339
307
|
## Architecture
|
|
340
308
|
|
|
@@ -354,99 +322,79 @@ POST /administration/permissions/user-branch/assign
|
|
|
354
322
|
```
|
|
355
323
|
|
|
356
324
|
**Key Points:**
|
|
357
|
-
-
|
|
358
|
-
-
|
|
359
|
-
-
|
|
360
|
-
-
|
|
361
|
-
-
|
|
362
|
-
|
|
363
|
-
**Result:** IAM and Storage modules only depend on Shared, not Auth.
|
|
325
|
+
- JwtAuthGuard in shared → All modules can use without depending on Auth
|
|
326
|
+
- JwtStrategy in auth → Registers JWT validation with Passport
|
|
327
|
+
- AuthConfigService in auth → Provides JWT settings
|
|
328
|
+
- Token generation handled by AuthenticationService
|
|
329
|
+
- Token validation uses JwtStrategy registered by Auth module
|
|
364
330
|
|
|
365
331
|
### JwtStrategy Architecture
|
|
366
332
|
|
|
367
|
-
The `JwtStrategy` is a
|
|
333
|
+
The `JwtStrategy` is a singleton but needs to access the request-scoped `AuthDataSourceProvider`. This is solved using `ModuleRef`:
|
|
368
334
|
|
|
369
335
|
```typescript
|
|
370
336
|
@Injectable()
|
|
371
337
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|
372
338
|
constructor(
|
|
373
339
|
private readonly authConfig: AuthConfigService,
|
|
374
|
-
private readonly moduleRef: ModuleRef,
|
|
340
|
+
private readonly moduleRef: ModuleRef,
|
|
375
341
|
) {
|
|
376
342
|
super({
|
|
377
343
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
378
344
|
secretOrKey: authConfig.getJwtSecret(),
|
|
379
|
-
passReqToCallback: true,
|
|
345
|
+
passReqToCallback: true,
|
|
380
346
|
});
|
|
381
347
|
}
|
|
382
348
|
|
|
383
349
|
async validate(_req: Request, payload: TokenPayload) {
|
|
384
|
-
// Resolve request-scoped AuthDataSourceProvider per request
|
|
385
350
|
const dataSourceProvider = await this.moduleRef.resolve(
|
|
386
351
|
AuthDataSourceProvider,
|
|
387
352
|
undefined,
|
|
388
353
|
{ strict: false },
|
|
389
354
|
);
|
|
390
|
-
|
|
391
355
|
const userRepository = await dataSourceProvider.getRepository(User);
|
|
392
356
|
// ... validate user
|
|
393
357
|
}
|
|
394
358
|
}
|
|
395
359
|
```
|
|
396
360
|
|
|
397
|
-
**Why this pattern:**
|
|
398
|
-
- ✅ Works for both **single-tenant** and **multi-tenant** modes
|
|
399
|
-
- ✅ `AuthDataSourceProvider` handles tenant detection from request headers
|
|
400
|
-
- ✅ Connection pools are cached internally (no reconnect per request)
|
|
401
|
-
- ✅ Singleton strategy + request-scoped provider = best of both worlds
|
|
402
|
-
|
|
403
361
|
### Package Structure
|
|
404
362
|
|
|
405
363
|
```
|
|
406
364
|
┌─────────────────────────────────────────────────────────┐
|
|
407
|
-
│ @flusys/nestjs-auth
|
|
408
|
-
│
|
|
409
|
-
│ ┌─────────────────────────────────────────────────┐
|
|
410
|
-
│ │ CORE AUTH (Always Loaded) │
|
|
411
|
-
│ │ │
|
|
412
|
-
│ │
|
|
413
|
-
│ │ │
|
|
414
|
-
│ │
|
|
415
|
-
│ │
|
|
416
|
-
│ │
|
|
417
|
-
│
|
|
418
|
-
│ │
|
|
419
|
-
│ │
|
|
420
|
-
│
|
|
421
|
-
│
|
|
422
|
-
│
|
|
423
|
-
│ │
|
|
424
|
-
│ │
|
|
425
|
-
│ │
|
|
426
|
-
│ │
|
|
427
|
-
│ │ │ │ │ │ │Permission │ │ │
|
|
428
|
-
│ │ └─────────┘ └──────────────┘ └────────────┘ │ │
|
|
429
|
-
│ │ │ │
|
|
430
|
-
│ │ • Multi-company support │ │
|
|
431
|
-
│ │ • Hierarchical branches │ │
|
|
432
|
-
│ │ • User-company-branch assignments │ │
|
|
433
|
-
│ │ • Company-scoped permissions │ │
|
|
434
|
-
│ │ • Enhanced login with selection │ │
|
|
435
|
-
│ └─────────────────────────────────────────────────┘ │
|
|
365
|
+
│ @flusys/nestjs-auth │
|
|
366
|
+
│ │
|
|
367
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
368
|
+
│ │ CORE AUTH (Always Loaded) │ │
|
|
369
|
+
│ │ │ │
|
|
370
|
+
│ │ • User login/registration │ │
|
|
371
|
+
│ │ • Password hashing (bcrypt) │ │
|
|
372
|
+
│ │ • JWT token generation │ │
|
|
373
|
+
│ │ • JwtStrategy (Passport integration) │ │
|
|
374
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
375
|
+
│ │
|
|
376
|
+
│ ┌─────────────────────────────────────────────────┐ │
|
|
377
|
+
│ │ COMPANY FEATURE (Optional - Flag Enabled) │ │
|
|
378
|
+
│ │ │ │
|
|
379
|
+
│ │ • Multi-company support │ │
|
|
380
|
+
│ │ • Hierarchical branches │ │
|
|
381
|
+
│ │ • User-company-branch assignments │ │
|
|
382
|
+
│ │ • Company-scoped permissions │ │
|
|
383
|
+
│ │ • Enhanced login with selection │ │
|
|
384
|
+
│ └─────────────────────────────────────────────────┘ │
|
|
436
385
|
└─────────────────────────────────────────────────────────┘
|
|
437
386
|
```
|
|
438
387
|
|
|
439
388
|
### Entity Relationships
|
|
440
389
|
|
|
441
|
-
#### Without Company Feature
|
|
390
|
+
#### Without Company Feature
|
|
442
391
|
|
|
443
392
|
```
|
|
444
393
|
┌──────────────┐
|
|
445
394
|
│ User │
|
|
446
395
|
├──────────────┤
|
|
447
|
-
│ id
|
|
448
|
-
│
|
|
449
|
-
│ email │◄────── Authentication
|
|
396
|
+
│ id, name │
|
|
397
|
+
│ email │◄── Authentication
|
|
450
398
|
│ password │
|
|
451
399
|
│ phone │
|
|
452
400
|
└──────────────┘
|
|
@@ -458,29 +406,17 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|
|
458
406
|
┌──────────────┐ ┌───────────────────┐ ┌──────────────────┐
|
|
459
407
|
│ User │ │ UserCompany │ │ Company │
|
|
460
408
|
├──────────────┤ │ Permission │ ├──────────────────┤
|
|
461
|
-
│ id │◄──────┤ userId │ │ id
|
|
462
|
-
│ name
|
|
463
|
-
│
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
│ id │
|
|
473
|
-
│ name, companyId │
|
|
474
|
-
│ parentId ◄──┐ │
|
|
475
|
-
│ isActive │ │
|
|
476
|
-
└─────────────┴────┘
|
|
477
|
-
Hierarchical
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
**Polymorphic Permission Design:**
|
|
481
|
-
- `permissionType`: Enum ('company' | 'branch') determines target type
|
|
482
|
-
- `targetId`: UUID of the company OR branch (single field, not both)
|
|
483
|
-
- One table handles both company and branch permissions
|
|
409
|
+
│ id │◄──────┤ userId │ │ id, name, slug │
|
|
410
|
+
│ name, email │ │ permissionType │──────►│ isActive │
|
|
411
|
+
│ password │ │ targetId (UUID) │ └──────────────────┘
|
|
412
|
+
└──────────────┘ │ isActive │ │
|
|
413
|
+
└───────────────────┘ ▼
|
|
414
|
+
│ ┌──────────────────┐
|
|
415
|
+
permissionType: 'company'|'branch' │ CompanyBranch │
|
|
416
|
+
targetId: company.id OR branch.id │ id, name, companyId │
|
|
417
|
+
└──────────────►│ parentId (hierarchical) │
|
|
418
|
+
└──────────────────┘
|
|
419
|
+
```
|
|
484
420
|
|
|
485
421
|
### Services
|
|
486
422
|
|
|
@@ -493,6 +429,8 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|
|
493
429
|
| `UserPermissionService` | REQUEST | User-company/branch permission management |
|
|
494
430
|
| `AuthDataSourceProvider` | REQUEST | Dynamic datasource for single/multi-tenant |
|
|
495
431
|
| `AuthConfigService` | SINGLETON | JWT and module configuration access |
|
|
432
|
+
| `CompanySelectionSessionService` | SINGLETON | Session management for company selection |
|
|
433
|
+
| `AuthEmailService` | SINGLETON | Email verification and password reset |
|
|
496
434
|
|
|
497
435
|
## Login Flows
|
|
498
436
|
|
|
@@ -537,96 +475,112 @@ POST /auth/login { email, password }
|
|
|
537
475
|
POST /auth/login { email, password }
|
|
538
476
|
│
|
|
539
477
|
▼
|
|
540
|
-
┌─────────────────────────┐
|
|
541
|
-
│ AuthenticationService │
|
|
542
|
-
│ .login() │
|
|
543
|
-
└────────────┬────────────┘
|
|
544
|
-
│
|
|
545
|
-
│ Found: Multiple companies/branches
|
|
546
|
-
▼
|
|
547
478
|
{ requiresSelection: true, sessionId, companies: [...] }
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
479
|
+
│
|
|
480
|
+
│ User selects company & branch
|
|
481
|
+
▼
|
|
551
482
|
POST /auth/select { sessionId, companyId, branchId }
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
┌─────────────────────────┐
|
|
555
|
-
│ AuthenticationService │
|
|
556
|
-
│ .select() │
|
|
557
|
-
└────────────┬────────────┘
|
|
558
|
-
│
|
|
559
|
-
│ 1. Validate access via UserCompanyPermission
|
|
560
|
-
│ 2. Generate token with company context
|
|
561
|
-
▼
|
|
483
|
+
│
|
|
484
|
+
▼
|
|
562
485
|
{ accessToken, refreshToken, user, company, branch }
|
|
563
486
|
```
|
|
564
487
|
|
|
565
488
|
## User Management
|
|
566
489
|
|
|
567
|
-
###
|
|
490
|
+
### Company Permission Assignment Flow
|
|
491
|
+
|
|
492
|
+
When creating users with `enableCompanyFeature: true`, the `UserService` automatically assigns company and branch permissions:
|
|
568
493
|
|
|
569
|
-
|
|
494
|
+
**Flow Diagram:**
|
|
495
|
+
|
|
496
|
+
```
|
|
497
|
+
User Creation (with companyId/branchId)
|
|
498
|
+
│
|
|
499
|
+
▼
|
|
500
|
+
┌─────────────────────────────────────────────────────────┐
|
|
501
|
+
│ 1. Insert user record │
|
|
502
|
+
└─────────────────────────────────────────────────────────┘
|
|
503
|
+
│
|
|
504
|
+
▼
|
|
505
|
+
┌─────────────────────────────────────────────────────────┐
|
|
506
|
+
│ 2. Check if company permission exists │
|
|
507
|
+
│ - Lookup UserCompanyPermission for user + company │
|
|
508
|
+
└─────────────────────────────────────────────────────────┘
|
|
509
|
+
│
|
|
510
|
+
exists? ─┴── no ──┐
|
|
511
|
+
│ │
|
|
512
|
+
▼ ▼
|
|
513
|
+
[Skip] ┌─────────────────────────────────────┐
|
|
514
|
+
│ 3. Create company permission record │
|
|
515
|
+
│ { userId, targetId: companyId, │
|
|
516
|
+
│ permissionType: 'company' } │
|
|
517
|
+
└─────────────────────────────────────┘
|
|
518
|
+
│
|
|
519
|
+
▼
|
|
520
|
+
┌─────────────────────────────────────────────────────────┐
|
|
521
|
+
│ 4. Check if branch permission exists │
|
|
522
|
+
│ - Lookup UserCompanyPermission for user + branch │
|
|
523
|
+
└─────────────────────────────────────────────────────────┘
|
|
524
|
+
│
|
|
525
|
+
exists? ─┴── no ──┐
|
|
526
|
+
│ │
|
|
527
|
+
▼ ▼
|
|
528
|
+
[Skip] ┌─────────────────────────────────────┐
|
|
529
|
+
│ 5. Create branch permission record │
|
|
530
|
+
│ { userId, targetId: branchId, │
|
|
531
|
+
│ permissionType: 'branch' } │
|
|
532
|
+
└─────────────────────────────────────┘
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Code Example:**
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
// When inserting a user with company feature enabled
|
|
539
|
+
await userService.insert({
|
|
540
|
+
name: 'John Doe',
|
|
541
|
+
email: 'john@example.com',
|
|
542
|
+
password: 'securePassword123',
|
|
543
|
+
companyId: 'company-uuid',
|
|
544
|
+
branchId: 'branch-uuid',
|
|
545
|
+
});
|
|
546
|
+
// UserService automatically:
|
|
547
|
+
// 1. Creates user record
|
|
548
|
+
// 2. Creates UserCompanyPermission with targetId=companyId
|
|
549
|
+
// 3. Creates UserCompanyPermission with targetId=branchId
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Key Points:**
|
|
553
|
+
- Permissions are created idempotently (won't duplicate if exists)
|
|
554
|
+
- Both company and branch permissions are required for user login flow
|
|
555
|
+
- Branch permission uses `permissionType: 'branch'` to distinguish from company
|
|
556
|
+
- This logic runs within a transaction for consistency
|
|
557
|
+
|
|
558
|
+
### User Active Status
|
|
570
559
|
|
|
571
560
|
**When company feature is disabled:**
|
|
572
561
|
- Updates `User.isActive` directly
|
|
573
|
-
- Simple on/off for the user globally
|
|
574
562
|
|
|
575
563
|
**When company feature is enabled:**
|
|
576
564
|
- Updates `UserCompanyPermission.isActive` for the logged user's company
|
|
577
565
|
- User can be active in Company A but inactive in Company B
|
|
578
|
-
- `getAll` users filters by company permissions
|
|
579
|
-
- `isActive` in responses reflects company permission status
|
|
580
566
|
|
|
581
567
|
### Registration Flow
|
|
582
568
|
|
|
583
569
|
**Simple registration:**
|
|
584
570
|
```json
|
|
585
|
-
{
|
|
586
|
-
"name": "John Doe",
|
|
587
|
-
"email": "john@example.com",
|
|
588
|
-
"password": "password123"
|
|
589
|
-
}
|
|
571
|
+
{ "name": "John Doe", "email": "john@example.com", "password": "password123" }
|
|
590
572
|
```
|
|
591
573
|
|
|
592
574
|
**Join existing company:**
|
|
593
575
|
```json
|
|
594
|
-
{
|
|
595
|
-
"
|
|
596
|
-
"email": "john@example.com",
|
|
597
|
-
"password": "password123",
|
|
598
|
-
"companySlug": "acme-corp",
|
|
599
|
-
"branchSlug": "main-branch"
|
|
600
|
-
}
|
|
576
|
+
{ "name": "John Doe", "email": "john@example.com", "password": "password123",
|
|
577
|
+
"companySlug": "acme-corp", "branchSlug": "main-branch" }
|
|
601
578
|
```
|
|
602
579
|
|
|
603
580
|
**Create new company:**
|
|
604
581
|
```json
|
|
605
|
-
{
|
|
606
|
-
"
|
|
607
|
-
"email": "john@example.com",
|
|
608
|
-
"password": "password123",
|
|
609
|
-
"newCompanyName": "My Company",
|
|
610
|
-
"newBranchName": "Headquarters"
|
|
611
|
-
}
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
### Hierarchical Branch Structure
|
|
615
|
-
|
|
616
|
-
```
|
|
617
|
-
Company: "Acme Corporation"
|
|
618
|
-
│
|
|
619
|
-
├── Main Office (Branch)
|
|
620
|
-
│ ├── Sales Department (Sub-Branch)
|
|
621
|
-
│ │ ├── Inside Sales (Sub-Sub-Branch)
|
|
622
|
-
│ │ └── Outside Sales
|
|
623
|
-
│ └── Support Department
|
|
624
|
-
│ ├── Tier 1 Support
|
|
625
|
-
│ └── Tier 2 Support
|
|
626
|
-
│
|
|
627
|
-
└── Regional Office (Branch)
|
|
628
|
-
├── East Branch
|
|
629
|
-
└── West Branch
|
|
582
|
+
{ "name": "John Doe", "email": "john@example.com", "password": "password123",
|
|
583
|
+
"newCompanyName": "My Company", "newBranchName": "Headquarters" }
|
|
630
584
|
```
|
|
631
585
|
|
|
632
586
|
## Environment Variables
|
|
@@ -645,22 +599,18 @@ JWT_EXPIRATION=1h
|
|
|
645
599
|
REFRESH_TOKEN_SECRET=your-refresh-secret
|
|
646
600
|
REFRESH_TOKEN_EXPIRATION=7d
|
|
647
601
|
REFRESH_TOKEN_COOKIE_NAME=fsn_refresh_token
|
|
602
|
+
|
|
603
|
+
# Frontend (for email links)
|
|
604
|
+
FRONTEND_URL=http://localhost:2001
|
|
648
605
|
```
|
|
649
606
|
|
|
650
607
|
## Migrations
|
|
651
608
|
|
|
652
609
|
See [MIGRATION.md](./MIGRATION.md) for detailed migration commands.
|
|
653
610
|
|
|
654
|
-
### Quick Commands
|
|
655
|
-
|
|
656
611
|
```bash
|
|
657
|
-
# Generate migration
|
|
658
612
|
npm run migration:generate --name=InitialSetup
|
|
659
|
-
|
|
660
|
-
# Run migrations
|
|
661
613
|
npm run migration:run
|
|
662
|
-
|
|
663
|
-
# Check status
|
|
664
614
|
npm run migration:status
|
|
665
615
|
```
|
|
666
616
|
|
|
@@ -670,107 +620,130 @@ npm run migration:status
|
|
|
670
620
|
|
|
671
621
|
```typescript
|
|
672
622
|
AuthModule.forRoot({
|
|
673
|
-
bootstrapAppConfig: {
|
|
674
|
-
enableCompanyFeature: false,
|
|
675
|
-
},
|
|
623
|
+
bootstrapAppConfig: { enableCompanyFeature: false },
|
|
676
624
|
})
|
|
677
625
|
|
|
678
626
|
Entities: User
|
|
679
|
-
Tables: 1
|
|
680
627
|
Features: Basic login, registration
|
|
681
|
-
Size: Minimal
|
|
682
|
-
Performance: Fast
|
|
683
628
|
```
|
|
684
629
|
|
|
685
630
|
### Mode 2: Full Featured (With Companies)
|
|
686
631
|
|
|
687
632
|
```typescript
|
|
688
633
|
AuthModule.forRoot({
|
|
689
|
-
|
|
690
|
-
bootstrapAppConfig: {
|
|
691
|
-
enableCompanyFeature: true,
|
|
692
|
-
},
|
|
634
|
+
bootstrapAppConfig: { enableCompanyFeature: true },
|
|
693
635
|
})
|
|
694
636
|
|
|
695
637
|
Entities: User, Company, CompanyBranch, UserCompanyPermission
|
|
696
|
-
(+ IAM entities if includeIAM: true)
|
|
697
|
-
Tables: 4 (or more with IAM)
|
|
698
638
|
Features: Multi-tenant, hierarchical branches
|
|
699
|
-
Size: Complete
|
|
700
|
-
Performance: Scalable
|
|
701
639
|
```
|
|
702
640
|
|
|
703
|
-
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## No-IAM Mode (Bypassing Permission Guards)
|
|
704
644
|
|
|
705
|
-
|
|
645
|
+
When you don't use the `@flusys/nestjs-iam` module but your controllers use `level: 'permission'` security, the `PermissionGuard` will fail because permissions aren't cached.
|
|
706
646
|
|
|
707
|
-
|
|
647
|
+
**Solution:** Populate the cache with wildcard permissions after login.
|
|
708
648
|
|
|
709
|
-
|
|
710
|
-
```typescript
|
|
711
|
-
// Auth module calls TypeOrmModule.forRoot() with auth entities
|
|
712
|
-
TypeOrmModule.forRoot({ entities: [User, Company, ...] })
|
|
649
|
+
### How Permission Guard Works
|
|
713
650
|
|
|
714
|
-
// IAM module tries to use forFeature() but entities not registered
|
|
715
|
-
TypeOrmModule.forFeature([Action, Role, Permission]) // ❌ Error!
|
|
716
651
|
```
|
|
652
|
+
Controller (level: 'permission')
|
|
653
|
+
↓
|
|
654
|
+
PermissionGuard.canActivate()
|
|
655
|
+
↓
|
|
656
|
+
getUserPermissions() → cache.get(`permissions:user:{userId}`)
|
|
657
|
+
↓
|
|
658
|
+
No IAM = Empty cache → NoPermissionsFoundException ❌
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Fix: Set Wildcard Permissions After Login
|
|
662
|
+
|
|
663
|
+
Add this to your `AuthenticationService.login()` or create a custom login hook:
|
|
717
664
|
|
|
718
|
-
**The Solution:**
|
|
719
665
|
```typescript
|
|
720
|
-
|
|
721
|
-
AuthModule.forRoot({
|
|
722
|
-
includeIAM: true, // Auth module loads IAM entities
|
|
723
|
-
// ...
|
|
724
|
-
})
|
|
666
|
+
import { HybridCache, CACHE_INSTANCE, PERMISSIONS_CACHE_PREFIX } from '@flusys/nestjs-shared';
|
|
725
667
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
668
|
+
@Injectable()
|
|
669
|
+
export class AuthenticationService {
|
|
670
|
+
constructor(
|
|
671
|
+
@Inject(CACHE_INSTANCE) private readonly cache: HybridCache,
|
|
672
|
+
// ... other dependencies
|
|
673
|
+
) {}
|
|
731
674
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
- ❌ Multi-tenant mode → Not needed (each tenant has own connection)
|
|
735
|
-
- ❌ Not using IAM → `includeIAM: false` (default)
|
|
675
|
+
async login(dto: LoginDto): Promise<LoginResponse> {
|
|
676
|
+
// ... validate credentials, generate tokens
|
|
736
677
|
|
|
737
|
-
|
|
678
|
+
// When IAM is NOT used, grant all permissions via wildcard
|
|
679
|
+
await this.setWildcardPermissions(user.id);
|
|
738
680
|
|
|
681
|
+
return { accessToken, user };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Sets wildcard permissions for users when IAM module is not installed.
|
|
686
|
+
* The '*' permission grants access to all protected endpoints.
|
|
687
|
+
*/
|
|
688
|
+
private async setWildcardPermissions(userId: string): Promise<void> {
|
|
689
|
+
const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:user:${userId}`;
|
|
690
|
+
await this.cache.set(cacheKey, ['*'], 86400000); // 24 hours TTL
|
|
691
|
+
}
|
|
692
|
+
}
|
|
739
693
|
```
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
694
|
+
|
|
695
|
+
### With Company Feature Enabled
|
|
696
|
+
|
|
697
|
+
If using `enableCompanyFeature: true`, the cache key format is different:
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
private async setWildcardPermissions(
|
|
701
|
+
userId: string,
|
|
702
|
+
companyId?: string,
|
|
703
|
+
branchId?: string,
|
|
704
|
+
): Promise<void> {
|
|
705
|
+
if (companyId) {
|
|
706
|
+
// Company-scoped permissions
|
|
707
|
+
const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
|
|
708
|
+
await this.cache.set(cacheKey, ['*'], 86400000);
|
|
709
|
+
} else {
|
|
710
|
+
// User-level permissions
|
|
711
|
+
const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:user:${userId}`;
|
|
712
|
+
await this.cache.set(cacheKey, ['*'], 86400000);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
746
715
|
```
|
|
747
716
|
|
|
748
|
-
|
|
717
|
+
### Wildcard Permission Syntax
|
|
749
718
|
|
|
750
|
-
|
|
719
|
+
The `PermissionGuard` supports these wildcard patterns:
|
|
720
|
+
|
|
721
|
+
| Pattern | Meaning |
|
|
722
|
+
|---------|---------|
|
|
723
|
+
| `*` | Full access to everything |
|
|
724
|
+
| `user.*` | All user permissions (user.create, user.read, etc.) |
|
|
725
|
+
| `branch.*` | All branch permissions |
|
|
751
726
|
|
|
752
|
-
|
|
727
|
+
### For Jest Tests
|
|
753
728
|
|
|
754
|
-
|
|
729
|
+
Mock the cache to return wildcard permissions:
|
|
755
730
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
731
|
+
```typescript
|
|
732
|
+
const mockCache = {
|
|
733
|
+
get: jest.fn().mockResolvedValue(['*']),
|
|
734
|
+
set: jest.fn().mockResolvedValue(undefined),
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
beforeEach(async () => {
|
|
738
|
+
const module = await Test.createTestingModule({
|
|
739
|
+
providers: [
|
|
740
|
+
YourService,
|
|
741
|
+
{ provide: CACHE_INSTANCE, useValue: mockCache },
|
|
742
|
+
],
|
|
743
|
+
}).compile();
|
|
744
|
+
});
|
|
745
|
+
```
|
|
763
746
|
|
|
764
747
|
---
|
|
765
748
|
|
|
766
|
-
**Last Updated:** 2026-02-
|
|
767
|
-
**Version:** 4.1.6
|
|
768
|
-
|
|
769
|
-
**Recent Improvements:**
|
|
770
|
-
- 2026-02-09: Added explicit `@Inject()` decorators to all services for esbuild bundling compatibility
|
|
771
|
-
- 2026-02-09: All services now use REQUEST scope with DataSource Provider pattern
|
|
772
|
-
- 2026-02-07: Fixed entity diagram to show polymorphic design (permissionType/targetId)
|
|
773
|
-
- 2026-02-07: Updated service names to match actual implementation
|
|
774
|
-
- 2026-02-07: Simplified login flow diagrams
|
|
775
|
-
- 2026-01-15: Fixed JwtStrategy to use `ModuleRef` for request-scoped `AuthDataSourceProvider` resolution
|
|
776
|
-
- 2026-01-14: Added `UserPermissionController` for batch permission management
|
|
749
|
+
**Last Updated:** 2026-02-19
|