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