@lark-apaas/fullstack-nestjs-core 1.0.0 → 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 +2 -2
- 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 +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
@@ -158,7 +158,7 @@ var CsrfTokenMiddleware = class _CsrfTokenMiddleware {
|
|
|
158
158
|
static {
|
|
159
159
|
__name(this, "CsrfTokenMiddleware");
|
|
160
160
|
}
|
|
161
|
-
static options;
|
|
161
|
+
static options = resolveCsrfTokenOptions({});
|
|
162
162
|
static configure(opts) {
|
|
163
163
|
this.options = resolveCsrfTokenOptions(opts);
|
|
164
164
|
}
|
|
@@ -216,7 +216,7 @@ var CsrfMiddleware = class _CsrfMiddleware {
|
|
|
216
216
|
static {
|
|
217
217
|
__name(this, "CsrfMiddleware");
|
|
218
218
|
}
|
|
219
|
-
static options;
|
|
219
|
+
static options = resolveCsrfOptions({});
|
|
220
220
|
static configure(opts) {
|
|
221
221
|
this.options = resolveCsrfOptions(opts);
|
|
222
222
|
}
|
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","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","configure","opts","resolveCsrfTokenOptions","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","configure","opts","resolveCsrfOptions","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"]}
|
|
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
|
@@ -121,7 +121,7 @@ var CsrfTokenMiddleware = class _CsrfTokenMiddleware {
|
|
|
121
121
|
static {
|
|
122
122
|
__name(this, "CsrfTokenMiddleware");
|
|
123
123
|
}
|
|
124
|
-
static options;
|
|
124
|
+
static options = resolveCsrfTokenOptions({});
|
|
125
125
|
static configure(opts) {
|
|
126
126
|
this.options = resolveCsrfTokenOptions(opts);
|
|
127
127
|
}
|
|
@@ -179,7 +179,7 @@ var CsrfMiddleware = class _CsrfMiddleware {
|
|
|
179
179
|
static {
|
|
180
180
|
__name(this, "CsrfMiddleware");
|
|
181
181
|
}
|
|
182
|
-
static options;
|
|
182
|
+
static options = resolveCsrfOptions({});
|
|
183
183
|
static configure(opts) {
|
|
184
184
|
this.options = resolveCsrfOptions(opts);
|
|
185
185
|
}
|
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":";;;;AAEA,SAASA,eAAeC,uBAAuB;AAC/C,SAASC,aAAAA,kBAAiB;AAC1B,SAASC,eAAe;;;ACJxB,SAASC,eAAe;AACxB,SAASC,eAAeC,iBAAiB;AASlC,SAASC,kBAAkBC,aAAmB;AACnD,QAAMC,qBAAqBD,YAAYE,WAAW,GAAA,IAC9CF,cACA,IAAIA,WAAAA;AACR,SAAOC,mBAAmBE,SAAS,GAAA,IAC/BF,mBAAmBG,MAAM,GAAG,EAAC,IAC7BH;AACN;AAPgBF;AAeT,SAASM,4BACdC,SAAwB;AAExB,QAAMC,WAAWR,kBAAkBO,QAAQC,YAAY,GAAA;AACvD,QAAMC,WAAWT,kBAAkBO,QAAQE,YAAY,UAAU;AACjE,SAAO;IACL,GAAGF;IACHG,iBAAiBH,QAAQG,mBAAmB;IAC5CF;IACAC,UAAU,GAAGD,QAAAA,GAAWC,QAAAA;IACxBE,YAAYJ,QAAQI,cAAc;IAClCC,cAAcL,QAAQK,gBAAgB;IACtCC,uBAAuBN,QAAQM,yBAAyB;IACxDC,gBAAgB;MACdC,OAAOR,QAAQO,gBAAgBC,SAAS;MACxCC,SAAST,QAAQO,gBAAgBE,WAAW;MAC5CC,iBACEV,QAAQO,gBAAgBG,mBAAmB;MAC7CC,WACEX,QAAQO,gBAAgBI,aACxB;IACJ;EACF;AACF;AAvBgBZ;AAyBT,SAASa,kBAAkBC,UAAkBC,SAAe;AAEjE,QAAMC,MAAMC,QAAQH,QAAAA;AAGpBI,YAAUF,KAAK;IAAEG,WAAW;EAAK,CAAA;AAGjCC,gBAAcN,UAAUC,OAAAA;AAC1B;AATgBF;;;ADzCT,IAAMQ,iBAAN,MAAMA;EAPb,OAOaA;;;EACX,aAAaC,MAAMC,KAAuBC,OAAwB,CAAC,GAAG;AACpE,UAAMC,UAAUC,4BAA4BF,IAAAA;AAC5C,UAAMG,cAAcC,QAAQC,IAAG;AAG/B,UAAMC,UAAU,IAAIC,gBAAAA,EACjBC,SAASP,QAAQQ,eAAeC,KAAK,EACrCC,WAAWV,QAAQQ,eAAeG,OAAO;AAC5C,UAAMC,WAAWC,cAAcC,eAAehB,KAAKO,QAAQU,MAAK,GAAI;MAClEC,oBAAoB,wBAACC,IAAIC,MAAMA,GAAX;IACtB,CAAA;AAEA,QAAGlB,QAAQmB,iBAAgB;AACzBN,oBAAcO,MAAMpB,QAAQqB,UAAUvB,KAAKc,UAAU;QACnDU,iBAAiBtB,QAAQQ,eAAec;QACxCC,WAAWvB,QAAQQ,eAAee;QAClCf,gBAAgB;UAAEgB,sBAAsB;QAAK;MAC/C,CAAA;AACAC,cAAQC,IAAI,iDAA6B1B,QAAQqB,QAAQ,EAAE;IAC7D;AAGA,UAAMM,cAAcC,QAAQ1B,aAAaF,QAAQ6B,UAAU;AAC3DC,sBAAkBH,aAAaI,KAAKC,UAAUpB,UAAU,MAAM,CAAA,CAAA;AAG9D,QAAIZ,QAAQiC,uBAAuB;AACjC,YAAMC,mBAAmBN,QAAQ1B,aAAaF,QAAQmC,YAAY;AAClEC,MAAAA,WAAUF,kBAAkB;QAAEG,WAAW;MAAK,CAAA;AAC9C,YAAM,EAAEC,SAAQ,IAAK,MAAM,OAAO,4BAAA;AAClC,YAAMA,SAAS;QACbC,OAAOZ;QACPa,QAAQN;QACRO,YAAY;QACZC,YAAY;QACZC,gBAAgB;MAClB,CAAA;AACAlB,cAAQC,IAAI,yEAAA;IACd;EACF;AACF;;;AElDA,SAASkB,kBAAkC;;;ACA3C,OAAOC,YAAY;AAGZ,SAASC,wBACdC,SAAyB;AAEzB,SAAO;IACL,GAAGA;IACHC,WAAWD,QAAQC,aAAa;IAChCC,cAAcF,QAAQE,gBAAgB,MAAM,KAAK,KAAK,KAAK;IAC3DC,YAAYH,QAAQG,cAAc;EACpC;AACF;AATgBJ;AAYT,SAASK,WAAAA;AAEd,QAAMC,KAAKC,KAAKC,MAAMC,KAAKC,IAAG,IAAK,GAAA;AAGnC,QAAMC,YAAYC,OAChB,OAAOC,OAAOC,YAAY,CAAA,EAAGC,SAAS,KAAA,CAAA,EACtCA,SAAQ;AAGV,QAAMC,IAAI,GAAGL,SAAAA,IAAaL,EAAAA;AAG1B,QAAMW,OAAOJ,OAAOK,WAAW,MAAA;AAC/BD,OAAKE,OAAOH,CAAAA;AAGZ,SAAO,GAAGC,KAAKG,OAAO,KAAA,CAAA,IAAUd,EAAAA;AAClC;AAlBgBD;;;;;;;;;;ADRT,IAAMgB,sBAAN,MAAMA,qBAAAA;SAAAA;;;EACX,OAAeC;EAEf,OAAcC,UAAUC,MAAwB;AAC9C,SAAKF,UAAUG,wBAAwBD,IAAAA;EACzC;EAEOE,IAAIC,KAAcC,KAAeC,MAAoB;AAC1D,UAAM,EAAEC,WAAWC,cAAcC,WAAU,IAAKX,qBAAoBC;AACpE,UAAMW,cAAcN,IAAIO,QAAQJ,UAAUK,YAAW,CAAA;AAErD,QAAIF,aAAa;AACfN,UAAIS,YAAYH;AAChBJ,WAAAA;IACF,OAAO;AAEL,YAAMQ,QAAQC,SAAAA;AACdX,UAAIS,YAAYC;AAChBT,UAAIW,OAAOT,WAAWO,OAAO;QAC3BG,QAAQT;QACRU,MAAMT;QACNU,UAAU;QACVC,QAAQ;QACRC,UAAU;QACVC,aAAa;MACf,CAAA;AACAhB,WAAAA;IACF;EACF;AACF;;;;;;AEpCA,SAASiB,cAAAA,mBAAkC;;;ACIpC,SAASC,mBAAmBC,SAAoB;AACrD,SAAO;IACL,GAAGA;IACHC,WAAWD,QAAQC,aAAa;IAChCC,WAAWF,QAAQE,aAAa;EAClC;AACF;AANgBH;AAQT,SAASI,cAAcC,KAAeC,SAAe;AAC1DD,MAAIE,OAAO,GAAA,EAAKC,KAAK,kBAAaF,OAAAA,EAAS;AAC7C;AAFgBF;;;;;;;;;;ADLT,IAAMK,iBAAN,MAAMA,gBAAAA;SAAAA;;;EACX,OAAeC;EAEf,OAAcC,UAAUC,MAAmB;AACzC,SAAKF,UAAUG,mBAAmBD,IAAAA;EACpC;EAEOE,IAAIC,KAAcC,KAAeC,MAAoB;AAC1D,UAAM,EAAEC,WAAWC,UAAS,IAAKV,gBAAeC;AAChD,UAAMU,kBAAkBL,IAAIM,QAAQF,UAAUG,YAAW,CAAA;AACzD,QAAI,CAACF,iBAAiB;AACpBG,oBAAcP,KAAK,iCAAA;AACnB;IACF;AACA,UAAMQ,kBAAkBT,IAAIU,QAAQP,UAAUI,YAAW,CAAA;AACzD,QAAI,CAACE,iBAAiB;AACpBD,oBAAcP,KAAK,iCAAA;AACnB;IACF;AACA,QAAII,oBAAoBI,iBAAiB;AACvCD,oBAAcP,KAAK,uBAAA;AACnB;IACF;AACAC,SAAAA;EACF;AACF;;;;;;AEhCA,SAASS,cAAAA,mBAAkC;;;ACE3C,IAAMC,uBAAuB;AAQtB,SAASC,qBAAqBC,KAAY;AAC/C,QAAMC,qBAAqBD,IAAIE,QAAQJ,oBAAAA;AAGvC,MAAI,CAACG,oBAAoB;AACvB,WAAO;EACT;AACA,MAAI;AACF,UAAME,qBAAqBC,mBAAmBH,kBAAAA;AAC9C,UAAMI,kBAAkBC,KAAKC,MAAMJ,kBAAAA;AACnC,WAAOE;EACT,SAASG,KAAK;AACZC,YAAQC,MAAM,iDAAiDF,GAAAA;AAC/D,WAAO;EACT;AACA,SAAO;AACT;AAhBgBT;;;;;;;;;;ADLT,IAAMY,wBAAN,MAAMA;SAAAA;;;EAEJC,IAAIC,KAAcC,MAAgBC,MAAoB;AAC3D,UAAMC,UAAUC,qBAAqBJ,GAAAA;AACrCA,QAAIK,cAAc;MAChBC,QAAQH,SAASI;MACjBC,UAAUL,SAASM;MACnBC,OAAOP,SAASQ,UAAU;IAC5B;AACAT,SAAAA;EACF;AACF;;;;","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","configure","opts","resolveCsrfTokenOptions","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","configure","opts","resolveCsrfOptions","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"]}
|
|
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":";;;;AAEA,SAASA,eAAeC,uBAAuB;AAC/C,SAASC,aAAAA,kBAAiB;AAC1B,SAASC,eAAe;;;ACJxB,SAASC,eAAe;AACxB,SAASC,eAAeC,iBAAiB;AASlC,SAASC,kBAAkBC,aAAmB;AACnD,QAAMC,qBAAqBD,YAAYE,WAAW,GAAA,IAC9CF,cACA,IAAIA,WAAAA;AACR,SAAOC,mBAAmBE,SAAS,GAAA,IAC/BF,mBAAmBG,MAAM,GAAG,EAAC,IAC7BH;AACN;AAPgBF;AAeT,SAASM,4BACdC,SAAwB;AAExB,QAAMC,WAAWR,kBAAkBO,QAAQC,YAAY,GAAA;AACvD,QAAMC,WAAWT,kBAAkBO,QAAQE,YAAY,UAAU;AACjE,SAAO;IACL,GAAGF;IACHG,iBAAiBH,QAAQG,mBAAmB;IAC5CF;IACAC,UAAU,GAAGD,QAAAA,GAAWC,QAAAA;IACxBE,YAAYJ,QAAQI,cAAc;IAClCC,cAAcL,QAAQK,gBAAgB;IACtCC,uBAAuBN,QAAQM,yBAAyB;IACxDC,gBAAgB;MACdC,OAAOR,QAAQO,gBAAgBC,SAAS;MACxCC,SAAST,QAAQO,gBAAgBE,WAAW;MAC5CC,iBACEV,QAAQO,gBAAgBG,mBAAmB;MAC7CC,WACEX,QAAQO,gBAAgBI,aACxB;IACJ;EACF;AACF;AAvBgBZ;AAyBT,SAASa,kBAAkBC,UAAkBC,SAAe;AAEjE,QAAMC,MAAMC,QAAQH,QAAAA;AAGpBI,YAAUF,KAAK;IAAEG,WAAW;EAAK,CAAA;AAGjCC,gBAAcN,UAAUC,OAAAA;AAC1B;AATgBF;;;ADzCT,IAAMQ,iBAAN,MAAMA;EAPb,OAOaA;;;EACX,aAAaC,MAAMC,KAAuBC,OAAwB,CAAC,GAAG;AACpE,UAAMC,UAAUC,4BAA4BF,IAAAA;AAC5C,UAAMG,cAAcC,QAAQC,IAAG;AAG/B,UAAMC,UAAU,IAAIC,gBAAAA,EACjBC,SAASP,QAAQQ,eAAeC,KAAK,EACrCC,WAAWV,QAAQQ,eAAeG,OAAO;AAC5C,UAAMC,WAAWC,cAAcC,eAAehB,KAAKO,QAAQU,MAAK,GAAI;MAClEC,oBAAoB,wBAACC,IAAIC,MAAMA,GAAX;IACtB,CAAA;AAEA,QAAGlB,QAAQmB,iBAAgB;AACzBN,oBAAcO,MAAMpB,QAAQqB,UAAUvB,KAAKc,UAAU;QACnDU,iBAAiBtB,QAAQQ,eAAec;QACxCC,WAAWvB,QAAQQ,eAAee;QAClCf,gBAAgB;UAAEgB,sBAAsB;QAAK;MAC/C,CAAA;AACAC,cAAQC,IAAI,iDAA6B1B,QAAQqB,QAAQ,EAAE;IAC7D;AAGA,UAAMM,cAAcC,QAAQ1B,aAAaF,QAAQ6B,UAAU;AAC3DC,sBAAkBH,aAAaI,KAAKC,UAAUpB,UAAU,MAAM,CAAA,CAAA;AAG9D,QAAIZ,QAAQiC,uBAAuB;AACjC,YAAMC,mBAAmBN,QAAQ1B,aAAaF,QAAQmC,YAAY;AAClEC,MAAAA,WAAUF,kBAAkB;QAAEG,WAAW;MAAK,CAAA;AAC9C,YAAM,EAAEC,SAAQ,IAAK,MAAM,OAAO,4BAAA;AAClC,YAAMA,SAAS;QACbC,OAAOZ;QACPa,QAAQN;QACRO,YAAY;QACZC,YAAY;QACZC,gBAAgB;MAClB,CAAA;AACAlB,cAAQC,IAAI,yEAAA;IACd;EACF;AACF;;;AElDA,SAASkB,kBAAkC;;;ACA3C,OAAOC,YAAY;AAGZ,SAASC,wBACdC,SAAyB;AAEzB,SAAO;IACL,GAAGA;IACHC,WAAWD,QAAQC,aAAa;IAChCC,cAAcF,QAAQE,gBAAgB,MAAM,KAAK,KAAK,KAAK;IAC3DC,YAAYH,QAAQG,cAAc;EACpC;AACF;AATgBJ;AAYT,SAASK,WAAAA;AAEd,QAAMC,KAAKC,KAAKC,MAAMC,KAAKC,IAAG,IAAK,GAAA;AAGnC,QAAMC,YAAYC,OAChB,OAAOC,OAAOC,YAAY,CAAA,EAAGC,SAAS,KAAA,CAAA,EACtCA,SAAQ;AAGV,QAAMC,IAAI,GAAGL,SAAAA,IAAaL,EAAAA;AAG1B,QAAMW,OAAOJ,OAAOK,WAAW,MAAA;AAC/BD,OAAKE,OAAOH,CAAAA;AAGZ,SAAO,GAAGC,KAAKG,OAAO,KAAA,CAAA,IAAUd,EAAAA;AAClC;AAlBgBD;;;;;;;;;;ADRT,IAAMgB,sBAAN,MAAMA,qBAAAA;SAAAA;;;EACX,OAAeC,UAAoCC,wBAAwB,CAAC,CAAA;EAE5E,OAAcC,UAAUC,MAAwB;AAC9C,SAAKH,UAAUC,wBAAwBE,IAAAA;EACzC;EAEOC,IAAIC,KAAcC,KAAeC,MAAoB;AAC1D,UAAM,EAAEC,WAAWC,cAAcC,WAAU,IAAKX,qBAAoBC;AACpE,UAAMW,cAAcN,IAAIO,QAAQJ,UAAUK,YAAW,CAAA;AAErD,QAAIF,aAAa;AACfN,UAAIS,YAAYH;AAChBJ,WAAAA;IACF,OAAO;AAEL,YAAMQ,QAAQC,SAAAA;AACdX,UAAIS,YAAYC;AAChBT,UAAIW,OAAOT,WAAWO,OAAO;QAC3BG,QAAQT;QACRU,MAAMT;QACNU,UAAU;QACVC,QAAQ;QACRC,UAAU;QACVC,aAAa;MACf,CAAA;AACAhB,WAAAA;IACF;EACF;AACF;;;;;;AEpCA,SAASiB,cAAAA,mBAAkC;;;ACIpC,SAASC,mBAAmBC,SAAoB;AACrD,SAAO;IACL,GAAGA;IACHC,WAAWD,QAAQC,aAAa;IAChCC,WAAWF,QAAQE,aAAa;EAClC;AACF;AANgBH;AAQT,SAASI,cAAcC,KAAeC,SAAe;AAC1DD,MAAIE,OAAO,GAAA,EAAKC,KAAK,kBAAaF,OAAAA,EAAS;AAC7C;AAFgBF;;;;;;;;;;ADLT,IAAMK,iBAAN,MAAMA,gBAAAA;SAAAA;;;EACX,OAAeC,UAA+BC,mBAAmB,CAAC,CAAA;EAElE,OAAcC,UAAUC,MAAmB;AACzC,SAAKH,UAAUC,mBAAmBE,IAAAA;EACpC;EAEOC,IAAIC,KAAcC,KAAeC,MAAoB;AAC1D,UAAM,EAAEC,WAAWC,UAAS,IAAKV,gBAAeC;AAChD,UAAMU,kBAAkBL,IAAIM,QAAQF,UAAUG,YAAW,CAAA;AACzD,QAAI,CAACF,iBAAiB;AACpBG,oBAAcP,KAAK,iCAAA;AACnB;IACF;AACA,UAAMQ,kBAAkBT,IAAIU,QAAQP,UAAUI,YAAW,CAAA;AACzD,QAAI,CAACE,iBAAiB;AACpBD,oBAAcP,KAAK,iCAAA;AACnB;IACF;AACA,QAAII,oBAAoBI,iBAAiB;AACvCD,oBAAcP,KAAK,uBAAA;AACnB;IACF;AACAC,SAAAA;EACF;AACF;;;;;;AEhCA,SAASS,cAAAA,mBAAkC;;;ACE3C,IAAMC,uBAAuB;AAQtB,SAASC,qBAAqBC,KAAY;AAC/C,QAAMC,qBAAqBD,IAAIE,QAAQJ,oBAAAA;AAGvC,MAAI,CAACG,oBAAoB;AACvB,WAAO;EACT;AACA,MAAI;AACF,UAAME,qBAAqBC,mBAAmBH,kBAAAA;AAC9C,UAAMI,kBAAkBC,KAAKC,MAAMJ,kBAAAA;AACnC,WAAOE;EACT,SAASG,KAAK;AACZC,YAAQC,MAAM,iDAAiDF,GAAAA;AAC/D,WAAO;EACT;AACA,SAAO;AACT;AAhBgBT;;;;;;;;;;ADLT,IAAMY,wBAAN,MAAMA;SAAAA;;;EAEJC,IAAIC,KAAcC,MAAgBC,MAAoB;AAC3D,UAAMC,UAAUC,qBAAqBJ,GAAAA;AACrCA,QAAIK,cAAc;MAChBC,QAAQH,SAASI;MACjBC,UAAUL,SAASM;MACnBC,OAAOP,SAASQ,UAAU;IAC5B;AACAT,SAAAA;EACF;AACF;;;;","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"]}
|