@flusys/nestjs-auth 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +610 -95
  2. package/cjs/controllers/authentication.controller.js +2 -173
  3. package/cjs/controllers/email-verification.controller.js +220 -0
  4. package/cjs/controllers/index.js +1 -0
  5. package/cjs/dtos/authentication.dto.js +12 -2
  6. package/cjs/entities/index.js +16 -17
  7. package/cjs/entities/pending-registration.entity.js +9 -0
  8. package/cjs/entities/user-company-permission.entity.js +3 -3
  9. package/cjs/modules/auth.module.js +33 -24
  10. package/cjs/services/auth-config.service.js +11 -7
  11. package/cjs/services/auth-datasource.provider.js +15 -27
  12. package/cjs/services/auth-email.service.js +8 -33
  13. package/cjs/services/authentication.service.js +123 -99
  14. package/cjs/services/user.service.js +28 -83
  15. package/controllers/authentication.controller.d.ts +2 -10
  16. package/controllers/email-verification.controller.d.ts +20 -0
  17. package/controllers/index.d.ts +1 -0
  18. package/dtos/authentication.dto.d.ts +1 -0
  19. package/entities/index.d.ts +5 -10
  20. package/entities/pending-registration.entity.d.ts +1 -0
  21. package/entities/user-company-permission.entity.d.ts +1 -1
  22. package/fesm/controllers/authentication.controller.js +3 -174
  23. package/fesm/controllers/email-verification.controller.js +210 -0
  24. package/fesm/controllers/index.js +1 -0
  25. package/fesm/dtos/authentication.dto.js +32 -25
  26. package/fesm/entities/index.js +17 -9
  27. package/fesm/entities/pending-registration.entity.js +9 -0
  28. package/fesm/entities/user-company-permission.entity.js +3 -3
  29. package/fesm/interfaces/user-enricher.interface.js +2 -24
  30. package/fesm/modules/auth.module.js +35 -26
  31. package/fesm/services/auth-config.service.js +11 -7
  32. package/fesm/services/auth-datasource.provider.js +15 -27
  33. package/fesm/services/auth-email.service.js +8 -33
  34. package/fesm/services/authentication.service.js +124 -100
  35. package/fesm/services/user.service.js +28 -83
  36. package/interfaces/user-enricher.interface.d.ts +3 -2
  37. package/modules/auth.module.d.ts +3 -1
  38. package/package.json +3 -3
  39. package/services/auth-config.service.d.ts +1 -0
  40. package/services/auth-datasource.provider.d.ts +1 -0
  41. package/services/auth-email.service.d.ts +1 -0
  42. package/services/authentication.service.d.ts +4 -1
  43. package/services/user.service.d.ts +5 -0
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Authentication Package Guide
2
2
 
3
3
  > **Package:** `@flusys/nestjs-auth`
4
+ > **Version:** 3.0.0
4
5
  > **Type:** Authentication system with JWT, email verification, multi-tenant, and company/branch support
5
6
 
6
7
  ## Table of Contents
@@ -17,6 +18,9 @@
17
18
  - [Email Verification Flow](#email-verification-flow)
18
19
  - [User Management](#user-management)
19
20
  - [No-IAM Mode](#no-iam-mode)
21
+ - [JWT Strategy](#jwt-strategy)
22
+ - [Interceptors](#interceptors)
23
+ - [Validators](#validators)
20
24
  - [API Reference](#api-reference)
21
25
 
22
26
  ---
@@ -153,6 +157,7 @@ interface AuthModuleOptions {
153
157
  bootstrapAppConfig?: {
154
158
  databaseMode: 'single' | 'multi-tenant';
155
159
  enableCompanyFeature: boolean;
160
+ enableEmailVerification?: boolean; // Default: true
156
161
  };
157
162
  config?: {
158
163
  defaultDatabaseConfig?: IDatabaseConfig;
@@ -169,6 +174,13 @@ interface AuthModuleOptions {
169
174
  }
170
175
  ```
171
176
 
177
+ **Bootstrap Configuration Flags:**
178
+
179
+ | Flag | Type | Default | Effect |
180
+ |------|------|---------|--------|
181
+ | `enableCompanyFeature` | `boolean` | `false` | Enables company/branch entities and APIs |
182
+ | `enableEmailVerification` | `boolean` | `true` | When `false`: excludes `AuthEmailService`, hides email-related APIs (forgot-password, reset-password, verify-email, resend-verification), and excludes `PendingRegistration` entity from migrations |
183
+
172
184
  ## Constants
173
185
 
174
186
  ```typescript
@@ -192,22 +204,53 @@ export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
192
204
 
193
205
  ### Entity Groups
194
206
 
195
- ```typescript
196
- // Core entities (always loaded)
197
- export const CoreEntities = [User, UserToken, PendingRegistration];
207
+ The auth module organizes entities into groups based on feature flags:
198
208
 
199
- // Company entities (when enableCompanyFeature: true)
200
- export const CompanyEntities = [Company, CompanyBranch, UserCompanyPermission];
209
+ ```typescript
210
+ // Internal entity groups (not exported directly)
211
+ // Base entities: User, UserToken
212
+ // Email entities: PendingRegistration
213
+ // Company entities: Company, CompanyBranch, UserCompanyPermission
214
+
215
+ // Exported interface for configuration
216
+ export interface IAuthEntitiesConfig {
217
+ enableCompanyFeature?: boolean;
218
+ enableEmailVerification?: boolean; // Default: true
219
+ }
201
220
 
202
- // All entities
203
- export const AllEntities = [...CoreEntities, ...CompanyEntities];
221
+ // Get entities based on feature flags (exported)
222
+ export function getEntitiesByConfig(config: IAuthEntitiesConfig): any[] {
223
+ const { enableCompanyFeature = false, enableEmailVerification = true } = config;
204
224
 
205
- // Helper function
206
- export function getEntitiesByConfig(enableCompanyFeature: boolean) {
207
- return enableCompanyFeature ? AllEntities : CoreEntities;
225
+ const entities = [User, UserToken];
226
+ if (enableEmailVerification) entities.push(PendingRegistration);
227
+ if (enableCompanyFeature) entities.push(Company, CompanyBranch, UserCompanyPermission);
228
+ return entities;
208
229
  }
209
230
  ```
210
231
 
232
+ **Usage:**
233
+ ```typescript
234
+ import { getEntitiesByConfig } from '@flusys/nestjs-auth/entities';
235
+
236
+ // Get entities for single database without company feature
237
+ const entities = getEntitiesByConfig({ enableCompanyFeature: false });
238
+ // Returns: [User, UserToken, PendingRegistration]
239
+
240
+ // Get entities with company feature, no email verification
241
+ const entities = getEntitiesByConfig({ enableCompanyFeature: true, enableEmailVerification: false });
242
+ // Returns: [User, UserToken, Company, CompanyBranch, UserCompanyPermission]
243
+ ```
244
+
245
+ **Entity Selection by Feature Flags:**
246
+
247
+ | enableEmailVerification | enableCompanyFeature | Entities |
248
+ |------------------------|---------------------|----------|
249
+ | `true` (default) | `false` | User, UserToken, PendingRegistration |
250
+ | `true` | `true` | All entities |
251
+ | `false` | `false` | User, UserToken only |
252
+ | `false` | `true` | User, UserToken, Company, CompanyBranch, UserCompanyPermission |
253
+
211
254
  ### User Entity
212
255
 
213
256
  Extends `UserRoot` from `@flusys/nestjs-shared`:
@@ -271,6 +314,7 @@ Stores registration data until email verification (prevents garbage accounts):
271
314
  | `token` | `varchar(64)` | SHA-256 hashed verification token |
272
315
  | `expiresAt` | `timestamp` | Expiration (24 hours) |
273
316
  | `companyData` | `json` | Company registration data |
317
+ | `additionalFields` | `json` | Extension fields for USER_ENRICHER |
274
318
  | `createdAt` | `timestamp` | Auto-generated |
275
319
 
276
320
  **Indexes:**
@@ -283,25 +327,48 @@ Stores registration data until email verification (prevents garbage accounts):
283
327
  | Field | Type | Description |
284
328
  |-------|------|-------------|
285
329
  | `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 |
330
+ | `readOnly` | `boolean` | Read-only flag (default: false) |
331
+ | `name` | `varchar(255)` | Company name (unique) |
332
+ | `slug` | `varchar(255)` | URL-friendly identifier (unique) |
333
+ | `logoId` | `char(36)` | Reference to storage file |
334
+ | `address` | `text` | Company address |
335
+ | `phone` | `varchar(50)` | Contact phone |
336
+ | `email` | `varchar(255)` | Contact email |
337
+ | `website` | `varchar(100)` | Company website URL |
338
+ | `serial` | `int` | Display order |
339
+ | `isActive` | `boolean` | Company status (default: true) |
340
+ | `settings` | `json` | Company-specific settings |
341
+ | `additionalFields` | `json` | Extension fields |
342
+ | `createdAt` | `timestamp` | Auto-generated |
343
+ | `updatedAt` | `timestamp` | Auto-updated |
344
+
345
+ **Relations:**
346
+ - `branches` - One-to-Many relation to CompanyBranch
292
347
 
293
348
  ### CompanyBranch Entity
294
349
 
295
350
  | Field | Type | Description |
296
351
  |-------|------|-------------|
297
352
  | `id` | `uuid` | Primary key |
353
+ | `readOnly` | `boolean` | Read-only flag (default: false) |
298
354
  | `name` | `varchar(255)` | Branch name |
299
- | `slug` | `varchar(255)` | URL-friendly identifier |
300
- | `companyId` | `uuid` | Parent company |
301
- | `parentId` | `uuid` | Parent branch (hierarchical) |
355
+ | `slug` | `varchar(255)` | URL-friendly identifier (unique per company) |
356
+ | `logoId` | `char(36)` | Reference to storage file |
357
+ | `address` | `text` | Branch address |
358
+ | `phone` | `varchar(50)` | Branch phone |
359
+ | `email` | `varchar(255)` | Branch email |
302
360
  | `serial` | `int` | Display order |
303
- | `logoId` | `uuid` | Reference to storage file |
304
- | `isActive` | `boolean` | Branch status |
361
+ | `isActive` | `boolean` | Branch status (default: true) |
362
+ | `additionalFields` | `json` | Extension fields |
363
+ | `parentId` | `uuid` | Parent branch (for hierarchical branches) |
364
+ | `companyId` | `uuid` | Parent company ID |
365
+ | `createdAt` | `timestamp` | Auto-generated |
366
+ | `updatedAt` | `timestamp` | Auto-updated |
367
+
368
+ **Relations:**
369
+ - `company` - Many-to-One relation to Company (CASCADE delete)
370
+ - `parent` - Many-to-One relation to self (hierarchical, CASCADE delete)
371
+ - `children` - One-to-Many relation to self
305
372
 
306
373
  ### UserCompanyPermission Entity (Polymorphic)
307
374
 
@@ -316,9 +383,22 @@ enum PermissionType {
316
383
  |-------|------|-------------|
317
384
  | `id` | `uuid` | Primary key |
318
385
  | `userId` | `uuid` | Reference to user |
319
- | `permissionType` | `PermissionType` | company or branch |
386
+ | `permissionType` | `varchar(50)` | company or branch |
320
387
  | `targetId` | `uuid` | Company ID or Branch ID |
321
- | `isActive` | `boolean` | Permission status |
388
+ | `metadata` | `json` | Optional metadata for the permission |
389
+ | `isActive` | `boolean` | Permission status (default: true) |
390
+ | `createdAt` | `timestamp` | Auto-generated |
391
+ | `updatedAt` | `timestamp` | Auto-updated |
392
+
393
+ **Indexes:**
394
+ - Unique index on `(userId, permissionType, targetId)`
395
+ - Index on `(userId, permissionType)`
396
+ - Index on `(permissionType, targetId)`
397
+
398
+ **Relations:**
399
+ - `user` - Many-to-One relation to User (CASCADE delete)
400
+ - `company` - Many-to-One relation to Company (no FK constraint - polymorphic)
401
+ - `branch` - Many-to-One relation to CompanyBranch (no FK constraint - polymorphic)
322
402
 
323
403
  ## Provider Interfaces
324
404
 
@@ -370,6 +450,8 @@ Extends user list and profile without modifying base package:
370
450
 
371
451
  ```typescript
372
452
  interface IUserEnricher {
453
+ // ===== List Enrichment =====
454
+
373
455
  /** Enrich user list query with extra joins/selects */
374
456
  enrichListQuery?(
375
457
  query: SelectQueryBuilder<User>,
@@ -382,13 +464,15 @@ interface IUserEnricher {
382
464
  user: ILoggedUserInfo | null,
383
465
  ): Promise<IEnrichedUser[]>;
384
466
 
467
+ // ===== Profile Enrichment =====
468
+
385
469
  /** Get extra profile data (roles, permissions, sections) */
386
470
  getProfileExtras?(
387
471
  userId: string,
388
472
  user: ILoggedUserInfo,
389
473
  ): Promise<IProfileExtras>;
390
474
 
391
- /** Handle profile update with extra fields */
475
+ /** Handle profile update with extra fields (within transaction) */
392
476
  updateProfileExtras?(
393
477
  userId: string,
394
478
  extras: Record<string, any>,
@@ -402,27 +486,92 @@ interface IUserEnricher {
402
486
  user: ILoggedUserInfo,
403
487
  ): Promise<void>;
404
488
 
405
- // Multi-Step Profile Methods (LEADPRO-style)
489
+ // ===== Multi-Step Profile Methods =====
490
+
491
+ /** Get profile section definitions for multi-step profiles */
406
492
  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>;
493
+
494
+ /** Get specific section data for a user */
495
+ getProfileSectionData?(
496
+ userId: string,
497
+ sectionId: string,
498
+ user: ILoggedUserInfo,
499
+ ): Promise<IProfileSectionData>;
500
+
501
+ /** Update specific profile section (within transaction) */
502
+ updateProfileSection?(
503
+ userId: string,
504
+ sectionId: string,
505
+ data: Record<string, any>,
506
+ queryRunner: QueryRunner,
507
+ ): Promise<void>;
508
+
509
+ /** Handle file upload for profile section */
510
+ handleSectionFileUpload?(
511
+ userId: string,
512
+ sectionId: string,
513
+ field: string,
514
+ fileInfo: { name: string; url: string; size: number; type: string },
515
+ queryRunner: QueryRunner,
516
+ ): Promise<IProfileFile>;
517
+
518
+ /** Handle file deletion for profile section */
519
+ handleSectionFileDelete?(
520
+ userId: string,
521
+ sectionId: string,
522
+ field: string,
523
+ fileId: string,
524
+ queryRunner: QueryRunner,
525
+ ): Promise<void>;
526
+
527
+ /** Calculate profile completion percentage (0-100) */
528
+ calculateProfileCompletion?(
529
+ userId: string,
530
+ user: ILoggedUserInfo,
531
+ ): Promise<number>;
532
+
533
+ // ===== Registration Hook =====
534
+
535
+ /**
536
+ * Hook called after user is created during registration
537
+ * Runs within the same database transaction as user creation
538
+ * Use this to create related entities (e.g., UserProfileInfo)
539
+ * Throw error to rollback the entire registration
540
+ */
541
+ onUserCreated?(
542
+ userId: string,
543
+ additionalFields: Record<string, any> | null,
544
+ queryRunner: QueryRunner,
545
+ ): Promise<void>;
412
546
  }
413
547
  ```
414
548
 
415
- **Profile Section Interface (for multi-step profiles):**
549
+ **Profile Related Interfaces:**
416
550
 
417
551
  ```typescript
418
552
  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
553
+ id: string; // Unique identifier
554
+ name: string; // Display name
555
+ order: number; // Stepper order
556
+ icon?: string; // PrimeNG icon class
557
+ required?: boolean; // Required for completion
558
+ hasFileUpload?: boolean; // Has file fields
559
+ editPermission?: IPermissionLogic; // Required permission
560
+ }
561
+
562
+ interface IProfileSectionData {
563
+ sectionId: string;
564
+ data: Record<string, any>;
565
+ isComplete: boolean;
566
+ files?: IProfileFile[];
567
+ }
568
+
569
+ interface IProfileFile {
570
+ field: string;
571
+ id: string;
572
+ name: string;
573
+ url?: string;
574
+ type?: string;
426
575
  }
427
576
 
428
577
  interface IProfileExtras {
@@ -431,6 +580,73 @@ interface IProfileExtras {
431
580
  sections?: IProfileSectionData[];
432
581
  completionPercentage?: number;
433
582
  }
583
+
584
+ interface IProfileRole {
585
+ id: string;
586
+ name: string;
587
+ description?: string | null;
588
+ }
589
+
590
+ interface IProfileAction {
591
+ id: string;
592
+ name: string;
593
+ code: string;
594
+ description?: string | null;
595
+ }
596
+
597
+ type IEnrichedUser = IUser & IProfileExtras;
598
+ ```
599
+
600
+ **Registration Hook Example (UserProfileInfo):**
601
+
602
+ ```typescript
603
+ // In your consuming app (e.g., FLUSYS-TEMPLATE)
604
+ import { USER_ENRICHER, IUserEnricher } from '@flusys/nestjs-auth';
605
+ import { QueryRunner } from 'typeorm';
606
+
607
+ @Injectable({ scope: Scope.REQUEST })
608
+ export class MyUserEnricher implements IUserEnricher {
609
+ /**
610
+ * Called after user creation, within the same transaction
611
+ * If this throws, the entire registration rolls back
612
+ */
613
+ async onUserCreated(
614
+ userId: string,
615
+ additionalFields: Record<string, any> | null,
616
+ queryRunner: QueryRunner,
617
+ ): Promise<void> {
618
+ if (!additionalFields) return;
619
+
620
+ // Create related entity (e.g., UserProfileInfo)
621
+ const profileInfo = queryRunner.manager.create(UserProfileInfo, {
622
+ userId,
623
+ prevCompanyName: additionalFields.prevCompanyName ?? null,
624
+ prevCompanyLocation: additionalFields.prevCompanyLocation ?? null,
625
+ });
626
+
627
+ await queryRunner.manager.save(UserProfileInfo, profileInfo);
628
+ }
629
+ }
630
+
631
+ // Register the provider
632
+ {
633
+ provide: USER_ENRICHER,
634
+ useClass: MyUserEnricher,
635
+ }
636
+ ```
637
+
638
+ **Registration with additionalFields:**
639
+
640
+ ```json
641
+ {
642
+ "name": "John Doe",
643
+ "email": "john@example.com",
644
+ "password": "password123",
645
+ "additionalFields": {
646
+ "prevCompanyName": "Acme Corp",
647
+ "prevCompanyLocation": "New York"
648
+ }
649
+ }
434
650
  ```
435
651
 
436
652
  ## Services
@@ -458,7 +674,7 @@ Token Security: All tokens are stored as SHA-256 hashes. Raw tokens are sent to
458
674
  | `createPendingRegistration(data)` | Create pending registration with verification email |
459
675
  | `forgotPassword(email, resetUrlBase)` | Send password reset email |
460
676
  | `resetPassword(token, newPassword)` | Reset password using token |
461
- | `verifyEmail(token)` | Verify email (pending registration or existing user) |
677
+ | `verifyEmail(token)` | Verify email (returns `{ type: 'pending', pending }` or `{ type: 'verified', message }`) |
462
678
  | `resendVerificationEmail(email, verifyUrlBase)` | Resend verification email |
463
679
  | `cleanupExpired()` | Clean up expired tokens and pending registrations |
464
680
 
@@ -466,70 +682,204 @@ Token Security: All tokens are stored as SHA-256 hashes. Raw tokens are sent to
466
682
  - Password Reset: 1 hour
467
683
  - Email Verification: 24 hours
468
684
 
685
+ ### AuthenticationService
686
+
687
+ | Method | Description |
688
+ |--------|-------------|
689
+ | `login(email, password)` | Validate credentials and return tokens |
690
+ | `register(dto, verifyUrlBase?)` | Register new user (creates pending registration if email enabled) |
691
+ | `createUserFromPendingRegistration(pending)` | Create user from verified pending registration |
692
+ | `select(sessionId, companyId, branchId)` | Complete company selection after login |
693
+ | `switchCompany(userId, companyId, branchId)` | Switch to different company/branch |
694
+ | `refreshToken(refreshToken)` | Refresh access token |
695
+ | `changePassword(userId, currentPassword, newPassword)` | Change user password |
696
+ | `logout(userId)` | Revoke all tokens for user |
697
+ | `isTokenRevoked(userId, tokenIssuedAt)` | Check if token is revoked |
698
+ | `getUserCompanies(userId)` | Get user's available companies |
699
+ | `getUserBranches(userId, companyId)` | Get user's branches for a company |
700
+ | `getUserInfo(userId, companyId?, branchId?)` | Get current user info |
701
+
702
+ ### UserService
703
+
704
+ Extends `RequestScopedApiService` for full CRUD support.
705
+
706
+ | Method | Description |
707
+ |--------|-------------|
708
+ | `insert(dto, user)` | Create user (auto-assigns company permissions) |
709
+ | `verifyEmail(id)` | Mark user email as verified |
710
+ | `verifyPhone(id)` | Mark user phone as verified |
711
+ | `updateStatus(id, isActive, user)` | Update user status (company-aware) |
712
+ | `profile(dto, user)` | Update own profile with optional extras |
713
+ | `getProfileWithExtras(userId, user)` | Get profile with enricher data |
714
+ | `getProfileSections()` | Get profile section definitions |
715
+ | `getProfileSectionData(userId, sectionId, user)` | Get specific section data |
716
+ | `updateProfileSection(userId, sectionId, data, user)` | Update specific section |
717
+ | `getProfileCompletion(userId, user)` | Calculate profile completion % |
718
+ | `getEnrichedList(users, user)` | Enrich user list with extra data |
719
+
720
+ ### CompanyService
721
+
722
+ Extends `RequestScopedApiService` for full CRUD support on Company entity.
723
+
724
+ ### BranchService
725
+
726
+ Extends `RequestScopedApiService` for full CRUD support on CompanyBranch entity.
727
+ Automatically applies company filter based on logged user context.
728
+
729
+ ### UserPermissionService
730
+
731
+ | Method | Description |
732
+ |--------|-------------|
733
+ | `findUserPermissions(userId, type)` | Get all permissions of type for user |
734
+ | `assignPermission(userId, targetId, type, metadata?)` | Assign permission to user |
735
+ | `revokePermission(userId, targetId, type)` | Revoke permission from user |
736
+ | `findUserCompanies(userId)` | Get user's company permissions |
737
+ | `findUserBranches(userId, companyId?)` | Get user's branch permissions |
738
+
739
+ ### CompanySelectionSessionService
740
+
741
+ Manages temporary sessions during company/branch selection flow.
742
+
743
+ | Method | Description |
744
+ |--------|-------------|
745
+ | `createSession(userId)` | Create session (returns sessionId, TTL: 5 minutes) |
746
+ | `getSession(sessionId)` | Get session without consuming |
747
+ | `consumeSession(sessionId)` | Get and delete session (one-time use) |
748
+ | `deleteSession(sessionId)` | Delete session |
749
+
750
+ ### AuthConfigService
751
+
752
+ Provides configuration access.
753
+
754
+ | Method | Description |
755
+ |--------|-------------|
756
+ | `getDatabaseMode()` | Returns 'single' or 'multi-tenant' |
757
+ | `isMultiTenant()` | Check if multi-tenant mode |
758
+ | `isCompanyFeatureEnabled()` | Check if company feature enabled |
759
+ | `isEmailVerificationEnabled()` | Check if email verification enabled |
760
+ | `getJwtSecret()` | Get JWT secret (throws if not configured) |
761
+ | `getJwtExpiration()` | Get JWT expiration (default: '1h') |
762
+ | `getRefreshTokenSecret()` | Get refresh token secret |
763
+ | `getRefreshTokenExpiration()` | Get refresh expiration (default: '7d') |
764
+ | `getRefreshTokenCookieName()` | Get cookie name (default: 'fsn_refresh_token') |
765
+ | `getFrontendUrl()` | Get frontend URL for email links |
766
+
767
+ ### AuthDataSourceProvider
768
+
769
+ Extends `MultiTenantDataSourceService` for dynamic datasource handling.
770
+
771
+ | Method | Description |
772
+ |--------|-------------|
773
+ | `getDataSource()` | Get current tenant's DataSource |
774
+ | `getRepository(entity)` | Get repository for entity |
775
+ | `isCompanyFeatureEnabled(tenant?)` | Check company feature for tenant |
776
+ | `isEmailVerificationEnabled(tenant?)` | Check email verification for tenant |
777
+ | `getAuthEntities()` | Get entities based on feature flags |
778
+
469
779
  ## API Endpoints
470
780
 
781
+ > **Note:** All endpoints use POST method following POST-only RPC pattern.
782
+
471
783
  ### Authentication Endpoints
472
784
 
473
- | Endpoint | Method | Description |
474
- |----------|--------|-------------|
475
- | `/auth/login` | POST | Login with email/password |
476
- | `/auth/register` | POST | Register new user |
477
- | `/auth/refresh` | POST | Refresh access token |
478
- | `/auth/logout` | POST | Logout (revokes all tokens) |
479
- | `/auth/change-password` | POST | Change user password |
480
- | `/auth/me` | GET | Get current user info |
785
+ | Endpoint | Method | Auth | Description |
786
+ |----------|--------|------|-------------|
787
+ | `/auth/login` | POST | No | Login with email/password |
788
+ | `/auth/register` | POST | No | Register new user |
789
+ | `/auth/refresh` | POST | No | Refresh access token |
790
+ | `/auth/logout` | POST | JWT | Logout (revokes all tokens) |
791
+ | `/auth/change-password` | POST | JWT | Change user password |
792
+ | `/auth/me` | POST | JWT | Get current user info |
793
+
794
+ **Rate Limits:**
795
+ - `/auth/login` - 5 attempts per minute
796
+ - `/auth/register` - 3 attempts per minute
797
+ - `/auth/refresh` - 30 attempts per minute
481
798
 
482
799
  ### Email Authentication Endpoints
483
800
 
484
- | Endpoint | Method | Description |
485
- |----------|--------|-------------|
486
- | `/auth/forgot-password` | POST | Request password reset email |
487
- | `/auth/reset-password` | POST | Reset password using token |
488
- | `/auth/verify-email` | POST | Verify email using token |
489
- | `/auth/resend-verification` | POST | Resend email verification link |
801
+ | Endpoint | Method | Auth | Description |
802
+ |----------|--------|------|-------------|
803
+ | `/auth/forgot-password` | POST | No | Request password reset email |
804
+ | `/auth/reset-password` | POST | No | Reset password using token |
805
+ | `/auth/verify-email` | POST | No | Verify email using token |
806
+ | `/auth/resend-verification` | POST | No | Resend email verification link |
807
+
808
+ **Rate Limits:**
809
+ - `/auth/forgot-password` - 3 requests per 5 minutes
810
+ - `/auth/reset-password` - 5 attempts per 15 minutes
811
+ - `/auth/verify-email` - 10 attempts per minute
812
+ - `/auth/resend-verification` - 3 requests per 5 minutes
490
813
 
491
- > **Note:** Email endpoints require `AUTH_EMAIL_PROVIDER` to be configured.
814
+ > **Note:** Email endpoints require `AUTH_EMAIL_PROVIDER` to be configured and `enableEmailVerification: true`.
492
815
 
493
816
  ### Company Selection Endpoints (when company feature enabled)
494
817
 
495
- | Endpoint | Method | Description |
496
- |----------|--------|-------------|
497
- | `/auth/select` | POST | Select company and branch after login |
498
- | `/auth/switch-company` | POST | Switch to different company/branch |
499
- | `/auth/companies` | GET | Get user's available companies |
500
- | `/auth/companies/:companyId/branches` | GET | Get branches for a company |
818
+ | Endpoint | Method | Auth | Description |
819
+ |----------|--------|------|-------------|
820
+ | `/auth/select` | POST | No | Select company and branch after login |
821
+ | `/auth/switch-company` | POST | JWT | Switch to different company/branch |
822
+ | `/auth/get-companies` | POST | JWT | Get user's available companies |
823
+ | `/auth/get-company-branches` | POST | JWT | Get branches for a company |
501
824
 
502
825
  ### User Management Endpoints
503
826
 
504
- | Endpoint | Method | Description |
505
- |----------|--------|-------------|
506
- | `/administration/users/insert` | POST | Create user |
507
- | `/administration/users/insert-many` | POST | Bulk create users |
508
- | `/administration/users/get/:id` | POST | Get user by ID |
509
- | `/administration/users/get-all` | POST | Get paginated user list |
510
- | `/administration/users/update` | POST | Update user |
511
- | `/administration/users/update-many` | POST | Bulk update users |
512
- | `/administration/users/delete` | POST | Delete/restore/permanent delete |
513
- | `/administration/users/profile` | POST | Update own profile |
514
- | `/administration/users/:id/verify-email` | POST | Mark email as verified |
515
- | `/administration/users/:id/verify-phone` | POST | Mark phone as verified |
516
- | `/administration/users/:id/status` | PUT | Update user active status |
827
+ | Endpoint | Method | Auth | Permission | Description |
828
+ |----------|--------|------|------------|-------------|
829
+ | `/administration/users/insert` | POST | JWT | `user.create` | Create user |
830
+ | `/administration/users/insert-many` | POST | JWT | `user.create` | Bulk create users |
831
+ | `/administration/users/get/:id` | POST | JWT | `user.read` | Get user by ID |
832
+ | `/administration/users/get-all` | POST | JWT | `user.read` | Get paginated user list |
833
+ | `/administration/users/update` | POST | JWT | `user.update` | Update user |
834
+ | `/administration/users/update-many` | POST | JWT | `user.update` | Bulk update users |
835
+ | `/administration/users/delete` | POST | JWT | `user.delete` | Delete/restore/permanent delete |
836
+ | `/administration/users/profile` | POST | JWT | - | Update own profile |
837
+ | `/administration/users/:id/verify-email` | POST | JWT | `user.update` | Mark email as verified |
838
+ | `/administration/users/:id/verify-phone` | POST | JWT | `user.update` | Mark phone as verified |
839
+ | `/administration/users/:id/status` | POST | JWT | `user.update` | Update user active status |
840
+
841
+ ### User Profile Extensions Endpoints
842
+
843
+ | Endpoint | Method | Auth | Permission | Description |
844
+ |----------|--------|------|------------|-------------|
845
+ | `/administration/users/:id/profile-extras` | POST | JWT | `user.read` | Get profile with enricher extras |
846
+ | `/administration/users/profile-sections` | POST | JWT | `user.read` | Get profile section definitions |
847
+ | `/administration/users/:id/profile-section/:sectionId` | POST | JWT | `user.read` | Get specific section data |
848
+ | `/administration/users/:id/profile-section/:sectionId/update` | POST | JWT | `user.update` | Update specific section |
849
+ | `/administration/users/:id/profile-completion` | POST | JWT | `user.read` | Get profile completion % |
517
850
 
518
851
  ### User Permission Endpoints (when company feature enabled)
519
852
 
520
- | Endpoint | Method | Description |
521
- |----------|--------|-------------|
522
- | `/administration/permissions/user-company/assign` | POST | Batch assign/revoke user-company permissions |
523
- | `/administration/permissions/user-company` | GET | Get user's assigned companies |
524
- | `/administration/permissions/user-branch/assign` | POST | Batch assign/revoke user-branch permissions |
525
- | `/administration/permissions/user-branch` | GET | Get user's assigned branches |
526
-
527
- ### Company/Branch Management Endpoints (when company feature enabled)
528
-
529
- | Resource | Endpoints |
530
- |----------|-----------|
531
- | Companies | `/administration/company` - CRUD operations |
532
- | Branches | `/administration/branch` - CRUD operations |
853
+ | Endpoint | Method | Auth | Permission | Description |
854
+ |----------|--------|------|------------|-------------|
855
+ | `/administration/permissions/user-company/assign` | POST | JWT | `user.update` | Batch assign/revoke user-company permissions |
856
+ | `/administration/permissions/get-user-company` | POST | JWT | `user.read` | Get user's assigned companies |
857
+ | `/administration/permissions/user-branch/assign` | POST | JWT | `user.update` | Batch assign/revoke user-branch permissions |
858
+ | `/administration/permissions/get-user-branch` | POST | JWT | `user.read` | Get user's assigned branches |
859
+
860
+ ### Company Management Endpoints (when company feature enabled)
861
+
862
+ | Endpoint | Method | Auth | Permission | Description |
863
+ |----------|--------|------|------------|-------------|
864
+ | `/administration/company/insert` | POST | JWT | `company.create` | Create company |
865
+ | `/administration/company/insert-many` | POST | JWT | `company.create` | Bulk create companies |
866
+ | `/administration/company/get/:id` | POST | JWT | `company.read` | Get company by ID |
867
+ | `/administration/company/get-all` | POST | JWT | `company.read` | Get paginated company list |
868
+ | `/administration/company/update` | POST | JWT | `company.update` | Update company |
869
+ | `/administration/company/update-many` | POST | JWT | `company.update` | Bulk update companies |
870
+ | `/administration/company/delete` | POST | JWT | `company.delete` | Delete/restore/permanent delete |
871
+
872
+ ### Branch Management Endpoints (when company feature enabled)
873
+
874
+ | Endpoint | Method | Auth | Permission | Description |
875
+ |----------|--------|------|------------|-------------|
876
+ | `/administration/branch/insert` | POST | JWT | `branch.create` | Create branch |
877
+ | `/administration/branch/insert-many` | POST | JWT | `branch.create` | Bulk create branches |
878
+ | `/administration/branch/get/:id` | POST | JWT | `branch.read` | Get branch by ID |
879
+ | `/administration/branch/get-all` | POST | JWT | `branch.read` | Get paginated branch list |
880
+ | `/administration/branch/update` | POST | JWT | `branch.update` | Update branch |
881
+ | `/administration/branch/update-many` | POST | JWT | `branch.update` | Bulk update branches |
882
+ | `/administration/branch/delete` | POST | JWT | `branch.delete` | Delete/restore/permanent delete |
533
883
 
534
884
  ## Login Flows
535
885
 
@@ -743,6 +1093,22 @@ User Creation (with companyId/branchId)
743
1093
  }
744
1094
  ```
745
1095
 
1096
+ **Registration with extension fields (additionalFields):**
1097
+ ```json
1098
+ {
1099
+ "name": "John Doe",
1100
+ "email": "john@example.com",
1101
+ "password": "password123",
1102
+ "additionalFields": {
1103
+ "prevCompanyName": "Acme Corporation",
1104
+ "prevCompanyLocation": "New York, USA",
1105
+ "prevIndustryType": "technology"
1106
+ }
1107
+ }
1108
+ ```
1109
+
1110
+ > **Note:** `additionalFields` are passed to the `USER_ENRICHER.onUserCreated` hook within the same database transaction. If the hook throws an error, the entire registration is rolled back, ensuring atomicity.
1111
+
746
1112
  ## No-IAM Mode
747
1113
 
748
1114
  When not using `@flusys/nestjs-iam` but controllers use `level: 'permission'` security, set wildcard permissions after login:
@@ -798,13 +1164,112 @@ private async setWildcardPermissions(
798
1164
  | `user.*` | All user permissions |
799
1165
  | `branch.*` | All branch permissions |
800
1166
 
1167
+ ## JWT Strategy
1168
+
1169
+ The `JwtStrategy` is a singleton Passport strategy that validates JWT tokens and extracts user info.
1170
+
1171
+ **Token Payload:**
1172
+ ```typescript
1173
+ interface TokenPayload {
1174
+ id: string; // User ID
1175
+ email: string; // User email
1176
+ type: 'access' | 'refresh'; // Token type
1177
+ profilePictureId?: string; // Profile picture file ID
1178
+ tenantId?: string; // Tenant ID (multi-tenant)
1179
+ iat?: number; // Issued at
1180
+ exp?: number; // Expiration
1181
+ }
1182
+
1183
+ // When company feature enabled
1184
+ interface TokenPayloadWithCompany extends TokenPayload {
1185
+ companyId: string; // Selected company ID
1186
+ branchId?: string; // Selected branch ID (optional)
1187
+ companyLogoId?: string | null; // Company logo file ID
1188
+ branchLogoId?: string | null; // Branch logo file ID
1189
+ }
1190
+ ```
1191
+
1192
+ **Validation Flow:**
1193
+ 1. Extract token from `Authorization: Bearer <token>` header
1194
+ 2. Verify token signature using JWT secret
1195
+ 3. Check token revocation status (if cache available)
1196
+ 4. Validate user exists and is active
1197
+ 5. Return `ILoggedUserInfo` with user context
1198
+
1199
+ **Token Revocation:**
1200
+ - Uses cache to track revoked tokens
1201
+ - Key format: `auth:revoked:{userId}`
1202
+ - Stores timestamp of last logout
1203
+ - Tokens issued before logout timestamp are rejected
1204
+
1205
+ ## Interceptors
1206
+
1207
+ ### SetToken Interceptor
1208
+
1209
+ Automatically sets refresh token as HTTP-only cookie for browser clients:
1210
+
1211
+ ```typescript
1212
+ @UseInterceptors(SetToken)
1213
+ async login(@Body() loginDto: LoginDto) { ... }
1214
+ ```
1215
+
1216
+ **Behavior:**
1217
+ - Detects browser requests via `User-Agent` and `x-client-type` header
1218
+ - For browsers: Sets `refreshToken` as HTTP-only cookie, removes from response body
1219
+ - For API/Mobile: Returns `refreshToken` in response body
1220
+ - Cookie options: `httpOnly: true`, `secure: true` (production), `sameSite: 'lax'`
1221
+
1222
+ ### ClearToken Interceptor
1223
+
1224
+ Clears refresh token cookie on logout:
1225
+
1226
+ ```typescript
1227
+ @UseInterceptors(ClearToken)
1228
+ async logout(@CurrentUser('id') userId: string) { ... }
1229
+ ```
1230
+
1231
+ ## Validators
1232
+
1233
+ ### IsStrongPassword
1234
+
1235
+ Custom password validation decorator:
1236
+
1237
+ ```typescript
1238
+ @IsStrongPassword()
1239
+ password!: string;
1240
+
1241
+ // With custom options
1242
+ @IsStrongPassword({ minLength: 12, requireSpecialChar: false })
1243
+ password!: string;
1244
+ ```
1245
+
1246
+ **Default Requirements:**
1247
+ - Minimum 8 characters
1248
+ - Maximum 128 characters
1249
+ - At least one uppercase letter
1250
+ - At least one lowercase letter
1251
+ - At least one number
1252
+ - At least one special character (`!@#$%^&*()_+-=[]{}...`)
1253
+
1254
+ **Custom Options:**
1255
+ ```typescript
1256
+ interface PasswordComplexityOptions {
1257
+ minLength?: number; // Default: 8
1258
+ maxLength?: number; // Default: 128
1259
+ requireUppercase?: boolean; // Default: true
1260
+ requireLowercase?: boolean; // Default: true
1261
+ requireNumber?: boolean; // Default: true
1262
+ requireSpecialChar?: boolean; // Default: true
1263
+ }
1264
+ ```
1265
+
801
1266
  ## API Reference
802
1267
 
803
1268
  ### Exports
804
1269
 
805
1270
  ```typescript
806
1271
  // Config
807
- export { AUTH_MODULE_OPTIONS, BCRYPT_SALT_ROUNDS, DEFAULT_COOKIE_NAME, MAX_COOKIE_BYTES, SESSION_CACHE_PREFIX, TOKEN_REVOKED_PREFIX } from './config';
1272
+ export { AUTH_MODULE_OPTIONS, USER_ENRICHER, BCRYPT_SALT_ROUNDS, DEFAULT_COOKIE_NAME, MAX_COOKIE_BYTES, SESSION_CACHE_PREFIX, TOKEN_REVOKED_PREFIX } from './config';
808
1273
 
809
1274
  // Entities
810
1275
  export { User } from './entities/user.entity';
@@ -813,16 +1278,19 @@ export { PendingRegistration } from './entities/pending-registration.entity';
813
1278
  export { Company } from './entities/company.entity';
814
1279
  export { CompanyBranch } from './entities/company-branch.entity';
815
1280
  export { UserCompanyPermission, PermissionType } from './entities/user-company-permission.entity';
816
- export { CoreEntities, CompanyEntities, AllEntities, getEntitiesByConfig } from './entities';
1281
+ export { getEntitiesByConfig, IAuthEntitiesConfig } from './entities';
817
1282
 
818
1283
  // Enums
819
1284
  export { PermissionType } from './enums';
820
1285
 
821
1286
  // Interfaces
822
1287
  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';
1288
+ export { IUserEnricher, IProfileSection, IProfileSectionData, IProfileFile, IProfileExtras, IProfileRole, IProfileAction, IEnrichedUser } from './interfaces/user-enricher.interface';
1289
+ export { AuthModuleOptions, AuthModuleAsyncOptions, AuthOptionsFactory, IAuthModuleConfig } from './interfaces/auth-module-options.interface';
1290
+ export { TokenPayload, TokenPayloadWithCompany, LoginResponse, LoginResponseRequiresSelection, LoginResponseRequiresEmailVerification, MeResponse, RegistrationResponse, CompanyWithBranches, BranchForSelection } from './interfaces/authentication.interface';
1291
+ export { IUser, IUserMini, IUserWithPassword } from './interfaces/user.interface';
1292
+ export { ICompany, ICompanyMini } from './interfaces/company.interface';
1293
+ export { ICompanyBranch, ICompanyBranchMini } from './interfaces/company-branch.interface';
826
1294
 
827
1295
  // Services
828
1296
  export { AuthenticationService } from './services/authentication.service';
@@ -838,6 +1306,15 @@ export { CompanySelectionSessionService } from './services/company-selection-ses
838
1306
  // Modules
839
1307
  export { AuthModule } from './modules/auth.module';
840
1308
 
1309
+ // Controllers
1310
+ export { AuthenticationController } from './controllers/authentication.controller';
1311
+ export { EmailVerificationController } from './controllers/email-verification.controller';
1312
+ export { CompanySelectionController } from './controllers/company-selection.controller';
1313
+ export { UserController } from './controllers/user.controller';
1314
+ export { CompanyController } from './controllers/company.controller';
1315
+ export { BranchController } from './controllers/branch.controller';
1316
+ export { UserPermissionController } from './controllers/user-permission.controller';
1317
+
841
1318
  // Strategies
842
1319
  export { JwtStrategy } from './strategies/jwt.strategy';
843
1320
 
@@ -845,6 +1322,9 @@ export { JwtStrategy } from './strategies/jwt.strategy';
845
1322
  export { SetToken } from './interceptors/set-token.interceptor';
846
1323
  export { ClearToken } from './interceptors/clear-token.interceptor';
847
1324
 
1325
+ // Validators
1326
+ export { IsStrongPassword, PasswordComplexityOptions } from './validators/password.validator';
1327
+
848
1328
  // DTOs
849
1329
  export * from './dtos';
850
1330
 
@@ -855,14 +1335,49 @@ export { authSwaggerConfig } from './docs/auth-swagger.config';
855
1335
  ### Module Static Methods
856
1336
 
857
1337
  ```typescript
858
- // Get entities based on feature flags
859
- AuthModule.getEntities(enableCompanyFeature?: boolean): any[]
1338
+ // Get entities based on feature flags (requires IBootstrapAppConfig)
1339
+ AuthModule.getEntities(config: IBootstrapAppConfig): any[]
1340
+
1341
+ // Examples:
1342
+ AuthModule.getEntities({ enableCompanyFeature: false })
1343
+ // Returns: [User, UserToken, PendingRegistration]
860
1344
 
861
- // Configure module
1345
+ AuthModule.getEntities({ enableCompanyFeature: true })
1346
+ // Returns: [User, UserToken, PendingRegistration, Company, CompanyBranch, UserCompanyPermission]
1347
+
1348
+ AuthModule.getEntities({ enableCompanyFeature: true, enableEmailVerification: false })
1349
+ // Returns: [User, UserToken, Company, CompanyBranch, UserCompanyPermission]
1350
+
1351
+ // Configure module synchronously
862
1352
  AuthModule.forRoot(options: AuthModuleOptions): DynamicModule
1353
+
1354
+ // Configure module asynchronously
863
1355
  AuthModule.forRootAsync(options: AuthModuleAsyncOptions): DynamicModule
864
1356
  ```
865
1357
 
1358
+ **AuthModuleOptions Interface:**
1359
+ ```typescript
1360
+ interface AuthModuleOptions {
1361
+ global?: boolean; // Make module global (default: false)
1362
+ includeController?: boolean; // Include default controllers (default: true)
1363
+ bootstrapAppConfig?: IBootstrapAppConfig;
1364
+ config?: IAuthModuleConfig;
1365
+ providers?: Provider[]; // Additional providers (AUTH_EMAIL_PROVIDER, USER_ENRICHER)
1366
+ }
1367
+
1368
+ interface IAuthModuleConfig {
1369
+ defaultDatabaseConfig?: IDatabaseConfig;
1370
+ tenantDefaultDatabaseConfig?: IDatabaseConfig;
1371
+ tenants?: ITenantDatabaseConfig[];
1372
+ jwtSecret?: string;
1373
+ jwtExpiration?: string; // Default: '1h'
1374
+ refreshTokenSecret?: string;
1375
+ refreshTokenExpiration?: string; // Default: '7d'
1376
+ refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
1377
+ frontendUrl?: string; // For email links
1378
+ }
1379
+ ```
1380
+
866
1381
  ### Swagger Schema Behavior
867
1382
 
868
1383
  When `enableCompanyFeature: false`, the following are automatically hidden from Swagger:
@@ -880,4 +1395,4 @@ When `enableCompanyFeature: false`, the following are automatically hidden from
880
1395
 
881
1396
  ---
882
1397
 
883
- **Last Updated:** 2026-02-21
1398
+ **Last Updated:** 2026-02-25