@flusys/nestjs-auth 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,27 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flusys/nestjs-auth
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
> Production-grade authentication and user management module for NestJS — with JWT, email verification, multi-company hierarchy, multi-tenant support, and pluggable extensibility.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/nestjs-auth)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://nestjs.com/)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
|
|
11
|
+
---
|
|
6
12
|
|
|
7
13
|
## Table of Contents
|
|
8
14
|
|
|
15
|
+
- [Overview](#overview)
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Compatibility](#compatibility)
|
|
9
18
|
- [Installation](#installation)
|
|
10
19
|
- [Quick Start](#quick-start)
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
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)
|
|
16
29
|
- [API Endpoints](#api-endpoints)
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
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)
|
|
22
35
|
- [Interceptors](#interceptors)
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
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
|
+
```
|
|
25
94
|
|
|
26
95
|
---
|
|
27
96
|
|
|
@@ -31,9 +100,21 @@
|
|
|
31
100
|
npm install @flusys/nestjs-auth @flusys/nestjs-shared @flusys/nestjs-core
|
|
32
101
|
```
|
|
33
102
|
|
|
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
|
+
---
|
|
114
|
+
|
|
34
115
|
## Quick Start
|
|
35
116
|
|
|
36
|
-
|
|
117
|
+
Register `AuthModule` in your root `AppModule`. This minimal setup enables JWT authentication, user CRUD, and email verification in a single-database application:
|
|
37
118
|
|
|
38
119
|
```typescript
|
|
39
120
|
import { Module } from '@nestjs/common';
|
|
@@ -47,18 +128,22 @@ import { AuthModule } from '@flusys/nestjs-auth';
|
|
|
47
128
|
bootstrapAppConfig: {
|
|
48
129
|
databaseMode: 'single',
|
|
49
130
|
enableCompanyFeature: false,
|
|
131
|
+
enableEmailVerification: true,
|
|
50
132
|
},
|
|
51
133
|
config: {
|
|
52
134
|
defaultDatabaseConfig: {
|
|
53
135
|
type: 'postgres',
|
|
54
|
-
host:
|
|
55
|
-
port: 5432,
|
|
56
|
-
username:
|
|
57
|
-
password:
|
|
58
|
-
database:
|
|
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,
|
|
59
141
|
},
|
|
60
142
|
jwtSecret: process.env.JWT_SECRET,
|
|
61
|
-
jwtExpiration: '
|
|
143
|
+
jwtExpiration: '15m',
|
|
144
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
|
|
145
|
+
refreshTokenExpiration: '7d',
|
|
146
|
+
frontendUrl: process.env.FRONTEND_URL,
|
|
62
147
|
},
|
|
63
148
|
}),
|
|
64
149
|
],
|
|
@@ -66,7 +151,13 @@ import { AuthModule } from '@flusys/nestjs-auth';
|
|
|
66
151
|
export class AppModule {}
|
|
67
152
|
```
|
|
68
153
|
|
|
69
|
-
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Module Registration
|
|
157
|
+
|
|
158
|
+
### forRoot (Sync)
|
|
159
|
+
|
|
160
|
+
Use when all configuration values are available synchronously (environment variables, hardcoded values):
|
|
70
161
|
|
|
71
162
|
```typescript
|
|
72
163
|
AuthModule.forRoot({
|
|
@@ -74,355 +165,371 @@ AuthModule.forRoot({
|
|
|
74
165
|
includeController: true,
|
|
75
166
|
bootstrapAppConfig: {
|
|
76
167
|
databaseMode: 'single',
|
|
77
|
-
enableCompanyFeature:
|
|
168
|
+
enableCompanyFeature: false,
|
|
169
|
+
enableEmailVerification: true,
|
|
78
170
|
},
|
|
79
171
|
config: {
|
|
80
|
-
defaultDatabaseConfig: { /*
|
|
172
|
+
defaultDatabaseConfig: { /* TypeORM DataSourceOptions */ },
|
|
81
173
|
jwtSecret: process.env.JWT_SECRET,
|
|
174
|
+
jwtExpiration: '15m',
|
|
82
175
|
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
|
|
83
176
|
refreshTokenExpiration: '7d',
|
|
84
|
-
|
|
177
|
+
frontendUrl: 'http://localhost:3000',
|
|
85
178
|
},
|
|
179
|
+
providers: [], // Optional: AUTH_EMAIL_PROVIDER, USER_ENRICHER, etc.
|
|
86
180
|
})
|
|
87
181
|
```
|
|
88
182
|
|
|
89
|
-
###
|
|
183
|
+
### forRootAsync (Factory)
|
|
184
|
+
|
|
185
|
+
Use when configuration is loaded from an external service or config module:
|
|
90
186
|
|
|
91
187
|
```typescript
|
|
92
|
-
|
|
188
|
+
import { ConfigService } from '@nestjs/config';
|
|
189
|
+
import { AuthModule } from '@flusys/nestjs-auth';
|
|
190
|
+
|
|
191
|
+
AuthModule.forRootAsync({
|
|
93
192
|
global: true,
|
|
94
193
|
includeController: true,
|
|
194
|
+
imports: [ConfigModule],
|
|
95
195
|
bootstrapAppConfig: {
|
|
96
|
-
databaseMode: '
|
|
196
|
+
databaseMode: 'single',
|
|
97
197
|
enableCompanyFeature: true,
|
|
198
|
+
enableEmailVerification: true,
|
|
98
199
|
},
|
|
99
|
-
|
|
100
|
-
|
|
200
|
+
useFactory: (configService: ConfigService) => ({
|
|
201
|
+
defaultDatabaseConfig: {
|
|
101
202
|
type: 'postgres',
|
|
102
|
-
host: '
|
|
103
|
-
port:
|
|
104
|
-
username: '
|
|
105
|
-
password: '
|
|
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'),
|
|
106
208
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
},
|
|
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'),
|
|
214
|
+
}),
|
|
215
|
+
inject: [ConfigService],
|
|
216
|
+
providers: [emailProviderFactory, userEnricherProvider],
|
|
113
217
|
})
|
|
114
218
|
```
|
|
115
219
|
|
|
116
|
-
###
|
|
220
|
+
### forRootAsync (Class)
|
|
221
|
+
|
|
222
|
+
Use when configuration is encapsulated in a dedicated service:
|
|
117
223
|
|
|
118
224
|
```typescript
|
|
119
|
-
import {
|
|
120
|
-
import {
|
|
225
|
+
import { AuthOptionsFactory, IAuthModuleConfig } from '@flusys/nestjs-auth';
|
|
226
|
+
import { Injectable } from '@nestjs/common';
|
|
121
227
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
variables: { verifyUrl, token },
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
}),
|
|
143
|
-
inject: [EmailSendService],
|
|
144
|
-
},
|
|
145
|
-
],
|
|
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:
|
|
242
|
+
AuthModule.forRootAsync({
|
|
243
|
+
bootstrapAppConfig: { databaseMode: 'single', enableCompanyFeature: false },
|
|
244
|
+
useClass: MyAuthConfigFactory,
|
|
146
245
|
})
|
|
147
246
|
```
|
|
148
247
|
|
|
149
|
-
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Configuration Reference
|
|
251
|
+
|
|
252
|
+
### Bootstrap Config
|
|
150
253
|
|
|
151
|
-
|
|
254
|
+
Bootstrap configuration is **fixed at startup** — it determines which entities, controllers, and services are registered. It cannot change at runtime.
|
|
152
255
|
|
|
153
256
|
```typescript
|
|
154
|
-
interface
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
defaultDatabaseConfig?: IDatabaseConfig;
|
|
164
|
-
tenantDefaultDatabaseConfig?: IDatabaseConfig;
|
|
165
|
-
tenants?: ITenantDatabaseConfig[];
|
|
166
|
-
jwtSecret?: string;
|
|
167
|
-
jwtExpiration?: string; // Default: '1h'
|
|
168
|
-
refreshTokenSecret?: string;
|
|
169
|
-
refreshTokenExpiration?: string; // Default: '7d'
|
|
170
|
-
refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
|
|
171
|
-
frontendUrl?: string; // For email links
|
|
172
|
-
};
|
|
173
|
-
providers?: Provider[]; // Additional providers (AUTH_EMAIL_PROVIDER, USER_ENRICHER)
|
|
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;
|
|
174
266
|
}
|
|
175
267
|
```
|
|
176
268
|
|
|
177
|
-
|
|
269
|
+
### Runtime Config
|
|
178
270
|
|
|
179
|
-
|
|
180
|
-
|------|------|---------|--------|
|
|
181
|
-
| `enableCompanyFeature` | `boolean` | `false` | Enables company/branch entities and APIs |
|
|
182
|
-
| `enableEmailVerification` | `boolean` | `true` | When `false`: excludes `AuthEmailService`, hides email-related APIs (forgot-password, reset-password, verify-email, resend-verification), and excludes `PendingRegistration` entity from migrations |
|
|
183
|
-
|
|
184
|
-
## Constants
|
|
271
|
+
Runtime configuration is loaded at startup but can be sourced from environment variables, config files, or external APIs.
|
|
185
272
|
|
|
186
273
|
```typescript
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
274
|
+
interface IAuthModuleConfig {
|
|
275
|
+
/** TypeORM DataSource options for the default (or single) database */
|
|
276
|
+
defaultDatabaseConfig?: DataSourceOptions;
|
|
190
277
|
|
|
191
|
-
|
|
192
|
-
|
|
278
|
+
/** Tenant database configs (multi-tenant mode only) */
|
|
279
|
+
tenants?: Array<{ id: string; databaseConfig: DataSourceOptions }>;
|
|
280
|
+
|
|
281
|
+
/** JWT access token secret */
|
|
282
|
+
jwtSecret?: string;
|
|
193
283
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
export const MAX_COOKIE_BYTES = 3500;
|
|
284
|
+
/** JWT access token expiration (e.g. '15m', '1h', '7d') */
|
|
285
|
+
jwtExpiration?: string;
|
|
197
286
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
}
|
|
201
299
|
```
|
|
202
300
|
|
|
203
|
-
|
|
301
|
+
---
|
|
204
302
|
|
|
205
|
-
|
|
303
|
+
## Feature Toggles
|
|
206
304
|
|
|
207
|
-
|
|
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)
|
|
208
317
|
|
|
209
318
|
```typescript
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Exported interface for configuration
|
|
216
|
-
export interface IAuthEntitiesConfig {
|
|
217
|
-
enableCompanyFeature?: boolean;
|
|
218
|
-
enableEmailVerification?: boolean; // Default: true
|
|
319
|
+
bootstrapAppConfig: {
|
|
320
|
+
databaseMode: 'single',
|
|
321
|
+
enableCompanyFeature: false,
|
|
322
|
+
enableEmailVerification: true,
|
|
219
323
|
}
|
|
324
|
+
```
|
|
220
325
|
|
|
221
|
-
|
|
222
|
-
export function getEntitiesByConfig(config: IAuthEntitiesConfig): any[] {
|
|
223
|
-
const { enableCompanyFeature = false, enableEmailVerification = true } = config;
|
|
326
|
+
### Single Database + Multi-Company
|
|
224
327
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
328
|
+
```typescript
|
|
329
|
+
bootstrapAppConfig: {
|
|
330
|
+
databaseMode: 'single',
|
|
331
|
+
enableCompanyFeature: true,
|
|
332
|
+
enableEmailVerification: true,
|
|
229
333
|
}
|
|
230
334
|
```
|
|
231
335
|
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
import { getEntitiesByConfig } from '@flusys/nestjs-auth/entities';
|
|
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.
|
|
235
337
|
|
|
236
|
-
|
|
237
|
-
const entities = getEntitiesByConfig({ enableCompanyFeature: false });
|
|
238
|
-
// Returns: [User, UserToken, PendingRegistration]
|
|
338
|
+
### Multi-Tenant (Separate DB per Tenant)
|
|
239
339
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
+
}
|
|
243
355
|
```
|
|
244
356
|
|
|
245
|
-
|
|
357
|
+
---
|
|
246
358
|
|
|
247
|
-
|
|
248
|
-
|------------------------|---------------------|----------|
|
|
249
|
-
| `true` (default) | `false` | User, UserToken, PendingRegistration |
|
|
250
|
-
| `true` | `true` | All entities |
|
|
251
|
-
| `false` | `false` | User, UserToken only |
|
|
252
|
-
| `false` | `true` | User, UserToken, Company, CompanyBranch, UserCompanyPermission |
|
|
359
|
+
## API Endpoints
|
|
253
360
|
|
|
254
|
-
|
|
361
|
+
All endpoints use **POST** (POST-only RPC design). Authentication required unless marked **Public**.
|
|
255
362
|
|
|
256
|
-
|
|
363
|
+
### Authentication — `POST /auth/*`
|
|
257
364
|
|
|
258
|
-
|
|
|
259
|
-
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
266
|
-
| `emailVerified` | `boolean` | Email verification status |
|
|
267
|
-
| `phoneVerified` | `boolean` | Phone verification status |
|
|
268
|
-
| `profilePictureId` | `uuid` | Reference to storage file |
|
|
269
|
-
| `lastLoginAt` | `timestamp` | Last login timestamp |
|
|
270
|
-
| `additionalFields` | `json` | Extensible JSON fields |
|
|
271
|
-
| `createdAt` | `timestamp` | Auto-generated |
|
|
272
|
-
| `updatedAt` | `timestamp` | Auto-updated |
|
|
273
|
-
| `deletedAt` | `timestamp` | Soft delete timestamp |
|
|
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 |
|
|
274
373
|
|
|
275
|
-
|
|
374
|
+
#### Login Response Variants
|
|
276
375
|
|
|
277
|
-
|
|
376
|
+
Login returns one of three shapes depending on configuration:
|
|
278
377
|
|
|
279
378
|
```typescript
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
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
|
|
285
397
|
```
|
|
286
398
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
|
292
|
-
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
311
|
-
| `
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
| `
|
|
316
|
-
| `
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
- `
|
|
322
|
-
- `
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
### Company
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
|
330
|
-
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `
|
|
335
|
-
| `
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
|
358
|
-
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
|
365
|
-
|
|
366
|
-
| `
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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`)
|
|
475
|
+
|
|
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`:
|
|
374
491
|
|
|
375
492
|
```typescript
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
493
|
+
import { AuthModule } from '@flusys/nestjs-auth';
|
|
494
|
+
|
|
495
|
+
TypeOrmModule.forRoot({
|
|
496
|
+
// ...
|
|
497
|
+
entities: [
|
|
498
|
+
...AuthModule.getEntities({ enableCompanyFeature: true, enableEmailVerification: true }),
|
|
499
|
+
// other app entities
|
|
500
|
+
],
|
|
501
|
+
})
|
|
380
502
|
```
|
|
381
503
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
| `targetId` | `uuid` | Company ID or Branch ID |
|
|
388
|
-
| `metadata` | `json` | Optional metadata for the permission |
|
|
389
|
-
| `isActive` | `boolean` | Permission status (default: true) |
|
|
390
|
-
| `createdAt` | `timestamp` | Auto-generated |
|
|
391
|
-
| `updatedAt` | `timestamp` | Auto-updated |
|
|
392
|
-
|
|
393
|
-
**Indexes:**
|
|
394
|
-
- Unique index on `(userId, permissionType, targetId)`
|
|
395
|
-
- Index on `(userId, permissionType)`
|
|
396
|
-
- Index on `(permissionType, targetId)`
|
|
397
|
-
|
|
398
|
-
**Relations:**
|
|
399
|
-
- `user` - Many-to-One relation to User (CASCADE delete)
|
|
400
|
-
- `company` - Many-to-One relation to Company (no FK constraint - polymorphic)
|
|
401
|
-
- `branch` - Many-to-One relation to CompanyBranch (no FK constraint - polymorphic)
|
|
402
|
-
|
|
403
|
-
## Provider Interfaces
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## Provider Interfaces (Extensibility)
|
|
507
|
+
|
|
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.
|
|
404
509
|
|
|
405
510
|
### AUTH_EMAIL_PROVIDER
|
|
406
511
|
|
|
407
|
-
|
|
512
|
+
Enables password-reset and email-verification emails without a hard dependency on any email module.
|
|
513
|
+
|
|
514
|
+
**Interface:**
|
|
408
515
|
|
|
409
516
|
```typescript
|
|
517
|
+
import { IAuthEmailProvider, AUTH_EMAIL_PROVIDER } from '@flusys/nestjs-auth';
|
|
518
|
+
|
|
410
519
|
interface IAuthEmailProvider {
|
|
411
|
-
/** Send password reset email */
|
|
412
520
|
sendPasswordResetEmail(email: string, token: string, resetUrl: string): Promise<void>;
|
|
413
|
-
|
|
414
|
-
/** Send email verification email */
|
|
415
521
|
sendVerificationEmail(email: string, token: string, verifyUrl: string): Promise<void>;
|
|
416
|
-
|
|
417
|
-
/** Send welcome email after registration (optional) */
|
|
418
|
-
sendWelcomeEmail?(email: string, name: string): Promise<void>;
|
|
522
|
+
sendWelcomeEmail?(email: string, name: string): Promise<void>; // optional
|
|
419
523
|
}
|
|
420
524
|
```
|
|
421
525
|
|
|
422
|
-
**
|
|
526
|
+
**Implementation example (using `@flusys/nestjs-email`):**
|
|
423
527
|
|
|
424
528
|
```typescript
|
|
425
|
-
{
|
|
529
|
+
import { AUTH_EMAIL_PROVIDER } from '@flusys/nestjs-auth';
|
|
530
|
+
import { EmailSendService } from '@flusys/nestjs-email';
|
|
531
|
+
|
|
532
|
+
export const authEmailProvider = {
|
|
426
533
|
provide: AUTH_EMAIL_PROVIDER,
|
|
427
534
|
useFactory: (emailSendService: EmailSendService) => ({
|
|
428
535
|
sendPasswordResetEmail: async (email, token, resetUrl) => {
|
|
@@ -439,960 +546,295 @@ interface IAuthEmailProvider {
|
|
|
439
546
|
variables: { verifyUrl, token },
|
|
440
547
|
});
|
|
441
548
|
},
|
|
549
|
+
sendWelcomeEmail: async (email, name) => {
|
|
550
|
+
await emailSendService.sendTemplateEmail({
|
|
551
|
+
templateSlug: 'welcome',
|
|
552
|
+
to: email,
|
|
553
|
+
variables: { name },
|
|
554
|
+
});
|
|
555
|
+
},
|
|
442
556
|
}),
|
|
443
557
|
inject: [EmailSendService],
|
|
444
|
-
}
|
|
558
|
+
};
|
|
445
559
|
```
|
|
446
560
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
Extends user list and profile without modifying base package:
|
|
561
|
+
**Register in `forRootAsync`:**
|
|
450
562
|
|
|
451
563
|
```typescript
|
|
452
|
-
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
enrichListQuery?(
|
|
457
|
-
query: SelectQueryBuilder<User>,
|
|
458
|
-
user: ILoggedUserInfo | null,
|
|
459
|
-
): Promise<{ extraFields: string[] }>;
|
|
460
|
-
|
|
461
|
-
/** Transform user list items with extra computed data */
|
|
462
|
-
enrichListItems?(
|
|
463
|
-
users: IUser[],
|
|
464
|
-
user: ILoggedUserInfo | null,
|
|
465
|
-
): Promise<IEnrichedUser[]>;
|
|
466
|
-
|
|
467
|
-
// ===== Profile Enrichment =====
|
|
468
|
-
|
|
469
|
-
/** Get extra profile data (roles, permissions, sections) */
|
|
470
|
-
getProfileExtras?(
|
|
471
|
-
userId: string,
|
|
472
|
-
user: ILoggedUserInfo,
|
|
473
|
-
): Promise<IProfileExtras>;
|
|
474
|
-
|
|
475
|
-
/** Handle profile update with extra fields (within transaction) */
|
|
476
|
-
updateProfileExtras?(
|
|
477
|
-
userId: string,
|
|
478
|
-
extras: Record<string, any>,
|
|
479
|
-
queryRunner: QueryRunner,
|
|
480
|
-
): Promise<void>;
|
|
481
|
-
|
|
482
|
-
/** Validate extra fields before profile update */
|
|
483
|
-
validateProfileExtras?(
|
|
484
|
-
userId: string,
|
|
485
|
-
extras: Record<string, any>,
|
|
486
|
-
user: ILoggedUserInfo,
|
|
487
|
-
): Promise<void>;
|
|
488
|
-
|
|
489
|
-
// ===== Multi-Step Profile Methods =====
|
|
490
|
-
|
|
491
|
-
/** Get profile section definitions for multi-step profiles */
|
|
492
|
-
getProfileSections?(): IProfileSection[];
|
|
493
|
-
|
|
494
|
-
/** Get specific section data for a user */
|
|
495
|
-
getProfileSectionData?(
|
|
496
|
-
userId: string,
|
|
497
|
-
sectionId: string,
|
|
498
|
-
user: ILoggedUserInfo,
|
|
499
|
-
): Promise<IProfileSectionData>;
|
|
500
|
-
|
|
501
|
-
/** Update specific profile section (within transaction) */
|
|
502
|
-
updateProfileSection?(
|
|
503
|
-
userId: string,
|
|
504
|
-
sectionId: string,
|
|
505
|
-
data: Record<string, any>,
|
|
506
|
-
queryRunner: QueryRunner,
|
|
507
|
-
): Promise<void>;
|
|
508
|
-
|
|
509
|
-
/** Handle file upload for profile section */
|
|
510
|
-
handleSectionFileUpload?(
|
|
511
|
-
userId: string,
|
|
512
|
-
sectionId: string,
|
|
513
|
-
field: string,
|
|
514
|
-
fileInfo: { name: string; url: string; size: number; type: string },
|
|
515
|
-
queryRunner: QueryRunner,
|
|
516
|
-
): Promise<IProfileFile>;
|
|
517
|
-
|
|
518
|
-
/** Handle file deletion for profile section */
|
|
519
|
-
handleSectionFileDelete?(
|
|
520
|
-
userId: string,
|
|
521
|
-
sectionId: string,
|
|
522
|
-
field: string,
|
|
523
|
-
fileId: string,
|
|
524
|
-
queryRunner: QueryRunner,
|
|
525
|
-
): Promise<void>;
|
|
526
|
-
|
|
527
|
-
/** Calculate profile completion percentage (0-100) */
|
|
528
|
-
calculateProfileCompletion?(
|
|
529
|
-
userId: string,
|
|
530
|
-
user: ILoggedUserInfo,
|
|
531
|
-
): Promise<number>;
|
|
532
|
-
|
|
533
|
-
// ===== Registration Hook =====
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Hook called after user is created during registration
|
|
537
|
-
* Runs within the same database transaction as user creation
|
|
538
|
-
* Use this to create related entities (e.g., UserProfileInfo)
|
|
539
|
-
* Throw error to rollback the entire registration
|
|
540
|
-
*/
|
|
541
|
-
onUserCreated?(
|
|
542
|
-
userId: string,
|
|
543
|
-
additionalFields: Record<string, any> | null,
|
|
544
|
-
queryRunner: QueryRunner,
|
|
545
|
-
): Promise<void>;
|
|
546
|
-
}
|
|
564
|
+
AuthModule.forRootAsync({
|
|
565
|
+
// ...
|
|
566
|
+
providers: [authEmailProvider],
|
|
567
|
+
})
|
|
547
568
|
```
|
|
548
569
|
|
|
549
|
-
|
|
570
|
+
When `AUTH_EMAIL_PROVIDER` is not provided, the auth module runs without email sending — registration still works but verification emails are silently skipped.
|
|
550
571
|
|
|
551
|
-
|
|
552
|
-
interface IProfileSection {
|
|
553
|
-
id: string; // Unique identifier
|
|
554
|
-
name: string; // Display name
|
|
555
|
-
order: number; // Stepper order
|
|
556
|
-
icon?: string; // PrimeNG icon class
|
|
557
|
-
required?: boolean; // Required for completion
|
|
558
|
-
hasFileUpload?: boolean; // Has file fields
|
|
559
|
-
editPermission?: IPermissionLogic; // Required permission
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
interface IProfileSectionData {
|
|
563
|
-
sectionId: string;
|
|
564
|
-
data: Record<string, any>;
|
|
565
|
-
isComplete: boolean;
|
|
566
|
-
files?: IProfileFile[];
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
interface IProfileFile {
|
|
570
|
-
field: string;
|
|
571
|
-
id: string;
|
|
572
|
-
name: string;
|
|
573
|
-
url?: string;
|
|
574
|
-
type?: string;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
interface IProfileExtras {
|
|
578
|
-
roles?: IProfileRole[];
|
|
579
|
-
actions?: IProfileAction[];
|
|
580
|
-
sections?: IProfileSectionData[];
|
|
581
|
-
completionPercentage?: number;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
interface IProfileRole {
|
|
585
|
-
id: string;
|
|
586
|
-
name: string;
|
|
587
|
-
description?: string | null;
|
|
588
|
-
}
|
|
572
|
+
---
|
|
589
573
|
|
|
590
|
-
|
|
591
|
-
id: string;
|
|
592
|
-
name: string;
|
|
593
|
-
code: string;
|
|
594
|
-
description?: string | null;
|
|
595
|
-
}
|
|
574
|
+
### USER_ENRICHER
|
|
596
575
|
|
|
597
|
-
|
|
598
|
-
```
|
|
576
|
+
Allows extending the user list and profile responses with data from external modules (roles, permissions, custom profile sections) without creating circular dependencies.
|
|
599
577
|
|
|
600
|
-
**
|
|
578
|
+
**Interface:**
|
|
601
579
|
|
|
602
580
|
```typescript
|
|
603
|
-
|
|
604
|
-
import { USER_ENRICHER, IUserEnricher } from '@flusys/nestjs-auth';
|
|
605
|
-
import { QueryRunner } from 'typeorm';
|
|
606
|
-
|
|
607
|
-
@Injectable({ scope: Scope.REQUEST })
|
|
608
|
-
export class MyUserEnricher implements IUserEnricher {
|
|
609
|
-
/**
|
|
610
|
-
* Called after user creation, within the same transaction
|
|
611
|
-
* If this throws, the entire registration rolls back
|
|
612
|
-
*/
|
|
613
|
-
async onUserCreated(
|
|
614
|
-
userId: string,
|
|
615
|
-
additionalFields: Record<string, any> | null,
|
|
616
|
-
queryRunner: QueryRunner,
|
|
617
|
-
): Promise<void> {
|
|
618
|
-
if (!additionalFields) return;
|
|
619
|
-
|
|
620
|
-
// Create related entity (e.g., UserProfileInfo)
|
|
621
|
-
const profileInfo = queryRunner.manager.create(UserProfileInfo, {
|
|
622
|
-
userId,
|
|
623
|
-
prevCompanyName: additionalFields.prevCompanyName ?? null,
|
|
624
|
-
prevCompanyLocation: additionalFields.prevCompanyLocation ?? null,
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
await queryRunner.manager.save(UserProfileInfo, profileInfo);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
581
|
+
import { IUserEnricher, USER_ENRICHER } from '@flusys/nestjs-auth';
|
|
630
582
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
useClass: MyUserEnricher,
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
**Registration with additionalFields:**
|
|
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[] }>;
|
|
639
586
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
"name": "John Doe",
|
|
643
|
-
"email": "john@example.com",
|
|
644
|
-
"password": "password123",
|
|
645
|
-
"additionalFields": {
|
|
646
|
-
"prevCompanyName": "Acme Corp",
|
|
647
|
-
"prevCompanyLocation": "New York"
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
```
|
|
587
|
+
// Transform user list items (add computed fields)
|
|
588
|
+
enrichListItems?(users: IUser[], user: ILoggedUserInfo | null): Promise<IEnrichedUser[]>;
|
|
651
589
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
| Service | Scope | Description |
|
|
655
|
-
|---------|-------|-------------|
|
|
656
|
-
| `AuthenticationService` | REQUEST | Login, register, token management, company selection |
|
|
657
|
-
| `AuthEmailService` | REQUEST | Email verification, password reset, pending registrations |
|
|
658
|
-
| `UserService` | REQUEST | User CRUD, extends `RequestScopedApiService` |
|
|
659
|
-
| `CompanyService` | REQUEST | Company CRUD (when company feature enabled) |
|
|
660
|
-
| `BranchService` | REQUEST | Branch CRUD with company filtering |
|
|
661
|
-
| `UserPermissionService` | REQUEST | User-company/branch permission management |
|
|
662
|
-
| `AuthDataSourceProvider` | REQUEST | Dynamic datasource for single/multi-tenant |
|
|
663
|
-
| `AuthConfigService` | SINGLETON | JWT and module configuration access |
|
|
664
|
-
| `CompanySelectionSessionService` | SINGLETON | Session management for company selection |
|
|
665
|
-
|
|
666
|
-
### AuthEmailService
|
|
667
|
-
|
|
668
|
-
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.
|
|
669
|
-
|
|
670
|
-
| Method | Description |
|
|
671
|
-
|--------|-------------|
|
|
672
|
-
| `isEmailEnabled()` | Check if email provider is configured |
|
|
673
|
-
| `hasPendingRegistration(email)` | Check for pending registration |
|
|
674
|
-
| `createPendingRegistration(data)` | Create pending registration with verification email |
|
|
675
|
-
| `forgotPassword(email, resetUrlBase)` | Send password reset email |
|
|
676
|
-
| `resetPassword(token, newPassword)` | Reset password using token |
|
|
677
|
-
| `verifyEmail(token)` | Verify email (returns `{ type: 'pending', pending }` or `{ type: 'verified', message }`) |
|
|
678
|
-
| `resendVerificationEmail(email, verifyUrlBase)` | Resend verification email |
|
|
679
|
-
| `cleanupExpired()` | Clean up expired tokens and pending registrations |
|
|
680
|
-
|
|
681
|
-
**Token Expiry:**
|
|
682
|
-
- Password Reset: 1 hour
|
|
683
|
-
- Email Verification: 24 hours
|
|
684
|
-
|
|
685
|
-
### AuthenticationService
|
|
686
|
-
|
|
687
|
-
| Method | Description |
|
|
688
|
-
|--------|-------------|
|
|
689
|
-
| `login(email, password)` | Validate credentials and return tokens |
|
|
690
|
-
| `register(dto, verifyUrlBase?)` | Register new user (creates pending registration if email enabled) |
|
|
691
|
-
| `createUserFromPendingRegistration(pending)` | Create user from verified pending registration |
|
|
692
|
-
| `select(sessionId, companyId, branchId)` | Complete company selection after login |
|
|
693
|
-
| `switchCompany(userId, companyId, branchId)` | Switch to different company/branch |
|
|
694
|
-
| `refreshToken(refreshToken)` | Refresh access token |
|
|
695
|
-
| `changePassword(userId, currentPassword, newPassword)` | Change user password |
|
|
696
|
-
| `logout(userId)` | Revoke all tokens for user |
|
|
697
|
-
| `isTokenRevoked(userId, tokenIssuedAt)` | Check if token is revoked |
|
|
698
|
-
| `getUserCompanies(userId)` | Get user's available companies |
|
|
699
|
-
| `getUserBranches(userId, companyId)` | Get user's branches for a company |
|
|
700
|
-
| `getUserInfo(userId, companyId?, branchId?)` | Get current user info |
|
|
701
|
-
|
|
702
|
-
### UserService
|
|
703
|
-
|
|
704
|
-
Extends `RequestScopedApiService` for full CRUD support.
|
|
705
|
-
|
|
706
|
-
| Method | Description |
|
|
707
|
-
|--------|-------------|
|
|
708
|
-
| `insert(dto, user)` | Create user (auto-assigns company permissions) |
|
|
709
|
-
| `verifyEmail(id)` | Mark user email as verified |
|
|
710
|
-
| `verifyPhone(id)` | Mark user phone as verified |
|
|
711
|
-
| `updateStatus(id, isActive, user)` | Update user status (company-aware) |
|
|
712
|
-
| `profile(dto, user)` | Update own profile with optional extras |
|
|
713
|
-
| `getProfileWithExtras(userId, user)` | Get profile with enricher data |
|
|
714
|
-
| `getProfileSections()` | Get profile section definitions |
|
|
715
|
-
| `getProfileSectionData(userId, sectionId, user)` | Get specific section data |
|
|
716
|
-
| `updateProfileSection(userId, sectionId, data, user)` | Update specific section |
|
|
717
|
-
| `getProfileCompletion(userId, user)` | Calculate profile completion % |
|
|
718
|
-
| `getEnrichedList(users, user)` | Enrich user list with extra data |
|
|
719
|
-
|
|
720
|
-
### CompanyService
|
|
721
|
-
|
|
722
|
-
Extends `RequestScopedApiService` for full CRUD support on Company entity.
|
|
723
|
-
|
|
724
|
-
### BranchService
|
|
725
|
-
|
|
726
|
-
Extends `RequestScopedApiService` for full CRUD support on CompanyBranch entity.
|
|
727
|
-
Automatically applies company filter based on logged user context.
|
|
728
|
-
|
|
729
|
-
### UserPermissionService
|
|
730
|
-
|
|
731
|
-
| Method | Description |
|
|
732
|
-
|--------|-------------|
|
|
733
|
-
| `findUserPermissions(userId, type)` | Get all permissions of type for user |
|
|
734
|
-
| `assignPermission(userId, targetId, type, metadata?)` | Assign permission to user |
|
|
735
|
-
| `revokePermission(userId, targetId, type)` | Revoke permission from user |
|
|
736
|
-
| `findUserCompanies(userId)` | Get user's company permissions |
|
|
737
|
-
| `findUserBranches(userId, companyId?)` | Get user's branch permissions |
|
|
738
|
-
|
|
739
|
-
### CompanySelectionSessionService
|
|
740
|
-
|
|
741
|
-
Manages temporary sessions during company/branch selection flow.
|
|
742
|
-
|
|
743
|
-
| Method | Description |
|
|
744
|
-
|--------|-------------|
|
|
745
|
-
| `createSession(userId)` | Create session (returns sessionId, TTL: 5 minutes) |
|
|
746
|
-
| `getSession(sessionId)` | Get session without consuming |
|
|
747
|
-
| `consumeSession(sessionId)` | Get and delete session (one-time use) |
|
|
748
|
-
| `deleteSession(sessionId)` | Delete session |
|
|
749
|
-
|
|
750
|
-
### AuthConfigService
|
|
751
|
-
|
|
752
|
-
Provides configuration access.
|
|
753
|
-
|
|
754
|
-
| Method | Description |
|
|
755
|
-
|--------|-------------|
|
|
756
|
-
| `getDatabaseMode()` | Returns 'single' or 'multi-tenant' |
|
|
757
|
-
| `isMultiTenant()` | Check if multi-tenant mode |
|
|
758
|
-
| `isCompanyFeatureEnabled()` | Check if company feature enabled |
|
|
759
|
-
| `isEmailVerificationEnabled()` | Check if email verification enabled |
|
|
760
|
-
| `getJwtSecret()` | Get JWT secret (throws if not configured) |
|
|
761
|
-
| `getJwtExpiration()` | Get JWT expiration (default: '1h') |
|
|
762
|
-
| `getRefreshTokenSecret()` | Get refresh token secret |
|
|
763
|
-
| `getRefreshTokenExpiration()` | Get refresh expiration (default: '7d') |
|
|
764
|
-
| `getRefreshTokenCookieName()` | Get cookie name (default: 'fsn_refresh_token') |
|
|
765
|
-
| `getFrontendUrl()` | Get frontend URL for email links |
|
|
766
|
-
|
|
767
|
-
### AuthDataSourceProvider
|
|
768
|
-
|
|
769
|
-
Extends `MultiTenantDataSourceService` for dynamic datasource handling.
|
|
770
|
-
|
|
771
|
-
| Method | Description |
|
|
772
|
-
|--------|-------------|
|
|
773
|
-
| `getDataSource()` | Get current tenant's DataSource |
|
|
774
|
-
| `getRepository(entity)` | Get repository for entity |
|
|
775
|
-
| `isCompanyFeatureEnabled(tenant?)` | Check company feature for tenant |
|
|
776
|
-
| `isEmailVerificationEnabled(tenant?)` | Check email verification for tenant |
|
|
777
|
-
| `getAuthEntities()` | Get entities based on feature flags |
|
|
590
|
+
// Return extra profile data (roles, actions, etc.)
|
|
591
|
+
getProfileExtras?(userId: string, user: ILoggedUserInfo): Promise<IProfileExtras>;
|
|
778
592
|
|
|
779
|
-
|
|
593
|
+
// Update extra profile fields inside a transaction
|
|
594
|
+
updateProfileExtras?(userId: string, extras: Record<string, any>, queryRunner: QueryRunner): Promise<void>;
|
|
780
595
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
### Authentication Endpoints
|
|
784
|
-
|
|
785
|
-
| Endpoint | Method | Auth | Description |
|
|
786
|
-
|----------|--------|------|-------------|
|
|
787
|
-
| `/auth/login` | POST | No | Login with email/password |
|
|
788
|
-
| `/auth/register` | POST | No | Register new user |
|
|
789
|
-
| `/auth/refresh` | POST | No | Refresh access token |
|
|
790
|
-
| `/auth/logout` | POST | JWT | Logout (revokes all tokens) |
|
|
791
|
-
| `/auth/change-password` | POST | JWT | Change user password |
|
|
792
|
-
| `/auth/me` | POST | JWT | Get current user info |
|
|
793
|
-
|
|
794
|
-
**Rate Limits:**
|
|
795
|
-
- `/auth/login` - 5 attempts per minute
|
|
796
|
-
- `/auth/register` - 3 attempts per minute
|
|
797
|
-
- `/auth/refresh` - 30 attempts per minute
|
|
798
|
-
|
|
799
|
-
### Email Authentication Endpoints
|
|
800
|
-
|
|
801
|
-
| Endpoint | Method | Auth | Description |
|
|
802
|
-
|----------|--------|------|-------------|
|
|
803
|
-
| `/auth/forgot-password` | POST | No | Request password reset email |
|
|
804
|
-
| `/auth/reset-password` | POST | No | Reset password using token |
|
|
805
|
-
| `/auth/verify-email` | POST | No | Verify email using token |
|
|
806
|
-
| `/auth/resend-verification` | POST | No | Resend email verification link |
|
|
807
|
-
|
|
808
|
-
**Rate Limits:**
|
|
809
|
-
- `/auth/forgot-password` - 3 requests per 5 minutes
|
|
810
|
-
- `/auth/reset-password` - 5 attempts per 15 minutes
|
|
811
|
-
- `/auth/verify-email` - 10 attempts per minute
|
|
812
|
-
- `/auth/resend-verification` - 3 requests per 5 minutes
|
|
813
|
-
|
|
814
|
-
> **Note:** Email endpoints require `AUTH_EMAIL_PROVIDER` to be configured and `enableEmailVerification: true`.
|
|
815
|
-
|
|
816
|
-
### Company Selection Endpoints (when company feature enabled)
|
|
817
|
-
|
|
818
|
-
| Endpoint | Method | Auth | Description |
|
|
819
|
-
|----------|--------|------|-------------|
|
|
820
|
-
| `/auth/select` | POST | No | Select company and branch after login |
|
|
821
|
-
| `/auth/switch-company` | POST | JWT | Switch to different company/branch |
|
|
822
|
-
| `/auth/get-companies` | POST | JWT | Get user's available companies |
|
|
823
|
-
| `/auth/get-company-branches` | POST | JWT | Get branches for a company |
|
|
824
|
-
|
|
825
|
-
### User Management Endpoints
|
|
826
|
-
|
|
827
|
-
| Endpoint | Method | Auth | Permission | Description |
|
|
828
|
-
|----------|--------|------|------------|-------------|
|
|
829
|
-
| `/administration/users/insert` | POST | JWT | `user.create` | Create user |
|
|
830
|
-
| `/administration/users/insert-many` | POST | JWT | `user.create` | Bulk create users |
|
|
831
|
-
| `/administration/users/get/:id` | POST | JWT | `user.read` | Get user by ID |
|
|
832
|
-
| `/administration/users/get-all` | POST | JWT | `user.read` | Get paginated user list |
|
|
833
|
-
| `/administration/users/update` | POST | JWT | `user.update` | Update user |
|
|
834
|
-
| `/administration/users/update-many` | POST | JWT | `user.update` | Bulk update users |
|
|
835
|
-
| `/administration/users/delete` | POST | JWT | `user.delete` | Delete/restore/permanent delete |
|
|
836
|
-
| `/administration/users/profile` | POST | JWT | - | Update own profile |
|
|
837
|
-
| `/administration/users/:id/verify-email` | POST | JWT | `user.update` | Mark email as verified |
|
|
838
|
-
| `/administration/users/:id/verify-phone` | POST | JWT | `user.update` | Mark phone as verified |
|
|
839
|
-
| `/administration/users/:id/status` | POST | JWT | `user.update` | Update user active status |
|
|
840
|
-
|
|
841
|
-
### User Profile Extensions Endpoints
|
|
842
|
-
|
|
843
|
-
| Endpoint | Method | Auth | Permission | Description |
|
|
844
|
-
|----------|--------|------|------------|-------------|
|
|
845
|
-
| `/administration/users/:id/profile-extras` | POST | JWT | `user.read` | Get profile with enricher extras |
|
|
846
|
-
| `/administration/users/profile-sections` | POST | JWT | `user.read` | Get profile section definitions |
|
|
847
|
-
| `/administration/users/:id/profile-section/:sectionId` | POST | JWT | `user.read` | Get specific section data |
|
|
848
|
-
| `/administration/users/:id/profile-section/:sectionId/update` | POST | JWT | `user.update` | Update specific section |
|
|
849
|
-
| `/administration/users/:id/profile-completion` | POST | JWT | `user.read` | Get profile completion % |
|
|
850
|
-
|
|
851
|
-
### User Permission Endpoints (when company feature enabled)
|
|
852
|
-
|
|
853
|
-
| Endpoint | Method | Auth | Permission | Description |
|
|
854
|
-
|----------|--------|------|------------|-------------|
|
|
855
|
-
| `/administration/permissions/user-company/assign` | POST | JWT | `user.update` | Batch assign/revoke user-company permissions |
|
|
856
|
-
| `/administration/permissions/get-user-company` | POST | JWT | `user.read` | Get user's assigned companies |
|
|
857
|
-
| `/administration/permissions/user-branch/assign` | POST | JWT | `user.update` | Batch assign/revoke user-branch permissions |
|
|
858
|
-
| `/administration/permissions/get-user-branch` | POST | JWT | `user.read` | Get user's assigned branches |
|
|
859
|
-
|
|
860
|
-
### Company Management Endpoints (when company feature enabled)
|
|
861
|
-
|
|
862
|
-
| Endpoint | Method | Auth | Permission | Description |
|
|
863
|
-
|----------|--------|------|------------|-------------|
|
|
864
|
-
| `/administration/company/insert` | POST | JWT | `company.create` | Create company |
|
|
865
|
-
| `/administration/company/insert-many` | POST | JWT | `company.create` | Bulk create companies |
|
|
866
|
-
| `/administration/company/get/:id` | POST | JWT | `company.read` | Get company by ID |
|
|
867
|
-
| `/administration/company/get-all` | POST | JWT | `company.read` | Get paginated company list |
|
|
868
|
-
| `/administration/company/update` | POST | JWT | `company.update` | Update company |
|
|
869
|
-
| `/administration/company/update-many` | POST | JWT | `company.update` | Bulk update companies |
|
|
870
|
-
| `/administration/company/delete` | POST | JWT | `company.delete` | Delete/restore/permanent delete |
|
|
871
|
-
|
|
872
|
-
### Branch Management Endpoints (when company feature enabled)
|
|
873
|
-
|
|
874
|
-
| Endpoint | Method | Auth | Permission | Description |
|
|
875
|
-
|----------|--------|------|------------|-------------|
|
|
876
|
-
| `/administration/branch/insert` | POST | JWT | `branch.create` | Create branch |
|
|
877
|
-
| `/administration/branch/insert-many` | POST | JWT | `branch.create` | Bulk create branches |
|
|
878
|
-
| `/administration/branch/get/:id` | POST | JWT | `branch.read` | Get branch by ID |
|
|
879
|
-
| `/administration/branch/get-all` | POST | JWT | `branch.read` | Get paginated branch list |
|
|
880
|
-
| `/administration/branch/update` | POST | JWT | `branch.update` | Update branch |
|
|
881
|
-
| `/administration/branch/update-many` | POST | JWT | `branch.update` | Bulk update branches |
|
|
882
|
-
| `/administration/branch/delete` | POST | JWT | `branch.delete` | Delete/restore/permanent delete |
|
|
883
|
-
|
|
884
|
-
## Login Flows
|
|
885
|
-
|
|
886
|
-
### Simple Mode (Company Feature Disabled)
|
|
596
|
+
// Validate extra fields before saving
|
|
597
|
+
validateProfileExtras?(userId: string, extras: Record<string, any>, user: ILoggedUserInfo): Promise<void>;
|
|
887
598
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
│
|
|
891
|
-
▼
|
|
892
|
-
┌─────────────────────────┐
|
|
893
|
-
│ AuthenticationService │
|
|
894
|
-
│ .login() │
|
|
895
|
-
└────────────┬────────────┘
|
|
896
|
-
│
|
|
897
|
-
│ 1. Check pending registration
|
|
898
|
-
│ 2. Verify credentials
|
|
899
|
-
│ 3. Check email verified (if email enabled)
|
|
900
|
-
│ 4. Generate JWT tokens
|
|
901
|
-
▼
|
|
902
|
-
{ accessToken, refreshToken, user }
|
|
903
|
-
```
|
|
599
|
+
// Return multi-step profile section definitions
|
|
600
|
+
getProfileSections?(): IProfileSection[];
|
|
904
601
|
|
|
905
|
-
|
|
602
|
+
// Return data for a specific profile section
|
|
603
|
+
getProfileSectionData?(userId: string, sectionId: string, user: ILoggedUserInfo): Promise<IProfileSectionData>;
|
|
906
604
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
│
|
|
910
|
-
▼
|
|
911
|
-
┌─────────────────────────┐
|
|
912
|
-
│ AuthenticationService │
|
|
913
|
-
│ .login() │
|
|
914
|
-
└────────────┬────────────┘
|
|
915
|
-
│
|
|
916
|
-
│ 1. Verify credentials
|
|
917
|
-
│ 2. Check UserCompanyPermission
|
|
918
|
-
│ 3. Found: 1 company, 1 branch → Auto-select
|
|
919
|
-
▼
|
|
920
|
-
{ accessToken, refreshToken, user, company, branch }
|
|
921
|
-
```
|
|
605
|
+
// Update a specific profile section inside a transaction
|
|
606
|
+
updateProfileSection?(userId: string, sectionId: string, data: Record<string, any>, queryRunner: QueryRunner): Promise<void>;
|
|
922
607
|
|
|
923
|
-
|
|
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>;
|
|
924
610
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
│
|
|
928
|
-
▼
|
|
929
|
-
{ requiresSelection: true, sessionId, companies: [...] }
|
|
930
|
-
│
|
|
931
|
-
│ User selects company & branch
|
|
932
|
-
▼
|
|
933
|
-
POST /auth/select { sessionId, companyId, branchId }
|
|
934
|
-
│
|
|
935
|
-
▼
|
|
936
|
-
{ accessToken, refreshToken, user, company, branch }
|
|
937
|
-
```
|
|
611
|
+
// Handle file deletion for a profile section
|
|
612
|
+
handleSectionFileDelete?(userId: string, sectionId: string, field: string, fileId: string, queryRunner: QueryRunner): Promise<void>;
|
|
938
613
|
|
|
939
|
-
|
|
614
|
+
// Return profile completion percentage (0–100)
|
|
615
|
+
calculateProfileCompletion?(userId: string, user: ILoggedUserInfo): Promise<number>;
|
|
940
616
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
▼
|
|
945
|
-
If pending registration OR email not verified:
|
|
946
|
-
{ requiresEmailVerification: true, email, message }
|
|
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
|
+
}
|
|
947
620
|
```
|
|
948
621
|
|
|
949
|
-
|
|
622
|
+
**Implementation example (adding IAM roles to user profile):**
|
|
950
623
|
|
|
951
|
-
|
|
624
|
+
```typescript
|
|
625
|
+
import { USER_ENRICHER, IUserEnricher } from '@flusys/nestjs-auth';
|
|
626
|
+
import { PermissionService } from '@flusys/nestjs-iam';
|
|
952
627
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
957
|
-
│
|
|
958
|
-
▼
|
|
959
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
960
|
-
│ 1. Check existing user (must not exist) │
|
|
961
|
-
│ 2. Hash password with bcrypt (SALT_ROUNDS = 12) │
|
|
962
|
-
│ 3. Generate verification token │
|
|
963
|
-
│ 4. Hash token with SHA-256 for storage │
|
|
964
|
-
│ 5. Create PendingRegistration record │
|
|
965
|
-
│ 6. Send verification email via AUTH_EMAIL_PROVIDER │
|
|
966
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
967
|
-
│
|
|
968
|
-
▼
|
|
969
|
-
{ requiresEmailVerification: true, message: "Check your email" }
|
|
970
|
-
│
|
|
971
|
-
│ User clicks email link
|
|
972
|
-
▼
|
|
973
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
974
|
-
│ POST /auth/verify-email { token } │
|
|
975
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
976
|
-
│
|
|
977
|
-
▼
|
|
978
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
979
|
-
│ 1. Hash incoming token with SHA-256 │
|
|
980
|
-
│ 2. Find PendingRegistration by hashed token │
|
|
981
|
-
│ 3. Check expiration │
|
|
982
|
-
│ 4. Delete PendingRegistration record │
|
|
983
|
-
│ 5. Create User with emailVerified: true │
|
|
984
|
-
│ 6. Process company assignment if companyData present │
|
|
985
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
986
|
-
│
|
|
987
|
-
▼
|
|
988
|
-
{ accessToken, refreshToken, user, company?, branch? }
|
|
989
|
-
```
|
|
628
|
+
@Injectable()
|
|
629
|
+
export class IamUserEnricher implements IUserEnricher {
|
|
630
|
+
constructor(private readonly permissionService: PermissionService) {}
|
|
990
631
|
|
|
991
|
-
|
|
632
|
+
async getProfileExtras(userId: string, user: ILoggedUserInfo) {
|
|
633
|
+
const roles = await this.permissionService.getRolesForUser(userId);
|
|
634
|
+
const actions = await this.permissionService.getActionsForUser(userId);
|
|
635
|
+
return { roles, actions };
|
|
636
|
+
}
|
|
637
|
+
}
|
|
992
638
|
|
|
639
|
+
export const userEnricherProvider = {
|
|
640
|
+
provide: USER_ENRICHER,
|
|
641
|
+
useClass: IamUserEnricher,
|
|
642
|
+
};
|
|
993
643
|
```
|
|
994
|
-
POST /auth/forgot-password { email }
|
|
995
|
-
│
|
|
996
|
-
▼
|
|
997
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
998
|
-
│ 1. Find user by email │
|
|
999
|
-
│ 2. Delete existing PASSWORD_RESET tokens for user │
|
|
1000
|
-
│ 3. Generate new token, hash with SHA-256 │
|
|
1001
|
-
│ 4. Create UserToken record (expires in 1 hour) │
|
|
1002
|
-
│ 5. Send reset email via AUTH_EMAIL_PROVIDER │
|
|
1003
|
-
│ 6. Return success (same response even if user not found) │
|
|
1004
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
1005
|
-
│
|
|
1006
|
-
▼
|
|
1007
|
-
{ success: true, message: "If email exists, reset link sent" }
|
|
1008
|
-
│
|
|
1009
|
-
│ User clicks email link
|
|
1010
|
-
▼
|
|
1011
|
-
POST /auth/reset-password { token, newPassword }
|
|
1012
|
-
│
|
|
1013
|
-
▼
|
|
1014
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
1015
|
-
│ 1. Hash incoming token with SHA-256 │
|
|
1016
|
-
│ 2. Find UserToken by hashed token + type: PASSWORD_RESET │
|
|
1017
|
-
│ 3. Validate not expired, not used │
|
|
1018
|
-
│ 4. Update user password │
|
|
1019
|
-
│ 5. Mark token as used (usedAt = now) │
|
|
1020
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
1021
|
-
│
|
|
1022
|
-
▼
|
|
1023
|
-
{ success: true, message: "Password reset successfully" }
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
## User Management
|
|
1027
644
|
|
|
1028
|
-
|
|
645
|
+
**Register in `forRootAsync`:**
|
|
1029
646
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
▼
|
|
1036
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
1037
|
-
│ 1. Insert user record │
|
|
1038
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
1039
|
-
│
|
|
1040
|
-
▼
|
|
1041
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
1042
|
-
│ 2. Check if company permission exists │
|
|
1043
|
-
│ - Lookup UserCompanyPermission for user + company │
|
|
1044
|
-
│ - If not exists: create { userId, targetId: companyId, │
|
|
1045
|
-
│ permissionType: 'company' } │
|
|
1046
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
1047
|
-
│
|
|
1048
|
-
▼
|
|
1049
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
1050
|
-
│ 3. Check if branch permission exists │
|
|
1051
|
-
│ - Lookup UserCompanyPermission for user + branch │
|
|
1052
|
-
│ - If not exists: create { userId, targetId: branchId, │
|
|
1053
|
-
│ permissionType: 'branch' } │
|
|
1054
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
647
|
+
```typescript
|
|
648
|
+
AuthModule.forRootAsync({
|
|
649
|
+
// ...
|
|
650
|
+
providers: [userEnricherProvider],
|
|
651
|
+
})
|
|
1055
652
|
```
|
|
1056
653
|
|
|
1057
|
-
|
|
654
|
+
All methods in `IUserEnricher` are optional. Implement only the hooks you need.
|
|
1058
655
|
|
|
1059
|
-
|
|
1060
|
-
- Updates `User.isActive` directly
|
|
656
|
+
---
|
|
1061
657
|
|
|
1062
|
-
|
|
1063
|
-
- Updates `UserCompanyPermission.isActive` for the logged user's company
|
|
1064
|
-
- User can be active in Company A but inactive in Company B
|
|
658
|
+
## Exported Services
|
|
1065
659
|
|
|
1066
|
-
|
|
660
|
+
These services are exported by `AuthModule` and can be injected into your application:
|
|
1067
661
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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 |
|
|
1072
675
|
|
|
1073
|
-
**
|
|
1074
|
-
```json
|
|
1075
|
-
{
|
|
1076
|
-
"name": "John Doe",
|
|
1077
|
-
"email": "john@example.com",
|
|
1078
|
-
"password": "password123",
|
|
1079
|
-
"companySlug": "acme-corp",
|
|
1080
|
-
"branchSlug": "main-branch"
|
|
1081
|
-
}
|
|
1082
|
-
```
|
|
676
|
+
**Usage:**
|
|
1083
677
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
{
|
|
1087
|
-
"name": "John Doe",
|
|
1088
|
-
"email": "john@example.com",
|
|
1089
|
-
"password": "password123",
|
|
1090
|
-
"newCompanyName": "My Company",
|
|
1091
|
-
"newCompanyPhone": "+1234567890",
|
|
1092
|
-
"newCompanyAddress": "123 Main St"
|
|
1093
|
-
}
|
|
1094
|
-
```
|
|
678
|
+
```typescript
|
|
679
|
+
import { AuthenticationService } from '@flusys/nestjs-auth';
|
|
1095
680
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
"prevCompanyLocation": "New York, USA",
|
|
1105
|
-
"prevIndustryType": "technology"
|
|
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);
|
|
1106
689
|
}
|
|
1107
690
|
}
|
|
1108
691
|
```
|
|
1109
692
|
|
|
1110
|
-
> **Note:**
|
|
693
|
+
> **Note:** Always use `@Inject(ServiceClass)` explicitly. This package is distributed as an esbuild bundle where TypeScript metadata is not preserved.
|
|
1111
694
|
|
|
1112
|
-
|
|
695
|
+
---
|
|
1113
696
|
|
|
1114
|
-
|
|
697
|
+
## Interceptors
|
|
1115
698
|
|
|
1116
|
-
|
|
1117
|
-
|
|
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 |
|
|
1118
703
|
|
|
1119
|
-
|
|
1120
|
-
export class AuthenticationService {
|
|
1121
|
-
constructor(
|
|
1122
|
-
@Inject(CACHE_INSTANCE) private readonly cache: HybridCache,
|
|
1123
|
-
) {}
|
|
704
|
+
Applied automatically by the auth controllers. Export them from `AuthModule` if you need them in custom controllers.
|
|
1124
705
|
|
|
1125
|
-
|
|
1126
|
-
// ... validate credentials, generate tokens
|
|
706
|
+
---
|
|
1127
707
|
|
|
1128
|
-
|
|
1129
|
-
await this.setWildcardPermissions(user.id);
|
|
708
|
+
## Security
|
|
1130
709
|
|
|
1131
|
-
|
|
1132
|
-
}
|
|
710
|
+
### Token Strategy
|
|
1133
711
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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) |
|
|
1140
718
|
|
|
1141
|
-
###
|
|
719
|
+
### Password Hashing
|
|
1142
720
|
|
|
1143
|
-
|
|
1144
|
-
private async setWildcardPermissions(
|
|
1145
|
-
userId: string,
|
|
1146
|
-
companyId?: string,
|
|
1147
|
-
branchId?: string,
|
|
1148
|
-
): Promise<void> {
|
|
1149
|
-
if (companyId) {
|
|
1150
|
-
const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
|
|
1151
|
-
await this.cache.set(cacheKey, ['*'], 86400000);
|
|
1152
|
-
} else {
|
|
1153
|
-
const cacheKey = `${PERMISSIONS_CACHE_PREFIX}:user:${userId}`;
|
|
1154
|
-
await this.cache.set(cacheKey, ['*'], 86400000);
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
```
|
|
721
|
+
bcrypt with **12 salt rounds** (OWASP recommended minimum).
|
|
1158
722
|
|
|
1159
|
-
###
|
|
723
|
+
### Rate Limiting
|
|
1160
724
|
|
|
1161
|
-
|
|
1162
|
-
|---------|---------|
|
|
1163
|
-
| `*` | Full access to everything |
|
|
1164
|
-
| `user.*` | All user permissions |
|
|
1165
|
-
| `branch.*` | All branch permissions |
|
|
725
|
+
Built-in via `@nestjs/throttler`:
|
|
1166
726
|
|
|
1167
|
-
|
|
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 |
|
|
1168
733
|
|
|
1169
|
-
|
|
734
|
+
### SQL Injection Protection
|
|
1170
735
|
|
|
1171
|
-
|
|
1172
|
-
```typescript
|
|
1173
|
-
interface TokenPayload {
|
|
1174
|
-
id: string; // User ID
|
|
1175
|
-
email: string; // User email
|
|
1176
|
-
type: 'access' | 'refresh'; // Token type
|
|
1177
|
-
profilePictureId?: string; // Profile picture file ID
|
|
1178
|
-
tenantId?: string; // Tenant ID (multi-tenant)
|
|
1179
|
-
iat?: number; // Issued at
|
|
1180
|
-
exp?: number; // Expiration
|
|
1181
|
-
}
|
|
736
|
+
All database queries use TypeORM parameterized statements. No raw string interpolation.
|
|
1182
737
|
|
|
1183
|
-
|
|
1184
|
-
interface TokenPayloadWithCompany extends TokenPayload {
|
|
1185
|
-
companyId: string; // Selected company ID
|
|
1186
|
-
branchId?: string; // Selected branch ID (optional)
|
|
1187
|
-
companyLogoId?: string | null; // Company logo file ID
|
|
1188
|
-
branchLogoId?: string | null; // Branch logo file ID
|
|
1189
|
-
}
|
|
1190
|
-
```
|
|
738
|
+
### Important Notes
|
|
1191
739
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
3. Check token revocation status (if cache available)
|
|
1196
|
-
4. Validate user exists and is active
|
|
1197
|
-
5. Return `ILoggedUserInfo` with user context
|
|
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.
|
|
1198
743
|
|
|
1199
|
-
|
|
1200
|
-
- Uses cache to track revoked tokens
|
|
1201
|
-
- Key format: `auth:revoked:{userId}`
|
|
1202
|
-
- Stores timestamp of last logout
|
|
1203
|
-
- Tokens issued before logout timestamp are rejected
|
|
744
|
+
---
|
|
1204
745
|
|
|
1205
|
-
##
|
|
746
|
+
## Environment Variables
|
|
1206
747
|
|
|
1207
|
-
|
|
748
|
+
```env
|
|
749
|
+
# Database
|
|
750
|
+
DB_HOST=localhost
|
|
751
|
+
DB_PORT=5432
|
|
752
|
+
DB_USER=postgres
|
|
753
|
+
DB_PASSWORD=secret
|
|
754
|
+
DB_NAME=myapp
|
|
1208
755
|
|
|
1209
|
-
|
|
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
|
|
1210
761
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
762
|
+
# Application
|
|
763
|
+
FRONTEND_URL=http://localhost:3000
|
|
764
|
+
PORT=3001
|
|
1214
765
|
```
|
|
1215
766
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
- For API/Mobile: Returns `refreshToken` in response body
|
|
1220
|
-
- Cookie options: `httpOnly: true`, `secure: true` (production), `sameSite: 'lax'`
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
## Troubleshooting
|
|
1221
770
|
|
|
1222
|
-
|
|
771
|
+
**`Cannot read properties of undefined` on service injection**
|
|
1223
772
|
|
|
1224
|
-
|
|
773
|
+
You are likely missing `@Inject()` on a constructor parameter. This package is bundled with esbuild and TypeScript metadata is not preserved:
|
|
1225
774
|
|
|
1226
775
|
```typescript
|
|
1227
|
-
|
|
1228
|
-
|
|
776
|
+
// Wrong
|
|
777
|
+
constructor(private readonly authService: AuthenticationService) {}
|
|
778
|
+
|
|
779
|
+
// Correct
|
|
780
|
+
constructor(@Inject(AuthenticationService) private readonly authService: AuthenticationService) {}
|
|
1229
781
|
```
|
|
1230
782
|
|
|
1231
|
-
|
|
783
|
+
---
|
|
1232
784
|
|
|
1233
|
-
|
|
785
|
+
**Login returns `requiresEmailVerification: true` unexpectedly**
|
|
1234
786
|
|
|
1235
|
-
|
|
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.
|
|
1236
791
|
|
|
1237
|
-
|
|
1238
|
-
@IsStrongPassword()
|
|
1239
|
-
password!: string;
|
|
792
|
+
---
|
|
1240
793
|
|
|
1241
|
-
|
|
1242
|
-
@IsStrongPassword({ minLength: 12, requireSpecialChar: false })
|
|
1243
|
-
password!: string;
|
|
1244
|
-
```
|
|
794
|
+
**Login returns `requiresSelection: true` unexpectedly**
|
|
1245
795
|
|
|
1246
|
-
|
|
1247
|
-
- Minimum 8 characters
|
|
1248
|
-
- Maximum 128 characters
|
|
1249
|
-
- At least one uppercase letter
|
|
1250
|
-
- At least one lowercase letter
|
|
1251
|
-
- At least one number
|
|
1252
|
-
- At least one special character (`!@#$%^&*()_+-=[]{}...`)
|
|
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.
|
|
1253
797
|
|
|
1254
|
-
|
|
1255
|
-
```typescript
|
|
1256
|
-
interface PasswordComplexityOptions {
|
|
1257
|
-
minLength?: number; // Default: 8
|
|
1258
|
-
maxLength?: number; // Default: 128
|
|
1259
|
-
requireUppercase?: boolean; // Default: true
|
|
1260
|
-
requireLowercase?: boolean; // Default: true
|
|
1261
|
-
requireNumber?: boolean; // Default: true
|
|
1262
|
-
requireSpecialChar?: boolean; // Default: true
|
|
1263
|
-
}
|
|
1264
|
-
```
|
|
798
|
+
---
|
|
1265
799
|
|
|
1266
|
-
|
|
800
|
+
**`No metadata for entity` error on startup**
|
|
1267
801
|
|
|
1268
|
-
|
|
802
|
+
Ensure `AuthModule.getEntities()` is called with the correct config when registering `TypeOrmModule`:
|
|
1269
803
|
|
|
1270
804
|
```typescript
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
export { CompanyBranch } from './entities/company-branch.entity';
|
|
1280
|
-
export { UserCompanyPermission, PermissionType } from './entities/user-company-permission.entity';
|
|
1281
|
-
export { getEntitiesByConfig, IAuthEntitiesConfig } from './entities';
|
|
1282
|
-
|
|
1283
|
-
// Enums
|
|
1284
|
-
export { PermissionType } from './enums';
|
|
1285
|
-
|
|
1286
|
-
// Interfaces
|
|
1287
|
-
export { IAuthEmailProvider, AUTH_EMAIL_PROVIDER } from './interfaces/auth-email-provider.interface';
|
|
1288
|
-
export { IUserEnricher, IProfileSection, IProfileSectionData, IProfileFile, IProfileExtras, IProfileRole, IProfileAction, IEnrichedUser } from './interfaces/user-enricher.interface';
|
|
1289
|
-
export { AuthModuleOptions, AuthModuleAsyncOptions, AuthOptionsFactory, IAuthModuleConfig } from './interfaces/auth-module-options.interface';
|
|
1290
|
-
export { TokenPayload, TokenPayloadWithCompany, LoginResponse, LoginResponseRequiresSelection, LoginResponseRequiresEmailVerification, MeResponse, RegistrationResponse, CompanyWithBranches, BranchForSelection } from './interfaces/authentication.interface';
|
|
1291
|
-
export { IUser, IUserMini, IUserWithPassword } from './interfaces/user.interface';
|
|
1292
|
-
export { ICompany, ICompanyMini } from './interfaces/company.interface';
|
|
1293
|
-
export { ICompanyBranch, ICompanyBranchMini } from './interfaces/company-branch.interface';
|
|
1294
|
-
|
|
1295
|
-
// Services
|
|
1296
|
-
export { AuthenticationService } from './services/authentication.service';
|
|
1297
|
-
export { AuthEmailService } from './services/auth-email.service';
|
|
1298
|
-
export { AuthConfigService } from './services/auth-config.service';
|
|
1299
|
-
export { AuthDataSourceProvider } from './services/auth-datasource.provider';
|
|
1300
|
-
export { UserService } from './services/user.service';
|
|
1301
|
-
export { CompanyService } from './services/company.service';
|
|
1302
|
-
export { BranchService } from './services/branch.service';
|
|
1303
|
-
export { UserPermissionService } from './services/user-permission.service';
|
|
1304
|
-
export { CompanySelectionSessionService } from './services/company-selection-session.service';
|
|
1305
|
-
|
|
1306
|
-
// Modules
|
|
1307
|
-
export { AuthModule } from './modules/auth.module';
|
|
1308
|
-
|
|
1309
|
-
// Controllers
|
|
1310
|
-
export { AuthenticationController } from './controllers/authentication.controller';
|
|
1311
|
-
export { EmailVerificationController } from './controllers/email-verification.controller';
|
|
1312
|
-
export { CompanySelectionController } from './controllers/company-selection.controller';
|
|
1313
|
-
export { UserController } from './controllers/user.controller';
|
|
1314
|
-
export { CompanyController } from './controllers/company.controller';
|
|
1315
|
-
export { BranchController } from './controllers/branch.controller';
|
|
1316
|
-
export { UserPermissionController } from './controllers/user-permission.controller';
|
|
1317
|
-
|
|
1318
|
-
// Strategies
|
|
1319
|
-
export { JwtStrategy } from './strategies/jwt.strategy';
|
|
1320
|
-
|
|
1321
|
-
// Interceptors
|
|
1322
|
-
export { SetToken } from './interceptors/set-token.interceptor';
|
|
1323
|
-
export { ClearToken } from './interceptors/clear-token.interceptor';
|
|
1324
|
-
|
|
1325
|
-
// Validators
|
|
1326
|
-
export { IsStrongPassword, PasswordComplexityOptions } from './validators/password.validator';
|
|
1327
|
-
|
|
1328
|
-
// DTOs
|
|
1329
|
-
export * from './dtos';
|
|
1330
|
-
|
|
1331
|
-
// Swagger Config
|
|
1332
|
-
export { authSwaggerConfig } from './docs/auth-swagger.config';
|
|
805
|
+
TypeOrmModule.forRoot({
|
|
806
|
+
entities: [
|
|
807
|
+
...AuthModule.getEntities({
|
|
808
|
+
enableCompanyFeature: true,
|
|
809
|
+
enableEmailVerification: true,
|
|
810
|
+
}),
|
|
811
|
+
],
|
|
812
|
+
})
|
|
1333
813
|
```
|
|
1334
814
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
```typescript
|
|
1338
|
-
// Get entities based on feature flags (requires IBootstrapAppConfig)
|
|
1339
|
-
AuthModule.getEntities(config: IBootstrapAppConfig): any[]
|
|
1340
|
-
|
|
1341
|
-
// Examples:
|
|
1342
|
-
AuthModule.getEntities({ enableCompanyFeature: false })
|
|
1343
|
-
// Returns: [User, UserToken, PendingRegistration]
|
|
1344
|
-
|
|
1345
|
-
AuthModule.getEntities({ enableCompanyFeature: true })
|
|
1346
|
-
// Returns: [User, UserToken, PendingRegistration, Company, CompanyBranch, UserCompanyPermission]
|
|
1347
|
-
|
|
1348
|
-
AuthModule.getEntities({ enableCompanyFeature: true, enableEmailVerification: false })
|
|
1349
|
-
// Returns: [User, UserToken, Company, CompanyBranch, UserCompanyPermission]
|
|
815
|
+
---
|
|
1350
816
|
|
|
1351
|
-
|
|
1352
|
-
AuthModule.forRoot(options: AuthModuleOptions): DynamicModule
|
|
817
|
+
**Refresh token cookie not being set**
|
|
1353
818
|
|
|
1354
|
-
|
|
1355
|
-
AuthModule.forRootAsync(options: AuthModuleAsyncOptions): DynamicModule
|
|
1356
|
-
```
|
|
819
|
+
Ensure your Express app has `cookie-parser` middleware installed and registered before NestJS:
|
|
1357
820
|
|
|
1358
|
-
**AuthModuleOptions Interface:**
|
|
1359
821
|
```typescript
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
includeController?: boolean; // Include default controllers (default: true)
|
|
1363
|
-
bootstrapAppConfig?: IBootstrapAppConfig;
|
|
1364
|
-
config?: IAuthModuleConfig;
|
|
1365
|
-
providers?: Provider[]; // Additional providers (AUTH_EMAIL_PROVIDER, USER_ENRICHER)
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
interface IAuthModuleConfig {
|
|
1369
|
-
defaultDatabaseConfig?: IDatabaseConfig;
|
|
1370
|
-
tenantDefaultDatabaseConfig?: IDatabaseConfig;
|
|
1371
|
-
tenants?: ITenantDatabaseConfig[];
|
|
1372
|
-
jwtSecret?: string;
|
|
1373
|
-
jwtExpiration?: string; // Default: '1h'
|
|
1374
|
-
refreshTokenSecret?: string;
|
|
1375
|
-
refreshTokenExpiration?: string; // Default: '7d'
|
|
1376
|
-
refreshTokenCookieName?: string; // Default: 'fsn_refresh_token'
|
|
1377
|
-
frontendUrl?: string; // For email links
|
|
1378
|
-
}
|
|
822
|
+
import * as cookieParser from 'cookie-parser';
|
|
823
|
+
app.use(cookieParser());
|
|
1379
824
|
```
|
|
1380
825
|
|
|
1381
|
-
|
|
826
|
+
---
|
|
827
|
+
|
|
828
|
+
**Password reset / verification emails not being sent**
|
|
1382
829
|
|
|
1383
|
-
|
|
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)).
|
|
1384
831
|
|
|
1385
|
-
|
|
832
|
+
---
|
|
1386
833
|
|
|
1387
|
-
|
|
834
|
+
## License
|
|
1388
835
|
|
|
1389
|
-
|
|
1390
|
-
|-----|---------------|
|
|
1391
|
-
| `RegistrationDto` | `companySlug`, `branchSlug`, `newCompanyName`, `newCompanyPhone`, `newCompanyAddress` |
|
|
1392
|
-
| `RegistrationResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
|
|
1393
|
-
| `LoginResponseDto` | `company`, `branch`, `companyFeatureEnabled` |
|
|
1394
|
-
| `MeResponseDto` | `company`, `branch` |
|
|
836
|
+
MIT © FLUSYS
|
|
1395
837
|
|
|
1396
838
|
---
|
|
1397
839
|
|
|
1398
|
-
**
|
|
840
|
+
> Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
|