@flusys/nestjs-auth 1.0.0-beta → 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.
Files changed (95) hide show
  1. package/README.md +274 -291
  2. package/cjs/config/auth.constants.js +12 -0
  3. package/cjs/controllers/authentication.controller.js +219 -10
  4. package/cjs/controllers/branch.controller.js +44 -1
  5. package/cjs/controllers/company-selection.controller.js +27 -20
  6. package/cjs/controllers/company.controller.js +44 -1
  7. package/cjs/controllers/user-permission.controller.js +40 -25
  8. package/cjs/controllers/user.controller.js +211 -7
  9. package/cjs/dtos/authentication.dto.js +119 -13
  10. package/cjs/dtos/user-permission.dto.js +28 -0
  11. package/cjs/dtos/user.dto.js +19 -4
  12. package/cjs/entities/index.js +5 -1
  13. package/cjs/entities/pending-registration.entity.js +125 -0
  14. package/cjs/entities/user-company-permission.entity.js +0 -6
  15. package/cjs/entities/user-token.entity.js +129 -0
  16. package/cjs/guards/auth-throttler.guard.js +53 -0
  17. package/cjs/interceptors/clear-token.interceptor.js +3 -6
  18. package/cjs/interceptors/set-token.interceptor.js +38 -31
  19. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  20. package/cjs/interfaces/index.js +2 -0
  21. package/cjs/interfaces/user-enricher.interface.js +4 -0
  22. package/cjs/modules/auth.module.js +8 -20
  23. package/cjs/services/auth-config.service.js +23 -2
  24. package/cjs/services/auth-datasource.provider.js +5 -3
  25. package/cjs/services/auth-email.service.js +471 -0
  26. package/cjs/services/authentication.service.js +169 -77
  27. package/cjs/services/branch.service.js +6 -5
  28. package/cjs/services/company-selection-session.service.js +1 -1
  29. package/cjs/services/index.js +1 -0
  30. package/cjs/services/user-permission.service.js +0 -2
  31. package/cjs/services/user.service.js +213 -34
  32. package/cjs/validators/password.validator.js +45 -0
  33. package/config/auth.constants.d.ts +3 -0
  34. package/controllers/authentication.controller.d.ts +13 -9
  35. package/controllers/company-selection.controller.d.ts +6 -2
  36. package/controllers/user.controller.d.ts +33 -0
  37. package/dtos/authentication.dto.d.ts +21 -3
  38. package/dtos/company-branch.dto.d.ts +0 -1
  39. package/dtos/company.dto.d.ts +0 -1
  40. package/dtos/user-permission.dto.d.ts +4 -0
  41. package/dtos/user.dto.d.ts +1 -2
  42. package/entities/index.d.ts +7 -3
  43. package/entities/pending-registration.entity.d.ts +17 -0
  44. package/entities/user-company-permission.entity.d.ts +0 -2
  45. package/entities/user-token.entity.d.ts +15 -0
  46. package/fesm/config/auth.constants.js +6 -2
  47. package/fesm/controllers/authentication.controller.js +221 -12
  48. package/fesm/controllers/branch.controller.js +45 -2
  49. package/fesm/controllers/company-selection.controller.js +28 -21
  50. package/fesm/controllers/company.controller.js +45 -2
  51. package/fesm/controllers/user-permission.controller.js +41 -26
  52. package/fesm/controllers/user.controller.js +214 -10
  53. package/fesm/dtos/authentication.dto.js +106 -14
  54. package/fesm/dtos/user-permission.dto.js +28 -0
  55. package/fesm/dtos/user.dto.js +20 -5
  56. package/fesm/entities/index.js +7 -1
  57. package/fesm/entities/pending-registration.entity.js +115 -0
  58. package/fesm/entities/user-company-permission.entity.js +0 -6
  59. package/fesm/entities/user-token.entity.js +113 -0
  60. package/fesm/guards/auth-throttler.guard.js +43 -0
  61. package/fesm/interceptors/clear-token.interceptor.js +2 -5
  62. package/fesm/interceptors/set-token.interceptor.js +37 -30
  63. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  64. package/fesm/interfaces/authentication.interface.js +1 -1
  65. package/fesm/interfaces/index.js +2 -0
  66. package/fesm/interfaces/user-enricher.interface.js +26 -0
  67. package/fesm/modules/auth.module.js +10 -22
  68. package/fesm/services/auth-config.service.js +24 -3
  69. package/fesm/services/auth-datasource.provider.js +6 -4
  70. package/fesm/services/auth-email.service.js +420 -0
  71. package/fesm/services/authentication.service.js +169 -77
  72. package/fesm/services/branch.service.js +6 -5
  73. package/fesm/services/company-selection-session.service.js +1 -1
  74. package/fesm/services/index.js +1 -0
  75. package/fesm/services/user-permission.service.js +1 -3
  76. package/fesm/services/user.service.js +215 -36
  77. package/fesm/validators/password.validator.js +48 -0
  78. package/guards/auth-throttler.guard.d.ts +7 -0
  79. package/interceptors/set-token.interceptor.d.ts +1 -0
  80. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  81. package/interfaces/auth-module-options.interface.d.ts +4 -1
  82. package/interfaces/authentication.interface.d.ts +14 -0
  83. package/interfaces/index.d.ts +2 -0
  84. package/interfaces/user-enricher.interface.d.ts +64 -0
  85. package/interfaces/user.interface.d.ts +1 -0
  86. package/modules/auth.module.d.ts +0 -1
  87. package/package.json +4 -4
  88. package/services/auth-config.service.d.ts +5 -1
  89. package/services/auth-email.service.d.ts +51 -0
  90. package/services/authentication.service.d.ts +16 -11
  91. package/services/company-selection-session.service.d.ts +1 -1
  92. package/services/index.d.ts +1 -0
  93. package/services/user-permission.service.d.ts +0 -1
  94. package/services/user.service.d.ts +19 -3
  95. 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, // Enable company/branch support
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, // Implements AuthOptionsFactory
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
- includeIAM?: boolean; // Include IAM entities in TypeORM.forRoot() (required for IAM module)
147
- bootstrapAppConfig: {
140
+ bootstrapAppConfig?: {
148
141
  databaseMode: 'single' | 'multi-tenant';
149
- enableCompanyFeature: boolean; // Enable company/branch feature
142
+ enableCompanyFeature: boolean;
150
143
  };
151
- config: {
144
+ config?: {
152
145
  defaultDatabaseConfig?: IDatabaseConfig;
153
146
  tenantDefaultDatabaseConfig?: IDatabaseConfig;
154
147
  tenants?: ITenantDatabaseConfig[];
155
- jwtSecret: string;
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
- **Important Notes:**
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
- 1. **includeIAM Flag**: When using `@flusys/nestjs-iam` in single-tenant mode, set `includeIAM: true` in Auth module options. This registers IAM entities with TypeORM during initialization. Not required in multi-tenant mode.
171
+ // Cookie
172
+ export const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
173
+ export const MAX_COOKIE_BYTES = 3500;
167
174
 
168
- 2. **Module Separation**: Auth module handles authentication (login, JWT, user management). For authorization and permissions, use the separate `@flusys/nestjs-iam` package.
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
- #### GET /auth/me - Get Current User Info
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
- **Purpose:** Retrieve current authenticated user information with optional company/branch context
202
+ > **Note:** Email endpoints require `AuthEmailService` to be configured via the `providers` option.
186
203
 
187
- **Authentication:** Requires valid access token (Bearer token in Authorization header)
204
+ #### GET /auth/me - Get Current User Info
188
205
 
189
- **Response Behavior:**
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 (`/administration/users`)
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 (NEW - when company feature enabled)
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
- **Example: Revoke branch access**
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
- ### Company Management Endpoints (`/administration/company`) - When `enableCompanyFeature: true`
316
-
317
- | Endpoint | Method | Description |
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
- - ✅ **JwtAuthGuard** in shared → All modules can use without depending on Auth
358
- - ✅ **JwtStrategy** in auth → Registers JWT validation with Passport
359
- - ✅ **AuthConfigService** in auth → Provides JWT settings (secret, expiration)
360
- - ✅ **Token generation** handled by AuthenticationService
361
- - ✅ **Token validation** uses JwtStrategy registered by Auth module
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 **singleton** (required by Passport), but needs to access the **request-scoped** `AuthDataSourceProvider`. This is solved using `ModuleRef`:
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, // Used to resolve request-scoped providers
340
+ private readonly moduleRef: ModuleRef,
375
341
  ) {
376
342
  super({
377
343
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
378
344
  secretOrKey: authConfig.getJwtSecret(),
379
- passReqToCallback: true, // Pass request to validate method
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 v4.1.2+ │
408
- │ │
409
- │ ┌─────────────────────────────────────────────────┐ │
410
- │ │ CORE AUTH (Always Loaded) │ │
411
- │ │ │ │
412
- │ │ ┌──────────┐ │ │
413
- │ │ │ User │ │ │
414
- │ │ └──────────┘ │ │
415
- │ │ │ │
416
- │ │ • User login/registration │ │
417
- │ │ • Password hashing (bcrypt) │ │
418
- │ │ • JWT token generation │ │
419
- │ │ • JwtStrategy (Passport integration) │ │
420
- │ └─────────────────────────────────────────────────┘ │
421
- │ │
422
- │ ┌─────────────────────────────────────────────────┐ │
423
- │ │ COMPANY FEATURE (Optional - Flag Enabled) │ │
424
- │ │ │ │
425
- │ │ ┌─────────┐ ┌──────────────┐ ┌────────────┐ │ │
426
- │ │ │ Company │ │CompanyBranch │ │UserCompany │ │ │
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 (Simple)
390
+ #### Without Company Feature
442
391
 
443
392
  ```
444
393
  ┌──────────────┐
445
394
  │ User │
446
395
  ├──────────────┤
447
- │ id │
448
- │ name │
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 │ │ permissionType │──────►│ name │
463
- │ email │ │ targetId (UUID) │ │ slug │
464
- │ password │ │ isActive │ │ isActive │
465
- └──────────────┘ │ metadata │ └──────────────────┘
466
- └───────────────────┘ │
467
- │ │
468
- permissionType: 'company'|'branch' ▼
469
- targetId: company.id OR branch.id ┌──────────────────┐
470
- │ │ CompanyBranch │
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
- │ User selects company & branch
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
- ### User Active Status
490
+ ### Company Permission Assignment Flow
491
+
492
+ When creating users with `enableCompanyFeature: true`, the `UserService` automatically assigns company and branch permissions:
568
493
 
569
- The `PUT /users/:id/status` endpoint handles user active status based on company feature setting:
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
- "name": "John Doe",
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
- "name": "John Doe",
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,97 +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
- includeIAM: true, // If using IAM module
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
- ## IAM Integration
641
+ ---
642
+
643
+ ## No-IAM Mode (Bypassing Permission Guards)
704
644
 
705
- ### Why includeIAM Flag?
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
- When using `@flusys/nestjs-iam` with Auth module in **single-tenant mode**, TypeORM needs to know about all entities during initialization.
647
+ **Solution:** Populate the cache with wildcard permissions after login.
708
648
 
709
- **The Problem:**
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
- // Auth module dynamically includes IAM entities when includeIAM: true
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
- // Now TypeORM knows about all entities
727
- TypeOrmModule.forRoot({
728
- entities: [User, Company, Action, Role, Permission, ...]
729
- }) // ✅ Works!
730
- ```
668
+ @Injectable()
669
+ export class AuthenticationService {
670
+ constructor(
671
+ @Inject(CACHE_INSTANCE) private readonly cache: HybridCache,
672
+ // ... other dependencies
673
+ ) {}
731
674
 
732
- **When to use:**
733
- - ✅ Single-tenant mode + IAM module → `includeIAM: true`
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
- ### Architecture
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
- App Module
741
- ├── AuthModule.forRoot({ includeIAM: true })
742
- │ └── TypeOrmModule.forRoot([Auth entities + IAM entities])
743
- │
744
- └── IAMModule.forRoot()
745
- └── TypeOrmModule.forFeature([IAM entities])
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
- Auth module is responsible for database connection, but dynamically includes IAM entities when requested. This maintains separation while solving the TypeORM registration requirement.
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
- ## Summary
727
+ ### For Jest Tests
753
728
 
754
- The authentication package provides:
729
+ Mock the cache to return wildcard permissions:
755
730
 
756
- - **Flexibility** - Use simple or complex mode
757
- - **Scalability** - Grows with your needs
758
- - **Type Safety** - Full TypeScript support
759
- - **Clean Design** - Clear separation of concerns
760
- - **IAM Integration** - Seamless permission management
761
- - **Well Organized** - Logical module structure
762
- - **Performance** - Only load what you need
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-16
749
+ **Last Updated:** 2026-02-19