import { type LoaderFunctionArgs, redirect } from "@remix-run/node"; import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server"; import { OAuthClient } from "~/api/login/oauth-client"; import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server"; import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt"; /** * 端口号到地区的映射关系 * 根据 ecosystem.config.cjs 配置文件 */ const PORT_TO_AREA_MAP: Record = { '51703': '梅州', '51704': '云浮', '51705': '揭阳', '51706': '潮州', '51707': '省局' }; /** * 根据端口号获取地区 * @param port - 端口号 * @returns 地区名称,如果未找到则返回 undefined */ function getAreaByPort(port: string): string | undefined { return PORT_TO_AREA_MAP[port]; } /** * 辅助函数:使用 session flash 重定向到登录页面并传递错误信息 */ async function redirectToLoginWithError(request: Request, errorMessage: string) { const session = await sessionStorage.getSession(request.headers.get("Cookie")); session.flash("loginError", errorMessage); return redirect("/login", { headers: { "Set-Cookie": await sessionStorage.commitSession(session) } }); } export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); // const origin = url.origin; // 获取请求的源 (e.g., "http://10.79.97.17:51703") const port = url.port; // 获取端口号 const area = getAreaByPort(port); // 根据端口号获取地区 const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); const error = url.searchParams.get("error"); const error_description = url.searchParams.get("error_description"); // console.log("🔧 OAuth2.0回调参数:", { // code: code ? `${code.substring(0, 10)}...` : null, // state: state, // error: error, // error_description: error_description, // fullUrl: request.url, // port: port, // area: area // }); // 检查是否有错误 if (error) { console.error("❌ OAuth2.0授权失败:", error, error_description); return redirectToLoginWithError(request, error_description || error || "授权失败"); } // 检查是否有授权码 if (!code) { console.error("❌ OAuth2.0回调缺少授权码"); return redirectToLoginWithError(request, "登录过程中缺少授权码,请重新登录"); } // 验证状态值 if (!state || !state.endsWith("_idp")) { console.error("❌ OAuth2.0状态值验证失败:", { state, expectedSuffix: "_idp" }); return redirectToLoginWithError(request, "登录状态验证失败,请重新登录"); } console.log("✅ OAuth2.0回调参数验证通过"); // 声明在 try 外部,以便在 catch 中访问 let tokenResponse = null; const oauthClient = new OAuthClient(getServerOAuthConfigRuntime()); try { console.log("🔧 开始处理OAuth2.0回调"); // 开始获取访问令牌 tokenResponse = await oauthClient.getAccessToken(code); if (!tokenResponse) { console.error("获取访问令牌失败"); // 注意:此时还没有 access_token,无法调用 IDaaS 登出 // 只能重定向到登录页,让用户重新开始登录流程 return redirectToLoginWithError(request, "获取访问令牌失败,请重试"); } console.log("✅ [Callback] 访问令牌获取成功"); const userInfo = await oauthClient.getUserInfo(tokenResponse.access_token); if(!userInfo || !userInfo.success){ console.error('获取用户信息失败:',userInfo); // 🔑 关键:此时 IDaaS 那边已经登录成功,但我们获取用户信息失败 // 需要调用 IDaaS 登出,清除 IDaaS 的登录状态,避免用户下次登录时出现问题 try { const logoutUrl = `${url.protocol}//${url.host}/login`; const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl); if (logoutSuccess) { console.log("✅ [Callback] 已清除 IDaaS 登录状态"); } else { console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败"); } } catch (logoutError) { console.error("❌ [Callback] 调用 IDaaS 登出时出错:", logoutError); } return redirectToLoginWithError(request, "获取用户信息失败,请重试"); } console.log("✅ [Callback] 用户信息获取成功"); // TODO 根据用户信息判断用户角色,这里可以根据实际业务逻辑调整 暂定都是common const userRole = "common"; // 获取重定向URL const redirectTo = url.searchParams.get("redirect") || "/"; // 先生成一个临时 JWT const tempUserInfo = { sub: userInfo.data.sub, // user_id: userInfo.data.user_id || "", user_id: "", username: userInfo.data.username, nick_name: userInfo.data.nickname, email: userInfo.data.email, phone_number: userInfo.data.phone_number, ou_id: userInfo.data.ou_id, ou_name: userInfo.data.ou_name, // is_leader: userInfo.data.is_leader, is_leader: false, user_role: userRole as 'common' | 'developer' }; const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenResponse.expires_in); // 成功获取用户信息之后通过auth.server.ts中的saveUserInfo方法去写入自己的数据库中,通过sub作为唯一值去添加数据 const saveResult = await saveUserInfo(userInfo.data, tempToken, area); if (!saveResult.success) { console.error("保存用户信息到数据库失败:", saveResult.error); // 🔑 保存用户信息失败,需要清除 IDaaS 登录状态 try { const logoutUrl = `${url.protocol}//${url.host}/login`; const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl); if (logoutSuccess) { console.log("✅ [Callback] 已清除 IDaaS 登录状态(数据库保存失败)"); } else { console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败(数据库保存失败)"); } } catch (logoutError) { console.error("❌ [Callback] 调用 IDaaS 登出时出错(数据库保存失败):", logoutError); } return redirectToLoginWithError(request, "保存用户信息失败,请重新登录"); } console.log("用户信息已成功保存到数据库,地区:", area || "未设置"); const savedUserData = saveResult.data!; // 生成前端专用JWT(使用完整的用户信息,包括数据库 ID) const jwtUserInfo: UserInfoForJWT = { sub: userInfo.data.sub, user_id: savedUserData.id!, username: savedUserData.username, nick_name: savedUserData.nick_name, email: savedUserData.email, phone_number: savedUserData.phone_number, ou_id: savedUserData.ou_id, ou_name: savedUserData.ou_name, is_leader: savedUserData.is_leader, user_role: userRole as 'common' | 'developer' }; const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, tokenResponse.expires_in); // console.log("前端JWT已生成"); // 更新userInfo以包含数据库ID、JWT,并用数据库标准字段覆盖关键属性,确保 nick_name 等存在 const enhancedUserInfo = { ...userInfo.data, // 保留OAuth返回的原字段(包含 nickname 等) username: savedUserData.username, nick_name: savedUserData.nick_name, phone_number: savedUserData.phone_number, email: savedUserData.email, ou_id: savedUserData.ou_id, ou_name: savedUserData.ou_name, status: savedUserData.status, is_leader: savedUserData.is_leader, user_id: savedUserData.id, user_role: userRole, frontend_jwt: frontendJWT }; // 使用统一的session创建函数 return createUserSession({ isAuthenticated: true, userRole: userRole, redirectTo, accessToken: tokenResponse.access_token, refreshToken: tokenResponse.refresh_token, tokenExpiresIn: tokenResponse.expires_in, userInfo: enhancedUserInfo, frontendJWT }); } catch (error) { console.error("OAuth2.0回调处理失败:", error); // 🔑 如果已经获取到了 access_token,需要清除 IDaaS 登录状态 if (tokenResponse?.access_token) { try { const logoutUrl = `${url.protocol}//${url.host}/login`; const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl); if (logoutSuccess) { console.log("✅ [Callback] 已清除 IDaaS 登录状态(回调处理异常)"); } else { console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败(回调处理异常)"); } } catch (logoutError) { console.error("❌ [Callback] 调用 IDaaS 登出时出错(回调处理异常):", logoutError); } } return redirectToLoginWithError(request, "登录回调处理失败,请重新登录"); } } export default function Callback() { return (

正在处理登录...

); }