@flusys/nestjs-auth 4.1.1 → 5.0.1
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 +151 -719
- package/cjs/config/message-keys.js +6 -61
- package/cjs/controllers/authentication.controller.js +113 -87
- package/cjs/controllers/company-selection.controller.js +24 -4
- package/cjs/controllers/email-verification.controller.js +12 -13
- package/cjs/docs/auth-swagger.config.js +143 -106
- package/cjs/dtos/authentication.dto.js +0 -15
- package/cjs/entities/company-branch.entity.js +1 -1
- package/cjs/entities/company.entity.js +2 -2
- package/cjs/entities/pending-registration.entity.js +3 -2
- package/cjs/entities/user-company-permission.entity.js +0 -8
- package/cjs/entities/user-token.entity.js +0 -9
- package/cjs/interceptors/set-token.interceptor.js +15 -7
- package/cjs/services/auth-config.service.js +0 -4
- package/cjs/services/auth-email.service.js +8 -31
- package/cjs/services/authentication.service.js +99 -15
- package/cjs/services/user-permission.service.js +6 -8
- package/cjs/services/user.service.js +16 -13
- package/cjs/strategies/jwt.strategy.js +10 -3
- package/config/message-keys.d.ts +5 -153
- package/controllers/authentication.controller.d.ts +6 -7
- package/controllers/company-selection.controller.d.ts +3 -2
- package/controllers/email-verification.controller.d.ts +6 -2
- package/dtos/authentication.dto.d.ts +0 -2
- package/entities/user-company-permission.entity.d.ts +0 -1
- package/entities/user-token.entity.d.ts +0 -1
- package/fesm/config/message-keys.js +6 -53
- package/fesm/controllers/authentication.controller.js +115 -90
- package/fesm/controllers/company-selection.controller.js +25 -5
- package/fesm/controllers/email-verification.controller.js +13 -14
- package/fesm/docs/auth-swagger.config.js +146 -113
- package/fesm/dtos/authentication.dto.js +0 -15
- package/fesm/entities/company-branch.entity.js +2 -2
- package/fesm/entities/company.entity.js +3 -3
- package/fesm/entities/pending-registration.entity.js +3 -2
- package/fesm/entities/user-company-permission.entity.js +0 -8
- package/fesm/entities/user-token.entity.js +0 -9
- package/fesm/interceptors/set-token.interceptor.js +15 -7
- package/fesm/services/auth-config.service.js +0 -4
- package/fesm/services/auth-email.service.js +8 -32
- package/fesm/services/authentication.service.js +100 -17
- package/fesm/services/user-permission.service.js +6 -8
- package/fesm/services/user.service.js +16 -14
- package/fesm/strategies/jwt.strategy.js +10 -3
- package/interfaces/auth-module-options.interface.d.ts +0 -1
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +0 -1
- package/services/auth-email.service.d.ts +0 -4
- package/services/authentication.service.d.ts +1 -1
- package/services/user-permission.service.d.ts +1 -1
- package/services/user.service.d.ts +5 -5
package/README.md
CHANGED
|
@@ -1,96 +1,9 @@
|
|
|
1
1
|
# @flusys/nestjs-auth
|
|
2
2
|
|
|
3
|
-
|
|
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
|
[](https://www.npmjs.com/package/@flusys/nestjs-auth)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nestjs.com/)
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
-
[](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
|
-
|
|
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
|
-
|
|
19
|
+
### Sync (`forRoot`)
|
|
116
20
|
|
|
117
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
54
|
+
In multi-tenant mode you provide:
|
|
157
55
|
|
|
158
|
-
|
|
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
|
-
|
|
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: '
|
|
168
|
-
enableCompanyFeature:
|
|
66
|
+
databaseMode: 'multi-tenant',
|
|
67
|
+
enableCompanyFeature: true,
|
|
169
68
|
enableEmailVerification: true,
|
|
170
69
|
},
|
|
171
70
|
config: {
|
|
172
|
-
|
|
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
|
-
|
|
180
|
-
})
|
|
103
|
+
});
|
|
181
104
|
```
|
|
182
105
|
|
|
183
|
-
###
|
|
106
|
+
### Async (`forRootAsync`)
|
|
184
107
|
|
|
185
|
-
Use when
|
|
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: (
|
|
123
|
+
},why you cannot fix it,, need to need gap bellow LAB REPORT text
|
|
124
|
+
useFactory: (cfg: ConfigService) => ({
|
|
201
125
|
defaultDatabaseConfig: {
|
|
202
|
-
type: '
|
|
203
|
-
host:
|
|
204
|
-
port:
|
|
205
|
-
username:
|
|
206
|
-
password:
|
|
207
|
-
database:
|
|
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:
|
|
210
|
-
jwtExpiration:
|
|
211
|
-
refreshTokenSecret:
|
|
212
|
-
refreshTokenExpiration:
|
|
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: [
|
|
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
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
177
|
+
import { getEntitiesByConfig } from '@flusys/nestjs-auth/entities';
|
|
494
178
|
|
|
495
179
|
TypeOrmModule.forRoot({
|
|
496
|
-
// ...
|
|
497
180
|
entities: [
|
|
498
|
-
...
|
|
499
|
-
|
|
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
|
-
|
|
191
|
+
## 3. Pluggable Email — `AUTH_EMAIL_PROVIDER`
|
|
509
192
|
|
|
510
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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,
|
|
571
|
-
|
|
572
|
-
---
|
|
225
|
+
When `AUTH_EMAIL_PROVIDER` is not provided, registration still works but emails are silently skipped.
|
|
573
226
|
|
|
574
|
-
|
|
227
|
+
## 4. User Enrichment — `USER_ENRICHER`
|
|
575
228
|
|
|
576
|
-
|
|
229
|
+
Extend user list and profile responses without creating circular imports. All methods are optional — implement only what you need.
|
|
577
230
|
|
|
578
|
-
|
|
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,
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|