@lark-apaas/fullstack-nestjs-core 0.1.0-alpha.9 → 1.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 +268 -0
- package/dist/index.cjs +65 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +65 -74
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# @lark-apaas/fullstack-nestjs-core
|
|
2
|
+
|
|
3
|
+
FullStack NestJS Core 是一个为 NestJS 应用提供核心功能的工具包,包括 CSRF 保护、用户上下文管理和开发工具等。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **CSRF 保护**: 提供完整的 CSRF Token 生成和验证机制
|
|
8
|
+
- **用户上下文**: 自动从请求头中提取并注入用户上下文信息
|
|
9
|
+
- **开发工具**: 自动生成 Swagger 文档、OpenAPI JSON 和客户端 SDK
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @lark-apaas/fullstack-nestjs-core
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
或使用 yarn:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add @lark-apaas/fullstack-nestjs-core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 环境要求
|
|
24
|
+
|
|
25
|
+
- Node.js >= 18.0.0
|
|
26
|
+
- NestJS >= 10.4.20
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
### 1. CSRF 保护
|
|
31
|
+
|
|
32
|
+
CSRF 保护由两个中间件组成:`CsrfTokenMiddleware` 用于生成 token,`CsrfMiddleware` 用于验证 token。
|
|
33
|
+
|
|
34
|
+
#### 使用默认配置
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
|
|
38
|
+
import { CsrfTokenMiddleware, CsrfMiddleware } from '@lark-apaas/fullstack-nestjs-core';
|
|
39
|
+
|
|
40
|
+
@Module({})
|
|
41
|
+
export class AppModule implements NestModule {
|
|
42
|
+
configure(consumer: MiddlewareConsumer) {
|
|
43
|
+
// 直接应用中间件,使用默认配置
|
|
44
|
+
consumer
|
|
45
|
+
.apply(CsrfTokenMiddleware)
|
|
46
|
+
.forRoutes('*'); // 所有路由都生成 token
|
|
47
|
+
|
|
48
|
+
consumer
|
|
49
|
+
.apply(CsrfMiddleware)
|
|
50
|
+
.forRoutes('*'); // 所有路由都验证 CSRF token
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 自定义配置(可选)
|
|
56
|
+
|
|
57
|
+
如需自定义配置,可在应用中间件前调用 `configure` 方法:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
|
|
61
|
+
import { CsrfTokenMiddleware, CsrfMiddleware } from '@lark-apaas/fullstack-nestjs-core';
|
|
62
|
+
|
|
63
|
+
@Module({})
|
|
64
|
+
export class AppModule implements NestModule {
|
|
65
|
+
configure(consumer: MiddlewareConsumer) {
|
|
66
|
+
// 自定义配置 CSRF Token 生成中间件
|
|
67
|
+
CsrfTokenMiddleware.configure({
|
|
68
|
+
cookieKey: 'CSRF-TOKEN', // 默认: 'suda-csrf-token'
|
|
69
|
+
cookieMaxAge: 86400000, // 默认: 2592000000 (30天)
|
|
70
|
+
cookiePath: '/', // 默认: '/'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 自定义配置 CSRF 验证中间件
|
|
74
|
+
CsrfMiddleware.configure({
|
|
75
|
+
headerKey: 'X-CSRF-TOKEN', // 默认: 'x-suda-csrf-token'
|
|
76
|
+
cookieKey: 'CSRF-TOKEN', // 默认: 'suda-csrf-token'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 应用中间件
|
|
80
|
+
consumer
|
|
81
|
+
.apply(CsrfTokenMiddleware)
|
|
82
|
+
.forRoutes('*');
|
|
83
|
+
|
|
84
|
+
consumer
|
|
85
|
+
.apply(CsrfMiddleware)
|
|
86
|
+
.forRoutes('*');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**注意**: 如果使用自定义的 `cookieKey`,需要确保 `CsrfTokenMiddleware` 和 `CsrfMiddleware` 配置的 `cookieKey` 一致。
|
|
92
|
+
|
|
93
|
+
### 2. 用户上下文
|
|
94
|
+
|
|
95
|
+
自动从请求头中提取用户信息并注入到请求对象中。
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
|
|
99
|
+
import { UserContextMiddleware } from '@lark-apaas/fullstack-nestjs-core';
|
|
100
|
+
|
|
101
|
+
@Module({})
|
|
102
|
+
export class AppModule implements NestModule {
|
|
103
|
+
configure(consumer: MiddlewareConsumer) {
|
|
104
|
+
consumer
|
|
105
|
+
.apply(UserContextMiddleware)
|
|
106
|
+
.forRoutes('*');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
在控制器中使用:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Controller, Get, Req } from '@nestjs/common';
|
|
115
|
+
import { Request } from 'express';
|
|
116
|
+
|
|
117
|
+
@Controller()
|
|
118
|
+
export class AppController {
|
|
119
|
+
@Get()
|
|
120
|
+
getUser(@Req() req: Request) {
|
|
121
|
+
// 访问用户上下文
|
|
122
|
+
const { userId, tenantId, appId } = req.userContext;
|
|
123
|
+
return { userId, tenantId, appId };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. 开发工具模块
|
|
129
|
+
|
|
130
|
+
在开发环境中自动生成 Swagger 文档和客户端 SDK。
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { NestFactory } from '@nestjs/core';
|
|
134
|
+
import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';
|
|
135
|
+
import { AppModule } from './app.module';
|
|
136
|
+
|
|
137
|
+
async function bootstrap() {
|
|
138
|
+
const app = await NestFactory.create(AppModule);
|
|
139
|
+
|
|
140
|
+
// 挂载开发工具
|
|
141
|
+
await DevToolsModule.mount(app, {
|
|
142
|
+
docsPath: 'api-docs',
|
|
143
|
+
openapiOut: './openapi.json',
|
|
144
|
+
needSetupServer: true,
|
|
145
|
+
needGenerateClientSdk: true,
|
|
146
|
+
clientSdkOut: './src/sdk',
|
|
147
|
+
swaggerOptions: {
|
|
148
|
+
title: 'My API',
|
|
149
|
+
version: '1.0.0',
|
|
150
|
+
customSiteTitle: 'My API Documentation',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await app.listen(3000);
|
|
155
|
+
}
|
|
156
|
+
bootstrap();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## API 文档
|
|
160
|
+
|
|
161
|
+
### CsrfTokenMiddleware
|
|
162
|
+
|
|
163
|
+
生成 CSRF Token 并设置到 Cookie 中。
|
|
164
|
+
|
|
165
|
+
#### 配置选项 (CsrfTokenOptions)
|
|
166
|
+
|
|
167
|
+
| 选项 | 类型 | 默认值 | 描述 |
|
|
168
|
+
|------|------|--------|------|
|
|
169
|
+
| cookieKey | string | `'suda-csrf-token'` | Cookie 中存储 CSRF Token 的键名 |
|
|
170
|
+
| cookieMaxAge | number | `2592000000` (30天) | Cookie 的过期时间(毫秒) |
|
|
171
|
+
| cookiePath | string | `'/'` | Cookie 的路径 |
|
|
172
|
+
|
|
173
|
+
#### 静态方法
|
|
174
|
+
|
|
175
|
+
- `configure(opts: CsrfTokenOptions)`: 配置中间件选项
|
|
176
|
+
|
|
177
|
+
### CsrfMiddleware
|
|
178
|
+
|
|
179
|
+
验证请求中的 CSRF Token。
|
|
180
|
+
|
|
181
|
+
#### 配置选项 (CsrfOptions)
|
|
182
|
+
|
|
183
|
+
| 选项 | 类型 | 默认值 | 描述 |
|
|
184
|
+
|------|------|--------|------|
|
|
185
|
+
| headerKey | string | `'x-suda-csrf-token'` | 请求头中 CSRF Token 的键名 |
|
|
186
|
+
| cookieKey | string | `'suda-csrf-token'` | Cookie 中 CSRF Token 的键名 |
|
|
187
|
+
|
|
188
|
+
#### 静态方法
|
|
189
|
+
|
|
190
|
+
- `configure(opts: CsrfOptions)`: 配置中间件选项
|
|
191
|
+
|
|
192
|
+
#### 验证规则
|
|
193
|
+
|
|
194
|
+
1. 检查 Cookie 中是否存在 CSRF Token
|
|
195
|
+
2. 检查请求头中是否存在 CSRF Token
|
|
196
|
+
3. 验证两者是否匹配
|
|
197
|
+
|
|
198
|
+
如果任何一步验证失败,将返回 403 Forbidden。
|
|
199
|
+
|
|
200
|
+
### UserContextMiddleware
|
|
201
|
+
|
|
202
|
+
从请求头中提取用户上下文信息。
|
|
203
|
+
|
|
204
|
+
#### 注入的上下文
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
req.userContext = {
|
|
208
|
+
userId: string | undefined; // 用户 ID
|
|
209
|
+
tenantId: string | undefined; // 租户 ID
|
|
210
|
+
appId: string; // 应用 ID
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### DevToolsModule
|
|
215
|
+
|
|
216
|
+
开发工具模块,用于生成 API 文档和客户端 SDK。
|
|
217
|
+
|
|
218
|
+
#### 配置选项 (DevToolsOptions)
|
|
219
|
+
|
|
220
|
+
| 选项 | 类型 | 默认值 | 描述 |
|
|
221
|
+
|------|------|--------|------|
|
|
222
|
+
| basePath | string | - | 基础路径 |
|
|
223
|
+
| docsPath | string | - | Swagger UI 的挂载路径 |
|
|
224
|
+
| openapiOut | string | - | OpenAPI JSON 文件的输出路径 |
|
|
225
|
+
| needSetupServer | boolean | - | 是否需要挂载 Swagger UI |
|
|
226
|
+
| needGenerateClientSdk | boolean | - | 是否生成客户端 SDK |
|
|
227
|
+
| clientSdkOut | string | - | 客户端 SDK 的输出目录 |
|
|
228
|
+
| swaggerOptions | object | - | Swagger 文档配置 |
|
|
229
|
+
| swaggerOptions.title | string | - | Swagger 文档标题 |
|
|
230
|
+
| swaggerOptions.version | string | - | API 版本号 |
|
|
231
|
+
| swaggerOptions.customSiteTitle | string | - | 自定义站点标题 |
|
|
232
|
+
| swaggerOptions.customCss | string | - | 自定义 CSS 样式 |
|
|
233
|
+
|
|
234
|
+
#### 静态方法
|
|
235
|
+
|
|
236
|
+
- `mount(app: INestApplication, opts?: DevToolsOptions)`: 挂载开发工具到应用
|
|
237
|
+
|
|
238
|
+
## TypeScript 支持
|
|
239
|
+
|
|
240
|
+
本包完全使用 TypeScript 编写,并提供完整的类型定义。
|
|
241
|
+
|
|
242
|
+
扩展 Express Request 类型:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
declare global {
|
|
246
|
+
namespace Express {
|
|
247
|
+
interface Request {
|
|
248
|
+
csrfToken?: string;
|
|
249
|
+
userContext?: {
|
|
250
|
+
userId?: string;
|
|
251
|
+
tenantId?: string;
|
|
252
|
+
appId: string;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 许可证
|
|
260
|
+
|
|
261
|
+
MIT
|
|
262
|
+
|
|
263
|
+
## 关键字
|
|
264
|
+
|
|
265
|
+
- plugin
|
|
266
|
+
- nestjs
|
|
267
|
+
- server
|
|
268
|
+
- typescript
|
package/dist/index.cjs
CHANGED
|
@@ -5,11 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
9
|
-
var __typeError = (msg) => {
|
|
10
|
-
throw TypeError(msg);
|
|
11
|
-
};
|
|
12
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
14
9
|
var __export = (target, all) => {
|
|
15
10
|
for (var name in all)
|
|
@@ -32,44 +27,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
27
|
mod
|
|
33
28
|
));
|
|
34
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
|
-
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
36
|
-
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
37
|
-
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
38
|
-
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
39
|
-
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
40
|
-
var __runInitializers = (array, flags, self, value) => {
|
|
41
|
-
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
42
|
-
return value;
|
|
43
|
-
};
|
|
44
|
-
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
45
|
-
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
46
|
-
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
47
|
-
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
48
|
-
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
49
|
-
return __privateGet(this, extra);
|
|
50
|
-
}, set [name](x) {
|
|
51
|
-
return __privateSet(this, extra, x);
|
|
52
|
-
} }, name));
|
|
53
|
-
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
54
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
55
|
-
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
56
|
-
if (k) {
|
|
57
|
-
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
58
|
-
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
59
|
-
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
60
|
-
}
|
|
61
|
-
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
62
|
-
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
63
|
-
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
64
|
-
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
65
|
-
}
|
|
66
|
-
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
67
|
-
};
|
|
68
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
69
|
-
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
70
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
71
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
72
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
73
30
|
|
|
74
31
|
// src/index.ts
|
|
75
32
|
var index_exports = {};
|
|
@@ -93,6 +50,7 @@ function normalizeBasePath(rawBasePath) {
|
|
|
93
50
|
const normalizedBasePath = rawBasePath.startsWith("/") ? rawBasePath : `/${rawBasePath}`;
|
|
94
51
|
return normalizedBasePath.endsWith("/") ? normalizedBasePath.slice(0, -1) : normalizedBasePath;
|
|
95
52
|
}
|
|
53
|
+
__name(normalizeBasePath, "normalizeBasePath");
|
|
96
54
|
function resolveOptsWithDefaultValue(options) {
|
|
97
55
|
const basePath = normalizeBasePath(options.basePath || "/");
|
|
98
56
|
const docsPath = normalizeBasePath(options.docsPath || `api/docs`);
|
|
@@ -112,26 +70,35 @@ function resolveOptsWithDefaultValue(options) {
|
|
|
112
70
|
}
|
|
113
71
|
};
|
|
114
72
|
}
|
|
73
|
+
__name(resolveOptsWithDefaultValue, "resolveOptsWithDefaultValue");
|
|
115
74
|
function ensureDirAndWrite(filePath, content) {
|
|
116
75
|
const dir = (0, import_node_path.dirname)(filePath);
|
|
117
|
-
(0, import_node_fs.mkdirSync)(dir, {
|
|
76
|
+
(0, import_node_fs.mkdirSync)(dir, {
|
|
77
|
+
recursive: true
|
|
78
|
+
});
|
|
118
79
|
(0, import_node_fs.writeFileSync)(filePath, content);
|
|
119
80
|
}
|
|
81
|
+
__name(ensureDirAndWrite, "ensureDirAndWrite");
|
|
120
82
|
|
|
121
83
|
// src/modules/devtool/index.ts
|
|
122
84
|
var DevToolsModule = class {
|
|
85
|
+
static {
|
|
86
|
+
__name(this, "DevToolsModule");
|
|
87
|
+
}
|
|
123
88
|
static async mount(app, opts = {}) {
|
|
124
89
|
const options = resolveOptsWithDefaultValue(opts);
|
|
125
90
|
const baseDirname = process.cwd();
|
|
126
91
|
const builder = new import_swagger.DocumentBuilder().setTitle(options.swaggerOptions.title).setVersion(options.swaggerOptions.version);
|
|
127
92
|
const document = import_swagger.SwaggerModule.createDocument(app, builder.build(), {
|
|
128
|
-
operationIdFactory: (_c, m) => m
|
|
93
|
+
operationIdFactory: /* @__PURE__ */ __name((_c, m) => m, "operationIdFactory")
|
|
129
94
|
});
|
|
130
95
|
if (options.needSetupServer) {
|
|
131
96
|
import_swagger.SwaggerModule.setup(options.docsPath, app, document, {
|
|
132
97
|
customSiteTitle: options.swaggerOptions.customSiteTitle,
|
|
133
98
|
customCss: options.swaggerOptions.customCss,
|
|
134
|
-
swaggerOptions: {
|
|
99
|
+
swaggerOptions: {
|
|
100
|
+
persistAuthorization: true
|
|
101
|
+
}
|
|
135
102
|
});
|
|
136
103
|
console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
|
|
137
104
|
}
|
|
@@ -139,7 +106,9 @@ var DevToolsModule = class {
|
|
|
139
106
|
ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
|
|
140
107
|
if (options.needGenerateClientSdk) {
|
|
141
108
|
const clientSdkOutPath = (0, import_node_path2.resolve)(baseDirname, options.clientSdkOut);
|
|
142
|
-
(0, import_node_fs2.mkdirSync)(clientSdkOutPath, {
|
|
109
|
+
(0, import_node_fs2.mkdirSync)(clientSdkOutPath, {
|
|
110
|
+
recursive: true
|
|
111
|
+
});
|
|
143
112
|
const { generate } = await import("openapi-typescript-codegen");
|
|
144
113
|
await generate({
|
|
145
114
|
input: openapiPath,
|
|
@@ -166,22 +135,30 @@ function resolveCsrfTokenOptions(options) {
|
|
|
166
135
|
cookiePath: options.cookiePath ?? "/"
|
|
167
136
|
};
|
|
168
137
|
}
|
|
138
|
+
__name(resolveCsrfTokenOptions, "resolveCsrfTokenOptions");
|
|
169
139
|
function genToken() {
|
|
170
140
|
const ts = Math.floor(Date.now() / 1e3);
|
|
171
|
-
const randInt64 = BigInt(
|
|
172
|
-
"0x" + import_crypto.default.randomBytes(8).toString("hex")
|
|
173
|
-
).toString();
|
|
141
|
+
const randInt64 = BigInt("0x" + import_crypto.default.randomBytes(8).toString("hex")).toString();
|
|
174
142
|
const s = `${randInt64}.${ts}`;
|
|
175
143
|
const sha1 = import_crypto.default.createHash("sha1");
|
|
176
144
|
sha1.update(s);
|
|
177
145
|
return `${sha1.digest("hex")}-${ts}`;
|
|
178
146
|
}
|
|
147
|
+
__name(genToken, "genToken");
|
|
179
148
|
|
|
180
149
|
// src/middlewares/csrf_token/index.ts
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
150
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
151
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
152
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
153
|
+
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;
|
|
154
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
155
|
+
}
|
|
156
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
157
|
+
var CsrfTokenMiddleware = class _CsrfTokenMiddleware {
|
|
158
|
+
static {
|
|
159
|
+
__name(this, "CsrfTokenMiddleware");
|
|
160
|
+
}
|
|
161
|
+
static options = resolveCsrfTokenOptions({});
|
|
185
162
|
static configure(opts) {
|
|
186
163
|
this.options = resolveCsrfTokenOptions(opts);
|
|
187
164
|
}
|
|
@@ -201,16 +178,14 @@ var _CsrfTokenMiddleware = class _CsrfTokenMiddleware {
|
|
|
201
178
|
secure: true,
|
|
202
179
|
sameSite: "none",
|
|
203
180
|
partitioned: true
|
|
204
|
-
// 默认开启 Partitioned Cookie
|
|
205
181
|
});
|
|
206
182
|
next();
|
|
207
183
|
}
|
|
208
184
|
}
|
|
209
185
|
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
var CsrfTokenMiddleware = _CsrfTokenMiddleware;
|
|
186
|
+
CsrfTokenMiddleware = _ts_decorate([
|
|
187
|
+
(0, import_common.Injectable)()
|
|
188
|
+
], CsrfTokenMiddleware);
|
|
214
189
|
|
|
215
190
|
// src/middlewares/csrf/index.ts
|
|
216
191
|
var import_common2 = require("@nestjs/common");
|
|
@@ -223,15 +198,25 @@ function resolveCsrfOptions(options) {
|
|
|
223
198
|
cookieKey: options.cookieKey ?? "suda-csrf-token"
|
|
224
199
|
};
|
|
225
200
|
}
|
|
201
|
+
__name(resolveCsrfOptions, "resolveCsrfOptions");
|
|
226
202
|
function sendForbidden(res, message) {
|
|
227
203
|
res.status(403).send(`Forbidden\uFF0C${message}`);
|
|
228
204
|
}
|
|
205
|
+
__name(sendForbidden, "sendForbidden");
|
|
229
206
|
|
|
230
207
|
// src/middlewares/csrf/index.ts
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
208
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
209
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
210
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
211
|
+
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;
|
|
212
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
213
|
+
}
|
|
214
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
215
|
+
var CsrfMiddleware = class _CsrfMiddleware {
|
|
216
|
+
static {
|
|
217
|
+
__name(this, "CsrfMiddleware");
|
|
218
|
+
}
|
|
219
|
+
static options = resolveCsrfOptions({});
|
|
235
220
|
static configure(opts) {
|
|
236
221
|
this.options = resolveCsrfOptions(opts);
|
|
237
222
|
}
|
|
@@ -254,10 +239,9 @@ var _CsrfMiddleware = class _CsrfMiddleware {
|
|
|
254
239
|
next();
|
|
255
240
|
}
|
|
256
241
|
};
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
var CsrfMiddleware = _CsrfMiddleware;
|
|
242
|
+
CsrfMiddleware = _ts_decorate2([
|
|
243
|
+
(0, import_common2.Injectable)()
|
|
244
|
+
], CsrfMiddleware);
|
|
261
245
|
|
|
262
246
|
// src/middlewares/user-context/index.ts
|
|
263
247
|
var import_common3 = require("@nestjs/common");
|
|
@@ -279,11 +263,20 @@ function getWebUserFromHeader(req) {
|
|
|
279
263
|
}
|
|
280
264
|
return null;
|
|
281
265
|
}
|
|
266
|
+
__name(getWebUserFromHeader, "getWebUserFromHeader");
|
|
282
267
|
|
|
283
268
|
// src/middlewares/user-context/index.ts
|
|
284
|
-
|
|
285
|
-
|
|
269
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
270
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
271
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
272
|
+
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;
|
|
273
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
274
|
+
}
|
|
275
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
286
276
|
var UserContextMiddleware = class {
|
|
277
|
+
static {
|
|
278
|
+
__name(this, "UserContextMiddleware");
|
|
279
|
+
}
|
|
287
280
|
use(req, _res, next) {
|
|
288
281
|
const webUser = getWebUserFromHeader(req);
|
|
289
282
|
req.userContext = {
|
|
@@ -294,9 +287,9 @@ var UserContextMiddleware = class {
|
|
|
294
287
|
next();
|
|
295
288
|
}
|
|
296
289
|
};
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
290
|
+
UserContextMiddleware = _ts_decorate3([
|
|
291
|
+
(0, import_common3.Injectable)()
|
|
292
|
+
], UserContextMiddleware);
|
|
300
293
|
// Annotate the CommonJS export names for ESM import in node:
|
|
301
294
|
0 && (module.exports = {
|
|
302
295
|
CsrfMiddleware,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import './types/express.d';\n\nexport { DevToolsModule } from './modules/devtool';\n\n// Middlewares\nexport { CsrfTokenMiddleware } from './middlewares/csrf_token';\nexport { CsrfMiddleware } from './middlewares/csrf';\nexport { UserContextMiddleware } from './middlewares/user-context';\n","import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n // 1.1) 挂载 Swagger UI(可关)\n if(options.needSetupServer){\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n console.log(`[OpenAPI] Swagger UI 已挂载至 ${options.docsPath}`);\n }\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n needSetupServer: options.needSetupServer ?? false,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions;\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 *60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions;\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","crypto","import_common","_init","import_common","_init"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import './types/express.d';\n\nexport { DevToolsModule } from './modules/devtool';\n\n// Middlewares\nexport { CsrfTokenMiddleware } from './middlewares/csrf_token';\nexport { CsrfMiddleware } from './middlewares/csrf';\nexport { UserContextMiddleware } from './middlewares/user-context';\n","import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n // 1.1) 挂载 Swagger UI(可关)\n if(options.needSetupServer){\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n console.log(`[OpenAPI] Swagger UI 已挂载至 ${options.docsPath}`);\n }\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n needSetupServer: options.needSetupServer ?? false,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions = resolveCsrfTokenOptions({});\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 *60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions = resolveCsrfOptions({});\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","normalizeBasePath","rawBasePath","normalizedBasePath","startsWith","endsWith","slice","resolveOptsWithDefaultValue","options","basePath","docsPath","needSetupServer","openapiOut","clientSdkOut","needGenerateClientSdk","swaggerOptions","title","version","customSiteTitle","customCss","ensureDirAndWrite","filePath","content","dir","dirname","mkdirSync","recursive","writeFileSync","DevToolsModule","mount","app","opts","options","resolveOptsWithDefaultValue","baseDirname","process","cwd","builder","DocumentBuilder","setTitle","swaggerOptions","title","setVersion","version","document","SwaggerModule","createDocument","build","operationIdFactory","_c","m","needSetupServer","setup","docsPath","customSiteTitle","customCss","persistAuthorization","console","log","openapiPath","resolve","openapiOut","ensureDirAndWrite","JSON","stringify","needGenerateClientSdk","clientSdkOutPath","clientSdkOut","mkdirSync","recursive","generate","input","output","httpClient","useOptions","exportServices","resolveCsrfTokenOptions","options","cookieKey","cookieMaxAge","cookiePath","genToken","ts","Math","floor","Date","now","randInt64","BigInt","crypto","randomBytes","toString","s","sha1","createHash","update","digest","CsrfTokenMiddleware","options","resolveCsrfTokenOptions","configure","opts","use","req","res","next","cookieKey","cookieMaxAge","cookiePath","originToken","cookies","toLowerCase","csrfToken","token","genToken","cookie","maxAge","path","httpOnly","secure","sameSite","partitioned","import_common","resolveCsrfOptions","options","headerKey","cookieKey","sendForbidden","res","message","status","send","CsrfMiddleware","options","resolveCsrfOptions","configure","opts","use","req","res","next","headerKey","cookieKey","cookieCsrfToken","cookies","toLowerCase","sendForbidden","headerCsrfToken","headers","import_common","sudaWebUserHeaderKey","getWebUserFromHeader","req","sudaWebUserContent","headers","sudaWebUserJsonStr","decodeURIComponent","sudaWebUserJson","JSON","parse","err","console","error","UserContextMiddleware","use","req","_res","next","webUser","getWebUserFromHeader","userContext","userId","user_id","tenantId","tenant_id","appId","app_id"]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,50 +1,5 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
1
|
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
5
|
-
var __typeError = (msg) => {
|
|
6
|
-
throw TypeError(msg);
|
|
7
|
-
};
|
|
8
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
-
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
11
|
-
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
12
|
-
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
13
|
-
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
14
|
-
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
15
|
-
var __runInitializers = (array, flags, self, value) => {
|
|
16
|
-
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
17
|
-
return value;
|
|
18
|
-
};
|
|
19
|
-
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
20
|
-
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
21
|
-
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
22
|
-
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
23
|
-
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
24
|
-
return __privateGet(this, extra);
|
|
25
|
-
}, set [name](x) {
|
|
26
|
-
return __privateSet(this, extra, x);
|
|
27
|
-
} }, name));
|
|
28
|
-
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
29
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
30
|
-
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
31
|
-
if (k) {
|
|
32
|
-
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
33
|
-
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
34
|
-
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
35
|
-
}
|
|
36
|
-
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
37
|
-
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
38
|
-
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
39
|
-
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
40
|
-
}
|
|
41
|
-
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
42
|
-
};
|
|
43
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
44
|
-
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
45
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
46
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
47
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
48
3
|
|
|
49
4
|
// src/modules/devtool/index.ts
|
|
50
5
|
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
|
|
@@ -58,6 +13,7 @@ function normalizeBasePath(rawBasePath) {
|
|
|
58
13
|
const normalizedBasePath = rawBasePath.startsWith("/") ? rawBasePath : `/${rawBasePath}`;
|
|
59
14
|
return normalizedBasePath.endsWith("/") ? normalizedBasePath.slice(0, -1) : normalizedBasePath;
|
|
60
15
|
}
|
|
16
|
+
__name(normalizeBasePath, "normalizeBasePath");
|
|
61
17
|
function resolveOptsWithDefaultValue(options) {
|
|
62
18
|
const basePath = normalizeBasePath(options.basePath || "/");
|
|
63
19
|
const docsPath = normalizeBasePath(options.docsPath || `api/docs`);
|
|
@@ -77,26 +33,35 @@ function resolveOptsWithDefaultValue(options) {
|
|
|
77
33
|
}
|
|
78
34
|
};
|
|
79
35
|
}
|
|
36
|
+
__name(resolveOptsWithDefaultValue, "resolveOptsWithDefaultValue");
|
|
80
37
|
function ensureDirAndWrite(filePath, content) {
|
|
81
38
|
const dir = dirname(filePath);
|
|
82
|
-
mkdirSync(dir, {
|
|
39
|
+
mkdirSync(dir, {
|
|
40
|
+
recursive: true
|
|
41
|
+
});
|
|
83
42
|
writeFileSync(filePath, content);
|
|
84
43
|
}
|
|
44
|
+
__name(ensureDirAndWrite, "ensureDirAndWrite");
|
|
85
45
|
|
|
86
46
|
// src/modules/devtool/index.ts
|
|
87
47
|
var DevToolsModule = class {
|
|
48
|
+
static {
|
|
49
|
+
__name(this, "DevToolsModule");
|
|
50
|
+
}
|
|
88
51
|
static async mount(app, opts = {}) {
|
|
89
52
|
const options = resolveOptsWithDefaultValue(opts);
|
|
90
53
|
const baseDirname = process.cwd();
|
|
91
54
|
const builder = new DocumentBuilder().setTitle(options.swaggerOptions.title).setVersion(options.swaggerOptions.version);
|
|
92
55
|
const document = SwaggerModule.createDocument(app, builder.build(), {
|
|
93
|
-
operationIdFactory: (_c, m) => m
|
|
56
|
+
operationIdFactory: /* @__PURE__ */ __name((_c, m) => m, "operationIdFactory")
|
|
94
57
|
});
|
|
95
58
|
if (options.needSetupServer) {
|
|
96
59
|
SwaggerModule.setup(options.docsPath, app, document, {
|
|
97
60
|
customSiteTitle: options.swaggerOptions.customSiteTitle,
|
|
98
61
|
customCss: options.swaggerOptions.customCss,
|
|
99
|
-
swaggerOptions: {
|
|
62
|
+
swaggerOptions: {
|
|
63
|
+
persistAuthorization: true
|
|
64
|
+
}
|
|
100
65
|
});
|
|
101
66
|
console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
|
|
102
67
|
}
|
|
@@ -104,7 +69,9 @@ var DevToolsModule = class {
|
|
|
104
69
|
ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
|
|
105
70
|
if (options.needGenerateClientSdk) {
|
|
106
71
|
const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);
|
|
107
|
-
mkdirSync2(clientSdkOutPath, {
|
|
72
|
+
mkdirSync2(clientSdkOutPath, {
|
|
73
|
+
recursive: true
|
|
74
|
+
});
|
|
108
75
|
const { generate } = await import("openapi-typescript-codegen");
|
|
109
76
|
await generate({
|
|
110
77
|
input: openapiPath,
|
|
@@ -131,22 +98,30 @@ function resolveCsrfTokenOptions(options) {
|
|
|
131
98
|
cookiePath: options.cookiePath ?? "/"
|
|
132
99
|
};
|
|
133
100
|
}
|
|
101
|
+
__name(resolveCsrfTokenOptions, "resolveCsrfTokenOptions");
|
|
134
102
|
function genToken() {
|
|
135
103
|
const ts = Math.floor(Date.now() / 1e3);
|
|
136
|
-
const randInt64 = BigInt(
|
|
137
|
-
"0x" + crypto.randomBytes(8).toString("hex")
|
|
138
|
-
).toString();
|
|
104
|
+
const randInt64 = BigInt("0x" + crypto.randomBytes(8).toString("hex")).toString();
|
|
139
105
|
const s = `${randInt64}.${ts}`;
|
|
140
106
|
const sha1 = crypto.createHash("sha1");
|
|
141
107
|
sha1.update(s);
|
|
142
108
|
return `${sha1.digest("hex")}-${ts}`;
|
|
143
109
|
}
|
|
110
|
+
__name(genToken, "genToken");
|
|
144
111
|
|
|
145
112
|
// src/middlewares/csrf_token/index.ts
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
113
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
114
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
115
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
116
|
+
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;
|
|
117
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
118
|
+
}
|
|
119
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
120
|
+
var CsrfTokenMiddleware = class _CsrfTokenMiddleware {
|
|
121
|
+
static {
|
|
122
|
+
__name(this, "CsrfTokenMiddleware");
|
|
123
|
+
}
|
|
124
|
+
static options = resolveCsrfTokenOptions({});
|
|
150
125
|
static configure(opts) {
|
|
151
126
|
this.options = resolveCsrfTokenOptions(opts);
|
|
152
127
|
}
|
|
@@ -166,16 +141,14 @@ var _CsrfTokenMiddleware = class _CsrfTokenMiddleware {
|
|
|
166
141
|
secure: true,
|
|
167
142
|
sameSite: "none",
|
|
168
143
|
partitioned: true
|
|
169
|
-
// 默认开启 Partitioned Cookie
|
|
170
144
|
});
|
|
171
145
|
next();
|
|
172
146
|
}
|
|
173
147
|
}
|
|
174
148
|
};
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
var CsrfTokenMiddleware = _CsrfTokenMiddleware;
|
|
149
|
+
CsrfTokenMiddleware = _ts_decorate([
|
|
150
|
+
Injectable()
|
|
151
|
+
], CsrfTokenMiddleware);
|
|
179
152
|
|
|
180
153
|
// src/middlewares/csrf/index.ts
|
|
181
154
|
import { Injectable as Injectable2 } from "@nestjs/common";
|
|
@@ -188,15 +161,25 @@ function resolveCsrfOptions(options) {
|
|
|
188
161
|
cookieKey: options.cookieKey ?? "suda-csrf-token"
|
|
189
162
|
};
|
|
190
163
|
}
|
|
164
|
+
__name(resolveCsrfOptions, "resolveCsrfOptions");
|
|
191
165
|
function sendForbidden(res, message) {
|
|
192
166
|
res.status(403).send(`Forbidden\uFF0C${message}`);
|
|
193
167
|
}
|
|
168
|
+
__name(sendForbidden, "sendForbidden");
|
|
194
169
|
|
|
195
170
|
// src/middlewares/csrf/index.ts
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
171
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
172
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
173
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
174
|
+
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;
|
|
175
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
176
|
+
}
|
|
177
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
178
|
+
var CsrfMiddleware = class _CsrfMiddleware {
|
|
179
|
+
static {
|
|
180
|
+
__name(this, "CsrfMiddleware");
|
|
181
|
+
}
|
|
182
|
+
static options = resolveCsrfOptions({});
|
|
200
183
|
static configure(opts) {
|
|
201
184
|
this.options = resolveCsrfOptions(opts);
|
|
202
185
|
}
|
|
@@ -219,10 +202,9 @@ var _CsrfMiddleware = class _CsrfMiddleware {
|
|
|
219
202
|
next();
|
|
220
203
|
}
|
|
221
204
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
var CsrfMiddleware = _CsrfMiddleware;
|
|
205
|
+
CsrfMiddleware = _ts_decorate2([
|
|
206
|
+
Injectable2()
|
|
207
|
+
], CsrfMiddleware);
|
|
226
208
|
|
|
227
209
|
// src/middlewares/user-context/index.ts
|
|
228
210
|
import { Injectable as Injectable3 } from "@nestjs/common";
|
|
@@ -244,11 +226,20 @@ function getWebUserFromHeader(req) {
|
|
|
244
226
|
}
|
|
245
227
|
return null;
|
|
246
228
|
}
|
|
229
|
+
__name(getWebUserFromHeader, "getWebUserFromHeader");
|
|
247
230
|
|
|
248
231
|
// src/middlewares/user-context/index.ts
|
|
249
|
-
|
|
250
|
-
|
|
232
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
233
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
234
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
235
|
+
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;
|
|
236
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
237
|
+
}
|
|
238
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
251
239
|
var UserContextMiddleware = class {
|
|
240
|
+
static {
|
|
241
|
+
__name(this, "UserContextMiddleware");
|
|
242
|
+
}
|
|
252
243
|
use(req, _res, next) {
|
|
253
244
|
const webUser = getWebUserFromHeader(req);
|
|
254
245
|
req.userContext = {
|
|
@@ -259,9 +250,9 @@ var UserContextMiddleware = class {
|
|
|
259
250
|
next();
|
|
260
251
|
}
|
|
261
252
|
};
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
253
|
+
UserContextMiddleware = _ts_decorate3([
|
|
254
|
+
Injectable3()
|
|
255
|
+
], UserContextMiddleware);
|
|
265
256
|
export {
|
|
266
257
|
CsrfMiddleware,
|
|
267
258
|
CsrfTokenMiddleware,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n // 1.1) 挂载 Swagger UI(可关)\n if(options.needSetupServer){\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n console.log(`[OpenAPI] Swagger UI 已挂载至 ${options.docsPath}`);\n }\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n needSetupServer: options.needSetupServer ?? false,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions;\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 *60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions;\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["mkdirSync","mkdirSync","Injectable","_init","Injectable","Injectable","_init","Injectable"]}
|
|
1
|
+
{"version":3,"sources":["../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n // 1.1) 挂载 Swagger UI(可关)\n if(options.needSetupServer){\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n console.log(`[OpenAPI] Swagger UI 已挂载至 ${options.docsPath}`);\n }\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n needSetupServer: options.needSetupServer ?? false,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions = resolveCsrfTokenOptions({});\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 1000 *60 * 60 * 24 * 30,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions = resolveCsrfOptions({});\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["SwaggerModule","DocumentBuilder","mkdirSync","resolve","dirname","writeFileSync","mkdirSync","normalizeBasePath","rawBasePath","normalizedBasePath","startsWith","endsWith","slice","resolveOptsWithDefaultValue","options","basePath","docsPath","needSetupServer","openapiOut","clientSdkOut","needGenerateClientSdk","swaggerOptions","title","version","customSiteTitle","customCss","ensureDirAndWrite","filePath","content","dir","dirname","mkdirSync","recursive","writeFileSync","DevToolsModule","mount","app","opts","options","resolveOptsWithDefaultValue","baseDirname","process","cwd","builder","DocumentBuilder","setTitle","swaggerOptions","title","setVersion","version","document","SwaggerModule","createDocument","build","operationIdFactory","_c","m","needSetupServer","setup","docsPath","customSiteTitle","customCss","persistAuthorization","console","log","openapiPath","resolve","openapiOut","ensureDirAndWrite","JSON","stringify","needGenerateClientSdk","clientSdkOutPath","clientSdkOut","mkdirSync","recursive","generate","input","output","httpClient","useOptions","exportServices","Injectable","crypto","resolveCsrfTokenOptions","options","cookieKey","cookieMaxAge","cookiePath","genToken","ts","Math","floor","Date","now","randInt64","BigInt","crypto","randomBytes","toString","s","sha1","createHash","update","digest","CsrfTokenMiddleware","options","resolveCsrfTokenOptions","configure","opts","use","req","res","next","cookieKey","cookieMaxAge","cookiePath","originToken","cookies","toLowerCase","csrfToken","token","genToken","cookie","maxAge","path","httpOnly","secure","sameSite","partitioned","Injectable","resolveCsrfOptions","options","headerKey","cookieKey","sendForbidden","res","message","status","send","CsrfMiddleware","options","resolveCsrfOptions","configure","opts","use","req","res","next","headerKey","cookieKey","cookieCsrfToken","cookies","toLowerCase","sendForbidden","headerCsrfToken","headers","Injectable","sudaWebUserHeaderKey","getWebUserFromHeader","req","sudaWebUserContent","headers","sudaWebUserJsonStr","decodeURIComponent","sudaWebUserJson","JSON","parse","err","console","error","UserContextMiddleware","use","req","_res","next","webUser","getWebUserFromHeader","userContext","userId","user_id","tenantId","tenant_id","appId","app_id"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/fullstack-nestjs-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "FullStack Nestjs Core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,6 +28,15 @@
|
|
|
28
28
|
"require": "./dist/index.cjs"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"lint": "echo 'ESLint skipped for TypeScript files'",
|
|
38
|
+
"lint:fix": "eslint src --ext .ts --fix"
|
|
39
|
+
},
|
|
31
40
|
"dependencies": {
|
|
32
41
|
"openapi-typescript-codegen": "^0.29.0"
|
|
33
42
|
},
|
|
@@ -38,20 +47,11 @@
|
|
|
38
47
|
"@types/cookie-parser": "^1.4.9",
|
|
39
48
|
"@types/express": "^5.0.3",
|
|
40
49
|
"tsup": "^8.0.0",
|
|
41
|
-
"typescript": "^5.
|
|
50
|
+
"typescript": "^5.0.0"
|
|
42
51
|
},
|
|
43
52
|
"peerDependencies": {
|
|
44
53
|
"@nestjs/common": "^10.4.20",
|
|
45
54
|
"@nestjs/platform-express": "^10.4.20",
|
|
46
55
|
"@nestjs/swagger": "^7.4.2"
|
|
47
|
-
},
|
|
48
|
-
"scripts": {
|
|
49
|
-
"build": "tsup",
|
|
50
|
-
"dev": "tsup --watch",
|
|
51
|
-
"typecheck": "tsc --noEmit",
|
|
52
|
-
"test": "vitest run",
|
|
53
|
-
"test:watch": "vitest",
|
|
54
|
-
"lint": "echo 'ESLint skipped for TypeScript files'",
|
|
55
|
-
"lint:fix": "eslint src --ext .ts --fix"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|