@flusys/nestjs-auth 4.1.0 → 5.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 (45) hide show
  1. package/README.md +151 -719
  2. package/cjs/config/message-keys.js +6 -61
  3. package/cjs/controllers/authentication.controller.js +113 -87
  4. package/cjs/controllers/company-selection.controller.js +24 -4
  5. package/cjs/controllers/email-verification.controller.js +12 -13
  6. package/cjs/docs/auth-swagger.config.js +143 -106
  7. package/cjs/dtos/authentication.dto.js +0 -15
  8. package/cjs/entities/user-company-permission.entity.js +0 -8
  9. package/cjs/entities/user-token.entity.js +0 -9
  10. package/cjs/interceptors/set-token.interceptor.js +15 -7
  11. package/cjs/services/auth-config.service.js +0 -4
  12. package/cjs/services/auth-email.service.js +8 -31
  13. package/cjs/services/authentication.service.js +99 -15
  14. package/cjs/services/user-permission.service.js +6 -8
  15. package/cjs/services/user.service.js +12 -9
  16. package/cjs/strategies/jwt.strategy.js +10 -3
  17. package/config/message-keys.d.ts +5 -153
  18. package/controllers/authentication.controller.d.ts +6 -7
  19. package/controllers/company-selection.controller.d.ts +3 -2
  20. package/controllers/email-verification.controller.d.ts +6 -2
  21. package/dtos/authentication.dto.d.ts +0 -2
  22. package/entities/user-company-permission.entity.d.ts +0 -1
  23. package/entities/user-token.entity.d.ts +0 -1
  24. package/fesm/config/message-keys.js +6 -53
  25. package/fesm/controllers/authentication.controller.js +115 -90
  26. package/fesm/controllers/company-selection.controller.js +25 -5
  27. package/fesm/controllers/email-verification.controller.js +13 -14
  28. package/fesm/docs/auth-swagger.config.js +146 -113
  29. package/fesm/dtos/authentication.dto.js +0 -15
  30. package/fesm/entities/user-company-permission.entity.js +0 -8
  31. package/fesm/entities/user-token.entity.js +0 -9
  32. package/fesm/interceptors/set-token.interceptor.js +15 -7
  33. package/fesm/services/auth-config.service.js +0 -4
  34. package/fesm/services/auth-email.service.js +8 -32
  35. package/fesm/services/authentication.service.js +100 -17
  36. package/fesm/services/user-permission.service.js +6 -8
  37. package/fesm/services/user.service.js +12 -10
  38. package/fesm/strategies/jwt.strategy.js +10 -3
  39. package/interfaces/auth-module-options.interface.d.ts +0 -1
  40. package/package.json +3 -3
  41. package/services/auth-config.service.d.ts +0 -1
  42. package/services/auth-email.service.d.ts +0 -4
  43. package/services/authentication.service.d.ts +1 -1
  44. package/services/user-permission.service.d.ts +1 -1
  45. package/services/user.service.d.ts +5 -5
package/README.md CHANGED
@@ -1,96 +1,9 @@
1
1
  # @flusys/nestjs-auth
2
2
 
3
- > Production-grade authentication and user management module for NestJS — with JWT, email verification, multi-company hierarchy, multi-tenant support, and pluggable extensibility.
3
+ Drop-in authentication for NestJS — JWT access/refresh tokens, bcrypt passwords, multi-company hierarchy, email verification, and pluggable email + user enrichment.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@flusys/nestjs-auth.svg)](https://www.npmjs.com/package/@flusys/nestjs-auth)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![NestJS](https://img.shields.io/badge/NestJS-11.x-red.svg)](https://nestjs.com/)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.x-green.svg)](https://nodejs.org/)
10
-
11
- ---
12
-
13
- ## Table of Contents
14
-
15
- - [Overview](#overview)
16
- - [Features](#features)
17
- - [Compatibility](#compatibility)
18
- - [Installation](#installation)
19
- - [Quick Start](#quick-start)
20
- - [Module Registration](#module-registration)
21
- - [forRoot (Sync)](#forroot-sync)
22
- - [forRootAsync (Factory)](#forrootasync-factory)
23
- - [forRootAsync (Class)](#forrootasync-class)
24
- - [Configuration Reference](#configuration-reference)
25
- - [Bootstrap Config](#bootstrap-config)
26
- - [Runtime Config](#runtime-config)
27
- - [Feature Toggles](#feature-toggles)
28
- - [Deployment Modes](#deployment-modes)
29
- - [API Endpoints](#api-endpoints)
30
- - [Entities](#entities)
31
- - [Provider Interfaces (Extensibility)](#provider-interfaces-extensibility)
32
- - [AUTH_EMAIL_PROVIDER](#auth_email_provider)
33
- - [USER_ENRICHER](#user_enricher)
34
- - [Exported Services](#exported-services)
35
- - [Interceptors](#interceptors)
36
- - [Security](#security)
37
- - [Environment Variables](#environment-variables)
38
- - [Troubleshooting](#troubleshooting)
39
- - [License](#license)
40
-
41
- ---
42
-
43
- ## Overview
44
-
45
- `@flusys/nestjs-auth` is a complete, drop-in authentication system for NestJS applications. It supports three deployment topologies out of the box:
46
-
47
- | Mode | Description |
48
- |------|-------------|
49
- | **Single** | One database, one tenant, no company hierarchy |
50
- | **Multi-Company** | One database, users belong to multiple companies/branches |
51
- | **Multi-Tenant** | Separate database per tenant, full data isolation |
52
-
53
- All features are opt-in via bootstrap configuration — enabling or disabling them at startup time.
54
-
55
- ---
56
-
57
- ## Features
58
-
59
- - **JWT Authentication** — Access token + refresh token pair with configurable expiration
60
- - **HTTP-only Cookie** — Refresh token stored in a secure, HTTP-only cookie for browser clients; body-based for mobile/API clients
61
- - **Email Verification** — Registration-gated email verification flow with token-based links
62
- - **Password Security** — bcrypt hashing with 12 salt rounds; SHA-256 hashing for stored tokens
63
- - **Rate Limiting** — Built-in throttle limits on login (5/min), register (3/min), and refresh (30/min)
64
- - **Company / Branch Hierarchy** — Optional multi-company support with branch sub-groups
65
- - **Company Selection Flow** — Session-based company selection when a user belongs to multiple companies
66
- - **User Management CRUD** — Full user administration endpoints with permission guards
67
- - **Profile Enrichment** — Pluggable `USER_ENRICHER` for adding roles, permissions, and multi-step profile sections
68
- - **Email Integration** — Pluggable `AUTH_EMAIL_PROVIDER` to send password-reset and verification emails via any provider
69
- - **Multi-Tenant Support** — Dynamic DataSource resolution per request for multiple databases
70
- - **Swagger Documentation** — All endpoints documented with `@nestjs/swagger`
71
- - **Passport.js + JWT Strategy** — Standard NestJS Passport integration
72
-
73
- ---
74
-
75
- ## Compatibility
76
-
77
- | Package | Version |
78
- |---------|---------|
79
- | `@nestjs/core` | `^11.0.0` |
80
- | `@nestjs/common` | `^11.0.0` |
81
- | `@nestjs/jwt` | `^10.0.0` |
82
- | `@nestjs/passport` | `^10.0.0` |
83
- | `typeorm` | `^0.3.0` |
84
- | `passport-jwt` | `^4.0.0` |
85
- | `bcrypt` | `^5.0.0` |
86
- | Node.js | `>= 18.x` |
87
- | TypeScript | `>= 5.0` |
88
-
89
- **Peer dependencies (must be installed in your app):**
90
-
91
- ```
92
- @flusys/nestjs-core @flusys/nestjs-shared
93
- ```
94
7
 
95
8
  ---
96
9
 
@@ -98,96 +11,107 @@ All features are opt-in via bootstrap configuration — enabling or disabling th
98
11
 
99
12
  ```bash
100
13
  npm install @flusys/nestjs-auth @flusys/nestjs-shared @flusys/nestjs-core
14
+ npm install @nestjs/jwt @nestjs/passport passport-jwt bcrypt cookie-parser
101
15
  ```
102
16
 
103
- ```bash
104
- # yarn
105
- yarn add @flusys/nestjs-auth @flusys/nestjs-shared @flusys/nestjs-core
106
- ```
107
-
108
- ```bash
109
- # pnpm
110
- pnpm add @flusys/nestjs-auth @flusys/nestjs-shared @flusys/nestjs-core
111
- ```
112
-
113
- ---
17
+ ## 1. Module Registration
114
18
 
115
- ## Quick Start
19
+ ### Sync (`forRoot`)
116
20
 
117
- Register `AuthModule` in your root `AppModule`. This minimal setup enables JWT authentication, user CRUD, and email verification in a single-database application:
21
+ #### Mode 1: Single Database
118
22
 
119
23
  ```typescript
120
- import { Module } from '@nestjs/common';
121
24
  import { AuthModule } from '@flusys/nestjs-auth';
122
25
 
123
- @Module({
124
- imports: [
125
- AuthModule.forRoot({
126
- global: true,
127
- includeController: true,
128
- bootstrapAppConfig: {
129
- databaseMode: 'single',
130
- enableCompanyFeature: false,
131
- enableEmailVerification: true,
132
- },
133
- config: {
134
- defaultDatabaseConfig: {
135
- type: 'postgres',
136
- host: process.env.DB_HOST,
137
- port: Number(process.env.DB_PORT ?? 5432),
138
- username: process.env.DB_USER,
139
- password: process.env.DB_PASSWORD,
140
- database: process.env.DB_NAME,
141
- },
142
- jwtSecret: process.env.JWT_SECRET,
143
- jwtExpiration: '15m',
144
- refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
145
- refreshTokenExpiration: '7d',
146
- frontendUrl: process.env.FRONTEND_URL,
147
- },
148
- }),
149
- ],
150
- })
151
- export class AppModule {}
26
+ AuthModule.forRoot({
27
+ global: true,
28
+ includeController: true,
29
+ bootstrapAppConfig: {
30
+ databaseMode: 'single',
31
+ enableCompanyFeature: false,
32
+ enableEmailVerification: true,
33
+ },
34
+ config: {
35
+ defaultDatabaseConfig: {
36
+ type: 'mysql',
37
+ host: process.env.DB_HOST,
38
+ port: Number(process.env.DB_PORT ?? 3306),
39
+ username: process.env.DB_USER,
40
+ password: process.env.DB_PASSWORD,
41
+ database: process.env.DB_NAME,
42
+ },
43
+ jwtSecret: process.env.JWT_SECRET,
44
+ jwtExpiration: '15m',
45
+ refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
46
+ refreshTokenExpiration: '7d',
47
+ // refreshTokenCookieName: 'fsn_refresh_token', // optional, this is the default
48
+ },
49
+ });
152
50
  ```
153
51
 
154
- ---
52
+ #### Mode 2: Multi-Tenant
155
53
 
156
- ## Module Registration
54
+ In multi-tenant mode you provide:
157
55
 
158
- ### forRoot (Sync)
56
+ - `tenantDefaultDatabaseConfig` — connection template applied to all tenants; individual tenants override only the fields that differ
57
+ - `tenants` — per-tenant database config (`ITenantDatabaseConfig[]`); tenant resolved per request via `x-tenant-id` header
159
58
 
160
- Use when all configuration values are available synchronously (environment variables, hardcoded values):
59
+ `defaultDatabaseConfig` is not needed — `tenantDefaultDatabaseConfig` serves as the fallback for both template building and single-datasource resolution.
161
60
 
162
61
  ```typescript
163
62
  AuthModule.forRoot({
164
63
  global: true,
165
64
  includeController: true,
166
65
  bootstrapAppConfig: {
167
- databaseMode: 'single',
168
- enableCompanyFeature: false,
66
+ databaseMode: 'multi-tenant',
67
+ enableCompanyFeature: true,
169
68
  enableEmailVerification: true,
170
69
  },
171
70
  config: {
172
- defaultDatabaseConfig: { /* TypeORM DataSourceOptions */ },
71
+ // Default template applied to every tenant unless overridden
72
+ tenantDefaultDatabaseConfig: {
73
+ type: 'mysql',
74
+ host: process.env.TENANT_DB_HOST,
75
+ port: Number(process.env.TENANT_DB_PORT ?? 3306),
76
+ username: process.env.TENANT_DB_USER,
77
+ password: process.env.TENANT_DB_PASSWORD,
78
+ database: process.env.TENANT_DB_NAME,
79
+ },
80
+ // Per-tenant overrides — only fields that differ from tenantDefaultDatabaseConfig
81
+ tenants: [
82
+ {
83
+ id: 'tenant-a',
84
+ name: 'Tenant A',
85
+ database: 'tenant_a_db',
86
+ enableCompanyFeature: true,
87
+ permissionMode: 'FULL',
88
+ },
89
+ {
90
+ id: 'tenant-b',
91
+ name: 'Tenant B',
92
+ host: 'tenant-b.db.example.com',
93
+ database: 'tenant_b_db',
94
+ enableCompanyFeature: false,
95
+ permissionMode: 'RBAC',
96
+ },
97
+ ],
173
98
  jwtSecret: process.env.JWT_SECRET,
174
99
  jwtExpiration: '15m',
175
100
  refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
176
101
  refreshTokenExpiration: '7d',
177
- frontendUrl: 'http://localhost:3000',
178
102
  },
179
- providers: [], // Optional: AUTH_EMAIL_PROVIDER, USER_ENRICHER, etc.
180
- })
103
+ });
181
104
  ```
182
105
 
183
- ### forRootAsync (Factory)
106
+ ### Async (`forRootAsync`)
184
107
 
185
- Use when configuration is loaded from an external service or config module:
108
+ Use when config comes from `ConfigService` or another async source:
186
109
 
187
110
  ```typescript
188
- import { ConfigService } from '@nestjs/config';
189
- import { AuthModule } from '@flusys/nestjs-auth';
111
+ import { ConfigModule, ConfigService } from '@nestjs/config';
112
+ import { AuthModule, ITenantDatabaseConfig } from '@flusys/nestjs-auth';
190
113
 
114
+ // Single database
191
115
  AuthModule.forRootAsync({
192
116
  global: true,
193
117
  includeController: true,
@@ -196,342 +120,85 @@ AuthModule.forRootAsync({
196
120
  databaseMode: 'single',
197
121
  enableCompanyFeature: true,
198
122
  enableEmailVerification: true,
199
- },
200
- useFactory: (configService: ConfigService) => ({
123
+ },why you cannot fix it,, need to need gap bellow LAB REPORT text
124
+ useFactory: (cfg: ConfigService) => ({
201
125
  defaultDatabaseConfig: {
202
- type: 'postgres',
203
- host: configService.get('DB_HOST'),
204
- port: configService.get<number>('DB_PORT'),
205
- username: configService.get('DB_USER'),
206
- password: configService.get('DB_PASSWORD'),
207
- database: configService.get('DB_NAME'),
126
+ type: 'mysql',
127
+ host: cfg.get('DB_HOST'),
128
+ port: cfg.get<number>('DB_PORT'),
129
+ username: cfg.get('DB_USER'),
130
+ password: cfg.get('DB_PASSWORD'),
131
+ database: cfg.get('DB_NAME'),
208
132
  },
209
- jwtSecret: configService.get('JWT_SECRET'),
210
- jwtExpiration: configService.get('JWT_EXPIRATION', '15m'),
211
- refreshTokenSecret: configService.get('REFRESH_TOKEN_SECRET'),
212
- refreshTokenExpiration: configService.get('REFRESH_TOKEN_EXPIRATION', '7d'),
213
- frontendUrl: configService.get('FRONTEND_URL'),
133
+ jwtSecret: cfg.get('JWT_SECRET'),
134
+ jwtExpiration: cfg.get('JWT_EXPIRATION', '15m'),
135
+ refreshTokenSecret: cfg.get('REFRESH_TOKEN_SECRET'),
136
+ refreshTokenExpiration: cfg.get('REFRESH_TOKEN_EXPIRATION', '7d'),
214
137
  }),
215
138
  inject: [ConfigService],
216
- providers: [emailProviderFactory, userEnricherProvider],
217
- })
218
- ```
219
-
220
- ### forRootAsync (Class)
221
-
222
- Use when configuration is encapsulated in a dedicated service:
223
-
224
- ```typescript
225
- import { AuthOptionsFactory, IAuthModuleConfig } from '@flusys/nestjs-auth';
226
- import { Injectable } from '@nestjs/common';
139
+ providers: [authEmailProvider, userEnricherProvider],
140
+ });
227
141
 
228
- @Injectable()
229
- export class MyAuthConfigFactory implements AuthOptionsFactory {
230
- createAuthOptions(): IAuthModuleConfig {
231
- return {
232
- jwtSecret: process.env.JWT_SECRET,
233
- jwtExpiration: '15m',
234
- refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
235
- refreshTokenExpiration: '7d',
236
- };
237
- }
238
- createOptions() { return this.createAuthOptions(); }
239
- }
240
-
241
- // In module:
142
+ // Multi-tenant
242
143
  AuthModule.forRootAsync({
243
- bootstrapAppConfig: { databaseMode: 'single', enableCompanyFeature: false },
244
- useClass: MyAuthConfigFactory,
245
- })
246
- ```
247
-
248
- ---
249
-
250
- ## Configuration Reference
251
-
252
- ### Bootstrap Config
253
-
254
- Bootstrap configuration is **fixed at startup** — it determines which entities, controllers, and services are registered. It cannot change at runtime.
255
-
256
- ```typescript
257
- interface IBootstrapAppConfig {
258
- /** Database topology */
259
- databaseMode: 'single' | 'multi-tenant';
260
-
261
- /** Enable company/branch hierarchy and related controllers */
262
- enableCompanyFeature: boolean;
263
-
264
- /** Enable email verification flow (default: true) */
265
- enableEmailVerification?: boolean;
266
- }
267
- ```
268
-
269
- ### Runtime Config
270
-
271
- Runtime configuration is loaded at startup but can be sourced from environment variables, config files, or external APIs.
272
-
273
- ```typescript
274
- interface IAuthModuleConfig {
275
- /** TypeORM DataSource options for the default (or single) database */
276
- defaultDatabaseConfig?: DataSourceOptions;
277
-
278
- /** Tenant database configs (multi-tenant mode only) */
279
- tenants?: Array<{ id: string; databaseConfig: DataSourceOptions }>;
280
-
281
- /** JWT access token secret */
282
- jwtSecret?: string;
283
-
284
- /** JWT access token expiration (e.g. '15m', '1h', '7d') */
285
- jwtExpiration?: string;
286
-
287
- /** JWT refresh token secret */
288
- refreshTokenSecret?: string;
289
-
290
- /** Refresh token expiration (e.g. '7d', '30d') */
291
- refreshTokenExpiration?: string;
292
-
293
- /** HTTP-only cookie name for refresh token (default: 'fsn_refresh_token') */
294
- refreshTokenCookieName?: string;
295
-
296
- /** Frontend URL used to build email verification and password reset links */
297
- frontendUrl?: string;
298
- }
299
- ```
300
-
301
- ---
302
-
303
- ## Feature Toggles
304
-
305
- All features are independently gated by `bootstrapAppConfig`:
306
-
307
- | Feature | Config Key | Default | Effect when enabled |
308
- |---------|-----------|---------|---------------------|
309
- | Company / Branch | `enableCompanyFeature: true` | `false` | Registers `Company`, `CompanyBranch`, `UserCompanyPermission` entities; adds company, branch, company-selection, and user-permission controllers |
310
- | Email Verification | `enableEmailVerification: true` | `true` | Registers `PendingRegistration` entity; adds email-verification controller; blocks login until email is confirmed |
311
-
312
- ---
313
-
314
- ## Deployment Modes
315
-
316
- ### Single Database (No Company)
317
-
318
- ```typescript
319
- bootstrapAppConfig: {
320
- databaseMode: 'single',
321
- enableCompanyFeature: false,
322
- enableEmailVerification: true,
323
- }
324
- ```
325
-
326
- ### Single Database + Multi-Company
327
-
328
- ```typescript
329
- bootstrapAppConfig: {
330
- databaseMode: 'single',
331
- enableCompanyFeature: true,
332
- enableEmailVerification: true,
333
- }
334
- ```
335
-
336
- When a user belongs to multiple companies, login returns a `CompanySelectionResponseDto` instead of tokens. The client must call `/auth/select-company` with a `sessionId` to complete login.
337
-
338
- ### Multi-Tenant (Separate DB per Tenant)
339
-
340
- ```typescript
341
- bootstrapAppConfig: {
342
- databaseMode: 'multi-tenant',
343
- enableCompanyFeature: true,
344
- enableEmailVerification: true,
345
- },
346
- config: {
347
- defaultDatabaseConfig: { /* fallback/core DB */ },
348
- tenants: [
349
- { id: 'tenant-a', databaseConfig: { /* ... */ } },
350
- { id: 'tenant-b', databaseConfig: { /* ... */ } },
351
- ],
352
- jwtSecret: process.env.JWT_SECRET,
353
- // ...
354
- }
355
- ```
356
-
357
- ---
358
-
359
- ## API Endpoints
360
-
361
- All endpoints use **POST** (POST-only RPC design). Authentication required unless marked **Public**.
362
-
363
- ### Authentication — `POST /auth/*`
364
-
365
- | Endpoint | Auth | Rate Limit | Description |
366
- |----------|------|-----------|-------------|
367
- | `POST /auth/login` | Public | 5/min | Login with email + password |
368
- | `POST /auth/register` | Public | 3/min | Register a new user account |
369
- | `POST /auth/refresh` | Public | 30/min | Refresh access token (cookie or body) |
370
- | `POST /auth/me` | JWT | — | Get current user info |
371
- | `POST /auth/logout` | JWT | — | Logout and clear refresh token cookie |
372
- | `POST /auth/change-password` | JWT | 5/min | Change authenticated user's password |
373
-
374
- #### Login Response Variants
375
-
376
- Login returns one of three shapes depending on configuration:
377
-
378
- ```typescript
379
- // 1. Standard login success
380
- { accessToken: string; refreshToken: string; user: IUser }
381
-
382
- // 2. Company selection required (enableCompanyFeature: true, multiple companies)
383
- { requiresSelection: true; sessionId: string; expiresAt: string; user: IUser; companies: ICompany[] }
384
-
385
- // 3. Email verification required (enableEmailVerification: true, unverified account)
386
- { requiresEmailVerification: true; email: string; message: string }
387
- ```
388
-
389
- #### Refresh Token — Browser vs Mobile
390
-
391
- ```
392
- Browser clients: Refresh token read from HTTP-only cookie (set automatically on login).
393
- Falls back to request body if cookie is absent.
394
-
395
- Mobile / API: Send refreshToken in request body.
396
- Set header: x-client-type: mobile OR x-client-type: api
144
+ global: true,
145
+ includeController: true,
146
+ imports: [ConfigModule],
147
+ bootstrapAppConfig: {
148
+ databaseMode: 'multi-tenant',
149
+ enableCompanyFeature: true,
150
+ enableEmailVerification: true,
151
+ },
152
+ useFactory: (cfg: ConfigService) => ({
153
+ tenantDefaultDatabaseConfig: {
154
+ type: 'mysql',
155
+ host: cfg.get('TENANT_DB_HOST'),
156
+ port: cfg.get<number>('TENANT_DB_PORT'),
157
+ username: cfg.get('TENANT_DB_USER'),
158
+ password: cfg.get('TENANT_DB_PASSWORD'),
159
+ database: cfg.get('TENANT_DB_NAME'),
160
+ },
161
+ tenants: cfg.get<ITenantDatabaseConfig[]>('TENANTS'),
162
+ jwtSecret: cfg.get('JWT_SECRET'),
163
+ jwtExpiration: cfg.get('JWT_EXPIRATION', '15m'),
164
+ refreshTokenSecret: cfg.get('REFRESH_TOKEN_SECRET'),
165
+ refreshTokenExpiration: cfg.get('REFRESH_TOKEN_EXPIRATION', '7d'),
166
+ }),
167
+ inject: [ConfigService],
168
+ providers: [authEmailProvider, userEnricherProvider],
169
+ });
397
170
  ```
398
171
 
399
- ### Email Verification — `POST /auth/email-verification/*`
400
-
401
- Registered when `enableEmailVerification: true`.
402
-
403
- | Endpoint | Auth | Description |
404
- |----------|------|-------------|
405
- | `POST /auth/email-verification/verify` | Public | Verify email with token from link |
406
- | `POST /auth/email-verification/resend` | Public | Resend verification email |
407
-
408
- ### Company Selection — `POST /auth/company-selection/*`
409
-
410
- Registered when `enableCompanyFeature: true`.
411
-
412
- | Endpoint | Auth | Description |
413
- |----------|------|-------------|
414
- | `POST /auth/company-selection/select` | Public + Session | Complete login by selecting company/branch |
415
-
416
- ### User Administration — `POST /administration/users/*`
417
-
418
- | Endpoint | Permission | Description |
419
- |----------|-----------|-------------|
420
- | `POST /administration/users/insert` | `user.create` | Create a new user |
421
- | `POST /administration/users/insert-many` | `user.create` | Bulk create users |
422
- | `POST /administration/users/get-all` | `user.read` | Paginated user list |
423
- | `POST /administration/users/get/:id` | `user.read` | Get user by ID |
424
- | `POST /administration/users/update` | `user.update` | Update user fields |
425
- | `POST /administration/users/update-many` | `user.update` | Bulk update users |
426
- | `POST /administration/users/delete` | `user.delete` | Soft delete user |
427
- | `POST /administration/users/profile` | JWT | Update own profile |
428
- | `POST /administration/users/profile-sections` | `user.read` | List profile section definitions |
429
- | `POST /administration/users/:id/profile-extras` | `user.read` | Get user with enriched extras |
430
- | `POST /administration/users/:id/profile-section/:sectionId` | `user.read` | Get profile section data |
431
- | `POST /administration/users/:id/profile-section/:sectionId/update` | `user.update` | Update profile section |
432
- | `POST /administration/users/:id/profile-completion` | `user.read` | Profile completion percentage |
433
- | `POST /administration/users/:id/verify-email` | `user.update` | Admin: mark email as verified |
434
- | `POST /administration/users/:id/verify-phone` | `user.update` | Admin: mark phone as verified |
435
- | `POST /administration/users/:id/status` | `user.update` | Enable / disable user |
436
-
437
- ### Company & Branch — `POST /companies/*`, `POST /branches/*`
438
-
439
- Registered when `enableCompanyFeature: true`.
440
-
441
- | Endpoint | Permission | Description |
442
- |----------|-----------|-------------|
443
- | `POST /companies/insert` | `company.create` | Create company |
444
- | `POST /companies/get-all` | `company.read` | List companies |
445
- | `POST /companies/get/:id` | `company.read` | Get company by ID |
446
- | `POST /companies/update` | `company.update` | Update company |
447
- | `POST /companies/delete` | `company.delete` | Delete company |
448
- | `POST /branches/insert` | `branch.create` | Create branch |
449
- | `POST /branches/get-all` | `branch.read` | List branches |
450
- | `POST /branches/update` | `branch.update` | Update branch |
451
- | `POST /branches/delete` | `branch.delete` | Delete branch |
452
-
453
- ### User Permissions — `POST /administration/user-permissions/*`
454
-
455
- Registered when `enableCompanyFeature: true`.
456
-
457
- | Endpoint | Permission | Description |
458
- |----------|-----------|-------------|
459
- | `POST /administration/user-permissions/assign` | `user.update` | Assign user to company/branch |
460
- | `POST /administration/user-permissions/remove` | `user.update` | Remove user from company/branch |
461
- | `POST /administration/user-permissions/get-by-user` | `user.read` | Get all permissions for a user |
462
-
463
- ---
464
-
465
- ## Entities
466
-
467
- ### Core Entities (always registered)
468
-
469
- | Entity | Table | Description |
470
- |--------|-------|-------------|
471
- | `User` | `user` | User accounts. Passwords hashed with bcrypt (12 rounds) |
472
- | `UserToken` | `user_token` | Password-reset and email-verification tokens (SHA-256 hashed at rest) |
473
-
474
- ### Email Verification Entities (`enableEmailVerification: true`)
172
+ ## 2. Entity Registration
475
173
 
476
- | Entity | Table | Description |
477
- |--------|-------|-------------|
478
- | `PendingRegistration` | `pending_registration` | Holds unverified registration data until email is confirmed |
479
-
480
- ### Company Feature Entities (`enableCompanyFeature: true`)
481
-
482
- | Entity | Table | Description |
483
- |--------|-------|-------------|
484
- | `Company` | `company` | Company with name, slug, logo, active flag |
485
- | `CompanyBranch` | `company_branch` | Branch within a company |
486
- | `UserCompanyPermission` | `user_company_permission` | User ↔ Company / Branch assignment with `PermissionType` |
487
-
488
- #### Entity Registration in TypeORM
489
-
490
- Use `AuthModule.getEntities()` to include auth entities in your `TypeOrmModule`:
174
+ Pass auth entities to your TypeORM setup using the static helper:
491
175
 
492
176
  ```typescript
493
- import { AuthModule } from '@flusys/nestjs-auth';
177
+ import { getEntitiesByConfig } from '@flusys/nestjs-auth/entities';
494
178
 
495
179
  TypeOrmModule.forRoot({
496
- // ...
497
180
  entities: [
498
- ...AuthModule.getEntities({ enableCompanyFeature: true, enableEmailVerification: true }),
499
- // other app entities
181
+ ...getEntitiesByConfig({
182
+ enableCompanyFeature: true,
183
+ enableEmailVerification: false,
184
+ }),
500
185
  ],
501
- })
186
+ });
502
187
  ```
503
188
 
504
- ---
505
-
506
- ## Provider Interfaces (Extensibility)
189
+ Keep the flags here in sync with those passed to `forRoot`/`forRootAsync`.
507
190
 
508
- `@flusys/nestjs-auth` is designed to be standalone — it does not import `@flusys/nestjs-email` or `@flusys/nestjs-iam`. Optional features are connected via **provider interfaces** defined in this package and implemented in your application.
191
+ ## 3. Pluggable Email — `AUTH_EMAIL_PROVIDER`
509
192
 
510
- ### AUTH_EMAIL_PROVIDER
511
-
512
- Enables password-reset and email-verification emails without a hard dependency on any email module.
513
-
514
- **Interface:**
193
+ Auth never imports `nestjs-email`. Connect them via a factory provider:
515
194
 
516
195
  ```typescript
517
- import { IAuthEmailProvider, AUTH_EMAIL_PROVIDER } from '@flusys/nestjs-auth';
518
-
519
- interface IAuthEmailProvider {
520
- sendPasswordResetEmail(email: string, token: string, resetUrl: string): Promise<void>;
521
- sendVerificationEmail(email: string, token: string, verifyUrl: string): Promise<void>;
522
- sendWelcomeEmail?(email: string, name: string): Promise<void>; // optional
523
- }
524
- ```
525
-
526
- **Implementation example (using `@flusys/nestjs-email`):**
527
-
528
- ```typescript
529
- import { AUTH_EMAIL_PROVIDER } from '@flusys/nestjs-auth';
196
+ import { AUTH_EMAIL_PROVIDER, IAuthEmailProvider } from '@flusys/nestjs-auth';
530
197
  import { EmailSendService } from '@flusys/nestjs-email';
531
198
 
532
199
  export const authEmailProvider = {
533
200
  provide: AUTH_EMAIL_PROVIDER,
534
- useFactory: (emailSendService: EmailSendService) => ({
201
+ useFactory: (emailSendService: EmailSendService): IAuthEmailProvider => ({
535
202
  sendPasswordResetEmail: async (email, token, resetUrl) => {
536
203
  await emailSendService.sendTemplateEmail({
537
204
  templateSlug: 'password-reset',
@@ -546,93 +213,48 @@ export const authEmailProvider = {
546
213
  variables: { verifyUrl, token },
547
214
  });
548
215
  },
549
- sendWelcomeEmail: async (email, name) => {
550
- await emailSendService.sendTemplateEmail({
551
- templateSlug: 'welcome',
552
- to: email,
553
- variables: { name },
554
- });
555
- },
216
+ // sendWelcomeEmail is optional
556
217
  }),
557
218
  inject: [EmailSendService],
558
219
  };
559
- ```
560
-
561
- **Register in `forRootAsync`:**
562
220
 
563
- ```typescript
564
- AuthModule.forRootAsync({
565
- // ...
566
- providers: [authEmailProvider],
567
- })
221
+ // Register:
222
+ AuthModule.forRootAsync({ ..., providers: [authEmailProvider] });
568
223
  ```
569
224
 
570
- When `AUTH_EMAIL_PROVIDER` is not provided, the auth module runs without email sending — registration still works but verification emails are silently skipped.
571
-
572
- ---
225
+ When `AUTH_EMAIL_PROVIDER` is not provided, registration still works but emails are silently skipped.
573
226
 
574
- ### USER_ENRICHER
227
+ ## 4. User Enrichment — `USER_ENRICHER`
575
228
 
576
- Allows extending the user list and profile responses with data from external modules (roles, permissions, custom profile sections) without creating circular dependencies.
229
+ Extend user list and profile responses without creating circular imports. All methods are optional — implement only what you need.
577
230
 
578
- **Interface:**
231
+ | Method | When called | Purpose |
232
+ | --- | --- | --- |
233
+ | `enrichListQuery` | User list query build | Add extra joins/selects to the query |
234
+ | `enrichListItems` | After user list fetch | Attach computed data to list items |
235
+ | `getProfileExtras` | `POST /users/get/:id` | Add roles, actions, sections to profile |
236
+ | `updateProfileExtras` | Profile update (in tx) | Persist extra profile fields |
237
+ | `validateProfileExtras` | Before profile update | Validate extra fields |
238
+ | `getProfileSections` | Profile form init | Return multi-step section definitions |
239
+ | `getProfileSectionData` | Section fetch | Return section-specific data |
240
+ | `updateProfileSection` | Section save (in tx) | Persist section data |
241
+ | `handleSectionFileUpload` | File upload (in tx) | Handle file attach to section |
242
+ | `handleSectionFileDelete` | File delete (in tx) | Handle file removal from section |
243
+ | `calculateProfileCompletion` | Profile fetch | Return 0–100 completion score |
244
+ | `onUserCreated` | Registration (in tx) | Hook after user row created; throw to rollback |
579
245
 
580
246
  ```typescript
581
- import { IUserEnricher, USER_ENRICHER } from '@flusys/nestjs-auth';
582
-
583
- interface IUserEnricher {
584
- // Extend the user list SQL query with extra joins/selects
585
- enrichListQuery?(query: SelectQueryBuilder<User>, user: ILoggedUserInfo | null): Promise<{ extraFields: string[] }>;
586
-
587
- // Transform user list items (add computed fields)
588
- enrichListItems?(users: IUser[], user: ILoggedUserInfo | null): Promise<IEnrichedUser[]>;
589
-
590
- // Return extra profile data (roles, actions, etc.)
591
- getProfileExtras?(userId: string, user: ILoggedUserInfo): Promise<IProfileExtras>;
592
-
593
- // Update extra profile fields inside a transaction
594
- updateProfileExtras?(userId: string, extras: Record<string, any>, queryRunner: QueryRunner): Promise<void>;
595
-
596
- // Validate extra fields before saving
597
- validateProfileExtras?(userId: string, extras: Record<string, any>, user: ILoggedUserInfo): Promise<void>;
598
-
599
- // Return multi-step profile section definitions
600
- getProfileSections?(): IProfileSection[];
601
-
602
- // Return data for a specific profile section
603
- getProfileSectionData?(userId: string, sectionId: string, user: ILoggedUserInfo): Promise<IProfileSectionData>;
604
-
605
- // Update a specific profile section inside a transaction
606
- updateProfileSection?(userId: string, sectionId: string, data: Record<string, any>, queryRunner: QueryRunner): Promise<void>;
607
-
608
- // Handle file upload for a profile section
609
- handleSectionFileUpload?(userId: string, sectionId: string, field: string, fileInfo: { name: string; url: string; size: number; type: string }, queryRunner: QueryRunner): Promise<IProfileFile>;
610
-
611
- // Handle file deletion for a profile section
612
- handleSectionFileDelete?(userId: string, sectionId: string, field: string, fileId: string, queryRunner: QueryRunner): Promise<void>;
613
-
614
- // Return profile completion percentage (0–100)
615
- calculateProfileCompletion?(userId: string, user: ILoggedUserInfo): Promise<number>;
616
-
617
- // Hook: called after user is created during registration (inside transaction — throw to rollback)
618
- onUserCreated?(userId: string, additionalFields: Record<string, any> | null, queryRunner: QueryRunner): Promise<void>;
619
- }
620
- ```
621
-
622
- **Implementation example (adding IAM roles to user profile):**
623
-
624
- ```typescript
625
- import { USER_ENRICHER, IUserEnricher } from '@flusys/nestjs-auth';
626
- import { PermissionService } from '@flusys/nestjs-iam';
247
+ import { USER_ENRICHER, IUserEnricher, IProfileExtras } from '@flusys/nestjs-auth';
248
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
249
+ import { Injectable, Inject } from '@nestjs/common';
627
250
 
628
251
  @Injectable()
629
252
  export class IamUserEnricher implements IUserEnricher {
630
- constructor(private readonly permissionService: PermissionService) {}
253
+ constructor(@Inject(PermissionService) private readonly permissionService: PermissionService) {}
631
254
 
632
- async getProfileExtras(userId: string, user: ILoggedUserInfo) {
255
+ async getProfileExtras(userId: string, user: ILoggedUserInfo): Promise<IProfileExtras> {
633
256
  const roles = await this.permissionService.getRolesForUser(userId);
634
- const actions = await this.permissionService.getActionsForUser(userId);
635
- return { roles, actions };
257
+ return { roles };
636
258
  }
637
259
  }
638
260
 
@@ -640,201 +262,11 @@ export const userEnricherProvider = {
640
262
  provide: USER_ENRICHER,
641
263
  useClass: IamUserEnricher,
642
264
  };
643
- ```
644
265
 
645
- **Register in `forRootAsync`:**
646
-
647
- ```typescript
648
- AuthModule.forRootAsync({
649
- // ...
650
- providers: [userEnricherProvider],
651
- })
266
+ // Register:
267
+ AuthModule.forRootAsync({ ..., providers: [userEnricherProvider] });
652
268
  ```
653
269
 
654
- All methods in `IUserEnricher` are optional. Implement only the hooks you need.
655
-
656
- ---
657
-
658
- ## Exported Services
659
-
660
- These services are exported by `AuthModule` and can be injected into your application:
661
-
662
- | Service | Description |
663
- |---------|-------------|
664
- | `AuthenticationService` | Core auth logic: login, register, refresh, logout, change password |
665
- | `UserService` | User CRUD, profile management, enrichment hooks |
666
- | `CompanyService` | Company CRUD (exported when `enableCompanyFeature: true`) |
667
- | `BranchService` | Branch CRUD (exported when `enableCompanyFeature: true`) |
668
- | `UserPermissionService` | User-company-branch assignment (exported when `enableCompanyFeature: true`) |
669
- | `CompanySelectionSessionService` | Session management for multi-company login flow |
670
- | `AuthEmailService` | Wraps `AUTH_EMAIL_PROVIDER` for internal email dispatch |
671
- | `AuthConfigService` | Provides runtime config values (JWT secrets, cookie name, frontend URL) |
672
- | `AuthDataSourceProvider` | Dynamic TypeORM DataSource resolution per request |
673
- | `JwtStrategy` | Passport JWT strategy |
674
- | `JwtAuthGuard` | Guard that validates Bearer token on protected endpoints |
675
-
676
- **Usage:**
677
-
678
- ```typescript
679
- import { AuthenticationService } from '@flusys/nestjs-auth';
680
-
681
- @Injectable()
682
- export class MyService {
683
- constructor(
684
- @Inject(AuthenticationService) private readonly authService: AuthenticationService,
685
- ) {}
686
-
687
- async validateToken(token: string) {
688
- return this.authService.refreshToken(token);
689
- }
690
- }
691
- ```
692
-
693
- > **Note:** Always use `@Inject(ServiceClass)` explicitly. This package is distributed as an esbuild bundle where TypeScript metadata is not preserved.
694
-
695
- ---
696
-
697
- ## Interceptors
698
-
699
- | Interceptor | Token | Description |
700
- |-------------|-------|-------------|
701
- | `SetToken` | `SetToken` | Writes the refresh token as an HTTP-only cookie on login/register responses |
702
- | `ClearToken` | `ClearToken` | Clears the refresh token cookie on logout |
703
-
704
- Applied automatically by the auth controllers. Export them from `AuthModule` if you need them in custom controllers.
705
-
706
- ---
707
-
708
- ## Security
709
-
710
- ### Token Strategy
711
-
712
- | Token | Storage | Hashing | Expiry |
713
- |-------|---------|---------|--------|
714
- | Access Token (JWT) | Client memory / Authorization header | JWT-signed | Short (e.g. 15m) |
715
- | Refresh Token | HTTP-only cookie (browser) / request body (mobile) | SHA-256 in DB | Long (e.g. 7d) |
716
- | Password Reset Token | Email link only | SHA-256 in DB | Short (e.g. 1h) |
717
- | Email Verification Token | Email link only | SHA-256 in DB | Short (e.g. 24h) |
718
-
719
- ### Password Hashing
720
-
721
- bcrypt with **12 salt rounds** (OWASP recommended minimum).
722
-
723
- ### Rate Limiting
724
-
725
- Built-in via `@nestjs/throttler`:
726
-
727
- | Endpoint | Limit |
728
- |----------|-------|
729
- | `POST /auth/login` | 5 requests / 60s |
730
- | `POST /auth/register` | 3 requests / 60s |
731
- | `POST /auth/change-password` | 5 requests / 60s |
732
- | `POST /auth/refresh` | 30 requests / 60s |
733
-
734
- ### SQL Injection Protection
735
-
736
- All database queries use TypeORM parameterized statements. No raw string interpolation.
737
-
738
- ### Important Notes
739
-
740
- - Never expose `jwtSecret` or `refreshTokenSecret` in version control.
741
- - Set `frontendUrl` correctly — it is used to construct password-reset and email-verification links sent via email.
742
- - In production, set `NODE_ENV=production` to enforce secure cookie flags.
743
-
744
- ---
745
-
746
- ## Environment Variables
747
-
748
- ```env
749
- # Database
750
- DB_HOST=localhost
751
- DB_PORT=5432
752
- DB_USER=postgres
753
- DB_PASSWORD=secret
754
- DB_NAME=myapp
755
-
756
- # JWT
757
- JWT_SECRET=change-me-in-production
758
- JWT_EXPIRATION=15m
759
- REFRESH_TOKEN_SECRET=change-me-in-production-2
760
- REFRESH_TOKEN_EXPIRATION=7d
761
-
762
- # Application
763
- FRONTEND_URL=http://localhost:3000
764
- PORT=3001
765
- ```
766
-
767
- ---
768
-
769
- ## Troubleshooting
770
-
771
- **`Cannot read properties of undefined` on service injection**
772
-
773
- You are likely missing `@Inject()` on a constructor parameter. This package is bundled with esbuild and TypeScript metadata is not preserved:
774
-
775
- ```typescript
776
- // Wrong
777
- constructor(private readonly authService: AuthenticationService) {}
778
-
779
- // Correct
780
- constructor(@Inject(AuthenticationService) private readonly authService: AuthenticationService) {}
781
- ```
782
-
783
- ---
784
-
785
- **Login returns `requiresEmailVerification: true` unexpectedly**
786
-
787
- The user registered but has not verified their email. Either:
788
- 1. Provide `AUTH_EMAIL_PROVIDER` so the verification email is actually sent.
789
- 2. Disable email verification: `enableEmailVerification: false`.
790
- 3. Use the admin endpoint `POST /administration/users/:id/verify-email` to manually verify.
791
-
792
- ---
793
-
794
- **Login returns `requiresSelection: true` unexpectedly**
795
-
796
- The user belongs to more than one company. Your frontend must handle the `CompanySelectionResponseDto` response and call `POST /auth/company-selection/select` with the `sessionId` and chosen company/branch IDs.
797
-
798
- ---
799
-
800
- **`No metadata for entity` error on startup**
801
-
802
- Ensure `AuthModule.getEntities()` is called with the correct config when registering `TypeOrmModule`:
803
-
804
- ```typescript
805
- TypeOrmModule.forRoot({
806
- entities: [
807
- ...AuthModule.getEntities({
808
- enableCompanyFeature: true,
809
- enableEmailVerification: true,
810
- }),
811
- ],
812
- })
813
- ```
814
-
815
- ---
816
-
817
- **Refresh token cookie not being set**
818
-
819
- Ensure your Express app has `cookie-parser` middleware installed and registered before NestJS:
820
-
821
- ```typescript
822
- import * as cookieParser from 'cookie-parser';
823
- app.use(cookieParser());
824
- ```
825
-
826
- ---
827
-
828
- **Password reset / verification emails not being sent**
829
-
830
- `AUTH_EMAIL_PROVIDER` is optional. If not provided, emails are silently skipped. Implement and register the provider to enable email sending (see [AUTH_EMAIL_PROVIDER](#auth_email_provider)).
831
-
832
- ---
833
-
834
270
  ## License
835
271
 
836
272
  MIT © FLUSYS
837
-
838
- ---
839
-
840
- > Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.