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