1
1
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-12-10 08:03:09 +00:00

Compare commits

..

No commits in common. "be1d8d1328a14e422bc85251013bb99694de159e" and "9f4f2a537f80ec0142a94dbcd6201961eff7b00f" have entirely different histories.

5 changed files with 7 additions and 73 deletions

View File

@ -24,7 +24,7 @@ const CONFIG = {
// 应用ID // 应用ID
appId: process.env.APP_ID || '1', appId: process.env.APP_ID || '1',
// 授权页面地址Classworks前端 // 授权页面地址Classworks前端
authPageUrl: process.env.FRONTEND_URL, authPageUrl: process.env.AUTH_PAGE_URL || 'http://localhost:5173/authorize',
// 本地回调服务器端口 // 本地回调服务器端口
callbackPort: process.env.CALLBACK_PORT || '8080', callbackPort: process.env.CALLBACK_PORT || '8080',
// 回调路径 // 回调路径

View File

@ -21,7 +21,7 @@ const CONFIG = {
// 应用ID // 应用ID
appId: process.env.APP_ID || '1', appId: process.env.APP_ID || '1',
// 授权页面地址Classworks前端 // 授权页面地址Classworks前端
authPageUrl: process.env.FRONTEND_URL, authPageUrl: process.env.AUTH_PAGE_URL || 'http://localhost:5173/authorize',
// 轮询间隔(秒) // 轮询间隔(秒)
pollInterval: 3, pollInterval: 3,
// 最大轮询次数 // 最大轮询次数

View File

@ -24,20 +24,6 @@ export const oauthProviders = {
color: "#6366f1", color: "#6366f1",
description: "使用 ZeroCat 账号登录", description: "使用 ZeroCat 账号登录",
}, },
hly: {
// 厚浪云Logto - OIDC Provider
clientId: process.env.HLY_CLIENT_ID,
clientSecret: process.env.HLY_CLIENT_SECRET, // 可选若使用PKCE且应用为Public可不配置
authorizationURL: "https://oauth.houlang.cloud/oidc/auth",
tokenURL: "https://oauth.houlang.cloud/oidc/token",
userInfoURL: "https://oauth.houlang.cloud/oidc/me",
scope: "openid profile email offline_access",
name: "厚浪云",
icon: "logto",
color: "#0ea5e9",
description: "使用厚浪云账号登录",
pkce: true, // 启用PKCE支持
},
}; };
// 获取OAuth回调URL // 获取OAuth回调URL

View File

@ -1,6 +1,6 @@
{ {
"name": "ClassworksKV", "name": "ClassworksKV",
"version": "1.0.9", "version": "1.0.8",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node ./bin/www", "start": "node ./bin/www",

View File

@ -11,24 +11,6 @@ const prisma = new PrismaClient();
// 存储OAuth state防止CSRF攻击生产环境应使用Redis等 // 存储OAuth state防止CSRF攻击生产环境应使用Redis等
const oauthStates = new Map(); const oauthStates = new Map();
// 生成PKCE code_verifier 和 code_challenge
function generatePkcePair() {
const codeVerifier = crypto
.randomBytes(32)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const challenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
return { codeVerifier, codeChallenge: challenge };
}
/** /**
* 生成安全的访问令牌 * 生成安全的访问令牌
*/ */
@ -45,8 +27,7 @@ router.get("/oauth/providers", (req, res) => {
for (const [key, config] of Object.entries(oauthProviders)) { for (const [key, config] of Object.entries(oauthProviders)) {
// 只返回已配置的提供者 // 只返回已配置的提供者
const pkceAllowed = !!config.pkce; if (config.clientId && config.clientSecret) {
if (config.clientId && (config.clientSecret || pkceAllowed)) {
providers.push({ providers.push({
id: key, id: key,
name: config.name, name: config.name,
@ -83,8 +64,7 @@ router.get("/oauth/:provider", (req, res) => {
}); });
} }
const pkceAllowed = !!providerConfig.pkce; if (!providerConfig.clientId || !providerConfig.clientSecret) {
if (!providerConfig.clientId || (!providerConfig.clientSecret && !pkceAllowed)) {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: `OAuth提供者 ${provider} 未配置`, message: `OAuth提供者 ${provider} 未配置`,
@ -94,20 +74,11 @@ router.get("/oauth/:provider", (req, res) => {
// 生成state参数 // 生成state参数
const state = generateState(); const state = generateState();
// PKCE: 若启用为此次会话生成code_verifier/challenge
let codeChallenge, codeVerifier;
if (pkceAllowed) {
const pair = generatePkcePair();
codeVerifier = pair.codeVerifier;
codeChallenge = pair.codeChallenge;
}
// 保存state和redirect_uri5分钟过期 // 保存state和redirect_uri5分钟过期
oauthStates.set(state, { oauthStates.set(state, {
provider, provider,
redirect_uri, redirect_uri,
timestamp: Date.now(), timestamp: Date.now(),
codeVerifier,
}); });
// 清理过期的state超过5分钟 // 清理过期的state超过5分钟
@ -132,11 +103,6 @@ router.get("/oauth/:provider", (req, res) => {
params.append("prompt", "consent"); params.append("prompt", "consent");
} }
if (pkceAllowed && codeChallenge) {
params.append("code_challenge", codeChallenge);
params.append("code_challenge_method", "S256");
}
const authUrl = `${providerConfig.authorizationURL}?${params.toString()}`; const authUrl = `${providerConfig.authorizationURL}?${params.toString()}`;
// 重定向到OAuth提供者 // 重定向到OAuth提供者
@ -187,12 +153,10 @@ router.get("/oauth/:provider/callback", async (req, res) => {
}, },
body: new URLSearchParams({ body: new URLSearchParams({
client_id: providerConfig.clientId, client_id: providerConfig.clientId,
...(providerConfig.clientSecret ? { client_secret: providerConfig.clientSecret } : {}), client_secret: providerConfig.clientSecret,
code: code, code: code,
grant_type: "authorization_code", grant_type: "authorization_code",
redirect_uri: getCallbackURL(provider), redirect_uri: getCallbackURL(provider),
// PKCE: 携带code_verifier
...(stateData?.codeVerifier ? { code_verifier: stateData.codeVerifier } : {}),
}), }),
}); });
@ -229,22 +193,6 @@ router.get("/oauth/:provider/callback", async (req, res) => {
name: userData.nickname || userData.username, name: userData.nickname || userData.username,
avatarUrl: userData.avatar, avatarUrl: userData.avatar,
}; };
} else if (provider === "hly") {
// 厚浪云Logto标准OIDC用户信息
normalizedUser = {
providerId: userData.sub,
email: userData.email_verified ? userData.email : null,
name: userData.name || userData.preferred_username || userData.nickname,
avatarUrl: userData.picture,
};
}
// 名称为空时,用邮箱@前部分回填(若邮箱可用)
if ((!normalizedUser.name || normalizedUser.name.trim() === "") && normalizedUser.email) {
const at = normalizedUser.email.indexOf("@");
if (at > 0) {
normalizedUser.name = normalizedUser.email.substring(0, at);
}
} }
// 4. 查找或创建账户 // 4. 查找或创建账户