@flusys/nestjs-auth 1.0.0-rc → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +530 -396
  2. package/cjs/controllers/authentication.controller.js +160 -28
  3. package/cjs/controllers/user-permission.controller.js +49 -136
  4. package/cjs/docs/auth-swagger.config.js +14 -7
  5. package/cjs/dtos/authentication.dto.js +0 -30
  6. package/cjs/dtos/index.js +0 -1
  7. package/cjs/entities/user-company-permission.entity.js +3 -3
  8. package/cjs/interceptors/clear-token.interceptor.js +8 -18
  9. package/cjs/interceptors/set-token.interceptor.js +8 -55
  10. package/cjs/interfaces/auth-email-provider.interface.js +1 -1
  11. package/cjs/modules/auth.module.js +63 -129
  12. package/cjs/services/auth-config.service.js +6 -29
  13. package/cjs/services/auth-datasource.provider.js +8 -20
  14. package/cjs/services/auth-email.service.js +72 -87
  15. package/cjs/services/authentication.service.js +211 -355
  16. package/cjs/services/branch.service.js +9 -5
  17. package/cjs/services/user-permission.service.js +43 -71
  18. package/cjs/services/user.service.js +18 -47
  19. package/cjs/strategies/jwt.strategy.js +44 -44
  20. package/controllers/authentication.controller.d.ts +5 -3
  21. package/controllers/user-permission.controller.d.ts +5 -4
  22. package/dtos/authentication.dto.d.ts +0 -4
  23. package/dtos/index.d.ts +0 -1
  24. package/entities/index.d.ts +3 -3
  25. package/entities/user-company-permission.entity.d.ts +1 -1
  26. package/fesm/controllers/authentication.controller.js +162 -30
  27. package/fesm/controllers/user-permission.controller.js +50 -137
  28. package/fesm/docs/auth-swagger.config.js +14 -7
  29. package/fesm/dtos/authentication.dto.js +0 -30
  30. package/fesm/dtos/index.js +0 -1
  31. package/fesm/entities/user-company-permission.entity.js +3 -3
  32. package/fesm/interceptors/clear-token.interceptor.js +8 -18
  33. package/fesm/interceptors/set-token.interceptor.js +8 -55
  34. package/fesm/interfaces/auth-email-provider.interface.js +1 -1
  35. package/fesm/modules/auth.module.js +63 -129
  36. package/fesm/services/auth-config.service.js +6 -29
  37. package/fesm/services/auth-datasource.provider.js +8 -20
  38. package/fesm/services/auth-email.service.js +72 -87
  39. package/fesm/services/authentication.service.js +211 -355
  40. package/fesm/services/branch.service.js +9 -5
  41. package/fesm/services/user-permission.service.js +44 -72
  42. package/fesm/services/user.service.js +18 -47
  43. package/fesm/strategies/jwt.strategy.js +44 -44
  44. package/interceptors/clear-token.interceptor.d.ts +0 -1
  45. package/interceptors/set-token.interceptor.d.ts +1 -2
  46. package/interfaces/authentication.interface.d.ts +0 -4
  47. package/modules/auth.module.d.ts +6 -2
  48. package/package.json +3 -3
  49. package/services/auth-config.service.d.ts +2 -7
  50. package/services/auth-datasource.provider.d.ts +2 -4
  51. package/services/auth-email.service.d.ts +7 -4
  52. package/services/authentication.service.d.ts +14 -8
  53. package/services/user-permission.service.d.ts +6 -8
  54. package/services/user.service.d.ts +1 -1
  55. package/strategies/jwt.strategy.d.ts +4 -1
  56. package/cjs/dtos/user-company-permission.dto.js +0 -110
  57. package/cjs/guards/auth-throttler.guard.js +0 -53
  58. package/dtos/user-company-permission.dto.d.ts +0 -10
  59. package/fesm/dtos/user-company-permission.dto.js +0 -98
  60. package/fesm/guards/auth-throttler.guard.js +0 -43
  61. package/guards/auth-throttler.guard.d.ts +0 -7
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Authentication Package Guide
2
2
 
3
3
  > **Package:** `@flusys/nestjs-auth`
4
- > **Type:** Authentication system with JWT, multi-tenant, and company/branch support
4
+ > **Type:** Authentication system with JWT, email verification, multi-tenant, and company/branch support
5
5
 
6
6
  ## Table of Contents
7
7
 
@@ -9,13 +9,15 @@
9
9
  - [Quick Start](#quick-start)
10
10
  - [Configuration](#configuration)
11
11
  - [Constants](#constants)
12
+ - [Entities](#entities)
13
+ - [Provider Interfaces](#provider-interfaces)
14
+ - [Services](#services)
12
15
  - [API Endpoints](#api-endpoints)
13
- - [Architecture](#architecture)
14
16
  - [Login Flows](#login-flows)
17
+ - [Email Verification Flow](#email-verification-flow)
15
18
  - [User Management](#user-management)
16
- - [Environment Variables](#environment-variables)
17
- - [Migrations](#migrations)
18
- - [Configuration Modes](#configuration-modes)
19
+ - [No-IAM Mode](#no-iam-mode)
20
+ - [API Reference](#api-reference)
19
21
 
20
22
  ---
21
23
 
@@ -44,10 +46,10 @@ import { AuthModule } from '@flusys/nestjs-auth';
44
46
  },
45
47
  config: {
46
48
  defaultDatabaseConfig: {
47
- type: 'mysql',
49
+ type: 'postgres',
48
50
  host: 'localhost',
49
- port: 3306,
50
- username: 'root',
51
+ port: 5432,
52
+ username: 'postgres',
51
53
  password: 'password',
52
54
  database: 'myapp',
53
55
  },
@@ -71,14 +73,7 @@ AuthModule.forRoot({
71
73
  enableCompanyFeature: true,
72
74
  },
73
75
  config: {
74
- defaultDatabaseConfig: {
75
- type: 'mysql',
76
- host: 'localhost',
77
- port: 3306,
78
- username: 'root',
79
- password: 'password',
80
- database: 'myapp',
81
- },
76
+ defaultDatabaseConfig: { /* ... */ },
82
77
  jwtSecret: process.env.JWT_SECRET,
83
78
  refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
84
79
  refreshTokenExpiration: '7d',
@@ -99,10 +94,10 @@ AuthModule.forRoot({
99
94
  },
100
95
  config: {
101
96
  tenantDefaultDatabaseConfig: {
102
- type: 'mysql',
97
+ type: 'postgres',
103
98
  host: 'localhost',
104
- port: 3306,
105
- username: 'root',
99
+ port: 5432,
100
+ username: 'postgres',
106
101
  password: 'password',
107
102
  },
108
103
  tenants: [
@@ -114,18 +109,36 @@ AuthModule.forRoot({
114
109
  })
115
110
  ```
116
111
 
117
- ### Async Configuration
112
+ ### With Email Provider (Password Reset, Email Verification)
118
113
 
119
114
  ```typescript
120
- AuthModule.forRootAsync({
121
- global: true,
122
- includeController: true,
123
- bootstrapAppConfig: {
124
- databaseMode: 'single',
125
- enableCompanyFeature: false,
126
- },
127
- imports: [HttpModule],
128
- useClass: AuthConfigFactory,
115
+ import { AUTH_EMAIL_PROVIDER } from '@flusys/nestjs-auth';
116
+ import { EmailSendService } from '@flusys/nestjs-email';
117
+
118
+ AuthModule.forRoot({
119
+ // ... config
120
+ providers: [
121
+ {
122
+ provide: AUTH_EMAIL_PROVIDER,
123
+ useFactory: (emailService: EmailSendService) => ({
124
+ sendPasswordResetEmail: async (email, token, resetUrl) => {
125
+ await emailService.sendTemplateEmail({
126
+ templateSlug: 'password-reset',
127
+ to: email,
128
+ variables: { resetUrl, token },
129
+ });
130
+ },
131
+ sendVerificationEmail: async (email, token, verifyUrl) => {
132
+ await emailService.sendTemplateEmail({
133
+ templateSlug: 'email-verification',
134
+ to: email,
135
+ variables: { verifyUrl, token },
136
+ });
137
+ },
138
+ }),
139
+ inject: [EmailSendService],
140
+ },
141
+ ],
129
142
  })
130
143
  ```
131
144
 
@@ -146,20 +159,18 @@ interface AuthModuleOptions {
146
159
  tenantDefaultDatabaseConfig?: IDatabaseConfig;
147
160
  tenants?: ITenantDatabaseConfig[];
148
161
  jwtSecret?: string;
149
- jwtExpiration?: string;
162
+ jwtExpiration?: string; // Default: '1h'
150
163
  refreshTokenSecret?: string;
151
- refreshTokenExpiration?: string;
152
- refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
153
- frontendUrl?: string; // For email links
164
+ refreshTokenExpiration?: string; // Default: '7d'
165
+ refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
166
+ frontendUrl?: string; // For email links
154
167
  };
155
- providers?: Provider[]; // Additional providers (e.g., AUTH_EMAIL_PROVIDER)
168
+ providers?: Provider[]; // Additional providers (AUTH_EMAIL_PROVIDER, USER_ENRICHER)
156
169
  }
157
170
  ```
158
171
 
159
172
  ## Constants
160
173
 
161
- Centralized constants in `auth.constants.ts`:
162
-
163
174
  ```typescript
164
175
  // Injection Tokens
165
176
  export const AUTH_MODULE_OPTIONS = 'AUTH_MODULE_OPTIONS';
@@ -177,6 +188,284 @@ export const SESSION_CACHE_PREFIX = 'auth:session';
177
188
  export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
178
189
  ```
179
190
 
191
+ ## Entities
192
+
193
+ ### Entity Groups
194
+
195
+ ```typescript
196
+ // Core entities (always loaded)
197
+ export const CoreEntities = [User, UserToken, PendingRegistration];
198
+
199
+ // Company entities (when enableCompanyFeature: true)
200
+ export const CompanyEntities = [Company, CompanyBranch, UserCompanyPermission];
201
+
202
+ // All entities
203
+ export const AllEntities = [...CoreEntities, ...CompanyEntities];
204
+
205
+ // Helper function
206
+ export function getEntitiesByConfig(enableCompanyFeature: boolean) {
207
+ return enableCompanyFeature ? AllEntities : CoreEntities;
208
+ }
209
+ ```
210
+
211
+ ### User Entity
212
+
213
+ Extends `UserRoot` from `@flusys/nestjs-shared`:
214
+
215
+ | Field | Type | Description |
216
+ |-------|------|-------------|
217
+ | `id` | `uuid` | Primary key |
218
+ | `name` | `varchar(255)` | User's name |
219
+ | `email` | `varchar(255)` | Unique email address |
220
+ | `password` | `text` | Bcrypt hashed password |
221
+ | `phone` | `varchar(255)` | Phone number |
222
+ | `isActive` | `boolean` | Account status |
223
+ | `emailVerified` | `boolean` | Email verification status |
224
+ | `phoneVerified` | `boolean` | Phone verification status |
225
+ | `profilePictureId` | `uuid` | Reference to storage file |
226
+ | `lastLoginAt` | `timestamp` | Last login timestamp |
227
+ | `additionalFields` | `json` | Extensible JSON fields |
228
+ | `createdAt` | `timestamp` | Auto-generated |
229
+ | `updatedAt` | `timestamp` | Auto-updated |
230
+ | `deletedAt` | `timestamp` | Soft delete timestamp |
231
+
232
+ ### UserToken Entity
233
+
234
+ Stores tokens for existing user actions:
235
+
236
+ ```typescript
237
+ enum TokenType {
238
+ PASSWORD_RESET = 'password_reset',
239
+ EMAIL_VERIFICATION = 'email_verification',
240
+ EMAIL_CHANGE = 'email_change',
241
+ }
242
+ ```
243
+
244
+ | Field | Type | Description |
245
+ |-------|------|-------------|
246
+ | `id` | `uuid` | Primary key |
247
+ | `userId` | `uuid` | Reference to user |
248
+ | `type` | `TokenType` | Token purpose |
249
+ | `token` | `varchar(64)` | SHA-256 hashed token |
250
+ | `expiresAt` | `timestamp` | Expiration time |
251
+ | `usedAt` | `timestamp` | When token was used |
252
+ | `metadata` | `varchar(255)` | Additional data (e.g., new email for EMAIL_CHANGE) |
253
+ | `createdAt` | `timestamp` | Auto-generated |
254
+
255
+ **Indexes:**
256
+ - `IDX_user_token_token` - Unique on token
257
+ - `IDX_user_token_user_type` - userId + type
258
+ - `IDX_user_token_expires` - For cleanup queries
259
+
260
+ ### PendingRegistration Entity
261
+
262
+ Stores registration data until email verification (prevents garbage accounts):
263
+
264
+ | Field | Type | Description |
265
+ |-------|------|-------------|
266
+ | `id` | `uuid` | Primary key |
267
+ | `email` | `varchar(255)` | Unique pending email |
268
+ | `name` | `varchar(255)` | User's name |
269
+ | `phone` | `varchar(255)` | Phone number |
270
+ | `passwordHash` | `text` | Pre-hashed password |
271
+ | `token` | `varchar(64)` | SHA-256 hashed verification token |
272
+ | `expiresAt` | `timestamp` | Expiration (24 hours) |
273
+ | `companyData` | `json` | Company registration data |
274
+ | `createdAt` | `timestamp` | Auto-generated |
275
+
276
+ **Indexes:**
277
+ - `IDX_pending_reg_email` - Unique
278
+ - `IDX_pending_reg_token` - Unique
279
+ - `IDX_pending_reg_expires` - For cleanup
280
+
281
+ ### Company Entity (when company feature enabled)
282
+
283
+ | Field | Type | Description |
284
+ |-------|------|-------------|
285
+ | `id` | `uuid` | Primary key |
286
+ | `name` | `varchar(255)` | Company name |
287
+ | `slug` | `varchar(255)` | URL-friendly identifier |
288
+ | `phone` | `varchar(255)` | Contact phone |
289
+ | `address` | `text` | Address |
290
+ | `logoId` | `uuid` | Reference to storage file |
291
+ | `isActive` | `boolean` | Company status |
292
+
293
+ ### CompanyBranch Entity
294
+
295
+ | Field | Type | Description |
296
+ |-------|------|-------------|
297
+ | `id` | `uuid` | Primary key |
298
+ | `name` | `varchar(255)` | Branch name |
299
+ | `slug` | `varchar(255)` | URL-friendly identifier |
300
+ | `companyId` | `uuid` | Parent company |
301
+ | `parentId` | `uuid` | Parent branch (hierarchical) |
302
+ | `serial` | `int` | Display order |
303
+ | `logoId` | `uuid` | Reference to storage file |
304
+ | `isActive` | `boolean` | Branch status |
305
+
306
+ ### UserCompanyPermission Entity (Polymorphic)
307
+
308
+ ```typescript
309
+ enum PermissionType {
310
+ COMPANY = 'company',
311
+ BRANCH = 'branch',
312
+ }
313
+ ```
314
+
315
+ | Field | Type | Description |
316
+ |-------|------|-------------|
317
+ | `id` | `uuid` | Primary key |
318
+ | `userId` | `uuid` | Reference to user |
319
+ | `permissionType` | `PermissionType` | company or branch |
320
+ | `targetId` | `uuid` | Company ID or Branch ID |
321
+ | `isActive` | `boolean` | Permission status |
322
+
323
+ ## Provider Interfaces
324
+
325
+ ### AUTH_EMAIL_PROVIDER
326
+
327
+ Allows auth to work independently of the email module:
328
+
329
+ ```typescript
330
+ interface IAuthEmailProvider {
331
+ /** Send password reset email */
332
+ sendPasswordResetEmail(email: string, token: string, resetUrl: string): Promise<void>;
333
+
334
+ /** Send email verification email */
335
+ sendVerificationEmail(email: string, token: string, verifyUrl: string): Promise<void>;
336
+
337
+ /** Send welcome email after registration (optional) */
338
+ sendWelcomeEmail?(email: string, name: string): Promise<void>;
339
+ }
340
+ ```
341
+
342
+ **Example Implementation:**
343
+
344
+ ```typescript
345
+ {
346
+ provide: AUTH_EMAIL_PROVIDER,
347
+ useFactory: (emailSendService: EmailSendService) => ({
348
+ sendPasswordResetEmail: async (email, token, resetUrl) => {
349
+ await emailSendService.sendTemplateEmail({
350
+ templateSlug: 'password-reset',
351
+ to: email,
352
+ variables: { resetUrl, token },
353
+ });
354
+ },
355
+ sendVerificationEmail: async (email, token, verifyUrl) => {
356
+ await emailSendService.sendTemplateEmail({
357
+ templateSlug: 'email-verification',
358
+ to: email,
359
+ variables: { verifyUrl, token },
360
+ });
361
+ },
362
+ }),
363
+ inject: [EmailSendService],
364
+ }
365
+ ```
366
+
367
+ ### USER_ENRICHER
368
+
369
+ Extends user list and profile without modifying base package:
370
+
371
+ ```typescript
372
+ interface IUserEnricher {
373
+ /** Enrich user list query with extra joins/selects */
374
+ enrichListQuery?(
375
+ query: SelectQueryBuilder<User>,
376
+ user: ILoggedUserInfo | null,
377
+ ): Promise<{ extraFields: string[] }>;
378
+
379
+ /** Transform user list items with extra computed data */
380
+ enrichListItems?(
381
+ users: IUser[],
382
+ user: ILoggedUserInfo | null,
383
+ ): Promise<IEnrichedUser[]>;
384
+
385
+ /** Get extra profile data (roles, permissions, sections) */
386
+ getProfileExtras?(
387
+ userId: string,
388
+ user: ILoggedUserInfo,
389
+ ): Promise<IProfileExtras>;
390
+
391
+ /** Handle profile update with extra fields */
392
+ updateProfileExtras?(
393
+ userId: string,
394
+ extras: Record<string, any>,
395
+ queryRunner: QueryRunner,
396
+ ): Promise<void>;
397
+
398
+ /** Validate extra fields before profile update */
399
+ validateProfileExtras?(
400
+ userId: string,
401
+ extras: Record<string, any>,
402
+ user: ILoggedUserInfo,
403
+ ): Promise<void>;
404
+
405
+ // Multi-Step Profile Methods (LEADPRO-style)
406
+ getProfileSections?(): IProfileSection[];
407
+ getProfileSectionData?(userId, sectionId, user): Promise<IProfileSectionData>;
408
+ updateProfileSection?(userId, sectionId, data, queryRunner): Promise<void>;
409
+ handleSectionFileUpload?(userId, sectionId, field, fileInfo, queryRunner): Promise<IProfileFile>;
410
+ handleSectionFileDelete?(userId, sectionId, field, fileId, queryRunner): Promise<void>;
411
+ calculateProfileCompletion?(userId, user): Promise<number>;
412
+ }
413
+ ```
414
+
415
+ **Profile Section Interface (for multi-step profiles):**
416
+
417
+ ```typescript
418
+ interface IProfileSection {
419
+ id: string; // Unique identifier
420
+ name: string; // Display name
421
+ order: number; // Stepper order
422
+ icon?: string; // PrimeNG icon class
423
+ required?: boolean; // Required for completion
424
+ hasFileUpload?: boolean; // Has file fields
425
+ editPermission?: string; // Required permission
426
+ }
427
+
428
+ interface IProfileExtras {
429
+ roles?: IProfileRole[];
430
+ actions?: IProfileAction[];
431
+ sections?: IProfileSectionData[];
432
+ completionPercentage?: number;
433
+ }
434
+ ```
435
+
436
+ ## Services
437
+
438
+ | Service | Scope | Description |
439
+ |---------|-------|-------------|
440
+ | `AuthenticationService` | REQUEST | Login, register, token management, company selection |
441
+ | `AuthEmailService` | REQUEST | Email verification, password reset, pending registrations |
442
+ | `UserService` | REQUEST | User CRUD, extends `RequestScopedApiService` |
443
+ | `CompanyService` | REQUEST | Company CRUD (when company feature enabled) |
444
+ | `BranchService` | REQUEST | Branch CRUD with company filtering |
445
+ | `UserPermissionService` | REQUEST | User-company/branch permission management |
446
+ | `AuthDataSourceProvider` | REQUEST | Dynamic datasource for single/multi-tenant |
447
+ | `AuthConfigService` | SINGLETON | JWT and module configuration access |
448
+ | `CompanySelectionSessionService` | SINGLETON | Session management for company selection |
449
+
450
+ ### AuthEmailService
451
+
452
+ Token Security: All tokens are stored as SHA-256 hashes. Raw tokens are sent to users via email, but only hashes are stored in the database.
453
+
454
+ | Method | Description |
455
+ |--------|-------------|
456
+ | `isEmailEnabled()` | Check if email provider is configured |
457
+ | `hasPendingRegistration(email)` | Check for pending registration |
458
+ | `createPendingRegistration(data)` | Create pending registration with verification email |
459
+ | `forgotPassword(email, resetUrlBase)` | Send password reset email |
460
+ | `resetPassword(token, newPassword)` | Reset password using token |
461
+ | `verifyEmail(token)` | Verify email (pending registration or existing user) |
462
+ | `resendVerificationEmail(email, verifyUrlBase)` | Resend verification email |
463
+ | `cleanupExpired()` | Clean up expired tokens and pending registrations |
464
+
465
+ **Token Expiry:**
466
+ - Password Reset: 1 hour
467
+ - Email Verification: 24 hours
468
+
180
469
  ## API Endpoints
181
470
 
182
471
  ### Authentication Endpoints
@@ -186,7 +475,7 @@ export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
186
475
  | `/auth/login` | POST | Login with email/password |
187
476
  | `/auth/register` | POST | Register new user |
188
477
  | `/auth/refresh` | POST | Refresh access token |
189
- | `/auth/logout` | POST | Logout (clears refresh token cookie) |
478
+ | `/auth/logout` | POST | Logout (revokes all tokens) |
190
479
  | `/auth/change-password` | POST | Change user password |
191
480
  | `/auth/me` | GET | Get current user info |
192
481
 
@@ -199,42 +488,7 @@ export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
199
488
  | `/auth/verify-email` | POST | Verify email using token |
200
489
  | `/auth/resend-verification` | POST | Resend email verification link |
201
490
 
202
- > **Note:** Email endpoints require `AuthEmailService` to be configured via the `providers` option.
203
-
204
- #### GET /auth/me - Get Current User Info
205
-
206
- **Authentication:** Requires valid access token (Bearer token)
207
-
208
- When `enableCompanyFeature: false`:
209
- ```json
210
- {
211
- "user": {
212
- "id": "550e8400-e29b-41d4-a716-446655440000",
213
- "email": "john@example.com",
214
- "name": "John Doe",
215
- "profilePictureId": null
216
- }
217
- }
218
- ```
219
-
220
- When `enableCompanyFeature: true`:
221
- ```json
222
- {
223
- "user": { ... },
224
- "company": {
225
- "id": "550e8400-e29b-41d4-a716-446655440001",
226
- "name": "Acme Corporation",
227
- "slug": "acme-corp",
228
- "logoId": null
229
- },
230
- "branch": {
231
- "id": "550e8400-e29b-41d4-a716-446655440002",
232
- "name": "Main Branch",
233
- "slug": "main-branch",
234
- "logoId": null
235
- }
236
- }
237
- ```
491
+ > **Note:** Email endpoints require `AUTH_EMAIL_PROVIDER` to be configured.
238
492
 
239
493
  ### Company Selection Endpoints (when company feature enabled)
240
494
 
@@ -245,21 +499,6 @@ When `enableCompanyFeature: true`:
245
499
  | `/auth/companies` | GET | Get user's available companies |
246
500
  | `/auth/companies/:companyId/branches` | GET | Get branches for a company |
247
501
 
248
- ### Swagger Schema Behavior
249
-
250
- When `enableCompanyFeature: false`, the following are automatically hidden from Swagger:
251
-
252
- **Excluded Tags:** Companies, Branches, User Permissions, Company Selection
253
-
254
- **Excluded DTO Properties:**
255
-
256
- | DTO | Hidden Fields |
257
- |-----|---------------|
258
- | `RegistrationDto` | `companySlug`, `branchSlug`, `newCompanyName`, `newCompanyPhone`, `newCompanyAddress` |
259
- | `RegistrationResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
260
- | `LoginResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
261
- | `MeResponseDto` | `company`, `branch` |
262
-
263
502
  ### User Management Endpoints
264
503
 
265
504
  | Endpoint | Method | Description |
@@ -285,18 +524,6 @@ When `enableCompanyFeature: false`, the following are automatically hidden from
285
524
  | `/administration/permissions/user-branch/assign` | POST | Batch assign/revoke user-branch permissions |
286
525
  | `/administration/permissions/user-branch` | GET | Get user's assigned branches |
287
526
 
288
- **Example: Assign companies to user**
289
- ```json
290
- POST /administration/permissions/user-company/assign
291
- {
292
- "userId": "user-123",
293
- "items": [
294
- { "targetId": "company-1", "isAdd": true },
295
- { "targetId": "company-2", "isAdd": true }
296
- ]
297
- }
298
- ```
299
-
300
527
  ### Company/Branch Management Endpoints (when company feature enabled)
301
528
 
302
529
  | Resource | Endpoints |
@@ -304,134 +531,6 @@ POST /administration/permissions/user-company/assign
304
531
  | Companies | `/administration/company` - CRUD operations |
305
532
  | Branches | `/administration/branch` - CRUD operations |
306
533
 
307
- ## Architecture
308
-
309
- ### Module Independence
310
-
311
- ```
312
- @flusys/nestjs-shared
313
- ├── JwtAuthGuard (validates tokens using Passport)
314
- └── PermissionGuard (checks permissions from cache)
315
- ↓
316
- @flusys/nestjs-auth
317
- ├── AuthConfigService (JWT settings via AUTH_MODULE_OPTIONS)
318
- ├── JwtStrategy (registers with Passport, validates users)
319
- ├── AuthenticationService (generates tokens)
320
- ├── User management
321
- └── Company/Branch management (optional)
322
- ```
323
-
324
- **Key Points:**
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
330
-
331
- ### JwtStrategy Architecture
332
-
333
- The `JwtStrategy` is a singleton but needs to access the request-scoped `AuthDataSourceProvider`. This is solved using `ModuleRef`:
334
-
335
- ```typescript
336
- @Injectable()
337
- export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
338
- constructor(
339
- private readonly authConfig: AuthConfigService,
340
- private readonly moduleRef: ModuleRef,
341
- ) {
342
- super({
343
- jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
344
- secretOrKey: authConfig.getJwtSecret(),
345
- passReqToCallback: true,
346
- });
347
- }
348
-
349
- async validate(_req: Request, payload: TokenPayload) {
350
- const dataSourceProvider = await this.moduleRef.resolve(
351
- AuthDataSourceProvider,
352
- undefined,
353
- { strict: false },
354
- );
355
- const userRepository = await dataSourceProvider.getRepository(User);
356
- // ... validate user
357
- }
358
- }
359
- ```
360
-
361
- ### Package Structure
362
-
363
- ```
364
- ┌─────────────────────────────────────────────────────────┐
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
- │ └─────────────────────────────────────────────────┘ │
385
- └─────────────────────────────────────────────────────────┘
386
- ```
387
-
388
- ### Entity Relationships
389
-
390
- #### Without Company Feature
391
-
392
- ```
393
- ┌──────────────┐
394
- │ User │
395
- ├──────────────┤
396
- │ id, name │
397
- │ email │◄── Authentication
398
- │ password │
399
- │ phone │
400
- └──────────────┘
401
- ```
402
-
403
- #### With Company Feature (Polymorphic Design)
404
-
405
- ```
406
- ┌──────────────┐ ┌───────────────────┐ ┌──────────────────┐
407
- │ User │ │ UserCompany │ │ Company │
408
- ├──────────────┤ │ Permission │ ├──────────────────┤
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
- ```
420
-
421
- ### Services
422
-
423
- | Service | Scope | Description |
424
- |---------|-------|-------------|
425
- | `AuthenticationService` | REQUEST | Login, register, token management, company selection |
426
- | `UserService` | REQUEST | User CRUD, extends `RequestScopedApiService` |
427
- | `CompanyService` | REQUEST | Company CRUD (when company feature enabled) |
428
- | `BranchService` | REQUEST | Branch CRUD with company filtering |
429
- | `UserPermissionService` | REQUEST | User-company/branch permission management |
430
- | `AuthDataSourceProvider` | REQUEST | Dynamic datasource for single/multi-tenant |
431
- | `AuthConfigService` | SINGLETON | JWT and module configuration access |
432
- | `CompanySelectionSessionService` | SINGLETON | Session management for company selection |
433
- | `AuthEmailService` | SINGLETON | Email verification and password reset |
434
-
435
534
  ## Login Flows
436
535
 
437
536
  ### Simple Mode (Company Feature Disabled)
@@ -445,8 +544,10 @@ POST /auth/login { email, password }
445
544
  │ .login() │
446
545
  └────────────┬────────────┘
447
546
  │
448
- │ 1. Verify credentials
449
- │ 2. Generate JWT token
547
+ │ 1. Check pending registration
548
+ │ 2. Verify credentials
549
+ │ 3. Check email verified (if email enabled)
550
+ │ 4. Generate JWT tokens
450
551
  ▼
451
552
  { accessToken, refreshToken, user }
452
553
  ```
@@ -485,76 +586,124 @@ POST /auth/select { sessionId, companyId, branchId }
485
586
  { accessToken, refreshToken, user, company, branch }
486
587
  ```
487
588
 
589
+ ### Login with Email Verification Required
590
+
591
+ ```
592
+ POST /auth/login { email, password }
593
+ │
594
+ ▼
595
+ If pending registration OR email not verified:
596
+ { requiresEmailVerification: true, email, message }
597
+ ```
598
+
599
+ ## Email Verification Flow
600
+
601
+ ### New User Registration (with email enabled)
602
+
603
+ ```
604
+ ┌─────────────────────────────────────────────────────────────────┐
605
+ │ POST /auth/register { email, password, name } │
606
+ └─────────────────────────────────────────────────────────────────┘
607
+ │
608
+ ▼
609
+ ┌─────────────────────────────────────────────────────────────────┐
610
+ │ 1. Check existing user (must not exist) │
611
+ │ 2. Hash password with bcrypt (SALT_ROUNDS = 12) │
612
+ │ 3. Generate verification token │
613
+ │ 4. Hash token with SHA-256 for storage │
614
+ │ 5. Create PendingRegistration record │
615
+ │ 6. Send verification email via AUTH_EMAIL_PROVIDER │
616
+ └─────────────────────────────────────────────────────────────────┘
617
+ │
618
+ ▼
619
+ { requiresEmailVerification: true, message: "Check your email" }
620
+ │
621
+ │ User clicks email link
622
+ ▼
623
+ ┌─────────────────────────────────────────────────────────────────┐
624
+ │ POST /auth/verify-email { token } │
625
+ └─────────────────────────────────────────────────────────────────┘
626
+ │
627
+ ▼
628
+ ┌─────────────────────────────────────────────────────────────────┐
629
+ │ 1. Hash incoming token with SHA-256 │
630
+ │ 2. Find PendingRegistration by hashed token │
631
+ │ 3. Check expiration │
632
+ │ 4. Delete PendingRegistration record │
633
+ │ 5. Create User with emailVerified: true │
634
+ │ 6. Process company assignment if companyData present │
635
+ └─────────────────────────────────────────────────────────────────┘
636
+ │
637
+ ▼
638
+ { accessToken, refreshToken, user, company?, branch? }
639
+ ```
640
+
641
+ ### Password Reset Flow
642
+
643
+ ```
644
+ POST /auth/forgot-password { email }
645
+ │
646
+ ▼
647
+ ┌─────────────────────────────────────────────────────────────────┐
648
+ │ 1. Find user by email │
649
+ │ 2. Delete existing PASSWORD_RESET tokens for user │
650
+ │ 3. Generate new token, hash with SHA-256 │
651
+ │ 4. Create UserToken record (expires in 1 hour) │
652
+ │ 5. Send reset email via AUTH_EMAIL_PROVIDER │
653
+ │ 6. Return success (same response even if user not found) │
654
+ └─────────────────────────────────────────────────────────────────┘
655
+ │
656
+ ▼
657
+ { success: true, message: "If email exists, reset link sent" }
658
+ │
659
+ │ User clicks email link
660
+ ▼
661
+ POST /auth/reset-password { token, newPassword }
662
+ │
663
+ ▼
664
+ ┌─────────────────────────────────────────────────────────────────┐
665
+ │ 1. Hash incoming token with SHA-256 │
666
+ │ 2. Find UserToken by hashed token + type: PASSWORD_RESET │
667
+ │ 3. Validate not expired, not used │
668
+ │ 4. Update user password │
669
+ │ 5. Mark token as used (usedAt = now) │
670
+ └─────────────────────────────────────────────────────────────────┘
671
+ │
672
+ ▼
673
+ { success: true, message: "Password reset successfully" }
674
+ ```
675
+
488
676
  ## User Management
489
677
 
490
678
  ### Company Permission Assignment Flow
491
679
 
492
- When creating users with `enableCompanyFeature: true`, the `UserService` automatically assigns company and branch permissions:
493
-
494
- **Flow Diagram:**
680
+ When creating users with `enableCompanyFeature: true`, permissions are automatically assigned:
495
681
 
496
682
  ```
497
683
  User Creation (with companyId/branchId)
498
684
  │
499
685
  ▼
500
- ┌─────────────────────────────────────────────────────────┐
501
- │ 1. Insert user record │
502
- └─────────────────────────────────────────────────────────┘
686
+ ┌─────────────────────────────────────────────────────────────────┐
687
+ │ 1. Insert user record │
688
+ └─────────────────────────────────────────────────────────────────┘
503
689
  │
504
690
  ▼
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
- └─────────────────────────────────────┘
691
+ ┌─────────────────────────────────────────────────────────────────┐
692
+ │ 2. Check if company permission exists │
693
+ │ - Lookup UserCompanyPermission for user + company │
694
+ │ - If not exists: create { userId, targetId: companyId, │
695
+ │ permissionType: 'company' } │
696
+ └─────────────────────────────────────────────────────────────────┘
518
697
  │
519
698
  ▼
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
- └─────────────────────────────────────┘
699
+ ┌─────────────────────────────────────────────────────────────────┐
700
+ │ 3. Check if branch permission exists │
701
+ │ - Lookup UserCompanyPermission for user + branch │
702
+ │ - If not exists: create { userId, targetId: branchId, │
703
+ │ permissionType: 'branch' } │
704
+ └─────────────────────────────────────────────────────────────────┘
533
705
  ```
534
706
 
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
707
  ### User Active Status
559
708
 
560
709
  **When company feature is disabled:**
@@ -564,7 +713,7 @@ await userService.insert({
564
713
  - Updates `UserCompanyPermission.isActive` for the logged user's company
565
714
  - User can be active in Company A but inactive in Company B
566
715
 
567
- ### Registration Flow
716
+ ### Registration Flows
568
717
 
569
718
  **Simple registration:**
570
719
  ```json
@@ -573,94 +722,30 @@ await userService.insert({
573
722
 
574
723
  **Join existing company:**
575
724
  ```json
576
- { "name": "John Doe", "email": "john@example.com", "password": "password123",
577
- "companySlug": "acme-corp", "branchSlug": "main-branch" }
725
+ {
726
+ "name": "John Doe",
727
+ "email": "john@example.com",
728
+ "password": "password123",
729
+ "companySlug": "acme-corp",
730
+ "branchSlug": "main-branch"
731
+ }
578
732
  ```
579
733
 
580
734
  **Create new company:**
581
735
  ```json
582
- { "name": "John Doe", "email": "john@example.com", "password": "password123",
583
- "newCompanyName": "My Company", "newBranchName": "Headquarters" }
584
- ```
585
-
586
- ## Environment Variables
587
-
588
- ```env
589
- # Database
590
- DB_HOST=localhost
591
- DB_PORT=3306
592
- DB_USERNAME=root
593
- DB_PASSWORD=password
594
- DB_DATABASE=myapp
595
-
596
- # JWT
597
- JWT_SECRET=your-secret-key-change-in-production
598
- JWT_EXPIRATION=1h
599
- REFRESH_TOKEN_SECRET=your-refresh-secret
600
- REFRESH_TOKEN_EXPIRATION=7d
601
- REFRESH_TOKEN_COOKIE_NAME=fsn_refresh_token
602
-
603
- # Frontend (for email links)
604
- FRONTEND_URL=http://localhost:2001
605
- ```
606
-
607
- ## Migrations
608
-
609
- See [MIGRATION.md](./MIGRATION.md) for detailed migration commands.
610
-
611
- ```bash
612
- npm run migration:generate --name=InitialSetup
613
- npm run migration:run
614
- npm run migration:status
615
- ```
616
-
617
- ## Configuration Modes
618
-
619
- ### Mode 1: Minimal (No Companies)
620
-
621
- ```typescript
622
- AuthModule.forRoot({
623
- bootstrapAppConfig: { enableCompanyFeature: false },
624
- })
625
-
626
- Entities: User
627
- Features: Basic login, registration
628
- ```
629
-
630
- ### Mode 2: Full Featured (With Companies)
631
-
632
- ```typescript
633
- AuthModule.forRoot({
634
- bootstrapAppConfig: { enableCompanyFeature: true },
635
- })
636
-
637
- Entities: User, Company, CompanyBranch, UserCompanyPermission
638
- Features: Multi-tenant, hierarchical branches
639
- ```
640
-
641
- ---
642
-
643
- ## No-IAM Mode (Bypassing Permission Guards)
644
-
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.
646
-
647
- **Solution:** Populate the cache with wildcard permissions after login.
648
-
649
- ### How Permission Guard Works
650
-
651
- ```
652
- Controller (level: 'permission')
653
- ↓
654
- PermissionGuard.canActivate()
655
- ↓
656
- getUserPermissions() → cache.get(`permissions:user:{userId}`)
657
- ↓
658
- No IAM = Empty cache → NoPermissionsFoundException ❌
736
+ {
737
+ "name": "John Doe",
738
+ "email": "john@example.com",
739
+ "password": "password123",
740
+ "newCompanyName": "My Company",
741
+ "newCompanyPhone": "+1234567890",
742
+ "newCompanyAddress": "123 Main St"
743
+ }
659
744
  ```
660
745
 
661
- ### Fix: Set Wildcard Permissions After Login
746
+ ## No-IAM Mode
662
747
 
663
- Add this to your `AuthenticationService.login()` or create a custom login hook:
748
+ When not using `@flusys/nestjs-iam` but controllers use `level: 'permission'` security, set wildcard permissions after login:
664
749
 
665
750
  ```typescript
666
751
  import { HybridCache, CACHE_INSTANCE, PERMISSIONS_CACHE_PREFIX } from '@flusys/nestjs-shared';
@@ -669,7 +754,6 @@ import { HybridCache, CACHE_INSTANCE, PERMISSIONS_CACHE_PREFIX } from '@flusys/n
669
754
  export class AuthenticationService {
670
755
  constructor(
671
756
  @Inject(CACHE_INSTANCE) private readonly cache: HybridCache,
672
- // ... other dependencies
673
757
  ) {}
674
758
 
675
759
  async login(dto: LoginDto): Promise<LoginResponse> {
@@ -681,10 +765,6 @@ export class AuthenticationService {
681
765
  return { accessToken, user };
682
766
  }
683
767
 
684
- /**
685
- * Sets wildcard permissions for users when IAM module is not installed.
686
- * The '*' permission grants access to all protected endpoints.
687
- */
688
768
  private async setWildcardPermissions(userId: string): Promise<void> {
689
769
  const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:user:${userId}`;
690
770
  await this.cache.set(cacheKey, ['*'], 86400000); // 24 hours TTL
@@ -694,8 +774,6 @@ export class AuthenticationService {
694
774
 
695
775
  ### With Company Feature Enabled
696
776
 
697
- If using `enableCompanyFeature: true`, the cache key format is different:
698
-
699
777
  ```typescript
700
778
  private async setWildcardPermissions(
701
779
  userId: string,
@@ -703,47 +781,103 @@ private async setWildcardPermissions(
703
781
  branchId?: string,
704
782
  ): Promise<void> {
705
783
  if (companyId) {
706
- // Company-scoped permissions
707
784
  const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
708
785
  await this.cache.set(cacheKey, ['*'], 86400000);
709
786
  } else {
710
- // User-level permissions
711
787
  const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:user:${userId}`;
712
788
  await this.cache.set(cacheKey, ['*'], 86400000);
713
789
  }
714
790
  }
715
791
  ```
716
792
 
717
- ### Wildcard Permission Syntax
718
-
719
- The `PermissionGuard` supports these wildcard patterns:
793
+ ### Wildcard Permission Patterns
720
794
 
721
795
  | Pattern | Meaning |
722
796
  |---------|---------|
723
797
  | `*` | Full access to everything |
724
- | `user.*` | All user permissions (user.create, user.read, etc.) |
798
+ | `user.*` | All user permissions |
725
799
  | `branch.*` | All branch permissions |
726
800
 
727
- ### For Jest Tests
801
+ ## API Reference
728
802
 
729
- Mock the cache to return wildcard permissions:
803
+ ### Exports
730
804
 
731
805
  ```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
- });
806
+ // Config
807
+ export { AUTH_MODULE_OPTIONS, BCRYPT_SALT_ROUNDS, DEFAULT_COOKIE_NAME, MAX_COOKIE_BYTES, SESSION_CACHE_PREFIX, TOKEN_REVOKED_PREFIX } from './config';
808
+
809
+ // Entities
810
+ export { User } from './entities/user.entity';
811
+ export { UserToken, TokenType } from './entities/user-token.entity';
812
+ export { PendingRegistration } from './entities/pending-registration.entity';
813
+ export { Company } from './entities/company.entity';
814
+ export { CompanyBranch } from './entities/company-branch.entity';
815
+ export { UserCompanyPermission, PermissionType } from './entities/user-company-permission.entity';
816
+ export { CoreEntities, CompanyEntities, AllEntities, getEntitiesByConfig } from './entities';
817
+
818
+ // Enums
819
+ export { PermissionType } from './enums';
820
+
821
+ // Interfaces
822
+ export { IAuthEmailProvider, AUTH_EMAIL_PROVIDER } from './interfaces/auth-email-provider.interface';
823
+ export { IUserEnricher, IProfileSection, IProfileExtras, IEnrichedUser } from './interfaces/user-enricher.interface';
824
+ export { AuthModuleOptions, AuthModuleAsyncOptions, AuthOptionsFactory } from './interfaces/auth-module-options.interface';
825
+ export { TokenPayload, TokenPayloadWithCompany, LoginResponse, MeResponse, RegistrationResponse } from './interfaces/authentication.interface';
826
+
827
+ // Services
828
+ export { AuthenticationService } from './services/authentication.service';
829
+ export { AuthEmailService } from './services/auth-email.service';
830
+ export { AuthConfigService } from './services/auth-config.service';
831
+ export { AuthDataSourceProvider } from './services/auth-datasource.provider';
832
+ export { UserService } from './services/user.service';
833
+ export { CompanyService } from './services/company.service';
834
+ export { BranchService } from './services/branch.service';
835
+ export { UserPermissionService } from './services/user-permission.service';
836
+ export { CompanySelectionSessionService } from './services/company-selection-session.service';
837
+
838
+ // Modules
839
+ export { AuthModule } from './modules/auth.module';
840
+
841
+ // Strategies
842
+ export { JwtStrategy } from './strategies/jwt.strategy';
843
+
844
+ // Interceptors
845
+ export { SetToken } from './interceptors/set-token.interceptor';
846
+ export { ClearToken } from './interceptors/clear-token.interceptor';
847
+
848
+ // DTOs
849
+ export * from './dtos';
850
+
851
+ // Swagger Config
852
+ export { authSwaggerConfig } from './docs/auth-swagger.config';
853
+ ```
854
+
855
+ ### Module Static Methods
856
+
857
+ ```typescript
858
+ // Get entities based on feature flags
859
+ AuthModule.getEntities(enableCompanyFeature?: boolean): any[]
860
+
861
+ // Configure module
862
+ AuthModule.forRoot(options: AuthModuleOptions): DynamicModule
863
+ AuthModule.forRootAsync(options: AuthModuleAsyncOptions): DynamicModule
745
864
  ```
746
865
 
866
+ ### Swagger Schema Behavior
867
+
868
+ When `enableCompanyFeature: false`, the following are automatically hidden from Swagger:
869
+
870
+ **Excluded Tags:** Companies, Branches, User Permissions, Company Selection
871
+
872
+ **Excluded DTO Properties:**
873
+
874
+ | DTO | Hidden Fields |
875
+ |-----|---------------|
876
+ | `RegistrationDto` | `companySlug`, `branchSlug`, `newCompanyName`, `newCompanyPhone`, `newCompanyAddress` |
877
+ | `RegistrationResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
878
+ | `LoginResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
879
+ | `MeResponseDto` | `company`, `branch` |
880
+
747
881
  ---
748
882
 
749
- **Last Updated:** 2026-02-19
883
+ **Last Updated:** 2026-02-21