nsgm-cli 2.1.9 → 2.1.11

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/lib/index.js CHANGED
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.startExpress = void 0;
8
+ // 加载环境变量
9
+ require('dotenv').config();
8
10
  // 仅在开发环境中禁用TLS证书验证
9
11
  if (process.env.NODE_ENV === 'development') {
10
12
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
@@ -23,6 +25,8 @@ const config_1 = __importDefault(require("next/config"));
23
25
  const generate_1 = require("./generate");
24
26
  const lodash_1 = __importDefault(require("lodash"));
25
27
  const cors_1 = __importDefault(require("cors"));
28
+ const express_session_1 = __importDefault(require("express-session"));
29
+ const csrf_1 = require("./server/csrf");
26
30
  const { resolve } = path_1.default;
27
31
  const curFolder = process.cwd();
28
32
  const processArgvs = (0, args_1.getProcessArgvs)(2);
@@ -90,19 +94,48 @@ const startExpress = (options, callback) => {
90
94
  // 不要因为数据库连接失败就退出,允许应用继续运行
91
95
  }
92
96
  const server = (0, express_1.default)();
97
+ // 配置 session(CSRF 保护需要)
98
+ server.use((0, express_session_1.default)({
99
+ secret: process.env.SESSION_SECRET || 'nsgm-default-secret-key-change-in-production',
100
+ resave: false,
101
+ saveUninitialized: true, // 改为 true,确保 session 被创建
102
+ name: 'sessionId', // 明确指定 session cookie 名称
103
+ cookie: {
104
+ secure: false, // 开发环境总是使用 false,生产环境再考虑 HTTPS
105
+ httpOnly: true,
106
+ maxAge: 24 * 60 * 60 * 1000, // 24小时
107
+ sameSite: 'lax', // 设置 SameSite 策略
108
+ domain: undefined // 不设置 domain,使用默认
109
+ }
110
+ }));
111
+ // 初始化 CSRF token - 移除全局初始化,让每个端点自己处理
112
+ // server.use(setupCSRFToken)
93
113
  server.use(body_parser_1.default.urlencoded({
94
114
  extended: false
95
115
  }));
96
116
  // 支持跨域,nsgm export 之后前后分离
97
- server.use((0, cors_1.default)());
117
+ server.use((0, cors_1.default)({
118
+ credentials: true, // 允许发送 cookies
119
+ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']
120
+ }));
98
121
  server.use(body_parser_1.default.json());
122
+ // 添加基本安全中间件
123
+ server.use(csrf_1.securityMiddleware.basicHeaders);
124
+ if (process.env.NODE_ENV === 'production') {
125
+ server.use((0, csrf_1.createCSPMiddleware)()); // 内容安全策略
126
+ }
127
+ // 添加 CSRF 保护中间件(在解析 body 之后)
128
+ server.use(csrf_1.csrfProtection);
99
129
  server.use((0, express_fileupload_1.default)());
100
130
  server.use('/static', express_1.default.static(path_1.default.join(__dirname, 'public')));
101
131
  server.use('/graphql', (0, graphql_1.default)(command));
102
132
  const nextConfig = (0, config_1.default)();
103
133
  const { publicRuntimeConfig } = nextConfig;
104
134
  const { host, port, prefix } = publicRuntimeConfig;
135
+ // 提供 CSRF token 的端点
136
+ server.get('/csrf-token', csrf_1.getCSRFToken);
105
137
  if (prefix !== '') {
138
+ server.get(`${prefix}/csrf-token`, csrf_1.getCSRFToken);
106
139
  server.use(`${prefix}/static`, express_1.default.static(path_1.default.join(__dirname, 'public')));
107
140
  server.use(`${prefix}/graphql`, (0, graphql_1.default)(command));
108
141
  }
@@ -0,0 +1,17 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ declare module 'express-session' {
3
+ interface SessionData {
4
+ _csrf?: string;
5
+ }
6
+ }
7
+ declare module 'express-serve-static-core' {
8
+ interface Request {
9
+ csrfToken?: () => string;
10
+ }
11
+ }
12
+ export declare const csrfProtection: (req: Request, res: Response, next: NextFunction) => unknown;
13
+ export declare const getCSRFToken: (req: Request, res: Response) => void;
14
+ export declare const securityMiddleware: {
15
+ basicHeaders: import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
16
+ };
17
+ export declare const createCSPMiddleware: () => import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createCSPMiddleware = exports.securityMiddleware = exports.getCSRFToken = exports.csrfProtection = void 0;
7
+ const lusca_1 = __importDefault(require("lusca"));
8
+ // Lusca CSRF 配置
9
+ const luscaConfig = {
10
+ // CSRF 保护 - 修正配置格式
11
+ csrf: {
12
+ header: 'x-csrf-token', // 从 header 中读取 token
13
+ cookie: '_csrf', // cookie 名称
14
+ key: 'csrf', // session key
15
+ secret: process.env.CSRF_SECRET || 'your-csrf-secret-change-in-production'
16
+ },
17
+ // 内容安全策略
18
+ csp: {
19
+ policy: {
20
+ 'default-src': "'self'",
21
+ 'script-src': "'self' 'unsafe-inline'",
22
+ 'style-src': "'self' 'unsafe-inline'",
23
+ 'img-src': "'self' data: https:",
24
+ 'font-src': "'self' https:",
25
+ 'connect-src': "'self'"
26
+ }
27
+ },
28
+ // 其他安全设置
29
+ xframe: 'SAMEORIGIN',
30
+ nosniff: true,
31
+ xssProtection: true,
32
+ referrerPolicy: 'same-origin'
33
+ };
34
+ // 条件性 CSRF 保护中间件
35
+ const csrfProtection = (req, res, next) => {
36
+ // console.log('CSRF 中间件 - 路径:', req.path, '方法:', req.method)
37
+ // 跳过 GET 请求和某些不需要 CSRF 保护的路径
38
+ if (req.method === 'GET' ||
39
+ req.path.startsWith('/static') ||
40
+ req.path === '/csrf-token' ||
41
+ req.path.startsWith('/_next') || // Next.js 内部资源
42
+ req.path.startsWith('/__next') // Next.js 开发模式内部端点
43
+ ) {
44
+ // console.log('CSRF 中间件 - 跳过保护')
45
+ return next();
46
+ }
47
+ // 对其他请求应用 Lusca CSRF 保护
48
+ // console.log('CSRF 中间件 - 应用 Lusca 保护')
49
+ return lusca_1.default.csrf(luscaConfig.csrf)(req, res, next);
50
+ };
51
+ exports.csrfProtection = csrfProtection;
52
+ // 获取 CSRF token 的路由处理器
53
+ const getCSRFToken = (req, res) => {
54
+ try {
55
+ // 尝试从 session 中获取已有的 token
56
+ const csrfToken = req.session._csrf || req.session[luscaConfig.csrf.key];
57
+ if (!csrfToken) {
58
+ // 如果没有 token,先生成一个
59
+ lusca_1.default.csrf(luscaConfig.csrf)(req, res, () => {
60
+ const newToken = req.session._csrf || req.session[luscaConfig.csrf.key] || req.csrfToken?.();
61
+ // console.log('生成新的 CSRF token:', newToken)
62
+ // console.log('Token 生成时的 Session ID:', req.sessionID)
63
+ res.json({
64
+ csrfToken: newToken
65
+ });
66
+ });
67
+ }
68
+ else {
69
+ // console.log('返回现有 CSRF token:', csrfToken)
70
+ // console.log('Token 生成时的 Session ID:', req.sessionID)
71
+ res.json({
72
+ csrfToken: csrfToken
73
+ });
74
+ }
75
+ }
76
+ catch (error) {
77
+ console.error('获取 CSRF token 错误:', error);
78
+ res.status(500).json({
79
+ error: 'Failed to generate CSRF token',
80
+ message: '生成 CSRF 令牌失败'
81
+ });
82
+ }
83
+ };
84
+ exports.getCSRFToken = getCSRFToken;
85
+ // Lusca 安全中间件配置
86
+ exports.securityMiddleware = {
87
+ // 基本的安全头
88
+ basicHeaders: (0, lusca_1.default)({
89
+ xframe: luscaConfig.xframe,
90
+ nosniff: luscaConfig.nosniff,
91
+ xssProtection: luscaConfig.xssProtection,
92
+ referrerPolicy: luscaConfig.referrerPolicy
93
+ })
94
+ };
95
+ // CSP 中间件
96
+ const createCSPMiddleware = () => {
97
+ return lusca_1.default.csp(luscaConfig.csp);
98
+ };
99
+ exports.createCSPMiddleware = createCSPMiddleware;