@authuser/nest 0.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/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/authuser.module.d.ts +12 -0
- package/dist/authuser.module.d.ts.map +1 -0
- package/dist/authuser.module.js +32 -0
- package/dist/authuser.module.js.map +1 -0
- package/dist/configure-http-app.d.ts +4 -0
- package/dist/configure-http-app.d.ts.map +1 -0
- package/dist/configure-http-app.js +64 -0
- package/dist/configure-http-app.js.map +1 -0
- package/dist/create-http-app.d.ts +6 -0
- package/dist/create-http-app.d.ts.map +1 -0
- package/dist/create-http-app.js +37 -0
- package/dist/create-http-app.js.map +1 -0
- package/dist/decorators.d.ts +7 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +8 -0
- package/dist/decorators.js.map +1 -0
- package/dist/http-exception.filter.d.ts +5 -0
- package/dist/http-exception.filter.d.ts.map +1 -0
- package/dist/http-exception.filter.js +43 -0
- package/dist/http-exception.filter.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/openapi.d.ts +5 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +31 -0
- package/dist/openapi.js.map +1 -0
- package/dist/options.d.ts +21 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +84 -0
- package/dist/options.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/configuration.md +72 -0
- package/docs/migration.md +22 -0
- package/docs/performance.md +25 -0
- package/docs/security.md +31 -0
- package/package.json +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
4
|
+
This project uses [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- NestJS 12 and Fastify 5 application factory.
|
|
13
|
+
- Secure, minimal and full presets.
|
|
14
|
+
- Secure headers, rate limiting, strict validation and bounded request bodies.
|
|
15
|
+
- Optional OpenAPI JSON and Swagger UI.
|
|
16
|
+
- Optional health endpoint and response compression.
|
|
17
|
+
- TypeScript 7 build and declarations.
|
|
18
|
+
- HTTP QUERY (RFC 10008) support in CORS defaults and integration coverage.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Authuser
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>@authuser/nest</h1>
|
|
3
|
+
<p><strong>A secure, fast and batteries-included NestJS foundation on Fastify.</strong></p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@authuser/nest"><img alt="npm" src="https://img.shields.io/npm/v/%40authuser%2Fnest?style=flat-square"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/@authuser/nest"><img alt="downloads" src="https://img.shields.io/npm/dm/%40authuser%2Fnest?style=flat-square"></a>
|
|
9
|
+
<img alt="Node 20+" src="https://img.shields.io/badge/node-%3E%3D20.11-339933?style=flat-square&logo=node.js&logoColor=white">
|
|
10
|
+
<img alt="NestJS 12" src="https://img.shields.io/badge/NestJS-12-E0234E?style=flat-square&logo=nestjs&logoColor=white">
|
|
11
|
+
<img alt="Fastify 5" src="https://img.shields.io/badge/Fastify-5-black?style=flat-square&logo=fastify">
|
|
12
|
+
<a href="./LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
`@authuser/nest` creates a normal `NestFastifyApplication` with careful production
|
|
16
|
+
defaults. It keeps controllers, modules, dependency injection and the rest of Nest
|
|
17
|
+
intact while removing repetitive bootstrap code.
|
|
18
|
+
|
|
19
|
+
## Why this package?
|
|
20
|
+
|
|
21
|
+
- One install: Nest, Fastify, validation, security and optional docs are included.
|
|
22
|
+
- Fast path: request logs, compression and Swagger UI are off unless requested.
|
|
23
|
+
- Secure defaults: Helmet, strict DTO validation, bounded bodies, strict CORS and
|
|
24
|
+
rate limiting.
|
|
25
|
+
- OpenAPI is optional at runtime. Its code is dynamically imported only when enabled.
|
|
26
|
+
- HTTP QUERY (RFC 10008) passes through Nest 12/Fastify 5 and is included in CORS defaults.
|
|
27
|
+
- Native ESM, strict declarations and a TypeScript 7 toolchain.
|
|
28
|
+
- Escape hatch: the result is a regular Nest application and Fastify instance.
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Node.js 20.11 or newer
|
|
33
|
+
- An ESM project is recommended (`"type": "module"`)
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @authuser/nest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No peer-dependency checklist is required. Runtime integrations such as
|
|
42
|
+
`@nestjs/swagger`, `class-validator` and the Fastify plugins are dependencies of
|
|
43
|
+
this package.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// src/main.ts
|
|
49
|
+
import { createNestApp } from '@authuser/nest';
|
|
50
|
+
import { AppModule } from './app.module.js';
|
|
51
|
+
|
|
52
|
+
const app = await createNestApp({
|
|
53
|
+
rootModule: AppModule,
|
|
54
|
+
appName: 'users-api',
|
|
55
|
+
preset: 'secure',
|
|
56
|
+
apiPrefix: 'api',
|
|
57
|
+
observability: {
|
|
58
|
+
logger: { level: process.env.LOG_LEVEL ?? 'info' },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await app.listen({ port: 3000, host: '0.0.0.0' });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
That application has Helmet, rate limiting, safe request IDs, strict validation,
|
|
66
|
+
a 1 MiB body limit and graceful shutdown hooks. Cross-origin requests are not
|
|
67
|
+
enabled implicitly.
|
|
68
|
+
|
|
69
|
+
## Presets
|
|
70
|
+
|
|
71
|
+
| Preset | Intended use | Enabled by default |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `minimal` | Lowest framework overhead | validation and exception filter |
|
|
74
|
+
| `secure` | Public production API (default) | minimal + Helmet + rate limit |
|
|
75
|
+
| `full` | Feature-rich service | secure + compression + health + OpenAPI JSON |
|
|
76
|
+
|
|
77
|
+
Explicit options always override a preset.
|
|
78
|
+
|
|
79
|
+
## OpenAPI and Swagger UI
|
|
80
|
+
|
|
81
|
+
OpenAPI is installed but disabled with `minimal` and `secure`. Enable only the
|
|
82
|
+
machine-readable document:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const app = await createNestApp({
|
|
86
|
+
rootModule: AppModule,
|
|
87
|
+
openApi: {
|
|
88
|
+
enabled: true,
|
|
89
|
+
title: 'Users API',
|
|
90
|
+
version: '2.0.0',
|
|
91
|
+
jsonPath: '/openapi.json',
|
|
92
|
+
bearerAuth: true,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Swagger UI is a separate opt-in because it adds routes and static assets:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
openApi: {
|
|
101
|
+
enabled: process.env.NODE_ENV !== 'production',
|
|
102
|
+
ui: true,
|
|
103
|
+
uiPath: '/docs',
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For an internet-facing production service, prefer exporting the JSON during CI or
|
|
108
|
+
protecting documentation routes at the gateway.
|
|
109
|
+
|
|
110
|
+
## Configuration examples
|
|
111
|
+
|
|
112
|
+
### CORS allowlist
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
security: {
|
|
116
|
+
cors: {
|
|
117
|
+
origin: ['https://app.example.com'],
|
|
118
|
+
credentials: true,
|
|
119
|
+
methods: ['GET', 'POST', 'PATCH', 'DELETE'],
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Rate limiting behind a proxy
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
network: { trustProxy: ['127.0.0.1', '10.0.0.0/8'] },
|
|
128
|
+
security: {
|
|
129
|
+
rateLimit: { max: 300, timeWindow: '1 minute' },
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Never enable `trustProxy: true` unless every direct connection comes through a
|
|
134
|
+
trusted proxy; client IPs are used as rate-limit keys.
|
|
135
|
+
|
|
136
|
+
### Existing Nest + Fastify application
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { configureHttpApp } from '@authuser/nest';
|
|
140
|
+
|
|
141
|
+
await configureHttpApp(app, {
|
|
142
|
+
preset: 'secure',
|
|
143
|
+
health: { enabled: true, path: '/healthz' },
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Call `configureHttpApp` before `app.init()` or `app.listen()` so Fastify can register
|
|
148
|
+
its plugins and routes.
|
|
149
|
+
|
|
150
|
+
## Public API
|
|
151
|
+
|
|
152
|
+
- `createNestApp(options)` / `createHttpApp(options)` — create and configure an app.
|
|
153
|
+
- `configureHttpApp(app, options)` — configure an existing Fastify-based Nest app.
|
|
154
|
+
- `AuthuserModule.forRoot(options)` — expose immutable options through Nest DI.
|
|
155
|
+
- `AUTHUSER_OPTIONS` — injection token for those options.
|
|
156
|
+
- `Public()` and `Roles(...roles)` — metadata helpers for your own guards.
|
|
157
|
+
- `HttpExceptionFilter` — the default safe JSON exception filter.
|
|
158
|
+
|
|
159
|
+
The decorators intentionally do not provide authentication. Authentication policy
|
|
160
|
+
belongs to the consuming application; pretending otherwise would create a dangerous
|
|
161
|
+
security boundary.
|
|
162
|
+
|
|
163
|
+
See the [complete option reference](./docs/configuration.md), the
|
|
164
|
+
[security model](./docs/security.md), and the [performance guide](./docs/performance.md).
|
|
165
|
+
|
|
166
|
+
## Performance notes
|
|
167
|
+
|
|
168
|
+
Fastify remains a strong fit when Nest's architecture is required. A raw Fastify
|
|
169
|
+
service will be lighter and usually faster because it removes Nest's routing and DI
|
|
170
|
+
overhead; frameworks such as µWebSockets.js can win synthetic throughput tests but
|
|
171
|
+
require a substantially different ecosystem and programming model. Measure the real
|
|
172
|
+
application before trading away Nest maintainability.
|
|
173
|
+
|
|
174
|
+
For the hottest path, use `preset: 'minimal'`, leave request logging and compression
|
|
175
|
+
off, declare response schemas in Fastify-compatible routes, and benchmark with your
|
|
176
|
+
actual payloads. See [docs/performance.md](./docs/performance.md).
|
|
177
|
+
|
|
178
|
+
## Migration from `@authuser/nest-fastify-kit`
|
|
179
|
+
|
|
180
|
+
```diff
|
|
181
|
+
- import { createHttpApp } from '@authuser/nest-fastify-kit';
|
|
182
|
+
+ import { createHttpApp } from '@authuser/nest';
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The new package targets Nest 12, uses ESM, calls Swagger configuration `openApi`, and
|
|
186
|
+
ships required integrations as normal dependencies. See the
|
|
187
|
+
[migration guide](./docs/migration.md).
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm install
|
|
193
|
+
npm run check
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
An executable API and an importable Postman collection are available in the
|
|
197
|
+
[`example`](./example) directory.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
[MIT](./LICENSE) © Authuser
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { ConfigureHttpAppOptions } from './types.js';
|
|
3
|
+
export declare const AUTHUSER_OPTIONS: unique symbol;
|
|
4
|
+
export interface AuthuserModuleAsyncOptions {
|
|
5
|
+
inject?: Array<string | symbol | Function>;
|
|
6
|
+
useFactory: (...dependencies: never[]) => ConfigureHttpAppOptions | Promise<ConfigureHttpAppOptions>;
|
|
7
|
+
}
|
|
8
|
+
export declare class AuthuserModule {
|
|
9
|
+
static forRoot(options?: ConfigureHttpAppOptions): DynamicModule;
|
|
10
|
+
static forRootAsync(options: AuthuserModuleAsyncOptions): DynamicModule;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=authuser.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authuser.module.d.ts","sourceRoot":"","sources":["../src/authuser.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,eAAO,MAAM,gBAAgB,eAAuC,CAAC;AAErE,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IAC3C,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,KAAK,EAAE,KAAK,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtG;AAED,qBAEa,cAAc;IACzB,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,uBAA4B,GAAG,aAAa,CAMnE;IAED,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,CAOtE;CACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var AuthuserModule_1;
|
|
8
|
+
import { Global, Module } from '@nestjs/common';
|
|
9
|
+
export const AUTHUSER_OPTIONS = Symbol.for('@authuser/nest/options');
|
|
10
|
+
let AuthuserModule = AuthuserModule_1 = class AuthuserModule {
|
|
11
|
+
static forRoot(options = {}) {
|
|
12
|
+
return {
|
|
13
|
+
module: AuthuserModule_1,
|
|
14
|
+
providers: [{ provide: AUTHUSER_OPTIONS, useValue: Object.freeze({ ...options }) }],
|
|
15
|
+
exports: [AUTHUSER_OPTIONS],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
static forRootAsync(options) {
|
|
19
|
+
const provider = {
|
|
20
|
+
provide: AUTHUSER_OPTIONS,
|
|
21
|
+
inject: options.inject ?? [],
|
|
22
|
+
useFactory: options.useFactory,
|
|
23
|
+
};
|
|
24
|
+
return { module: AuthuserModule_1, providers: [provider], exports: [provider] };
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
AuthuserModule = AuthuserModule_1 = __decorate([
|
|
28
|
+
Global(),
|
|
29
|
+
Module({})
|
|
30
|
+
], AuthuserModule);
|
|
31
|
+
export { AuthuserModule };
|
|
32
|
+
//# sourceMappingURL=authuser.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authuser.module.js","sourceRoot":"","sources":["../src/authuser.module.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAqC,MAAM,gBAAgB,CAAC;AAGnF,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAS9D,IAAM,cAAc,sBAApB,MAAM,cAAc;IAClB,AAAP,MAAM,CAAC,OAAO,CAAC,OAAO,GAA4B,EAAE;QAClD,OAAO;YACL,MAAM,kBAAgB;YACtB,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,EAAE,CAAC;YACnF,OAAO,EAAE,CAAC,gBAAgB,CAAC;SAC5B,CAAC;IACJ,CAAC;IAEM,AAAP,MAAM,CAAC,YAAY,CAAC,OAAmC;QACrD,MAAM,QAAQ,GAAa;YACzB,OAAO,EAAE,gBAAgB;YACzB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;YAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC;QACF,OAAO,EAAE,MAAM,kBAAgB,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;IAChF,CAAC;CACF,CAAA;AAjBY,cAAc;IAF1B,MAAM,EAAE;IACR,MAAM,CAAC,EAAE,CAAC;GACE,cAAc,CAiB1B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
2
|
+
import type { ConfigureHttpAppOptions } from './types.js';
|
|
3
|
+
export declare function configureHttpApp(app: NestFastifyApplication, input?: ConfigureHttpAppOptions): Promise<NestFastifyApplication>;
|
|
4
|
+
//# sourceMappingURL=configure-http-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure-http-app.d.ts","sourceRoot":"","sources":["../src/configure-http-app.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAKvE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,sBAAsB,EAC3B,KAAK,GAAE,uBAA4B,GAClC,OAAO,CAAC,sBAAsB,CAAC,CA2DjC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import compress from '@fastify/compress';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import helmet from '@fastify/helmet';
|
|
4
|
+
import rateLimit from '@fastify/rate-limit';
|
|
5
|
+
import { ValidationPipe, VersioningType } from '@nestjs/common';
|
|
6
|
+
import { HttpExceptionFilter } from './http-exception.filter.js';
|
|
7
|
+
import { registerOpenApi } from './openapi.js';
|
|
8
|
+
import { resolveOptions } from './options.js';
|
|
9
|
+
export async function configureHttpApp(app, input = {}) {
|
|
10
|
+
const options = resolveOptions(input);
|
|
11
|
+
const fastify = app.getHttpAdapter().getInstance();
|
|
12
|
+
if (options.security.helmet) {
|
|
13
|
+
await fastify.register(helmet, {
|
|
14
|
+
global: true,
|
|
15
|
+
contentSecurityPolicy: options.security.contentSecurityPolicy,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (options.security.cors !== false) {
|
|
19
|
+
await fastify.register(cors, options.security.cors);
|
|
20
|
+
}
|
|
21
|
+
if (options.security.rateLimit !== false && options.security.rateLimit.enabled) {
|
|
22
|
+
const { enabled: _enabled, ...rateLimitOptions } = options.security.rateLimit;
|
|
23
|
+
await fastify.register(rateLimit, rateLimitOptions);
|
|
24
|
+
}
|
|
25
|
+
if (options.compression.enabled) {
|
|
26
|
+
await fastify.register(compress, {
|
|
27
|
+
threshold: options.compression.threshold,
|
|
28
|
+
encodings: options.compression.encodings,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (options.apiPrefix)
|
|
32
|
+
app.setGlobalPrefix(options.apiPrefix);
|
|
33
|
+
if (options.versioning) {
|
|
34
|
+
app.enableVersioning(options.versioning === true
|
|
35
|
+
? { type: VersioningType.URI }
|
|
36
|
+
: options.versioning);
|
|
37
|
+
}
|
|
38
|
+
if (options.validation !== false)
|
|
39
|
+
app.useGlobalPipes(new ValidationPipe(options.validation));
|
|
40
|
+
app.useGlobalFilters(new HttpExceptionFilter());
|
|
41
|
+
if (options.shutdownHooks)
|
|
42
|
+
app.enableShutdownHooks();
|
|
43
|
+
if (options.observability.responseTimeHeader) {
|
|
44
|
+
const startTimes = new WeakMap();
|
|
45
|
+
fastify.addHook('onRequest', async (request) => {
|
|
46
|
+
startTimes.set(request, process.hrtime.bigint());
|
|
47
|
+
});
|
|
48
|
+
fastify.addHook('onSend', async (request, reply, payload) => {
|
|
49
|
+
const start = startTimes.get(request);
|
|
50
|
+
if (typeof start === 'bigint') {
|
|
51
|
+
reply.header('server-timing', `app;dur=${(Number(process.hrtime.bigint() - start) / 1e6).toFixed(2)}`);
|
|
52
|
+
}
|
|
53
|
+
return payload;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (options.health.enabled) {
|
|
57
|
+
fastify.get(options.health.path, {
|
|
58
|
+
config: { rateLimit: false },
|
|
59
|
+
}, async (_request, reply) => reply.send(options.health.response));
|
|
60
|
+
}
|
|
61
|
+
await registerOpenApi(app, fastify, options.openApi);
|
|
62
|
+
return app;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=configure-http-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure-http-app.js","sourceRoot":"","sources":["../src/configure-http-app.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AACzC,OAAO,IAAI,MAAM,eAAe,CAAC;AACjC,OAAO,MAAM,MAAM,iBAAiB,CAAC;AACrC,OAAO,SAAS,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAA2B,EAC3B,KAAK,GAA4B,EAAE;IAEnC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC,WAAW,EAAqB,CAAC;IAEtE,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE;YAC7B,MAAM,EAAE,IAAI;YACZ,qBAAqB,EAAE,OAAO,CAAC,QAAQ,CAAC,qBAAqB;SAC9D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACpC,MAAM,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,KAAK,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QAC/E,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,gBAAgB,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC9E,MAAM,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAChC,MAAM,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE;YAC/B,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,SAAS;YACxC,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,SAAS;SACzC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,CAAC,SAAS;QAAE,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI;YAC9C,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,GAAG,EAAE;YAC9B,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,KAAK,KAAK;QAAE,GAAG,CAAC,cAAc,CAAC,IAAI,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7F,GAAG,CAAC,gBAAgB,CAAC,IAAI,mBAAmB,EAAE,CAAC,CAAC;IAChD,IAAI,OAAO,CAAC,aAAa;QAAE,GAAG,CAAC,mBAAmB,EAAE,CAAC;IAErD,IAAI,OAAO,CAAC,aAAa,CAAC,kBAAkB,EAAE,CAAC;QAC7C,MAAM,UAAU,GAAG,IAAI,OAAO,EAAkB,CAAC;QACjD,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7C,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC1D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACzG,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;YAC/B,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SAC7B,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
2
|
+
import type { CreateHttpAppOptions } from './types.js';
|
|
3
|
+
export declare function createHttpApp(options: CreateHttpAppOptions): Promise<NestFastifyApplication>;
|
|
4
|
+
/** A descriptive alias for new projects. */
|
|
5
|
+
export declare const createNestApp: typeof createHttpApp;
|
|
6
|
+
//# sourceMappingURL=create-http-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-http-app.d.ts","sourceRoot":"","sources":["../src/create-http-app.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAIvD,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4BlG;AAED,4CAA4C;AAC5C,eAAO,MAAM,aAAa,sBAAgB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { LogController } from 'fastify';
|
|
3
|
+
import { NestFactory } from '@nestjs/core';
|
|
4
|
+
import { FastifyAdapter, } from '@nestjs/platform-fastify';
|
|
5
|
+
import { configureHttpApp } from './configure-http-app.js';
|
|
6
|
+
import { resolveOptions } from './options.js';
|
|
7
|
+
const SAFE_REQUEST_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
8
|
+
export async function createHttpApp(options) {
|
|
9
|
+
const resolved = resolveOptions(options);
|
|
10
|
+
const network = options.network ?? {};
|
|
11
|
+
const adapter = new FastifyAdapter({
|
|
12
|
+
logger: resolved.observability.logger,
|
|
13
|
+
logController: new LogController({
|
|
14
|
+
disableRequestLogging: !resolved.observability.requestLogging,
|
|
15
|
+
}),
|
|
16
|
+
trustProxy: network.trustProxy ?? false,
|
|
17
|
+
bodyLimit: network.bodyLimit ?? 1_048_576,
|
|
18
|
+
routerOptions: { maxParamLength: network.maxParamLength ?? 100 },
|
|
19
|
+
connectionTimeout: network.connectionTimeout ?? 0,
|
|
20
|
+
requestTimeout: network.requestTimeout ?? 0,
|
|
21
|
+
keepAliveTimeout: network.keepAliveTimeout ?? 72_000,
|
|
22
|
+
requestIdHeader: 'x-request-id',
|
|
23
|
+
genReqId: safeRequestId,
|
|
24
|
+
});
|
|
25
|
+
const nestOptions = resolved.observability.logger === false
|
|
26
|
+
? { ...options.nest, logger: false }
|
|
27
|
+
: { ...options.nest };
|
|
28
|
+
const app = await NestFactory.create(options.rootModule, adapter, nestOptions);
|
|
29
|
+
return configureHttpApp(app, options);
|
|
30
|
+
}
|
|
31
|
+
/** A descriptive alias for new projects. */
|
|
32
|
+
export const createNestApp = createHttpApp;
|
|
33
|
+
function safeRequestId(request) {
|
|
34
|
+
const incoming = request.headers['x-request-id'];
|
|
35
|
+
return typeof incoming === 'string' && SAFE_REQUEST_ID.test(incoming) ? incoming : randomUUID();
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=create-http-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-http-app.js","sourceRoot":"","sources":["../src/create-http-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACL,cAAc,GAEf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC;QACjC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,MAAM;QACrC,aAAa,EAAE,IAAI,aAAa,CAAC;YAC/B,qBAAqB,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,cAAc;SAC9D,CAAC;QACF,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;QACvC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,SAAS;QACzC,aAAa,EAAE,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,GAAG,EAAE;QAChE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,CAAC;QACjD,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,CAAC;QAC3C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,MAAM;QACpD,eAAe,EAAE,cAAc;QAC/B,QAAQ,EAAE,aAAa;KACxB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK;QACzD,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAc,EAAE;QAC7C,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,MAAM,CAClC,OAAO,CAAC,UAAU,EAClB,OAAO,EACP,WAAW,CACZ,CAAC;IAEF,OAAO,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED,4CAA4C;AAC5C,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAE3C,SAAS,aAAa,CAAC,OAAwB;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACjD,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const IS_PUBLIC_KEY = "authuser:isPublic";
|
|
2
|
+
export declare const ROLES_KEY = "authuser:roles";
|
|
3
|
+
/** Marks a handler or controller as public for application-defined guards. */
|
|
4
|
+
export declare const Public: () => MethodDecorator & ClassDecorator;
|
|
5
|
+
/** Attaches required roles for application-defined guards. */
|
|
6
|
+
export declare const Roles: (...roles: string[]) => MethodDecorator & ClassDecorator;
|
|
7
|
+
//# sourceMappingURL=decorators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD,eAAO,MAAM,SAAS,mBAAmB,CAAC;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,MAAM,QAAO,eAAe,GAAG,cAAkD,CAAC;AAE/F,8DAA8D;AAC9D,eAAO,MAAM,KAAK,aAAc,MAAM,EAAE,KAAG,eAAe,GAAG,cAA+C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SetMetadata } from '@nestjs/common';
|
|
2
|
+
export const IS_PUBLIC_KEY = 'authuser:isPublic';
|
|
3
|
+
export const ROLES_KEY = 'authuser:roles';
|
|
4
|
+
/** Marks a handler or controller as public for application-defined guards. */
|
|
5
|
+
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
|
6
|
+
/** Attaches required roles for application-defined guards. */
|
|
7
|
+
export const Roles = (...roles) => SetMetadata(ROLES_KEY, roles);
|
|
8
|
+
//# sourceMappingURL=decorators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAC;AACjD,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAE1C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,MAAM,GAAG,GAAqC,EAAE,CAAC,WAAW,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;AAE/F,8DAA8D;AAC9D,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAG,KAAe,EAAoC,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-exception.filter.d.ts","sourceRoot":"","sources":["../src/http-exception.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAIb,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAGxB,qBAAa,mBAAoB,YAAW,eAAe;IACzD,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAqBnD;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Catch, HttpException, HttpStatus, } from '@nestjs/common';
|
|
2
|
+
export class HttpExceptionFilter {
|
|
3
|
+
catch(exception, host) {
|
|
4
|
+
const context = host.switchToHttp();
|
|
5
|
+
const reply = context.getResponse();
|
|
6
|
+
const request = context.getRequest();
|
|
7
|
+
const httpException = getHttpException(exception);
|
|
8
|
+
const status = httpException?.getStatus() ?? HttpStatus.INTERNAL_SERVER_ERROR;
|
|
9
|
+
const response = httpException?.getResponse();
|
|
10
|
+
const details = typeof response === 'object' && response !== null ? response : {};
|
|
11
|
+
const message = typeof response === 'string'
|
|
12
|
+
? response
|
|
13
|
+
: getMessage(details) ?? (status === 500 ? 'Internal server error' : 'Request failed');
|
|
14
|
+
if (status === 500)
|
|
15
|
+
request.log.error({ err: exception }, 'Unhandled request error');
|
|
16
|
+
void reply.status(status).send({
|
|
17
|
+
statusCode: status,
|
|
18
|
+
error: HttpStatus[status] ?? 'Error',
|
|
19
|
+
message,
|
|
20
|
+
requestId: request.id,
|
|
21
|
+
path: request.url,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
Catch()(HttpExceptionFilter);
|
|
26
|
+
function getMessage(value) {
|
|
27
|
+
return 'message' in value ? value.message : undefined;
|
|
28
|
+
}
|
|
29
|
+
function getHttpException(exception) {
|
|
30
|
+
if (exception instanceof HttpException)
|
|
31
|
+
return exception;
|
|
32
|
+
if (typeof exception !== 'object' || exception === null)
|
|
33
|
+
return undefined;
|
|
34
|
+
const candidate = exception;
|
|
35
|
+
if (typeof candidate.getStatus !== 'function' || typeof candidate.getResponse !== 'function') {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const status = candidate.getStatus.call(exception);
|
|
39
|
+
return Number.isInteger(status) && status >= 400 && status <= 599
|
|
40
|
+
? candidate
|
|
41
|
+
: undefined;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=http-exception.filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-exception.filter.js","sourceRoot":"","sources":["../src/http-exception.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,aAAa,EACb,UAAU,GAEX,MAAM,gBAAgB,CAAC;AAGxB,MAAM,OAAO,mBAAmB;IAC9B,KAAK,CAAC,SAAkB,EAAE,IAAmB;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAgB,CAAC;QAClD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAkB,CAAC;QACrD,MAAM,aAAa,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,aAAa,EAAE,SAAS,EAAE,IAAI,UAAU,CAAC,qBAAqB,CAAC;QAC9E,MAAM,QAAQ,GAAG,aAAa,EAAE,WAAW,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,MAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,QAAQ;YAC1C,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAEzF,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,yBAAyB,CAAC,CAAC;QAErF,KAAK,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;YAC7B,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,OAAO;YACpC,OAAO;YACP,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,IAAI,EAAE,OAAO,CAAC,GAAG;SAClB,CAAC,CAAC;IACL,CAAC;CACF;AAED,KAAK,EAAE,CAAC,mBAAmB,CAAC,CAAC;AAE7B,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAOD,SAAS,gBAAgB,CAAC,SAAkB;IAC1C,IAAI,SAAS,YAAY,aAAa;QAAE,OAAO,SAAS,CAAC;IACzD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAE1E,MAAM,SAAS,GAAG,SAAuC,CAAC;IAC1D,IAAI,OAAO,SAAS,CAAC,SAAS,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG;QAC/D,CAAC,CAAC,SAA8B;QAChC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
export { AuthuserModule, AUTHUSER_OPTIONS } from './authuser.module.js';
|
|
3
|
+
export type { AuthuserModuleAsyncOptions } from './authuser.module.js';
|
|
4
|
+
export { configureHttpApp } from './configure-http-app.js';
|
|
5
|
+
export { createHttpApp, createNestApp } from './create-http-app.js';
|
|
6
|
+
export { Public, Roles, IS_PUBLIC_KEY, ROLES_KEY } from './decorators.js';
|
|
7
|
+
export { HttpExceptionFilter } from './http-exception.filter.js';
|
|
8
|
+
export type { CompressionOptions, ConfigureHttpAppOptions, CorsOptions, CreateHttpAppOptions, HealthOptions, NetworkOptions, ObservabilityOptions, OpenApiOptions, RateLimitOptions, SecurityOptions, SecurityPreset, } from './types.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
export { AuthuserModule, AUTHUSER_OPTIONS } from './authuser.module.js';
|
|
3
|
+
export { configureHttpApp } from './configure-http-app.js';
|
|
4
|
+
export { createHttpApp, createNestApp } from './create-http-app.js';
|
|
5
|
+
export { Public, Roles, IS_PUBLIC_KEY, ROLES_KEY } from './decorators.js';
|
|
6
|
+
export { HttpExceptionFilter } from './http-exception.filter.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
import type { ResolvedOptions } from './options.js';
|
|
4
|
+
export declare function registerOpenApi(app: NestFastifyApplication, fastify: FastifyInstance, options: ResolvedOptions['openApi']): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,wBAAsB,eAAe,CACnC,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
|
package/dist/openapi.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export async function registerOpenApi(app, fastify, options) {
|
|
2
|
+
if (!options.enabled)
|
|
3
|
+
return;
|
|
4
|
+
const { DocumentBuilder, SwaggerModule } = await import('@nestjs/swagger');
|
|
5
|
+
let builder = new DocumentBuilder()
|
|
6
|
+
.setTitle(options.title)
|
|
7
|
+
.setDescription(options.description)
|
|
8
|
+
.setVersion(options.version);
|
|
9
|
+
if (options.bearerAuth)
|
|
10
|
+
builder = builder.addBearerAuth();
|
|
11
|
+
const document = SwaggerModule.createDocument(app, builder.build());
|
|
12
|
+
if (options.json) {
|
|
13
|
+
fastify.get(options.jsonPath, async (_request, reply) => reply.send(document));
|
|
14
|
+
}
|
|
15
|
+
if (options.ui) {
|
|
16
|
+
const { default: swagger } = await import('@fastify/swagger');
|
|
17
|
+
const { default: swaggerUi } = await import('@fastify/swagger-ui');
|
|
18
|
+
await fastify.register(swagger, {
|
|
19
|
+
mode: 'static',
|
|
20
|
+
// Nest and Fastify publish structurally compatible but distinct OpenAPI types.
|
|
21
|
+
specification: { document: document },
|
|
22
|
+
});
|
|
23
|
+
await fastify.register(swaggerUi, {
|
|
24
|
+
routePrefix: options.uiPath,
|
|
25
|
+
uiConfig: { docExpansion: 'list', deepLinking: true, spec: document },
|
|
26
|
+
staticCSP: true,
|
|
27
|
+
transformStaticCSP: (header) => header,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAA2B,EAC3B,OAAwB,EACxB,OAAmC;IAEnC,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO;IAE7B,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC3E,IAAI,OAAO,GAAG,IAAI,eAAe,EAAE;SAChC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;SACvB,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC;SACnC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,OAAO,CAAC,UAAU;QAAE,OAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAE1D,MAAM,QAAQ,GAAG,aAAa,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAEpE,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC9D,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;QACnE,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE;YAC9B,IAAI,EAAE,QAAQ;YACd,+EAA+E;YAC/E,aAAa,EAAE,EAAE,QAAQ,EAAE,QAAiB,EAAE;SAC/C,CAAC,CAAC;QACH,MAAM,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE;YAChC,WAAW,EAAE,OAAO,CAAC,MAAM;YAC3B,QAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;YACrE,SAAS,EAAE,IAAI;YACf,kBAAkB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;SACvC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CompressionOptions, ConfigureHttpAppOptions, CorsOptions, HealthOptions, OpenApiOptions, RateLimitOptions, SecurityPreset } from './types.js';
|
|
2
|
+
export interface ResolvedOptions {
|
|
3
|
+
appName: string;
|
|
4
|
+
preset: SecurityPreset;
|
|
5
|
+
apiPrefix?: string;
|
|
6
|
+
versioning: ConfigureHttpAppOptions['versioning'];
|
|
7
|
+
validation: ConfigureHttpAppOptions['validation'];
|
|
8
|
+
security: {
|
|
9
|
+
helmet: boolean;
|
|
10
|
+
contentSecurityPolicy: boolean;
|
|
11
|
+
cors: false | CorsOptions;
|
|
12
|
+
rateLimit: false | (Required<Pick<RateLimitOptions, 'enabled' | 'max' | 'timeWindow' | 'ban'>> & RateLimitOptions);
|
|
13
|
+
};
|
|
14
|
+
compression: Required<CompressionOptions>;
|
|
15
|
+
openApi: Required<OpenApiOptions>;
|
|
16
|
+
health: Required<HealthOptions>;
|
|
17
|
+
observability: Required<NonNullable<ConfigureHttpAppOptions['observability']>>;
|
|
18
|
+
shutdownHooks: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function resolveOptions(options: ConfigureHttpAppOptions): ResolvedOptions;
|
|
21
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,QAAQ,EAAE;QACR,MAAM,EAAE,OAAO,CAAC;QAChB,qBAAqB,EAAE,OAAO,CAAC;QAC/B,IAAI,EAAE,KAAK,GAAG,WAAW,CAAC;QAC1B,SAAS,EAAE,KAAK,IAAG,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,CAAC,CAAC,GAAG,gBAAgB,CAAA,CAAC;KAClH,CAAC;IACF,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC1C,OAAO,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IAClC,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAChC,aAAa,EAAE,QAAQ,CAAC,WAAW,CAAC,uBAAuB,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC/E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,uBAAuB,GAAG,eAAe,CA+EhF"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function resolveOptions(options) {
|
|
2
|
+
const preset = options.preset ?? 'secure';
|
|
3
|
+
const isSecure = preset !== 'minimal';
|
|
4
|
+
const isFull = preset === 'full';
|
|
5
|
+
const security = options.security ?? {};
|
|
6
|
+
const rateLimitInput = security.rateLimit === false ? false : security.rateLimit ?? {};
|
|
7
|
+
const compressionInput = typeof options.compression === 'object' ? options.compression : {};
|
|
8
|
+
const openApiInput = typeof options.openApi === 'object' ? options.openApi : {};
|
|
9
|
+
const healthInput = typeof options.health === 'object' ? options.health : {};
|
|
10
|
+
const resolved = {
|
|
11
|
+
appName: options.appName ?? 'nest-api',
|
|
12
|
+
preset,
|
|
13
|
+
versioning: options.versioning ?? false,
|
|
14
|
+
validation: options.validation === undefined
|
|
15
|
+
? { whitelist: true, forbidNonWhitelisted: true, transform: false }
|
|
16
|
+
: options.validation,
|
|
17
|
+
security: {
|
|
18
|
+
helmet: security.helmet ?? isSecure,
|
|
19
|
+
contentSecurityPolicy: security.contentSecurityPolicy ?? true,
|
|
20
|
+
cors: security.cors === undefined || security.cors === false
|
|
21
|
+
? false
|
|
22
|
+
: {
|
|
23
|
+
...security.cors,
|
|
24
|
+
origin: security.cors.origin ?? false,
|
|
25
|
+
methods: security.cors.methods ?? [
|
|
26
|
+
'GET',
|
|
27
|
+
'HEAD',
|
|
28
|
+
'POST',
|
|
29
|
+
'PUT',
|
|
30
|
+
'PATCH',
|
|
31
|
+
'DELETE',
|
|
32
|
+
'OPTIONS',
|
|
33
|
+
'QUERY',
|
|
34
|
+
],
|
|
35
|
+
optionsSuccessStatus: security.cors.optionsSuccessStatus ?? 204,
|
|
36
|
+
strictPreflight: security.cors.strictPreflight ?? true,
|
|
37
|
+
},
|
|
38
|
+
rateLimit: rateLimitInput === false
|
|
39
|
+
? false
|
|
40
|
+
: {
|
|
41
|
+
...rateLimitInput,
|
|
42
|
+
enabled: rateLimitInput.enabled ?? isSecure,
|
|
43
|
+
max: rateLimitInput.max ?? 100,
|
|
44
|
+
timeWindow: rateLimitInput.timeWindow ?? '1 minute',
|
|
45
|
+
ban: rateLimitInput.ban ?? -1,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
compression: {
|
|
49
|
+
enabled: typeof options.compression === 'boolean' ? options.compression : compressionInput.enabled ?? isFull,
|
|
50
|
+
threshold: compressionInput.threshold ?? 1024,
|
|
51
|
+
encodings: compressionInput.encodings ?? ['br', 'gzip', 'deflate'],
|
|
52
|
+
},
|
|
53
|
+
openApi: {
|
|
54
|
+
enabled: typeof options.openApi === 'boolean' ? options.openApi : openApiInput.enabled ?? isFull,
|
|
55
|
+
title: openApiInput.title ?? options.appName ?? 'Nest API',
|
|
56
|
+
description: openApiInput.description ?? '',
|
|
57
|
+
version: openApiInput.version ?? '1.0.0',
|
|
58
|
+
json: openApiInput.json ?? true,
|
|
59
|
+
jsonPath: normalizePath(openApiInput.jsonPath ?? '/openapi.json'),
|
|
60
|
+
ui: openApiInput.ui ?? false,
|
|
61
|
+
uiPath: normalizePath(openApiInput.uiPath ?? '/docs'),
|
|
62
|
+
bearerAuth: openApiInput.bearerAuth ?? false,
|
|
63
|
+
},
|
|
64
|
+
health: {
|
|
65
|
+
enabled: typeof options.health === 'boolean' ? options.health : healthInput.enabled ?? isFull,
|
|
66
|
+
path: normalizePath(healthInput.path ?? '/health'),
|
|
67
|
+
response: healthInput.response ?? { status: 'ok' },
|
|
68
|
+
},
|
|
69
|
+
observability: {
|
|
70
|
+
logger: options.observability?.logger ?? true,
|
|
71
|
+
requestLogging: options.observability?.requestLogging ?? false,
|
|
72
|
+
responseTimeHeader: options.observability?.responseTimeHeader ?? false,
|
|
73
|
+
},
|
|
74
|
+
shutdownHooks: options.shutdownHooks ?? true,
|
|
75
|
+
};
|
|
76
|
+
if (options.apiPrefix !== undefined)
|
|
77
|
+
resolved.apiPrefix = options.apiPrefix.replace(/^\/+|\/+$/g, '');
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
function normalizePath(path) {
|
|
81
|
+
const normalized = `/${path}`.replace(/\/+/g, '/').replace(/\/$/, '');
|
|
82
|
+
return normalized || '/';
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { LoggerOptions } from 'pino';
|
|
2
|
+
import type { Type, ValidationPipeOptions, VersioningOptions } from '@nestjs/common';
|
|
3
|
+
import type { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
|
|
4
|
+
import type { FastifyRequest } from 'fastify';
|
|
5
|
+
export type SecurityPreset = 'minimal' | 'secure' | 'full';
|
|
6
|
+
export interface CorsOptions {
|
|
7
|
+
/** No cross-origin requests are allowed by default. */
|
|
8
|
+
origin?: boolean | string | RegExp | Array<string | RegExp>;
|
|
9
|
+
credentials?: boolean;
|
|
10
|
+
methods?: string[];
|
|
11
|
+
allowedHeaders?: string[];
|
|
12
|
+
exposedHeaders?: string[];
|
|
13
|
+
maxAge?: number;
|
|
14
|
+
/** Status returned for a successful preflight. HTTP 204 is the default. */
|
|
15
|
+
optionsSuccessStatus?: number;
|
|
16
|
+
strictPreflight?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface RateLimitOptions {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
max?: number;
|
|
21
|
+
timeWindow?: number | string;
|
|
22
|
+
ban?: number;
|
|
23
|
+
allowList?: string[];
|
|
24
|
+
keyGenerator?: (request: FastifyRequest) => string;
|
|
25
|
+
}
|
|
26
|
+
export interface SecurityOptions {
|
|
27
|
+
helmet?: boolean;
|
|
28
|
+
/** Helmet Content-Security-Policy. Enabled by default. */
|
|
29
|
+
contentSecurityPolicy?: boolean;
|
|
30
|
+
cors?: false | CorsOptions;
|
|
31
|
+
rateLimit?: false | RateLimitOptions;
|
|
32
|
+
}
|
|
33
|
+
export interface CompressionOptions {
|
|
34
|
+
enabled?: boolean;
|
|
35
|
+
threshold?: number;
|
|
36
|
+
encodings?: Array<'br' | 'gzip' | 'deflate' | 'identity'>;
|
|
37
|
+
}
|
|
38
|
+
export interface OpenApiOptions {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
title?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
/** Serve the generated OpenAPI document. Set false to generate only. */
|
|
44
|
+
json?: boolean;
|
|
45
|
+
jsonPath?: string;
|
|
46
|
+
/** Swagger UI is opt-in and disabled by default. */
|
|
47
|
+
ui?: boolean;
|
|
48
|
+
uiPath?: string;
|
|
49
|
+
bearerAuth?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface HealthOptions {
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
path?: string;
|
|
54
|
+
response?: Readonly<Record<string, unknown>>;
|
|
55
|
+
}
|
|
56
|
+
export interface ObservabilityOptions {
|
|
57
|
+
/** Fastify/Pino logging. False removes request logging overhead. */
|
|
58
|
+
logger?: boolean | LoggerOptions;
|
|
59
|
+
requestLogging?: boolean;
|
|
60
|
+
responseTimeHeader?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface NetworkOptions {
|
|
63
|
+
trustProxy?: boolean | string | string[];
|
|
64
|
+
bodyLimit?: number;
|
|
65
|
+
maxParamLength?: number;
|
|
66
|
+
connectionTimeout?: number;
|
|
67
|
+
requestTimeout?: number;
|
|
68
|
+
keepAliveTimeout?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface ConfigureHttpAppOptions {
|
|
71
|
+
appName?: string;
|
|
72
|
+
preset?: SecurityPreset;
|
|
73
|
+
apiPrefix?: string;
|
|
74
|
+
versioning?: boolean | VersioningOptions;
|
|
75
|
+
validation?: false | ValidationPipeOptions;
|
|
76
|
+
security?: SecurityOptions;
|
|
77
|
+
compression?: boolean | CompressionOptions;
|
|
78
|
+
openApi?: boolean | OpenApiOptions;
|
|
79
|
+
health?: boolean | HealthOptions;
|
|
80
|
+
observability?: ObservabilityOptions;
|
|
81
|
+
shutdownHooks?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export interface CreateHttpAppOptions extends ConfigureHttpAppOptions {
|
|
84
|
+
rootModule: Type<unknown>;
|
|
85
|
+
network?: NetworkOptions;
|
|
86
|
+
nest?: Omit<NestApplicationOptions, 'logger'>;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
All settings are optional except `rootModule` when using `createNestApp`.
|
|
4
|
+
|
|
5
|
+
## Application
|
|
6
|
+
|
|
7
|
+
| Option | Default | Description |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `rootModule` | required | Root Nest module. |
|
|
10
|
+
| `appName` | `nest-api` | Name used by generated OpenAPI metadata. |
|
|
11
|
+
| `preset` | `secure` | `minimal`, `secure`, or `full`. |
|
|
12
|
+
| `apiPrefix` | none | Global prefix without a leading slash. |
|
|
13
|
+
| `versioning` | `false` | `true` for URI versioning, or Nest versioning options. |
|
|
14
|
+
| `shutdownHooks` | `true` | Enable Nest shutdown signal hooks. |
|
|
15
|
+
| `validation` | strict defaults | `false` or Nest `ValidationPipeOptions`. |
|
|
16
|
+
| `nest` | `{}` | Extra `NestApplicationOptions` except logger. |
|
|
17
|
+
|
|
18
|
+
Default validation uses `whitelist: true`, `forbidNonWhitelisted: true`, and
|
|
19
|
+
`transform: false`. Transformation is off because implicit coercion costs work and
|
|
20
|
+
can hide unexpected input. Enable it explicitly when DTO conversion is required.
|
|
21
|
+
|
|
22
|
+
## Network
|
|
23
|
+
|
|
24
|
+
| Option | Default | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `network.bodyLimit` | `1048576` | Maximum body in bytes. |
|
|
27
|
+
| `network.maxParamLength` | `100` | Maximum route parameter length. |
|
|
28
|
+
| `network.trustProxy` | `false` | Fastify trusted-proxy policy. |
|
|
29
|
+
| `network.keepAliveTimeout` | `72000` | Keep-alive timeout in milliseconds. |
|
|
30
|
+
| `network.connectionTimeout` | `0` | Socket inactivity timeout. |
|
|
31
|
+
| `network.requestTimeout` | `0` | Incoming request timeout. Configure a finite value at the edge. |
|
|
32
|
+
|
|
33
|
+
## Security
|
|
34
|
+
|
|
35
|
+
`security.helmet` and `security.rateLimit` are enabled by secure/full presets.
|
|
36
|
+
CORS remains disabled unless an explicit configuration is supplied.
|
|
37
|
+
When enabled, the default advertised methods are GET, HEAD, POST, PUT, PATCH,
|
|
38
|
+
DELETE, OPTIONS and QUERY. Successful browser preflights return HTTP 204 by default;
|
|
39
|
+
`optionsSuccessStatus` can change this for a legacy client that requires 200.
|
|
40
|
+
|
|
41
|
+
Rate-limit defaults are 100 requests per minute per client IP. Available settings
|
|
42
|
+
include `max`, `timeWindow`, `ban`, `allowList`, and `keyGenerator`.
|
|
43
|
+
|
|
44
|
+
## Compression
|
|
45
|
+
|
|
46
|
+
Compression is enabled by the full preset only. The default threshold is 1024 bytes
|
|
47
|
+
and encodings are Brotli, gzip and deflate. Compression can increase CPU usage and
|
|
48
|
+
can amplify secrets reflected beside attacker-controlled data, so enable it for
|
|
49
|
+
known payloads rather than automatically for every API.
|
|
50
|
+
|
|
51
|
+
## OpenAPI
|
|
52
|
+
|
|
53
|
+
| Option | Default | Description |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `enabled` | full preset only | Generate an OpenAPI document. |
|
|
56
|
+
| `json` | `true` | Serve the document. |
|
|
57
|
+
| `jsonPath` | `/openapi.json` | JSON endpoint. |
|
|
58
|
+
| `ui` | `false` | Serve Swagger UI. |
|
|
59
|
+
| `uiPath` | `/docs` | UI route prefix. |
|
|
60
|
+
| `bearerAuth` | `false` | Add a bearer security scheme. |
|
|
61
|
+
|
|
62
|
+
Set `openApi: false` to guarantee the documentation integration is never loaded.
|
|
63
|
+
|
|
64
|
+
## Health and observability
|
|
65
|
+
|
|
66
|
+
The full preset serves `{ "status": "ok" }` at `/health`. This is a liveness probe,
|
|
67
|
+
not a dependency-readiness check. Supply a custom response or implement a Nest
|
|
68
|
+
controller for deeper readiness logic.
|
|
69
|
+
|
|
70
|
+
Fastify's Pino logger is enabled, while per-request logging is disabled to reduce
|
|
71
|
+
noise and overhead. Enable it with `observability.requestLogging`. The optional
|
|
72
|
+
`responseTimeHeader` emits a standards-compatible `Server-Timing` value.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Migration from `@authuser/nest-fastify-kit`
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- Upgrade the service to Node 20.11 or newer.
|
|
6
|
+
- Upgrade Nest packages to version 12.
|
|
7
|
+
- Use ESM-compatible imports and include `.js` on relative imports in NodeNext output.
|
|
8
|
+
|
|
9
|
+
## API changes
|
|
10
|
+
|
|
11
|
+
The application factory keeps its old `createHttpApp` name and also exports
|
|
12
|
+
`createNestApp`. Replace `docs` with `openApi`; replace `network` and security values
|
|
13
|
+
using the typed option reference.
|
|
14
|
+
|
|
15
|
+
The package no longer supplies an authentication guard with implied behavior.
|
|
16
|
+
`Public` and `Roles` remain metadata decorators and must be consumed by an explicit
|
|
17
|
+
application guard.
|
|
18
|
+
|
|
19
|
+
Swagger, validation packages and Nest/Fastify runtime packages are installed as
|
|
20
|
+
dependencies. Remove redundant direct declarations only after confirming the app
|
|
21
|
+
does not import those packages itself and that your package manager deduplicates a
|
|
22
|
+
single compatible Nest version.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Performance guide
|
|
2
|
+
|
|
3
|
+
There is no universal requests-per-second number: controller work, payload shape,
|
|
4
|
+
logging, validation, database latency, TLS and hardware dominate realistic results.
|
|
5
|
+
|
|
6
|
+
## Recommended sequence
|
|
7
|
+
|
|
8
|
+
1. Start with `preset: 'secure'` and production-shaped traffic.
|
|
9
|
+
2. Benchmark latency percentiles and CPU, not only average throughput.
|
|
10
|
+
3. Disable features one at a time to locate actual cost.
|
|
11
|
+
4. Add Fastify JSON response schemas on hot routes to gain compiled serialization.
|
|
12
|
+
5. Run multiple Node processes or containers; keep one event loop per CPU allocation.
|
|
13
|
+
6. Load-test through the same proxy and TLS path used in production.
|
|
14
|
+
|
|
15
|
+
Request logging is disabled by default. Compression is full-preset only because it
|
|
16
|
+
trades bandwidth for CPU. OpenAPI generation happens at startup, and its modules are
|
|
17
|
+
not imported when disabled.
|
|
18
|
+
|
|
19
|
+
## Choosing a lower-level stack
|
|
20
|
+
|
|
21
|
+
If profiling shows Nest itself is the bottleneck, raw Fastify is the closest smaller
|
|
22
|
+
step and preserves its plugin/schema ecosystem. A specialized HTTP engine can achieve
|
|
23
|
+
higher synthetic throughput, but moving away from Nest changes adapters, middleware,
|
|
24
|
+
testing and operational conventions. Make that decision from an application benchmark,
|
|
25
|
+
not a hello-world ranking.
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
The secure preset establishes a baseline, not a complete application security model.
|
|
4
|
+
|
|
5
|
+
## Included controls
|
|
6
|
+
|
|
7
|
+
- Helmet security headers with Content Security Policy.
|
|
8
|
+
- Strict DTO property allowlisting.
|
|
9
|
+
- A one MiB default request-body limit.
|
|
10
|
+
- Rate limiting by client IP.
|
|
11
|
+
- Request IDs restricted to 128 safe characters before log propagation.
|
|
12
|
+
- Generic internal errors; unexpected exceptions are logged server-side only.
|
|
13
|
+
- CORS disabled until an allowlist is configured.
|
|
14
|
+
|
|
15
|
+
## Application responsibilities
|
|
16
|
+
|
|
17
|
+
- Authenticate requests and authorize every sensitive operation.
|
|
18
|
+
- Validate business rules, uploaded files and non-HTTP input.
|
|
19
|
+
- Configure trusted proxies exactly; a wrong value allows IP spoofing.
|
|
20
|
+
- Store secrets outside source control and rotate them.
|
|
21
|
+
- Apply endpoint-specific rate limits for login and recovery flows.
|
|
22
|
+
- Use TLS at the load balancer or application edge.
|
|
23
|
+
- Keep Node and dependencies patched and review `npm audit` output.
|
|
24
|
+
- Avoid exposing Swagger UI and detailed readiness data publicly.
|
|
25
|
+
|
|
26
|
+
`Public()` and `Roles()` only attach metadata. They do nothing until an application
|
|
27
|
+
guard reads that metadata and enforces the intended policy.
|
|
28
|
+
|
|
29
|
+
## Reporting
|
|
30
|
+
|
|
31
|
+
Follow the private disclosure instructions in [SECURITY.md](../SECURITY.md).
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authuser/nest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Secure, high-performance NestJS foundation powered by Fastify",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nestjs",
|
|
7
|
+
"fastify",
|
|
8
|
+
"openapi",
|
|
9
|
+
"swagger",
|
|
10
|
+
"security",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Authuser",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"docs",
|
|
28
|
+
"README.md",
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20.11"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"clean": "rimraf dist coverage",
|
|
42
|
+
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:coverage": "vitest run --coverage",
|
|
46
|
+
"prepack": "npm run clean && npm run build",
|
|
47
|
+
"check": "npm run typecheck && npm test && npm run build && npm pack --dry-run"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@fastify/compress": "^9.2.0",
|
|
51
|
+
"@fastify/cors": "^11.3.0",
|
|
52
|
+
"@fastify/helmet": "^13.1.1",
|
|
53
|
+
"@fastify/rate-limit": "^11.2.0",
|
|
54
|
+
"@fastify/swagger": "^9.8.1",
|
|
55
|
+
"@fastify/swagger-ui": "^6.1.1",
|
|
56
|
+
"@nestjs/common": "^12.0.1",
|
|
57
|
+
"@nestjs/core": "^12.0.1",
|
|
58
|
+
"@nestjs/platform-fastify": "^12.0.1",
|
|
59
|
+
"@nestjs/swagger": "^12.0.1",
|
|
60
|
+
"class-transformer": "^0.5.1",
|
|
61
|
+
"class-validator": "^0.15.1",
|
|
62
|
+
"fastify": "^5.12.1",
|
|
63
|
+
"pino": "^10.3.1",
|
|
64
|
+
"reflect-metadata": "^0.2.2",
|
|
65
|
+
"rxjs": "^7.8.2"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/node": "^24.0.0",
|
|
69
|
+
"@vitest/coverage-v8": "^4.0.0",
|
|
70
|
+
"rimraf": "^6.0.0",
|
|
71
|
+
"typescript": "^7.0.2",
|
|
72
|
+
"vitest": "^4.0.0"
|
|
73
|
+
},
|
|
74
|
+
"directories": {
|
|
75
|
+
"doc": "docs",
|
|
76
|
+
"test": "test"
|
|
77
|
+
}
|
|
78
|
+
}
|