diff --git a/src-tauri/src/account/commands.rs b/src-tauri/src/account/commands.rs index 370ab199..2e5bfd89 100644 --- a/src-tauri/src/account/commands.rs +++ b/src-tauri/src/account/commands.rs @@ -586,16 +586,16 @@ pub fn delete_auth_server(app: AppHandle, url: String) -> XMCLResult<()> { use crate::account::helpers::authlib_injector::cauc; -/// CAUC 步骤 1: eduroam 登录 +/// CAUC 步骤 1: 表单登录 /// /// 返回是否需要绑定昵称 #[tauri::command] -pub async fn cauc_eduroam_login( +pub async fn cauc_login( app: AppHandle, student_id: String, oa_password: String, ) -> XMCLResult { - let auth_state = cauc::eduroam_login(&app, student_id, oa_password).await?; + let auth_state = cauc::cauc_login(&app, student_id, oa_password).await?; // 将 auth_state 临时存储到应用状态中 // 这样后续的绑定操作可以使用 @@ -650,7 +650,7 @@ pub async fn cauc_complete_login( log::info!("Auth state retrieved: {:?}", auth_state_option.is_some()); let auth_state = auth_state_option.ok_or_else(|| { - log::error!("Auth state is None - user must call cauc_eduroam_login first!"); + log::error!("Auth state is None - user must call cauc_login first!"); AccountError::Invalid })?; diff --git a/src-tauri/src/account/helpers/authlib_injector/cauc.rs b/src-tauri/src/account/helpers/authlib_injector/cauc.rs index 95276046..d6af4785 100644 --- a/src-tauri/src/account/helpers/authlib_injector/cauc.rs +++ b/src-tauri/src/account/helpers/authlib_injector/cauc.rs @@ -46,11 +46,14 @@ async fn get_player_name(client: &Client, cookie_jar: &[(String, String)]) -> XM .await .map_err(|_| AccountError::NetworkError)?; - // 使用正则提取 data-mark="nickname">玩家名< - let re = regex::Regex::new(r#"data-mark="nickname">([^<]+)<"#).unwrap(); - if let Some(caps) = re.captures(&html) { + // 使用正则提取 data-mark="nickname">玩家名<, 跳过页面里空的占位项 + let re = regex::Regex::new(r#"data-mark="nickname">([^<]*)<"#).unwrap(); + for caps in re.captures_iter(&html) { if let Some(name) = caps.get(1) { - return Ok(name.as_str().trim().to_string()); + let trimmed = name.as_str().trim(); + if !trimmed.is_empty() { + return Ok(trimmed.to_string()); + } } } @@ -99,17 +102,17 @@ pub struct CAUCAuthState { pub player_name: Option, } -pub async fn eduroam_login( +pub async fn cauc_login( app: &AppHandle, student_id: String, oa_password: String, ) -> XMCLResult { - log::info!("CAUC eduroam login attempt for student: {}", student_id); + log::info!("CAUC login attempt for student: {}", student_id); let client = build_cauc_client(app)?; - // 步骤 1: 先访问登录页面获取 CSRF token - let login_page_url = format!("{}/auth/eduroam/login", CAUC_BASE_URL); + // 步骤 1: 先访问 CAUC 登录页获取 CSRF token + let login_page_url = format!("{}/auth/cauc/login", CAUC_BASE_URL); log::info!("Fetching login page: {}", login_page_url); let page_response = client.get(&login_page_url).send().await.map_err(|e| { @@ -160,14 +163,14 @@ pub async fn eduroam_login( .post(&login_page_url) .header("Cookie", cookie_header.clone()) .form(&[ - ("student_number", student_id.as_str()), - ("password", oa_password.as_str()), + ("cauc_username", student_id.as_str()), + ("cauc_password", oa_password.as_str()), ("_token", csrf_token), ]) .send() .await .map_err(|e| { - log::error!("CAUC eduroam login network error: {}", e); + log::error!("CAUC login network error: {}", e); AccountError::NetworkError })?; @@ -189,7 +192,7 @@ pub async fn eduroam_login( } let status = response.status(); - log::info!("CAUC eduroam login response status: {}", status); + log::info!("CAUC login response status: {}", status); // 记录Location头用于调试重定向 if let Some(location_header) = response.headers().get("location") { @@ -224,6 +227,15 @@ pub async fn eduroam_login( log::info!("Login redirect to: {}", location); + // CAUC 表单登录成功重定向到 /user, 失败(账号密码错误)会重定向回登录页。 + if !location.contains("/user") { + log::error!( + "CAUC login failed: redirected to {} instead of /user", + location + ); + return Err(AccountError::Invalid.into()); + } + // 检查重定向位置来判断是否需要绑定昵称 let requires_bind = location.contains("/user/player/bind"); @@ -253,7 +265,7 @@ pub async fn eduroam_login( let requires_bind = requires_bind || (location.contains("/user") && player_name.is_none()); log::info!( - "CAUC eduroam login successful, requires_bind: {}, player_name: {:?}", + "CAUC login successful, requires_bind: {}, player_name: {:?}", requires_bind, player_name ); @@ -285,7 +297,7 @@ pub async fn eduroam_login( let requires_bind = redirect_url.contains("/user/player/bind"); log::info!( - "CAUC eduroam login successful (JSON response), requires_bind: {}", + "CAUC login successful (JSON response), requires_bind: {}", requires_bind ); @@ -306,7 +318,7 @@ pub async fn eduroam_login( ); Err(AccountError::Invalid.into()) } else { - log::error!("CAUC eduroam login failed: status {}", status); + log::error!("CAUC login failed: status {}", status); Err(AccountError::Invalid.into()) } } @@ -400,7 +412,10 @@ pub async fn authenticate( auth_state: &CAUCAuthState, oa_password: String, ) -> XMCLResult<(Vec, bool)> { - let mut candidate_usernames: Vec = vec![]; + // CAUC 皮肤站上的账号以 <学工号>@cauc.edu.cn 为用户名, 纯学工号或玩家名无法通过 + // yggdrasil authenticate, 所以邮箱优先。 + let email = format!("{}@cauc.edu.cn", auth_state.student_id); + let mut candidate_usernames: Vec = vec![email.clone()]; if let Some(player_name) = auth_state.player_name.as_ref() { let trimmed = player_name.trim(); @@ -416,10 +431,6 @@ pub async fn authenticate( candidate_usernames.push(auth_state.student_id.clone()); } - if candidate_usernames.is_empty() { - candidate_usernames.push(auth_state.student_id.clone()); - } - log::info!( "Authenticating with Yggdrasil API, candidates: {:?}", candidate_usernames @@ -490,7 +501,7 @@ pub async fn authenticate( Some(access_token), None, Some(CAUC_YGGDRASIL_URL.to_string()), - Some(auth_state.student_id.clone()), + Some(email.clone()), ) .await { @@ -521,7 +532,7 @@ pub async fn authenticate( Some(access_token.clone()), None, Some(CAUC_YGGDRASIL_URL.to_string()), - Some(auth_state.student_id.clone()), + Some(email.clone()), ) .await?; @@ -636,8 +647,8 @@ pub async fn login_flow( oa_password: String, player_name: Option, ) -> XMCLResult { - // 步骤 1: eduroam 登录 - let auth_state = eduroam_login(app, student_id.clone(), oa_password.clone()).await?; + // 步骤 1: CAUC 表单登录 + let auth_state = cauc_login(app, student_id.clone(), oa_password.clone()).await?; // 步骤 2: 检查是否需要绑定 if auth_state.requires_bind { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 656af15e..df2327d1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -93,7 +93,7 @@ pub async fn run() { account::commands::add_auth_server, account::commands::delete_auth_server, account::commands::fetch_auth_server, - account::commands::cauc_eduroam_login, + account::commands::cauc_login, account::commands::cauc_bind_player_name, account::commands::cauc_complete_login, instance::commands::retrieve_instance_list, diff --git a/src/components/cauc-login-modal-simple.tsx b/src/components/cauc-login-modal-simple.tsx index cd8b4de5..670a5242 100644 --- a/src/components/cauc-login-modal-simple.tsx +++ b/src/components/cauc-login-modal-simple.tsx @@ -30,7 +30,7 @@ interface CAUCLoginModalProps { * CAUC (中国民航大学) 登录模态框组件。 * * 认证流程: - * 1. 使用学工号 + OA 密码完成校园统一身份登录 (eduroam) + * 1. 使用学工号 + OA 密码完成 CAUC 校园统一身份登录 * 2. 根据返回决定是否需要绑定昵称 * 3. 完成 Yggdrasil 认证并写入启动器账号列表 */ @@ -108,7 +108,7 @@ export const CAUCLoginModal: React.FC = ({ return isValid; }; - const handleEduroamLogin = async () => { + const handleCaucLogin = async () => { if (!validateInputs()) { return; } @@ -116,14 +116,11 @@ export const CAUCLoginModal: React.FC = ({ setIsSubmitting(true); try { - console.log("Attempting CAUC eduroam login with student ID:", studentId); + console.log("Attempting CAUC login with student ID:", studentId); - const response = await CAUCService.caucEduroamLogin( - studentId, - oaPassword - ); + const response = await CAUCService.caucLogin(studentId, oaPassword); - console.log("CAUC eduroam login response:", response); + console.log("CAUC login response:", response); if (response.status === "success") { const needBind = Boolean(response.data); @@ -143,7 +140,7 @@ export const CAUCLoginModal: React.FC = ({ await completeLogin(studentId, oaPassword); } } else { - console.error("CAUC eduroam login failed:", response); + console.error("CAUC login failed:", response); toast({ title: response.message || "登录失败", description: response.details || "学工号或密码错误", @@ -152,7 +149,7 @@ export const CAUCLoginModal: React.FC = ({ }); } } catch (error) { - console.error("CAUC eduroam login error:", error); + console.error("CAUC login error:", error); toast({ title: "登录失败", description: @@ -275,7 +272,7 @@ export const CAUCLoginModal: React.FC = ({ placeholder="请输入学工号" value={studentId} onChange={(e) => setStudentId(e.target.value)} - onKeyPress={(e) => e.key === "Enter" && handleEduroamLogin()} + onKeyPress={(e) => e.key === "Enter" && handleCaucLogin()} /> {studentIdError} @@ -287,7 +284,7 @@ export const CAUCLoginModal: React.FC = ({ placeholder="请输入 OA 密码" value={oaPassword} onChange={(e) => setOaPassword(e.target.value)} - onKeyPress={(e) => e.key === "Enter" && handleEduroamLogin()} + onKeyPress={(e) => e.key === "Enter" && handleCaucLogin()} /> {passwordError} @@ -328,7 +325,7 @@ export const CAUCLoginModal: React.FC = ({