@lark-apaas/fullstack-nestjs-core 0.0.0-alpha.1 → 0.0.0-alpha.3

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/dist/index.cjs ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
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
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ 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
+
74
+ // src/index.ts
75
+ var index_exports = {};
76
+ __export(index_exports, {
77
+ CsrfMiddleware: () => CsrfMiddleware,
78
+ CsrfTokenMiddleware: () => CsrfTokenMiddleware,
79
+ DevToolsModule: () => DevToolsModule
80
+ });
81
+ module.exports = __toCommonJS(index_exports);
82
+
83
+ // src/modules/devtool/index.ts
84
+ var import_swagger = require("@nestjs/swagger");
85
+ var import_node_fs2 = require("fs");
86
+ var import_node_path2 = require("path");
87
+
88
+ // src/modules/devtool/helper.ts
89
+ var import_node_path = require("path");
90
+ var import_node_fs = require("fs");
91
+ function normalizeBasePath(rawBasePath) {
92
+ const normalizedBasePath = rawBasePath.startsWith("/") ? rawBasePath : `/${rawBasePath}`;
93
+ return normalizedBasePath.endsWith("/") ? normalizedBasePath.slice(0, -1) : normalizedBasePath;
94
+ }
95
+ function resolveOptsWithDefaultValue(options) {
96
+ const basePath = normalizeBasePath(options.basePath || "/");
97
+ const docsPath = normalizeBasePath(options.docsPath || `api/docs`);
98
+ return {
99
+ ...options,
100
+ basePath,
101
+ docsPath: `${basePath}${docsPath}`,
102
+ openapiOut: options.openapiOut || "./client/src/api/gen/openapi.json",
103
+ clientSdkOut: options.clientSdkOut || "./client/src/api/gen",
104
+ needGenerateClientSdk: options.needGenerateClientSdk ?? true,
105
+ swaggerOptions: {
106
+ title: options.swaggerOptions?.title ?? "NestJS Fullstack API",
107
+ version: options.swaggerOptions?.version ?? "1.0.0",
108
+ customSiteTitle: options.swaggerOptions?.customSiteTitle ?? "API Documentation",
109
+ customCss: options.swaggerOptions?.customCss ?? ".swagger-ui .topbar { display: none }"
110
+ }
111
+ };
112
+ }
113
+ function ensureDirAndWrite(filePath, content) {
114
+ const dir = (0, import_node_path.dirname)(filePath);
115
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
116
+ (0, import_node_fs.writeFileSync)(filePath, content);
117
+ }
118
+
119
+ // src/modules/devtool/index.ts
120
+ var DevToolsModule = class {
121
+ static async mount(app, opts = {}) {
122
+ const options = resolveOptsWithDefaultValue(opts);
123
+ const baseDirname = process.cwd();
124
+ const builder = new import_swagger.DocumentBuilder().setTitle(options.swaggerOptions.title).setVersion(options.swaggerOptions.version);
125
+ const document = import_swagger.SwaggerModule.createDocument(app, builder.build(), {
126
+ operationIdFactory: (_c, m) => m
127
+ });
128
+ import_swagger.SwaggerModule.setup(options.docsPath, app, document, {
129
+ customSiteTitle: options.swaggerOptions.customSiteTitle,
130
+ customCss: options.swaggerOptions.customCss,
131
+ swaggerOptions: { persistAuthorization: true }
132
+ });
133
+ const openapiPath = (0, import_node_path2.resolve)(baseDirname, options.openapiOut);
134
+ ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
135
+ if (options.needGenerateClientSdk) {
136
+ const clientSdkOutPath = (0, import_node_path2.resolve)(baseDirname, options.clientSdkOut);
137
+ (0, import_node_fs2.mkdirSync)(clientSdkOutPath, { recursive: true });
138
+ const { generate } = await import("openapi-typescript-codegen");
139
+ await generate({
140
+ input: openapiPath,
141
+ output: clientSdkOutPath,
142
+ httpClient: "axios",
143
+ useOptions: false,
144
+ exportServices: true
145
+ });
146
+ console.log("[OpenAPI] \u5BFC\u51FA openapi.json \u5E76\u751F\u6210 axios SDK \u2705");
147
+ }
148
+ }
149
+ };
150
+
151
+ // src/middlewares/csrf_token/index.ts
152
+ var import_common = require("@nestjs/common");
153
+
154
+ // src/middlewares/csrf_token/helper.ts
155
+ var import_crypto = __toESM(require("crypto"), 1);
156
+ function resolveCsrfTokenOptions(options) {
157
+ return {
158
+ ...options,
159
+ cookieKey: options.cookieKey ?? "suda-csrf-token",
160
+ cookieMaxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 7,
161
+ cookiePath: options.cookiePath ?? "/"
162
+ };
163
+ }
164
+ function genToken() {
165
+ const ts = Math.floor(Date.now() / 1e3);
166
+ const randInt64 = BigInt(
167
+ "0x" + import_crypto.default.randomBytes(8).toString("hex")
168
+ ).toString();
169
+ const s = `${randInt64}.${ts}`;
170
+ const sha1 = import_crypto.default.createHash("sha1");
171
+ sha1.update(s);
172
+ return `${sha1.digest("hex")}-${ts}`;
173
+ }
174
+
175
+ // src/middlewares/csrf_token/index.ts
176
+ var _CsrfTokenMiddleware_decorators, _init;
177
+ _CsrfTokenMiddleware_decorators = [(0, import_common.Injectable)()];
178
+ var _CsrfTokenMiddleware = class _CsrfTokenMiddleware {
179
+ static options;
180
+ static configure(opts) {
181
+ this.options = resolveCsrfTokenOptions(opts);
182
+ }
183
+ use(req, res, next) {
184
+ const { cookieKey, cookieMaxAge, cookiePath } = _CsrfTokenMiddleware.options;
185
+ const originToken = req.cookies[cookieKey.toLowerCase()];
186
+ if (originToken) {
187
+ req.csrfToken = originToken;
188
+ next();
189
+ } else {
190
+ const token = genToken();
191
+ req.csrfToken = token;
192
+ res.cookie(cookieKey, token, {
193
+ maxAge: cookieMaxAge,
194
+ path: cookiePath,
195
+ httpOnly: true,
196
+ secure: true,
197
+ sameSite: "none",
198
+ partitioned: true
199
+ // 默认开启 Partitioned Cookie
200
+ });
201
+ next();
202
+ }
203
+ }
204
+ };
205
+ _init = __decoratorStart(null);
206
+ _CsrfTokenMiddleware = __decorateElement(_init, 0, "CsrfTokenMiddleware", _CsrfTokenMiddleware_decorators, _CsrfTokenMiddleware);
207
+ __runInitializers(_init, 1, _CsrfTokenMiddleware);
208
+ var CsrfTokenMiddleware = _CsrfTokenMiddleware;
209
+
210
+ // src/middlewares/csrf/index.ts
211
+ var import_common2 = require("@nestjs/common");
212
+
213
+ // src/middlewares/csrf/helper.ts
214
+ function resolveCsrfOptions(options) {
215
+ return {
216
+ ...options,
217
+ headerKey: options.headerKey ?? "x-suda-csrf-token",
218
+ cookieKey: options.cookieKey ?? "suda-csrf-token"
219
+ };
220
+ }
221
+ function sendForbidden(res, message) {
222
+ res.status(403).send(`Forbidden\uFF0C${message}`);
223
+ }
224
+
225
+ // src/middlewares/csrf/index.ts
226
+ var _CsrfMiddleware_decorators, _init2;
227
+ _CsrfMiddleware_decorators = [(0, import_common2.Injectable)()];
228
+ var _CsrfMiddleware = class _CsrfMiddleware {
229
+ static options;
230
+ static configure(opts) {
231
+ this.options = resolveCsrfOptions(opts);
232
+ }
233
+ use(req, res, next) {
234
+ const { headerKey, cookieKey } = _CsrfMiddleware.options;
235
+ const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];
236
+ if (!cookieCsrfToken) {
237
+ sendForbidden(res, "csrf token not found in cookie.");
238
+ return;
239
+ }
240
+ const headerCsrfToken = req.headers[headerKey.toLowerCase()];
241
+ if (!headerCsrfToken) {
242
+ sendForbidden(res, "csrf token not found in header.");
243
+ return;
244
+ }
245
+ if (cookieCsrfToken !== headerCsrfToken) {
246
+ sendForbidden(res, "csrf token not match.");
247
+ return;
248
+ }
249
+ next();
250
+ }
251
+ };
252
+ _init2 = __decoratorStart(null);
253
+ _CsrfMiddleware = __decorateElement(_init2, 0, "CsrfMiddleware", _CsrfMiddleware_decorators, _CsrfMiddleware);
254
+ __runInitializers(_init2, 1, _CsrfMiddleware);
255
+ var CsrfMiddleware = _CsrfMiddleware;
256
+ // Annotate the CommonJS export names for ESM import in node:
257
+ 0 && (module.exports = {
258
+ CsrfMiddleware,
259
+ CsrfTokenMiddleware,
260
+ DevToolsModule
261
+ });
262
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"],"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';\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\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\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 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 ?? 60 * 60 * 24 * 7,\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"],"mappings":"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","names":["import_node_fs","import_node_path","crypto","import_common","_init"]}
@@ -0,0 +1,53 @@
1
+ import { INestApplication, NestMiddleware } from '@nestjs/common';
2
+ import { Request, Response, NextFunction } from 'express';
3
+
4
+ declare global {
5
+ namespace Express {
6
+ interface Request {
7
+ csrfToken?: string;
8
+ }
9
+ }
10
+ }
11
+
12
+ interface DevToolsOptions {
13
+ basePath?: string;
14
+ docsPath?: string;
15
+ openapiOut?: string;
16
+ needGenerateClientSdk?: boolean;
17
+ clientSdkOut?: string;
18
+ swaggerOptions?: {
19
+ title?: string;
20
+ version?: string;
21
+ customSiteTitle?: string;
22
+ customCss?: string;
23
+ };
24
+ }
25
+
26
+ declare class DevToolsModule {
27
+ static mount(app: INestApplication, opts?: DevToolsOptions): Promise<void>;
28
+ }
29
+
30
+ interface CsrfTokenOptions {
31
+ cookieKey?: string;
32
+ cookieMaxAge?: number;
33
+ cookiePath?: string;
34
+ }
35
+
36
+ declare class CsrfTokenMiddleware implements NestMiddleware {
37
+ private static options;
38
+ static configure(opts: CsrfTokenOptions): void;
39
+ use(req: Request, res: Response, next: NextFunction): void;
40
+ }
41
+
42
+ interface CsrfOptions {
43
+ headerKey: string;
44
+ cookieKey?: string;
45
+ }
46
+
47
+ declare class CsrfMiddleware implements NestMiddleware {
48
+ private static options;
49
+ static configure(opts: CsrfOptions): void;
50
+ use(req: Request, res: Response, next: NextFunction): void;
51
+ }
52
+
53
+ export { CsrfMiddleware, CsrfTokenMiddleware, DevToolsModule };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,53 @@
1
- declare function HelloWorld(): string;
1
+ import { INestApplication, NestMiddleware } from '@nestjs/common';
2
+ import { Request, Response, NextFunction } from 'express';
2
3
 
3
- export { HelloWorld, HelloWorld as default };
4
+ declare global {
5
+ namespace Express {
6
+ interface Request {
7
+ csrfToken?: string;
8
+ }
9
+ }
10
+ }
11
+
12
+ interface DevToolsOptions {
13
+ basePath?: string;
14
+ docsPath?: string;
15
+ openapiOut?: string;
16
+ needGenerateClientSdk?: boolean;
17
+ clientSdkOut?: string;
18
+ swaggerOptions?: {
19
+ title?: string;
20
+ version?: string;
21
+ customSiteTitle?: string;
22
+ customCss?: string;
23
+ };
24
+ }
25
+
26
+ declare class DevToolsModule {
27
+ static mount(app: INestApplication, opts?: DevToolsOptions): Promise<void>;
28
+ }
29
+
30
+ interface CsrfTokenOptions {
31
+ cookieKey?: string;
32
+ cookieMaxAge?: number;
33
+ cookiePath?: string;
34
+ }
35
+
36
+ declare class CsrfTokenMiddleware implements NestMiddleware {
37
+ private static options;
38
+ static configure(opts: CsrfTokenOptions): void;
39
+ use(req: Request, res: Response, next: NextFunction): void;
40
+ }
41
+
42
+ interface CsrfOptions {
43
+ headerKey: string;
44
+ cookieKey?: string;
45
+ }
46
+
47
+ declare class CsrfMiddleware implements NestMiddleware {
48
+ private static options;
49
+ static configure(opts: CsrfOptions): void;
50
+ use(req: Request, res: Response, next: NextFunction): void;
51
+ }
52
+
53
+ export { CsrfMiddleware, CsrfTokenMiddleware, DevToolsModule };
package/dist/index.js CHANGED
@@ -1,10 +1,227 @@
1
- // src/index.ts
2
- function HelloWorld() {
3
- return `Hello World!`;
1
+ var __create = Object.create;
2
+ 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
+ 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
+
49
+ // src/modules/devtool/index.ts
50
+ import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
51
+ import { mkdirSync as mkdirSync2 } from "fs";
52
+ import { resolve } from "path";
53
+
54
+ // src/modules/devtool/helper.ts
55
+ import { dirname } from "path";
56
+ import { writeFileSync, mkdirSync } from "fs";
57
+ function normalizeBasePath(rawBasePath) {
58
+ const normalizedBasePath = rawBasePath.startsWith("/") ? rawBasePath : `/${rawBasePath}`;
59
+ return normalizedBasePath.endsWith("/") ? normalizedBasePath.slice(0, -1) : normalizedBasePath;
60
+ }
61
+ function resolveOptsWithDefaultValue(options) {
62
+ const basePath = normalizeBasePath(options.basePath || "/");
63
+ const docsPath = normalizeBasePath(options.docsPath || `api/docs`);
64
+ return {
65
+ ...options,
66
+ basePath,
67
+ docsPath: `${basePath}${docsPath}`,
68
+ openapiOut: options.openapiOut || "./client/src/api/gen/openapi.json",
69
+ clientSdkOut: options.clientSdkOut || "./client/src/api/gen",
70
+ needGenerateClientSdk: options.needGenerateClientSdk ?? true,
71
+ swaggerOptions: {
72
+ title: options.swaggerOptions?.title ?? "NestJS Fullstack API",
73
+ version: options.swaggerOptions?.version ?? "1.0.0",
74
+ customSiteTitle: options.swaggerOptions?.customSiteTitle ?? "API Documentation",
75
+ customCss: options.swaggerOptions?.customCss ?? ".swagger-ui .topbar { display: none }"
76
+ }
77
+ };
78
+ }
79
+ function ensureDirAndWrite(filePath, content) {
80
+ const dir = dirname(filePath);
81
+ mkdirSync(dir, { recursive: true });
82
+ writeFileSync(filePath, content);
83
+ }
84
+
85
+ // src/modules/devtool/index.ts
86
+ var DevToolsModule = class {
87
+ static async mount(app, opts = {}) {
88
+ const options = resolveOptsWithDefaultValue(opts);
89
+ const baseDirname = process.cwd();
90
+ const builder = new DocumentBuilder().setTitle(options.swaggerOptions.title).setVersion(options.swaggerOptions.version);
91
+ const document = SwaggerModule.createDocument(app, builder.build(), {
92
+ operationIdFactory: (_c, m) => m
93
+ });
94
+ SwaggerModule.setup(options.docsPath, app, document, {
95
+ customSiteTitle: options.swaggerOptions.customSiteTitle,
96
+ customCss: options.swaggerOptions.customCss,
97
+ swaggerOptions: { persistAuthorization: true }
98
+ });
99
+ const openapiPath = resolve(baseDirname, options.openapiOut);
100
+ ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
101
+ if (options.needGenerateClientSdk) {
102
+ const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);
103
+ mkdirSync2(clientSdkOutPath, { recursive: true });
104
+ const { generate } = await import("openapi-typescript-codegen");
105
+ await generate({
106
+ input: openapiPath,
107
+ output: clientSdkOutPath,
108
+ httpClient: "axios",
109
+ useOptions: false,
110
+ exportServices: true
111
+ });
112
+ console.log("[OpenAPI] \u5BFC\u51FA openapi.json \u5E76\u751F\u6210 axios SDK \u2705");
113
+ }
114
+ }
115
+ };
116
+
117
+ // src/middlewares/csrf_token/index.ts
118
+ import { Injectable } from "@nestjs/common";
119
+
120
+ // src/middlewares/csrf_token/helper.ts
121
+ import crypto from "crypto";
122
+ function resolveCsrfTokenOptions(options) {
123
+ return {
124
+ ...options,
125
+ cookieKey: options.cookieKey ?? "suda-csrf-token",
126
+ cookieMaxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 7,
127
+ cookiePath: options.cookiePath ?? "/"
128
+ };
129
+ }
130
+ function genToken() {
131
+ const ts = Math.floor(Date.now() / 1e3);
132
+ const randInt64 = BigInt(
133
+ "0x" + crypto.randomBytes(8).toString("hex")
134
+ ).toString();
135
+ const s = `${randInt64}.${ts}`;
136
+ const sha1 = crypto.createHash("sha1");
137
+ sha1.update(s);
138
+ return `${sha1.digest("hex")}-${ts}`;
139
+ }
140
+
141
+ // src/middlewares/csrf_token/index.ts
142
+ var _CsrfTokenMiddleware_decorators, _init;
143
+ _CsrfTokenMiddleware_decorators = [Injectable()];
144
+ var _CsrfTokenMiddleware = class _CsrfTokenMiddleware {
145
+ static options;
146
+ static configure(opts) {
147
+ this.options = resolveCsrfTokenOptions(opts);
148
+ }
149
+ use(req, res, next) {
150
+ const { cookieKey, cookieMaxAge, cookiePath } = _CsrfTokenMiddleware.options;
151
+ const originToken = req.cookies[cookieKey.toLowerCase()];
152
+ if (originToken) {
153
+ req.csrfToken = originToken;
154
+ next();
155
+ } else {
156
+ const token = genToken();
157
+ req.csrfToken = token;
158
+ res.cookie(cookieKey, token, {
159
+ maxAge: cookieMaxAge,
160
+ path: cookiePath,
161
+ httpOnly: true,
162
+ secure: true,
163
+ sameSite: "none",
164
+ partitioned: true
165
+ // 默认开启 Partitioned Cookie
166
+ });
167
+ next();
168
+ }
169
+ }
170
+ };
171
+ _init = __decoratorStart(null);
172
+ _CsrfTokenMiddleware = __decorateElement(_init, 0, "CsrfTokenMiddleware", _CsrfTokenMiddleware_decorators, _CsrfTokenMiddleware);
173
+ __runInitializers(_init, 1, _CsrfTokenMiddleware);
174
+ var CsrfTokenMiddleware = _CsrfTokenMiddleware;
175
+
176
+ // src/middlewares/csrf/index.ts
177
+ import { Injectable as Injectable2 } from "@nestjs/common";
178
+
179
+ // src/middlewares/csrf/helper.ts
180
+ function resolveCsrfOptions(options) {
181
+ return {
182
+ ...options,
183
+ headerKey: options.headerKey ?? "x-suda-csrf-token",
184
+ cookieKey: options.cookieKey ?? "suda-csrf-token"
185
+ };
186
+ }
187
+ function sendForbidden(res, message) {
188
+ res.status(403).send(`Forbidden\uFF0C${message}`);
4
189
  }
5
- var index_default = HelloWorld;
190
+
191
+ // src/middlewares/csrf/index.ts
192
+ var _CsrfMiddleware_decorators, _init2;
193
+ _CsrfMiddleware_decorators = [Injectable2()];
194
+ var _CsrfMiddleware = class _CsrfMiddleware {
195
+ static options;
196
+ static configure(opts) {
197
+ this.options = resolveCsrfOptions(opts);
198
+ }
199
+ use(req, res, next) {
200
+ const { headerKey, cookieKey } = _CsrfMiddleware.options;
201
+ const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];
202
+ if (!cookieCsrfToken) {
203
+ sendForbidden(res, "csrf token not found in cookie.");
204
+ return;
205
+ }
206
+ const headerCsrfToken = req.headers[headerKey.toLowerCase()];
207
+ if (!headerCsrfToken) {
208
+ sendForbidden(res, "csrf token not found in header.");
209
+ return;
210
+ }
211
+ if (cookieCsrfToken !== headerCsrfToken) {
212
+ sendForbidden(res, "csrf token not match.");
213
+ return;
214
+ }
215
+ next();
216
+ }
217
+ };
218
+ _init2 = __decoratorStart(null);
219
+ _CsrfMiddleware = __decorateElement(_init2, 0, "CsrfMiddleware", _CsrfMiddleware_decorators, _CsrfMiddleware);
220
+ __runInitializers(_init2, 1, _CsrfMiddleware);
221
+ var CsrfMiddleware = _CsrfMiddleware;
6
222
  export {
7
- HelloWorld,
8
- index_default as default
223
+ CsrfMiddleware,
224
+ CsrfTokenMiddleware,
225
+ DevToolsModule
9
226
  };
10
227
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export function HelloWorld(): string {\n return `Hello World!`;\n}\n\nexport default HelloWorld;"],"mappings":";AAAO,SAAS,aAAqB;AACnC,SAAO;AACT;AAEA,IAAO,gBAAQ;","names":[]}
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"],"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\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\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 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 ?? 60 * 60 * 24 * 7,\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"],"mappings":"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","names":["mkdirSync","mkdirSync","Injectable","_init","Injectable"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-nestjs-core",
3
- "version": "0.0.0-alpha.1",
3
+ "version": "0.0.0-alpha.3",
4
4
  "description": "FullStack Nestjs Core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,8 @@
24
24
  "exports": {
25
25
  ".": {
26
26
  "import": "./dist/index.js",
27
- "types": "./dist/index.d.ts"
27
+ "types": "./dist/index.d.ts",
28
+ "require": "./dist/index.cjs"
28
29
  }
29
30
  },
30
31
  "scripts": {
@@ -36,9 +37,21 @@
36
37
  "lint": "echo 'ESLint skipped for TypeScript files'",
37
38
  "lint:fix": "eslint src --ext .ts --fix"
38
39
  },
39
- "dependencies": {},
40
+ "dependencies": {
41
+ "openapi-typescript-codegen": "^0.29.0"
42
+ },
40
43
  "devDependencies": {
41
- "typescript": "^5.2.0",
42
- "tsup": "^8.0.0"
44
+ "@nestjs/common": "^10.4.20",
45
+ "@nestjs/platform-express": "^10.4.20",
46
+ "@nestjs/swagger": "^7.4.2",
47
+ "@types/cookie-parser": "^1.4.9",
48
+ "@types/express": "^5.0.3",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.2.0"
51
+ },
52
+ "peerDependencies": {
53
+ "@nestjs/common": "^10.4.20",
54
+ "@nestjs/platform-express": "^10.4.20",
55
+ "@nestjs/swagger": "^7.4.2"
43
56
  }
44
- }
57
+ }