nsgm-cli 2.1.22 → 2.1.23
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/client/utils/common.ts +6 -2
- package/client/utils/fetch.ts +1 -1
- package/client/utils/sso.ts +13 -3
- package/lib/generate_create.js +9 -0
- package/lib/generators/dataloader-generator.d.ts +12 -0
- package/lib/generators/dataloader-generator.js +221 -0
- package/lib/generators/resolver-generator.d.ts +2 -1
- package/lib/generators/resolver-generator.js +117 -24
- package/lib/generators/schema-generator.js +1 -0
- package/lib/server/dataloaders/index.d.ts +38 -0
- package/lib/server/dataloaders/index.js +33 -0
- package/lib/server/dataloaders/template-dataloader.d.ts +48 -0
- package/lib/server/dataloaders/template-dataloader.js +131 -0
- package/lib/server/debug/dataloader-debug.d.ts +63 -0
- package/lib/server/debug/dataloader-debug.js +192 -0
- package/lib/server/graphql.js +9 -0
- package/lib/server/utils/dataloader-monitor.d.ts +87 -0
- package/lib/server/utils/dataloader-monitor.js +199 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils.js +1 -1
- package/next-i18next.config.js +7 -5
- package/next.config.js +19 -3
- package/package.json +2 -1
- package/pages/_app.tsx +6 -3
- package/pages/api/sso/ticketCheck.ts +117 -0
- package/pages/index.tsx +10 -2
- package/pages/login.tsx +40 -10
- package/pages/template/manage.tsx +15 -1
- package/server/apis/sso.js +22 -4
- package/server/modules/template/resolver.js +101 -21
- package/server/modules/template/schema.js +1 -0
package/lib/utils.js
CHANGED
package/next-i18next.config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @type {import('next-i18next').UserConfig}
|
|
3
5
|
*/
|
|
@@ -5,14 +7,14 @@ module.exports = {
|
|
|
5
7
|
i18n: {
|
|
6
8
|
defaultLocale: 'zh-CN',
|
|
7
9
|
locales: ['zh-CN', 'en-US', 'ja-JP'],
|
|
8
|
-
localeDetection: false,
|
|
10
|
+
localeDetection: false,
|
|
9
11
|
},
|
|
10
|
-
localePath: '
|
|
11
|
-
/** To avoid issues when deploying to some platforms, we can configure the cache */
|
|
12
|
+
localePath: path.resolve(process.cwd(), 'public/locales'),
|
|
12
13
|
saveMissing: false,
|
|
13
|
-
strictMode:
|
|
14
|
-
serializeConfig:
|
|
14
|
+
strictMode: false,
|
|
15
|
+
serializeConfig: true,
|
|
15
16
|
react: {
|
|
16
17
|
useSuspense: false,
|
|
17
18
|
},
|
|
19
|
+
reloadOnPrerender: process.env.NODE_ENV === 'development',
|
|
18
20
|
}
|
package/next.config.js
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
const { PHASE_DEVELOPMENT_SERVER, PHASE_EXPORT } = require('next/constants')
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const path = require('path')
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// 直接定义 i18n 配置,避免在 Vercel 上找不到配置文件
|
|
9
|
+
const i18n = {
|
|
10
|
+
defaultLocale: 'zh-CN',
|
|
11
|
+
locales: ['zh-CN', 'en-US', 'ja-JP'],
|
|
12
|
+
localeDetection: false,
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
module.exports = (phase, defaultConfig, options) => {
|
|
10
16
|
let projectConfig = null
|
|
@@ -16,8 +22,18 @@ module.exports = (phase, defaultConfig, options) => {
|
|
|
16
22
|
|
|
17
23
|
if (fs.existsSync(curProjectConfigPath)) {
|
|
18
24
|
projectConfig = require(curProjectConfigPath)
|
|
19
|
-
} else {
|
|
25
|
+
} else if (fs.existsSync('./project.config.js')) {
|
|
20
26
|
projectConfig = require('./project.config.js')
|
|
27
|
+
} else {
|
|
28
|
+
// 默认配置(用于 Vercel 等环境)
|
|
29
|
+
projectConfig = {
|
|
30
|
+
env: 'production',
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
prefix: '',
|
|
33
|
+
protocol: 'https',
|
|
34
|
+
host: 'localhost',
|
|
35
|
+
port: '443'
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
if (fs.existsSync(curPkgPath)) {
|
|
@@ -70,7 +86,7 @@ module.exports = (phase, defaultConfig, options) => {
|
|
|
70
86
|
esmExternals: true, // 支持 ESM 外部依赖
|
|
71
87
|
},
|
|
72
88
|
compiler: {
|
|
73
|
-
removeConsole: phase !== PHASE_DEVELOPMENT_SERVER,
|
|
89
|
+
removeConsole: phase !== PHASE_DEVELOPMENT_SERVER ? { exclude: ['warn', 'error'] } : false,
|
|
74
90
|
styledComponents: true,
|
|
75
91
|
},
|
|
76
92
|
...(phase === PHASE_DEVELOPMENT_SERVER && {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nsgm-cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.23",
|
|
4
4
|
"description": "A CLI tool to run Next/Style-components and Graphql/Mysql fullstack project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"commander": "14.0.0",
|
|
91
91
|
"cors": "2.8.5",
|
|
92
92
|
"critters": "0.0.25",
|
|
93
|
+
"dataloader": "2.2.3",
|
|
93
94
|
"dayjs": "1.11.13",
|
|
94
95
|
"dotenv": "17.2.1",
|
|
95
96
|
"exceljs": "4.4.0",
|
package/pages/_app.tsx
CHANGED
|
@@ -15,7 +15,6 @@ import SSRSafeAntdProvider from "@/components/SSRSafeAntdProvider";
|
|
|
15
15
|
import { login } from "@/utils/sso";
|
|
16
16
|
import { getAntdLocale } from "@/utils/i18n";
|
|
17
17
|
import { navigateToLogin } from "@/utils/navigation";
|
|
18
|
-
import nextI18NextConfig from "../next-i18next.config.js";
|
|
19
18
|
import "antd/dist/reset.css";
|
|
20
19
|
|
|
21
20
|
const theme = {
|
|
@@ -46,7 +45,10 @@ const App = ({ Component, pageProps }) => {
|
|
|
46
45
|
|
|
47
46
|
useEffect(() => {
|
|
48
47
|
setMounted(true);
|
|
49
|
-
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
// 同步当前语言
|
|
50
52
|
if (typeof window !== "undefined") {
|
|
51
53
|
const locale = router.locale || "zh-CN";
|
|
52
54
|
setCurrentLocale(locale);
|
|
@@ -133,4 +135,5 @@ const App = ({ Component, pageProps }) => {
|
|
|
133
135
|
);
|
|
134
136
|
};
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
// appWithTranslation 会自动从 next.config.js 读取 i18n 配置
|
|
139
|
+
export default appWithTranslation(App);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from "next";
|
|
2
|
+
import bcrypt from "bcrypt";
|
|
3
|
+
|
|
4
|
+
type ResponseData = {
|
|
5
|
+
name: string;
|
|
6
|
+
returnCode: number;
|
|
7
|
+
userAttr?: {
|
|
8
|
+
displayName: string;
|
|
9
|
+
username?: string;
|
|
10
|
+
};
|
|
11
|
+
cookieValue?: string;
|
|
12
|
+
cookieExpire?: number;
|
|
13
|
+
message?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getLoginCredentials = () => {
|
|
17
|
+
const username = process.env.LOGIN_USERNAME || "admin";
|
|
18
|
+
const passwordHash = process.env.LOGIN_PASSWORD_HASH;
|
|
19
|
+
|
|
20
|
+
console.warn("[SSO] getLoginCredentials - username:", username);
|
|
21
|
+
console.warn(
|
|
22
|
+
"[SSO] getLoginCredentials - passwordHash from env:",
|
|
23
|
+
passwordHash ? `${passwordHash.substring(0, 20)}...` : "undefined"
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!passwordHash) {
|
|
27
|
+
console.warn("[SSO] ⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希");
|
|
28
|
+
return {
|
|
29
|
+
username,
|
|
30
|
+
passwordHash: "$2b$10$K5O.TJLKGPmKGHJK8KzN5u8qUYKzq5vLcXlP7vGUzq5vLcXlP7vGUz",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.warn("[SSO] getLoginCredentials - final passwordHash:", `${passwordHash.substring(0, 20)}...`);
|
|
35
|
+
return { username, passwordHash };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const validateCredentials = async (inputUsername: string, inputPassword: string): Promise<boolean> => {
|
|
39
|
+
const { username, passwordHash } = getLoginCredentials();
|
|
40
|
+
|
|
41
|
+
if (inputUsername !== username) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return await bcrypt.compare(inputPassword, passwordHash);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn("[SSO] 密码验证失败:", error);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
|
|
54
|
+
if (req.method !== "GET") {
|
|
55
|
+
res.status(405).json({
|
|
56
|
+
name: "ticketCheck",
|
|
57
|
+
returnCode: -1,
|
|
58
|
+
message: "Method not allowed",
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { name } = req.query;
|
|
64
|
+
|
|
65
|
+
console.warn("[SSO] ticketCheck called, name:", name);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (!name || typeof name !== "string") {
|
|
69
|
+
throw new Error("Invalid name parameter");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 使用 Buffer 解码 Base64 字符串,然后使用 decodeURIComponent 处理特殊字符
|
|
73
|
+
const decodedBase64 = Buffer.from(name, "base64").toString("utf-8");
|
|
74
|
+
console.warn("[SSO] Decoded Base64:", decodedBase64);
|
|
75
|
+
|
|
76
|
+
const decodedName = decodeURIComponent(decodedBase64);
|
|
77
|
+
console.warn("[SSO] Decoded name:", decodedName);
|
|
78
|
+
|
|
79
|
+
const [inputUsername, inputPassword] = decodedName.split(",");
|
|
80
|
+
console.warn("[SSO] Input username:", inputUsername, "password length:", inputPassword?.length);
|
|
81
|
+
|
|
82
|
+
const { username: expectedUsername, passwordHash } = getLoginCredentials();
|
|
83
|
+
console.warn("[SSO] Expected username:", expectedUsername, "passwordHash length:", passwordHash?.length);
|
|
84
|
+
|
|
85
|
+
const isValid = await validateCredentials(inputUsername, inputPassword);
|
|
86
|
+
console.warn("[SSO] Credentials valid:", isValid);
|
|
87
|
+
|
|
88
|
+
if (isValid) {
|
|
89
|
+
console.warn("[SSO] Login successful for user:", inputUsername);
|
|
90
|
+
res.status(200).json({
|
|
91
|
+
name: "ticketCheck",
|
|
92
|
+
returnCode: 0,
|
|
93
|
+
userAttr: {
|
|
94
|
+
displayName: "System Admin",
|
|
95
|
+
username: inputUsername,
|
|
96
|
+
},
|
|
97
|
+
cookieValue: `login_cookie_${Date.now()}`,
|
|
98
|
+
cookieExpire: 7200000, // 2小时
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
console.warn(`[SSO] 登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}`);
|
|
102
|
+
|
|
103
|
+
res.status(200).json({
|
|
104
|
+
name: "ticketCheck",
|
|
105
|
+
returnCode: -1,
|
|
106
|
+
message: "用户名或密码错误",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.warn("[SSO] 登录验证出错:", error);
|
|
111
|
+
res.status(500).json({
|
|
112
|
+
name: "ticketCheck",
|
|
113
|
+
returnCode: -1,
|
|
114
|
+
message: "服务器内部错误",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
package/pages/index.tsx
CHANGED
|
@@ -159,12 +159,20 @@ const Page = () => {
|
|
|
159
159
|
};
|
|
160
160
|
|
|
161
161
|
export const getServerSideProps = async ({ locale }) => {
|
|
162
|
-
// 确保 locale 有默认值,避免 serverSideTranslations 报错
|
|
163
162
|
const currentLocale = locale || "zh-CN";
|
|
164
163
|
|
|
164
|
+
const path = require("path");
|
|
165
|
+
const i18nConfig = {
|
|
166
|
+
i18n: {
|
|
167
|
+
defaultLocale: "zh-CN",
|
|
168
|
+
locales: ["zh-CN", "en-US", "ja-JP"],
|
|
169
|
+
},
|
|
170
|
+
localePath: path.resolve(process.cwd(), "public/locales"),
|
|
171
|
+
};
|
|
172
|
+
|
|
165
173
|
return {
|
|
166
174
|
props: {
|
|
167
|
-
...(await serverSideTranslations(currentLocale, ["common", "home", "layout"])),
|
|
175
|
+
...(await serverSideTranslations(currentLocale, ["common", "home", "layout"], i18nConfig)),
|
|
168
176
|
},
|
|
169
177
|
};
|
|
170
178
|
};
|
package/pages/login.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { useState, useEffect } from "react";
|
|
|
6
6
|
import { Input, Button, Form, Typography, message } from "antd";
|
|
7
7
|
import { UserOutlined, LockOutlined } from "@ant-design/icons";
|
|
8
8
|
import { directLogin } from "../client/utils/sso";
|
|
9
|
+
import { getLocalApiPrefix } from "../client/utils/common";
|
|
9
10
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
10
11
|
import { useTranslation } from "next-i18next";
|
|
11
12
|
import { useRouter } from "next/router";
|
|
@@ -27,14 +28,18 @@ const renderArr: any = [];
|
|
|
27
28
|
renderArr.push("NSGM");
|
|
28
29
|
|
|
29
30
|
const Page = ({ html }) => {
|
|
30
|
-
const { t } = useTranslation(
|
|
31
|
+
const { t } = useTranslation("login");
|
|
31
32
|
const router = useRouter();
|
|
32
33
|
const [userName, setUserName] = useState("");
|
|
33
34
|
const [userPassword, setUserPassword] = useState("");
|
|
34
35
|
const [mounted, setMounted] = useState(false);
|
|
36
|
+
const [debugInfo, setDebugInfo] = useState("");
|
|
35
37
|
|
|
36
38
|
useEffect(() => {
|
|
37
39
|
setMounted(true);
|
|
40
|
+
// 显示调试信息
|
|
41
|
+
const apiPrefix = getLocalApiPrefix();
|
|
42
|
+
setDebugInfo(`API: ${apiPrefix}`);
|
|
38
43
|
}, []);
|
|
39
44
|
|
|
40
45
|
const createMarkup = () => {
|
|
@@ -47,11 +52,11 @@ const Page = ({ html }) => {
|
|
|
47
52
|
if (!mounted || typeof window === "undefined") return;
|
|
48
53
|
|
|
49
54
|
if (userName === "") {
|
|
50
|
-
message.error(t("login
|
|
55
|
+
message.error(t("login.errors.usernameRequired"));
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
53
58
|
if (userPassword === "") {
|
|
54
|
-
message.error(t("login
|
|
59
|
+
message.error(t("login.errors.passwordRequired"));
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -65,6 +70,7 @@ const Page = ({ html }) => {
|
|
|
65
70
|
// 检查是否是 Promise
|
|
66
71
|
if (result && typeof (result as any).then === "function") {
|
|
67
72
|
(result as Promise<any>).then((loginResult) => {
|
|
73
|
+
setDebugInfo(`Login result: ${JSON.stringify(loginResult)}`);
|
|
68
74
|
if (!loginResult.success) {
|
|
69
75
|
message.error(loginResult.message);
|
|
70
76
|
}
|
|
@@ -72,8 +78,9 @@ const Page = ({ html }) => {
|
|
|
72
78
|
} else {
|
|
73
79
|
// 直接返回的结果
|
|
74
80
|
const syncResult = result as { success: boolean; message?: string };
|
|
81
|
+
setDebugInfo(`Login result: ${JSON.stringify(syncResult)}`);
|
|
75
82
|
if (!syncResult.success) {
|
|
76
|
-
message.error(syncResult.message || t("login
|
|
83
|
+
message.error(syncResult.message || t("login.errors.loginFailed"));
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
};
|
|
@@ -91,15 +98,30 @@ const Page = ({ html }) => {
|
|
|
91
98
|
<div style={{ position: "absolute", top: "20px", right: "20px" }}>
|
|
92
99
|
<LanguageSwitcher />
|
|
93
100
|
</div>
|
|
101
|
+
{debugInfo && (
|
|
102
|
+
<div
|
|
103
|
+
style={{
|
|
104
|
+
position: "absolute",
|
|
105
|
+
top: "60px",
|
|
106
|
+
right: "20px",
|
|
107
|
+
fontSize: "12px",
|
|
108
|
+
color: "#999",
|
|
109
|
+
maxWidth: "300px",
|
|
110
|
+
wordBreak: "break-all",
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{debugInfo}
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
94
116
|
<div dangerouslySetInnerHTML={createMarkup()} />
|
|
95
117
|
<Typography.Title level={3} style={{ textAlign: "center", marginBottom: 24 }}>
|
|
96
|
-
{t("login
|
|
118
|
+
{t("login.title")}
|
|
97
119
|
</Typography.Title>
|
|
98
120
|
<Form layout="vertical" style={{ width: "100%" }}>
|
|
99
121
|
<Form.Item>
|
|
100
122
|
<Input
|
|
101
123
|
prefix={<UserOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
|
102
|
-
placeholder={t("login
|
|
124
|
+
placeholder={t("login.username")}
|
|
103
125
|
size="large"
|
|
104
126
|
value={userName}
|
|
105
127
|
onChange={doChangeName}
|
|
@@ -109,7 +131,7 @@ const Page = ({ html }) => {
|
|
|
109
131
|
<Form.Item>
|
|
110
132
|
<Input.Password
|
|
111
133
|
prefix={<LockOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
|
|
112
|
-
placeholder={t("login
|
|
134
|
+
placeholder={t("login.password")}
|
|
113
135
|
size="large"
|
|
114
136
|
value={userPassword}
|
|
115
137
|
onChange={doChangePassword}
|
|
@@ -118,7 +140,7 @@ const Page = ({ html }) => {
|
|
|
118
140
|
</Form.Item>
|
|
119
141
|
<Form.Item>
|
|
120
142
|
<Button type="primary" onClick={doLogin} size="large" block>
|
|
121
|
-
{t("login
|
|
143
|
+
{t("login.loginButton")}
|
|
122
144
|
</Button>
|
|
123
145
|
</Form.Item>
|
|
124
146
|
</Form>
|
|
@@ -127,7 +149,6 @@ const Page = ({ html }) => {
|
|
|
127
149
|
};
|
|
128
150
|
|
|
129
151
|
export const getServerSideProps = async ({ locale }) => {
|
|
130
|
-
// 确保 locale 有默认值,避免 serverSideTranslations 报错
|
|
131
152
|
const currentLocale = locale || "zh-CN";
|
|
132
153
|
|
|
133
154
|
// 处理 markdown 内容
|
|
@@ -136,10 +157,19 @@ export const getServerSideProps = async ({ locale }) => {
|
|
|
136
157
|
html += md.render(item);
|
|
137
158
|
});
|
|
138
159
|
|
|
160
|
+
const path = require("path");
|
|
161
|
+
const i18nConfig = {
|
|
162
|
+
i18n: {
|
|
163
|
+
defaultLocale: "zh-CN",
|
|
164
|
+
locales: ["zh-CN", "en-US", "ja-JP"],
|
|
165
|
+
},
|
|
166
|
+
localePath: path.resolve(process.cwd(), "public/locales"),
|
|
167
|
+
};
|
|
168
|
+
|
|
139
169
|
return {
|
|
140
170
|
props: {
|
|
141
171
|
html,
|
|
142
|
-
...(await serverSideTranslations(currentLocale, ["common", "layout", "login"])),
|
|
172
|
+
...(await serverSideTranslations(currentLocale, ["common", "layout", "login"], i18nConfig)),
|
|
143
173
|
},
|
|
144
174
|
};
|
|
145
175
|
};
|
|
@@ -412,7 +412,21 @@ export async function getServerSideProps(context) {
|
|
|
412
412
|
});
|
|
413
413
|
|
|
414
414
|
const { locale } = context;
|
|
415
|
-
|
|
415
|
+
|
|
416
|
+
const path = require("path");
|
|
417
|
+
const i18nConfig = {
|
|
418
|
+
i18n: {
|
|
419
|
+
defaultLocale: "zh-CN",
|
|
420
|
+
locales: ["zh-CN", "en-US", "ja-JP"],
|
|
421
|
+
},
|
|
422
|
+
localePath: path.resolve(process.cwd(), "public/locales"),
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const translations = await serverSideTranslations(
|
|
426
|
+
locale || "zh-CN",
|
|
427
|
+
["common", "template", "layout", "login"],
|
|
428
|
+
i18nConfig
|
|
429
|
+
);
|
|
416
430
|
|
|
417
431
|
return {
|
|
418
432
|
props: {
|
package/server/apis/sso.js
CHANGED
|
@@ -8,8 +8,12 @@ const getLoginCredentials = () => {
|
|
|
8
8
|
const username = process.env.LOGIN_USERNAME || 'admin'
|
|
9
9
|
let passwordHash = process.env.LOGIN_PASSWORD_HASH
|
|
10
10
|
|
|
11
|
+
console.warn('[SSO] getLoginCredentials - username:', username)
|
|
12
|
+
console.warn('[SSO] getLoginCredentials - passwordHash from env:', passwordHash ? `${passwordHash.substring(0, 20)}...` : 'undefined')
|
|
13
|
+
|
|
11
14
|
// 如果环境变量被截断,尝试手动读取 .env 文件
|
|
12
15
|
if (!passwordHash || passwordHash.length < 60) {
|
|
16
|
+
console.warn('[SSO] passwordHash too short or missing, trying to read from .env')
|
|
13
17
|
try {
|
|
14
18
|
const fs = require('fs')
|
|
15
19
|
const path = require('path')
|
|
@@ -21,16 +25,17 @@ const getLoginCredentials = () => {
|
|
|
21
25
|
if (line.startsWith('LOGIN_PASSWORD_HASH=')) {
|
|
22
26
|
// 移除引号和前缀
|
|
23
27
|
passwordHash = line.replace('LOGIN_PASSWORD_HASH=', '').replace(/"/g, '').trim()
|
|
28
|
+
console.warn('[SSO] Found passwordHash in .env:', `${passwordHash.substring(0, 20)}...`)
|
|
24
29
|
break
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
} catch (error) {
|
|
28
|
-
console.
|
|
33
|
+
console.warn('[SSO] 读取 .env 文件失败:', error.message)
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
if (!passwordHash) {
|
|
33
|
-
console.warn('⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希')
|
|
38
|
+
console.warn('[SSO] ⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希')
|
|
34
39
|
// 默认密码 "admin123" 的哈希值(仅用于开发环境)
|
|
35
40
|
return {
|
|
36
41
|
username,
|
|
@@ -38,6 +43,7 @@ const getLoginCredentials = () => {
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
console.warn('[SSO] getLoginCredentials - final passwordHash:', `${passwordHash.substring(0, 20)}...`)
|
|
41
47
|
return { username, passwordHash }
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -75,15 +81,27 @@ router.get('/ticketCheck', async (req, res) => {
|
|
|
75
81
|
const { query } = req
|
|
76
82
|
const { name } = query
|
|
77
83
|
|
|
84
|
+
console.warn('[SSO] ticketCheck called, name:', name)
|
|
85
|
+
|
|
78
86
|
try {
|
|
79
87
|
// 使用 Buffer 解码 Base64 字符串,然后使用 decodeURIComponent 处理特殊字符
|
|
80
88
|
const decodedBase64 = Buffer.from(name, 'base64').toString('utf-8')
|
|
89
|
+
console.warn('[SSO] Decoded Base64:', decodedBase64)
|
|
90
|
+
|
|
81
91
|
const decodedName = decodeURIComponent(decodedBase64)
|
|
92
|
+
console.warn('[SSO] Decoded name:', decodedName)
|
|
93
|
+
|
|
82
94
|
const [inputUsername, inputPassword] = decodedName.split(',')
|
|
95
|
+
console.warn('[SSO] Input username:', inputUsername, 'password length:', inputPassword?.length)
|
|
96
|
+
|
|
97
|
+
const { username: expectedUsername, passwordHash } = getLoginCredentials()
|
|
98
|
+
console.warn('[SSO] Expected username:', expectedUsername, 'passwordHash length:', passwordHash?.length)
|
|
83
99
|
|
|
84
100
|
const isValid = await validateCredentials(inputUsername, inputPassword)
|
|
101
|
+
console.warn('[SSO] Credentials valid:', isValid)
|
|
85
102
|
|
|
86
103
|
if (isValid) {
|
|
104
|
+
console.warn('[SSO] Login successful for user:', inputUsername)
|
|
87
105
|
res.json({
|
|
88
106
|
name: 'ticketCheck',
|
|
89
107
|
query,
|
|
@@ -97,7 +115,7 @@ router.get('/ticketCheck', async (req, res) => {
|
|
|
97
115
|
})
|
|
98
116
|
} else {
|
|
99
117
|
// 记录失败的登录尝试(用于安全审计)
|
|
100
|
-
console.
|
|
118
|
+
console.warn(`[SSO] 登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}, IP: ${req.ip}`)
|
|
101
119
|
|
|
102
120
|
res.json({
|
|
103
121
|
name: 'ticketCheck',
|
|
@@ -107,7 +125,7 @@ router.get('/ticketCheck', async (req, res) => {
|
|
|
107
125
|
})
|
|
108
126
|
}
|
|
109
127
|
} catch (error) {
|
|
110
|
-
console.
|
|
128
|
+
console.warn('[SSO] 登录验证出错:', error)
|
|
111
129
|
res.status(500).json({
|
|
112
130
|
name: 'ticketCheck',
|
|
113
131
|
returnCode: -1,
|