Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src-tauri/src/account/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> {
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 临时存储到应用状态中
// 这样后续的绑定操作可以使用
Expand Down Expand Up @@ -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
})?;

Expand Down
59 changes: 35 additions & 24 deletions src-tauri/src/account/helpers/authlib_injector/cauc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}

Expand Down Expand Up @@ -99,17 +102,17 @@ pub struct CAUCAuthState {
pub player_name: Option<String>,
}

pub async fn eduroam_login(
pub async fn cauc_login(
app: &AppHandle,
student_id: String,
oa_password: String,
) -> XMCLResult<CAUCAuthState> {
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| {
Expand Down Expand Up @@ -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
})?;

Expand All @@ -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") {
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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
);

Expand All @@ -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())
}
}
Expand Down Expand Up @@ -400,7 +412,10 @@ pub async fn authenticate(
auth_state: &CAUCAuthState,
oa_password: String,
) -> XMCLResult<(Vec<PlayerInfo>, bool)> {
let mut candidate_usernames: Vec<String> = vec![];
// CAUC 皮肤站上的账号以 <学工号>@cauc.edu.cn 为用户名, 纯学工号或玩家名无法通过
// yggdrasil authenticate, 所以邮箱优先。
let email = format!("{}@cauc.edu.cn", auth_state.student_id);
let mut candidate_usernames: Vec<String> = vec![email.clone()];

if let Some(player_name) = auth_state.player_name.as_ref() {
let trimmed = player_name.trim();
Expand All @@ -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
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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?;

Expand Down Expand Up @@ -636,8 +647,8 @@ pub async fn login_flow(
oa_password: String,
player_name: Option<String>,
) -> XMCLResult<LoginFlowResult> {
// 步骤 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 {
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 10 additions & 13 deletions src/components/cauc-login-modal-simple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface CAUCLoginModalProps {
* CAUC (中国民航大学) 登录模态框组件。
*
* 认证流程:
* 1. 使用学工号 + OA 密码完成校园统一身份登录 (eduroam)
* 1. 使用学工号 + OA 密码完成 CAUC 校园统一身份登录
* 2. 根据返回决定是否需要绑定昵称
* 3. 完成 Yggdrasil 认证并写入启动器账号列表
*/
Expand Down Expand Up @@ -108,22 +108,19 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
return isValid;
};

const handleEduroamLogin = async () => {
const handleCaucLogin = async () => {
if (!validateInputs()) {
return;
}

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);
Expand All @@ -143,7 +140,7 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
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 || "学工号或密码错误",
Expand All @@ -152,7 +149,7 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
});
}
} catch (error) {
console.error("CAUC eduroam login error:", error);
console.error("CAUC login error:", error);
toast({
title: "登录失败",
description:
Expand Down Expand Up @@ -275,7 +272,7 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
placeholder="请输入学工号"
value={studentId}
onChange={(e) => setStudentId(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleEduroamLogin()}
onKeyPress={(e) => e.key === "Enter" && handleCaucLogin()}
/>
<FormErrorMessage>{studentIdError}</FormErrorMessage>
</FormControl>
Expand All @@ -287,7 +284,7 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
placeholder="请输入 OA 密码"
value={oaPassword}
onChange={(e) => setOaPassword(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleEduroamLogin()}
onKeyPress={(e) => e.key === "Enter" && handleCaucLogin()}
/>
<FormErrorMessage>{passwordError}</FormErrorMessage>
</FormControl>
Expand Down Expand Up @@ -328,7 +325,7 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
</Button>
<Button
colorScheme="blue"
onClick={requiresBind ? handleBindPlayerName : handleEduroamLogin}
onClick={requiresBind ? handleBindPlayerName : handleCaucLogin}
isLoading={isSubmitting}
loadingText={requiresBind ? "处理中..." : "登录中..."}
>
Expand Down
7 changes: 2 additions & 5 deletions src/components/cauc-login-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,9 @@ export const CAUCLoginModal: React.FC<CAUCLoginModalProps> = ({
setIsLoading(true);

try {
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") {
// 保存凭据供后续使用
Expand Down
2 changes: 1 addition & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2095,7 +2095,7 @@
}
},
"cauc": {
"caucEduroamLogin": {
"caucLogin": {
"success": "CAUC campus login successful",
"error": {
"title": "CAUC campus login failed",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1880,7 +1880,7 @@
}
},
"cauc": {
"caucEduroamLogin": {
"caucLogin": {
"success": "Connexion campus CAUC réussie",
"error": {
"title": "Échec de la connexion campus CAUC",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1844,7 +1844,7 @@
}
},
"cauc": {
"caucEduroamLogin": {
"caucLogin": {
"success": "CAUCキャンパスログイン成功",
"error": {
"title": "CAUCキャンパスログイン失敗",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -2095,7 +2095,7 @@
}
},
"cauc": {
"caucEduroamLogin": {
"caucLogin": {
"success": "CAUC 校园登录成功",
"error": {
"title": "CAUC 校园登录失败",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -2083,7 +2083,7 @@
}
},
"cauc": {
"caucEduroamLogin": {
"caucLogin": {
"success": "CAUC 校園登入成功",
"error": {
"title": "CAUC 校園登入失敗",
Expand Down
10 changes: 5 additions & 5 deletions src/services/cauc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import { responseHandler } from "@/utils/response";
* CAUC (中国民航大学) 认证服务
*
* 认证流程:
* 1. caucEduroamLogin - 使用学工号和OA密码登录
* 1. caucLogin - 使用学工号和OA密码登录
* 2. 如果返回 true,需要绑定昵称,调用 caucBindPlayerName
* 3. caucCompleteLogin - 完成认证并添加玩家到账号列表
*/
export class CAUCService {
/**
* 步骤 1: 使用学工号和 OA 密码登录 eduroam
* 步骤 1: 使用学工号和 OA 密码提交 CAUC 表单登录
*
* @param {string} studentId - 学工号
* @param {string} oaPassword - OA 密码
* @returns {Promise<InvokeResponse<boolean>>} true 表示需要绑定昵称,false 表示可以直接登录
*/
@responseHandler("cauc")
static async caucEduroamLogin(
static async caucLogin(
studentId: string,
oaPassword: string
): Promise<InvokeResponse<boolean>> {
return await invoke("cauc_eduroam_login", {
return await invoke("cauc_login", {
studentId,
oaPassword,
});
Expand All @@ -33,7 +33,7 @@ export class CAUCService {
/**
* 步骤 2: 绑定游戏昵称 (仅新用户需要)
*
* 这个方法必须在 caucEduroamLogin 返回 true 后调用
* 这个方法必须在 caucLogin 返回 true 后调用
*
* @param {string} playerName - 游戏昵称
* @returns {Promise<InvokeResponse<void>>}
Expand Down
Loading