@flusys/nestjs-auth 0.1.0-beta.1 → 0.1.0-beta.2
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.
- package/README.md +776 -0
- package/cjs/config/auth.constants.js +23 -0
- package/cjs/config/index.js +18 -0
- package/cjs/controllers/authentication.controller.js +251 -0
- package/cjs/controllers/branch.controller.js +59 -0
- package/cjs/controllers/company-selection.controller.js +168 -0
- package/cjs/controllers/company.controller.js +59 -0
- package/cjs/controllers/index.js +23 -0
- package/cjs/controllers/user-permission.controller.js +299 -0
- package/cjs/controllers/user.controller.js +177 -0
- package/cjs/docs/auth-swagger.config.js +409 -0
- package/cjs/docs/index.js +18 -0
- package/cjs/dtos/authentication.dto.js +676 -0
- package/cjs/dtos/company-branch.dto.js +199 -0
- package/cjs/dtos/company.dto.js +203 -0
- package/cjs/dtos/index.js +23 -0
- package/cjs/dtos/user-company-permission.dto.js +110 -0
- package/cjs/dtos/user-permission.dto.js +316 -0
- package/cjs/dtos/user.dto.js +333 -0
- package/cjs/entities/company-branch.entity.js +164 -0
- package/cjs/entities/company.entity.js +146 -0
- package/cjs/entities/index.js +62 -0
- package/cjs/entities/user-company-permission.entity.js +167 -0
- package/cjs/entities/user.entity.js +25 -0
- package/cjs/enums/index.js +18 -0
- package/cjs/enums/user-org-permission-type.enum.js +15 -0
- package/cjs/index.js +27 -187
- package/cjs/interceptors/clear-token.interceptor.js +86 -0
- package/cjs/interceptors/index.js +19 -0
- package/cjs/interceptors/set-token.interceptor.js +130 -0
- package/cjs/interfaces/auth-module-options.interface.js +4 -0
- package/cjs/interfaces/authentication.interface.js +7 -0
- package/cjs/interfaces/company-branch.interface.js +4 -0
- package/cjs/interfaces/company.interface.js +4 -0
- package/cjs/interfaces/index.js +22 -0
- package/cjs/interfaces/user.interface.js +4 -0
- package/cjs/modules/auth.module.js +270 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/services/auth-config.service.js +134 -0
- package/cjs/services/auth-datasource.provider.js +218 -0
- package/cjs/services/authentication.service.js +901 -0
- package/cjs/services/branch.service.js +139 -0
- package/cjs/services/company-selection-session.service.js +102 -0
- package/cjs/services/company.service.js +122 -0
- package/cjs/services/index.js +25 -0
- package/cjs/services/user-permission.service.js +187 -0
- package/cjs/services/user.service.js +460 -0
- package/cjs/strategies/jwt.strategy.js +106 -0
- package/fesm/config/auth.constants.js +8 -0
- package/fesm/config/index.js +1 -0
- package/fesm/controllers/authentication.controller.js +241 -0
- package/fesm/controllers/branch.controller.js +49 -0
- package/fesm/controllers/company-selection.controller.js +158 -0
- package/fesm/controllers/company.controller.js +49 -0
- package/fesm/controllers/index.js +6 -0
- package/fesm/controllers/user-permission.controller.js +289 -0
- package/fesm/controllers/user.controller.js +167 -0
- package/fesm/docs/auth-swagger.config.js +406 -0
- package/fesm/docs/index.js +1 -0
- package/fesm/dtos/authentication.dto.js +701 -0
- package/fesm/dtos/company-branch.dto.js +184 -0
- package/fesm/dtos/company.dto.js +188 -0
- package/fesm/dtos/index.js +6 -0
- package/fesm/dtos/user-company-permission.dto.js +98 -0
- package/fesm/dtos/user-permission.dto.js +297 -0
- package/fesm/dtos/user.dto.js +314 -0
- package/fesm/entities/company-branch.entity.js +154 -0
- package/fesm/entities/company.entity.js +136 -0
- package/fesm/entities/index.js +23 -0
- package/fesm/entities/user-company-permission.entity.js +150 -0
- package/fesm/entities/user.entity.js +15 -0
- package/fesm/enums/index.js +1 -0
- package/fesm/enums/user-org-permission-type.enum.js +5 -0
- package/fesm/index.js +11 -188
- package/fesm/interceptors/clear-token.interceptor.js +76 -0
- package/fesm/interceptors/index.js +2 -0
- package/fesm/interceptors/set-token.interceptor.js +120 -0
- package/fesm/interfaces/auth-module-options.interface.js +3 -0
- package/fesm/interfaces/authentication.interface.js +7 -0
- package/fesm/interfaces/company-branch.interface.js +1 -0
- package/fesm/interfaces/company.interface.js +1 -0
- package/fesm/interfaces/index.js +5 -0
- package/fesm/interfaces/user.interface.js +1 -0
- package/fesm/modules/auth.module.js +260 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/services/auth-config.service.js +124 -0
- package/fesm/services/auth-datasource.provider.js +167 -0
- package/fesm/services/authentication.service.js +850 -0
- package/fesm/services/branch.service.js +129 -0
- package/fesm/services/company-selection-session.service.js +92 -0
- package/fesm/services/company.service.js +112 -0
- package/fesm/services/index.js +8 -0
- package/fesm/services/user-permission.service.js +177 -0
- package/fesm/services/user.service.js +409 -0
- package/fesm/strategies/jwt.strategy.js +96 -0
- package/package.json +28 -23
- package/cjs/config-index.js +0 -1
- package/cjs/controllers-index.js +0 -86
- package/cjs/docs-index.js +0 -103
- package/cjs/dtos-index.js +0 -1
- package/cjs/entities-index.js +0 -1
- package/cjs/enums-index.js +0 -1
- package/cjs/interceptors-index.js +0 -1
- package/cjs/interfaces-index.js +0 -1
- package/cjs/modules-index.js +0 -86
- package/cjs/services-index.js +0 -16
- package/fesm/config-index.js +0 -1
- package/fesm/controllers-index.js +0 -86
- package/fesm/docs-index.js +0 -103
- package/fesm/dtos-index.js +0 -1
- package/fesm/entities-index.js +0 -1
- package/fesm/enums-index.js +0 -1
- package/fesm/interceptors-index.js +0 -1
- package/fesm/interfaces-index.js +0 -0
- package/fesm/modules-index.js +0 -86
- package/fesm/services-index.js +0 -16
package/README.md
ADDED
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
# Authentication Package Guide
|
|
2
|
+
|
|
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.
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
- [API Endpoints](#api-endpoints)
|
|
14
|
+
- [Architecture](#architecture)
|
|
15
|
+
- [Login Flows](#login-flows)
|
|
16
|
+
- [User Management](#user-management)
|
|
17
|
+
- [Environment Variables](#environment-variables)
|
|
18
|
+
- [Migrations](#migrations)
|
|
19
|
+
- [Configuration Modes](#configuration-modes)
|
|
20
|
+
- [IAM Integration](#iam-integration)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @flusys/nestjs-auth @flusys/nestjs-shared @flusys/nestjs-core
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Basic Setup (Single Database, No Company Feature)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Module } from '@nestjs/common';
|
|
36
|
+
import { AuthModule } from '@flusys/nestjs-auth';
|
|
37
|
+
|
|
38
|
+
@Module({
|
|
39
|
+
imports: [
|
|
40
|
+
AuthModule.forRoot({
|
|
41
|
+
global: true,
|
|
42
|
+
includeController: true,
|
|
43
|
+
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
44
|
+
bootstrapAppConfig: {
|
|
45
|
+
databaseMode: 'single',
|
|
46
|
+
enableCompanyFeature: false,
|
|
47
|
+
},
|
|
48
|
+
config: {
|
|
49
|
+
defaultDatabaseConfig: {
|
|
50
|
+
type: 'mysql',
|
|
51
|
+
host: 'localhost',
|
|
52
|
+
port: 3306,
|
|
53
|
+
username: 'root',
|
|
54
|
+
password: 'password',
|
|
55
|
+
database: 'myapp',
|
|
56
|
+
},
|
|
57
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
58
|
+
jwtExpiration: '1h',
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
})
|
|
63
|
+
export class AppModule {}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With Company Feature
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
AuthModule.forRoot({
|
|
70
|
+
global: true,
|
|
71
|
+
includeController: true,
|
|
72
|
+
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
73
|
+
bootstrapAppConfig: {
|
|
74
|
+
databaseMode: 'single',
|
|
75
|
+
enableCompanyFeature: true, // Enable company/branch support
|
|
76
|
+
},
|
|
77
|
+
config: {
|
|
78
|
+
defaultDatabaseConfig: {
|
|
79
|
+
type: 'mysql',
|
|
80
|
+
host: 'localhost',
|
|
81
|
+
port: 3306,
|
|
82
|
+
username: 'root',
|
|
83
|
+
password: 'password',
|
|
84
|
+
database: 'myapp',
|
|
85
|
+
},
|
|
86
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
87
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
|
|
88
|
+
refreshTokenExpiration: '7d',
|
|
89
|
+
refreshTokenCookieName: 'fsn_refresh_token',
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Multi-Tenant Mode
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
AuthModule.forRoot({
|
|
98
|
+
global: true,
|
|
99
|
+
includeController: true,
|
|
100
|
+
includeIAM: true, // Include IAM entities if using @flusys/nestjs-iam
|
|
101
|
+
bootstrapAppConfig: {
|
|
102
|
+
databaseMode: 'multi-tenant',
|
|
103
|
+
enableCompanyFeature: true,
|
|
104
|
+
},
|
|
105
|
+
config: {
|
|
106
|
+
tenantDefaultDatabaseConfig: {
|
|
107
|
+
type: 'mysql',
|
|
108
|
+
host: 'localhost',
|
|
109
|
+
port: 3306,
|
|
110
|
+
username: 'root',
|
|
111
|
+
password: 'password',
|
|
112
|
+
},
|
|
113
|
+
tenants: [
|
|
114
|
+
{ id: 'tenant1', database: 'tenant1_db', name: 'Tenant 1', isActive: true },
|
|
115
|
+
{ id: 'tenant2', database: 'tenant2_db', name: 'Tenant 2', isActive: true },
|
|
116
|
+
],
|
|
117
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Async Configuration
|
|
123
|
+
|
|
124
|
+
```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
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
### Configuration Options
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
interface AuthModuleOptions {
|
|
144
|
+
global?: boolean; // Make module global
|
|
145
|
+
includeController?: boolean; // Include default controllers
|
|
146
|
+
includeIAM?: boolean; // Include IAM entities in TypeORM.forRoot() (required for IAM module)
|
|
147
|
+
bootstrapAppConfig: {
|
|
148
|
+
databaseMode: 'single' | 'multi-tenant';
|
|
149
|
+
enableCompanyFeature: boolean; // Enable company/branch feature
|
|
150
|
+
};
|
|
151
|
+
config: {
|
|
152
|
+
defaultDatabaseConfig?: IDatabaseConfig;
|
|
153
|
+
tenantDefaultDatabaseConfig?: IDatabaseConfig;
|
|
154
|
+
tenants?: ITenantDatabaseConfig[];
|
|
155
|
+
jwtSecret: string;
|
|
156
|
+
jwtExpiration?: string;
|
|
157
|
+
refreshTokenSecret?: string;
|
|
158
|
+
refreshTokenExpiration?: string;
|
|
159
|
+
refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Important Notes:**
|
|
165
|
+
|
|
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.
|
|
167
|
+
|
|
168
|
+
2. **Module Separation**: Auth module handles authentication (login, JWT, user management). For authorization and permissions, use the separate `@flusys/nestjs-iam` package.
|
|
169
|
+
|
|
170
|
+
## API Endpoints
|
|
171
|
+
|
|
172
|
+
### Authentication Endpoints
|
|
173
|
+
|
|
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 |
|
|
182
|
+
|
|
183
|
+
#### GET /auth/me - Get Current User Info
|
|
184
|
+
|
|
185
|
+
**Purpose:** Retrieve current authenticated user information with optional company/branch context
|
|
186
|
+
|
|
187
|
+
**Authentication:** Requires valid access token (Bearer token in Authorization header)
|
|
188
|
+
|
|
189
|
+
**Response Behavior:**
|
|
190
|
+
|
|
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
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
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
|
+
```
|
|
226
|
+
|
|
227
|
+
**Key Characteristics:**
|
|
228
|
+
- Uses HTTP GET (not POST like other auth endpoints)
|
|
229
|
+
- Returns only user context data (no tokens)
|
|
230
|
+
- `company` and `branch` fields are automatically excluded from Swagger docs when company feature is disabled
|
|
231
|
+
- Used for session restoration on page refresh (frontend calls this after token refresh)
|
|
232
|
+
|
|
233
|
+
**Related Service:** `AuthenticationService.getUserInfo()` in `authentication.service.ts`
|
|
234
|
+
|
|
235
|
+
### 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 |
|
|
243
|
+
|
|
244
|
+
> **Note:** These endpoints are automatically hidden from Swagger when `enableCompanyFeature: false`
|
|
245
|
+
|
|
246
|
+
### Swagger Schema Behavior
|
|
247
|
+
|
|
248
|
+
When `enableCompanyFeature: false`, the following are automatically hidden from Swagger:
|
|
249
|
+
|
|
250
|
+
**Excluded Tags:**
|
|
251
|
+
- Companies
|
|
252
|
+
- Branches
|
|
253
|
+
- User Permissions (NEW - v4.1.3+)
|
|
254
|
+
- Company Selection
|
|
255
|
+
|
|
256
|
+
**Excluded DTO Properties:**
|
|
257
|
+
|
|
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` |
|
|
264
|
+
|
|
265
|
+
### User Management Endpoints (`/administration/users`)
|
|
266
|
+
|
|
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
|
+
]
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Example: Revoke branch access**
|
|
303
|
+
```json
|
|
304
|
+
POST /administration/permissions/user-branch/assign
|
|
305
|
+
{
|
|
306
|
+
"userId": "user-123",
|
|
307
|
+
"items": [
|
|
308
|
+
{ "targetId": "branch-5", "isAdd": false }
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
> **Note:** These endpoints are only available when `enableCompanyFeature: true`
|
|
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
|
|
340
|
+
|
|
341
|
+
### Module Independence
|
|
342
|
+
|
|
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)
|
|
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
|
+
|
|
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`:
|
|
368
|
+
|
|
369
|
+
```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
|
+
);
|
|
390
|
+
|
|
391
|
+
const userRepository = await dataSourceProvider.getRepository(User);
|
|
392
|
+
// ... validate user
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
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
|
|
486
|
+
|
|
487
|
+
| Service | Scope | Description |
|
|
488
|
+
|---------|-------|-------------|
|
|
489
|
+
| `AuthenticationService` | REQUEST | Login, register, token management, company selection |
|
|
490
|
+
| `UserService` | REQUEST | User CRUD, extends `RequestScopedApiService` |
|
|
491
|
+
| `CompanyService` | REQUEST | Company CRUD (when company feature enabled) |
|
|
492
|
+
| `BranchService` | REQUEST | Branch CRUD with company filtering |
|
|
493
|
+
| `UserPermissionService` | REQUEST | User-company/branch permission management |
|
|
494
|
+
| `AuthDataSourceProvider` | REQUEST | Dynamic datasource for single/multi-tenant |
|
|
495
|
+
| `AuthConfigService` | SINGLETON | JWT and module configuration access |
|
|
496
|
+
|
|
497
|
+
## Login Flows
|
|
498
|
+
|
|
499
|
+
### Simple Mode (Company Feature Disabled)
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
POST /auth/login { email, password }
|
|
503
|
+
│
|
|
504
|
+
▼
|
|
505
|
+
┌─────────────────────────┐
|
|
506
|
+
│ AuthenticationService │
|
|
507
|
+
│ .login() │
|
|
508
|
+
└────────────┬────────────┘
|
|
509
|
+
│
|
|
510
|
+
│ 1. Verify credentials
|
|
511
|
+
│ 2. Generate JWT token
|
|
512
|
+
▼
|
|
513
|
+
{ accessToken, refreshToken, user }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Company Mode - Auto-Select (Single Company/Branch)
|
|
517
|
+
|
|
518
|
+
```
|
|
519
|
+
POST /auth/login { email, password }
|
|
520
|
+
│
|
|
521
|
+
▼
|
|
522
|
+
┌─────────────────────────┐
|
|
523
|
+
│ AuthenticationService │
|
|
524
|
+
│ .login() │
|
|
525
|
+
└────────────┬────────────┘
|
|
526
|
+
│
|
|
527
|
+
│ 1. Verify credentials
|
|
528
|
+
│ 2. Check UserCompanyPermission
|
|
529
|
+
│ 3. Found: 1 company, 1 branch → Auto-select
|
|
530
|
+
▼
|
|
531
|
+
{ accessToken, refreshToken, user, company, branch }
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Company Mode - Selection Required (Multiple Options)
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
POST /auth/login { email, password }
|
|
538
|
+
│
|
|
539
|
+
▼
|
|
540
|
+
┌─────────────────────────┐
|
|
541
|
+
│ AuthenticationService │
|
|
542
|
+
│ .login() │
|
|
543
|
+
└────────────┬────────────┘
|
|
544
|
+
│
|
|
545
|
+
│ Found: Multiple companies/branches
|
|
546
|
+
▼
|
|
547
|
+
{ requiresSelection: true, sessionId, companies: [...] }
|
|
548
|
+
│
|
|
549
|
+
│ User selects company & branch
|
|
550
|
+
▼
|
|
551
|
+
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
|
+
▼
|
|
562
|
+
{ accessToken, refreshToken, user, company, branch }
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
## User Management
|
|
566
|
+
|
|
567
|
+
### User Active Status
|
|
568
|
+
|
|
569
|
+
The `PUT /users/:id/status` endpoint handles user active status based on company feature setting:
|
|
570
|
+
|
|
571
|
+
**When company feature is disabled:**
|
|
572
|
+
- Updates `User.isActive` directly
|
|
573
|
+
- Simple on/off for the user globally
|
|
574
|
+
|
|
575
|
+
**When company feature is enabled:**
|
|
576
|
+
- Updates `UserCompanyPermission.isActive` for the logged user's company
|
|
577
|
+
- 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
|
+
|
|
581
|
+
### Registration Flow
|
|
582
|
+
|
|
583
|
+
**Simple registration:**
|
|
584
|
+
```json
|
|
585
|
+
{
|
|
586
|
+
"name": "John Doe",
|
|
587
|
+
"email": "john@example.com",
|
|
588
|
+
"password": "password123"
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**Join existing company:**
|
|
593
|
+
```json
|
|
594
|
+
{
|
|
595
|
+
"name": "John Doe",
|
|
596
|
+
"email": "john@example.com",
|
|
597
|
+
"password": "password123",
|
|
598
|
+
"companySlug": "acme-corp",
|
|
599
|
+
"branchSlug": "main-branch"
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Create new company:**
|
|
604
|
+
```json
|
|
605
|
+
{
|
|
606
|
+
"name": "John Doe",
|
|
607
|
+
"email": "john@example.com",
|
|
608
|
+
"password": "password123",
|
|
609
|
+
"newCompanyName": "My Company",
|
|
610
|
+
"newBranchName": "Headquarters"
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Hierarchical Branch Structure
|
|
615
|
+
|
|
616
|
+
```
|
|
617
|
+
Company: "Acme Corporation"
|
|
618
|
+
│
|
|
619
|
+
├── Main Office (Branch)
|
|
620
|
+
│ ├── Sales Department (Sub-Branch)
|
|
621
|
+
│ │ ├── Inside Sales (Sub-Sub-Branch)
|
|
622
|
+
│ │ └── Outside Sales
|
|
623
|
+
│ └── Support Department
|
|
624
|
+
│ ├── Tier 1 Support
|
|
625
|
+
│ └── Tier 2 Support
|
|
626
|
+
│
|
|
627
|
+
└── Regional Office (Branch)
|
|
628
|
+
├── East Branch
|
|
629
|
+
└── West Branch
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## Environment Variables
|
|
633
|
+
|
|
634
|
+
```env
|
|
635
|
+
# Database
|
|
636
|
+
DB_HOST=localhost
|
|
637
|
+
DB_PORT=3306
|
|
638
|
+
DB_USERNAME=root
|
|
639
|
+
DB_PASSWORD=password
|
|
640
|
+
DB_DATABASE=myapp
|
|
641
|
+
|
|
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
|
+
```
|
|
649
|
+
|
|
650
|
+
## Migrations
|
|
651
|
+
|
|
652
|
+
See [MIGRATION.md](./MIGRATION.md) for detailed migration commands.
|
|
653
|
+
|
|
654
|
+
### Quick Commands
|
|
655
|
+
|
|
656
|
+
```bash
|
|
657
|
+
# Generate migration
|
|
658
|
+
npm run migration:generate --name=InitialSetup
|
|
659
|
+
|
|
660
|
+
# Run migrations
|
|
661
|
+
npm run migration:run
|
|
662
|
+
|
|
663
|
+
# Check status
|
|
664
|
+
npm run migration:status
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
## Configuration Modes
|
|
668
|
+
|
|
669
|
+
### Mode 1: Minimal (No Companies)
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
AuthModule.forRoot({
|
|
673
|
+
bootstrapAppConfig: {
|
|
674
|
+
enableCompanyFeature: false,
|
|
675
|
+
},
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
Entities: User
|
|
679
|
+
Tables: 1
|
|
680
|
+
Features: Basic login, registration
|
|
681
|
+
Size: Minimal
|
|
682
|
+
Performance: Fast
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Mode 2: Full Featured (With Companies)
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
AuthModule.forRoot({
|
|
689
|
+
includeIAM: true, // If using IAM module
|
|
690
|
+
bootstrapAppConfig: {
|
|
691
|
+
enableCompanyFeature: true,
|
|
692
|
+
},
|
|
693
|
+
})
|
|
694
|
+
|
|
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
|
+
```
|
|
702
|
+
|
|
703
|
+
## IAM Integration
|
|
704
|
+
|
|
705
|
+
### Why includeIAM Flag?
|
|
706
|
+
|
|
707
|
+
When using `@flusys/nestjs-iam` with Auth module in **single-tenant mode**, TypeORM needs to know about all entities during initialization.
|
|
708
|
+
|
|
709
|
+
**The Problem:**
|
|
710
|
+
```typescript
|
|
711
|
+
// Auth module calls TypeOrmModule.forRoot() with auth entities
|
|
712
|
+
TypeOrmModule.forRoot({ entities: [User, Company, ...] })
|
|
713
|
+
|
|
714
|
+
// IAM module tries to use forFeature() but entities not registered
|
|
715
|
+
TypeOrmModule.forFeature([Action, Role, Permission]) // ❌ Error!
|
|
716
|
+
```
|
|
717
|
+
|
|
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
|
+
})
|
|
725
|
+
|
|
726
|
+
// Now TypeORM knows about all entities
|
|
727
|
+
TypeOrmModule.forRoot({
|
|
728
|
+
entities: [User, Company, Action, Role, Permission, ...]
|
|
729
|
+
}) // ✅ Works!
|
|
730
|
+
```
|
|
731
|
+
|
|
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)
|
|
736
|
+
|
|
737
|
+
### Architecture
|
|
738
|
+
|
|
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])
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Auth module is responsible for database connection, but dynamically includes IAM entities when requested. This maintains separation while solving the TypeORM registration requirement.
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## Summary
|
|
753
|
+
|
|
754
|
+
The authentication package provides:
|
|
755
|
+
|
|
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
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
**Last Updated:** 2026-02-09
|
|
767
|
+
**Version:** 4.1.6
|
|
768
|
+
|
|
769
|
+
**Recent Improvements:**
|
|
770
|
+
- 2026-02-09: Added explicit `@Inject()` decorators to all services for esbuild bundling compatibility
|
|
771
|
+
- 2026-02-09: All services now use REQUEST scope with DataSource Provider pattern
|
|
772
|
+
- 2026-02-07: Fixed entity diagram to show polymorphic design (permissionType/targetId)
|
|
773
|
+
- 2026-02-07: Updated service names to match actual implementation
|
|
774
|
+
- 2026-02-07: Simplified login flow diagrams
|
|
775
|
+
- 2026-01-15: Fixed JwtStrategy to use `ModuleRef` for request-scoped `AuthDataSourceProvider` resolution
|
|
776
|
+
- 2026-01-14: Added `UserPermissionController` for batch permission management
|