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