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/README.md +84 -57
- package/client/redux/store.ts +13 -0
- package/client/redux/template/manage/actions.ts +8 -28
- package/client/service/template/manage.ts +8 -8
- package/client/utils/common.ts +2 -2
- package/client/utils/fetch.ts +371 -36
- package/client/utils/sso.ts +54 -14
- package/generation/README.md +68 -28
- package/lib/index.js +34 -1
- package/lib/server/csrf.d.ts +17 -0
- package/lib/server/csrf.js +99 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/next.config.js +6 -0
- package/package.json +10 -2
- package/pages/_app.tsx +35 -4
- package/pages/login.tsx +22 -5
- package/pages/template/manage.tsx +35 -27
- package/scripts/generate-password-hash.js +43 -0
- package/server/apis/sso.js +97 -22
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;
|