From 83f657388c788fd85c592e4c17415658dbcafe4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E7=94=B7?= Date: Mon, 27 Apr 2026 22:38:59 +0800 Subject: [PATCH] fix(ws-org): enforce current_org_id for teacher-student APIs Require organization context in teacher/student relation reads and writes, and prevent fallback to cross-organization queries. Made-with: Cursor --- htyws/src/ws_all.rs | 200 +++++++++++++------------------------ htyws_models/src/models.rs | 20 ++++ 2 files changed, 87 insertions(+), 133 deletions(-) diff --git a/htyws/src/ws_all.rs b/htyws/src/ws_all.rs index 438c328..726253d 100644 --- a/htyws/src/ws_all.rs +++ b/htyws/src/ws_all.rs @@ -16,6 +16,10 @@ fn current_org_id_from_token_str(token_str: &String) -> Option { .and_then(|decoded| decoded.current_org_id) } +fn required_current_org_id_from_token_str(token_str: &String) -> anyhow::Result { + current_org_id_from_token_str(token_str).ok_or_else(|| anyhow!("current_org_id is required")) +} + use htycommons::common::{ current_local_datetime, get_page_and_page_size, get_some_from_query_params, strip_result_vec, HtyErr, HtyErrCode, HtyResponse, @@ -3142,6 +3146,7 @@ pub async fn raw_get_unclaimed_students( host: HtyHostHeader, db_pool: Arc, ) -> anyhow::Result> { + let current_org_id = required_current_org_id_from_token_str(&root.0)?; let url = format!("{}/find_users_with_info_by_role/STUDENT", get_uc_url(),); let client = reqwest::Client::new(); let ret_json = client @@ -3161,11 +3166,12 @@ pub async fn raw_get_unclaimed_students( })?; let mut resp = vec![]; for user_with_info in user_with_infos { - let exist = TeacherStudent::verify_exist_and_not_deleted_by_student_id( + let exist = TeacherStudent::verify_exist_and_not_deleted_by_student_id_in_org( user_with_info .hty_id .as_ref() .ok_or_else(|| anyhow!("hty_id is required"))?, + ¤t_org_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; if !exist { @@ -3217,21 +3223,13 @@ pub async fn raw_claim_student( .clone() .ok_or_else(|| anyhow!("id_student is required"))?; - let current_org_id = current_org_id_from_token_str(&(*token).clone()); - let res_exist = if let Some(org_id) = ¤t_org_id { - TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( - &id_teacher, - &id_student, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::verify_exist_by_teacher_id_and_student_id( - &id_teacher, - &id_student, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let current_org_id = required_current_org_id_from_token_str(&(*token).clone())?; + let res_exist = TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( + &id_teacher, + &id_student, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; if res_exist { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, @@ -3244,7 +3242,7 @@ pub async fn raw_claim_student( id: uuid(), status: String::from("Approved"), created_at: Some(current_local_datetime()), - org_id: current_org_id, + org_id: Some(current_org_id), }; let res = TeacherStudent::create( &in_record, @@ -3340,42 +3338,25 @@ pub async fn raw_disclaim_student( .clone() .ok_or_else(|| anyhow!("id_student is required"))?; - let current_org_id = current_org_id_from_token_str(&(*auth).clone()); - let res_exist = if let Some(org_id) = ¤t_org_id { - TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( - &id_teacher, - &id_student, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::verify_exist_by_teacher_id_and_student_id( - &id_teacher, - &id_student, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let current_org_id = required_current_org_id_from_token_str(&(*auth).clone())?; + let res_exist = TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( + &id_teacher, + &id_student, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; if !res_exist { return Err(anyhow!(HtyErr { code: HtyErrCode::WebErr, reason: Some("no existing record for this teacher and student".into()), })); } - let res = if let Some(org_id) = ¤t_org_id { - TeacherStudent::del_by_teacher_id_and_student_id_in_org( - &id_teacher, - &id_student, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - let record = TeacherStudent::find_by_teacher_id_and_student_id( - &id_teacher, - &id_student, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )?; - TeacherStudent::delete_by_id(&record.id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())? - }; + let res = TeacherStudent::del_by_teacher_id_and_student_id_in_org( + &id_teacher, + &id_student, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; Ok(res) } @@ -3579,19 +3560,12 @@ pub async fn raw_find_all_teachers_by_student_id( student_id: String, db_pool: Arc, ) -> anyhow::Result> { - let current_org_id = current_org_id_from_token_str(&root.0); - let teacher_ids = if let Some(org_id) = ¤t_org_id { - TeacherStudent::get_all_teachers_by_student_id_in_org( - &student_id, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::get_all_teachers_by_student_id( - &student_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let current_org_id = required_current_org_id_from_token_str(&root.0)?; + let teacher_ids = TeacherStudent::get_all_teachers_by_student_id_in_org( + &student_id, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; debug!( "raw_find_all_teachers_by_student_id -> teacher_ids {:?}", &teacher_ids @@ -3652,21 +3626,13 @@ pub async fn raw_delete_teacher_student( let _teacher = find_hty_user_with_info_by_id(&teacher_id, &root).await?; let _student = find_hty_user_with_info_by_id(&student_id, &root).await?; - let current_org_id = current_org_id_from_token_str(&root.0); - let is_exist = if let Some(org_id) = ¤t_org_id { - TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( - &teacher_id, - &student_id, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::verify_exist_by_teacher_id_and_student_id( - &teacher_id, - &student_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let current_org_id = required_current_org_id_from_token_str(&root.0)?; + let is_exist = TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( + &teacher_id, + &student_id, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; if !is_exist { return Err(anyhow!(HtyErr { @@ -3682,20 +3648,12 @@ pub async fn raw_delete_teacher_student( // )?; // record.status = status; - if let Some(org_id) = ¤t_org_id { - let _ = TeacherStudent::del_by_teacher_id_and_student_id_in_org( - &teacher_id, - &student_id, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )?; - } else { - let _ = TeacherStudent::del_by_teacher_id_and_student_id( - &teacher_id, - &student_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )?; - } + let _ = TeacherStudent::del_by_teacher_id_and_student_id_in_org( + &teacher_id, + &student_id, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; Ok(()) } @@ -3753,41 +3711,25 @@ pub async fn raw_update_teacher_student( let _teacher = find_hty_user_with_info_by_id(&teacher_id, &root).await?; let _student = find_hty_user_with_info_by_id(&student_id, &root).await?; - let current_org_id = current_org_id_from_token_str(&root.0); - let is_exist = if let Some(org_id) = ¤t_org_id { - TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( - &teacher_id, - &student_id, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::verify_exist_by_teacher_id_and_student_id( - &teacher_id, - &student_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let current_org_id = required_current_org_id_from_token_str(&root.0)?; + let is_exist = TeacherStudent::verify_exist_by_teacher_id_and_student_id_in_org( + &teacher_id, + &student_id, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; if !is_exist { return Err(anyhow!(HtyErr { code: HtyErrCode::NullErr, reason: Some("no record for this teacher and student".to_string()), })); } - let mut record = if let Some(org_id) = ¤t_org_id { - TeacherStudent::find_by_teacher_id_and_student_id_in_org( - &teacher_id, - &student_id, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::find_by_teacher_id_and_student_id( - &teacher_id, - &student_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let mut record = TeacherStudent::find_by_teacher_id_and_student_id_in_org( + &teacher_id, + &student_id, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; record.status = status; let _ = TeacherStudent::update(&record, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; Ok(()) @@ -3834,21 +3776,13 @@ pub async fn raw_find_all_students_by_teacher_id_and_status( let status = req_teacher_student .status .ok_or_else(|| anyhow!("status is required"))?; - let current_org_id = current_org_id_from_token_str(&root.0); - let student_ids = if let Some(org_id) = ¤t_org_id { - TeacherStudent::get_all_students_by_teacher_id_and_status_in_org( - &teacher_id, - &status, - org_id, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - } else { - TeacherStudent::get_all_students_by_teacher_id_and_status( - &teacher_id, - &status, - extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), - )? - }; + let current_org_id = required_current_org_id_from_token_str(&root.0)?; + let student_ids = TeacherStudent::get_all_students_by_teacher_id_and_status_in_org( + &teacher_id, + &status, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; debug!( "raw_find_all_students_by_teacher_id_and_status -> student_ids {:?}", &student_ids diff --git a/htyws_models/src/models.rs b/htyws_models/src/models.rs index 37d1a8a..18eea01 100644 --- a/htyws_models/src/models.rs +++ b/htyws_models/src/models.rs @@ -3087,6 +3087,26 @@ impl TeacherStudent { }) } + pub fn verify_exist_and_not_deleted_by_student_id_in_org( + id_student: &String, + id_org: &String, + conn: &mut PgConnection, + ) -> anyhow::Result { + use crate::schema::teacher_student::dsl::*; + select(exists( + teacher_student + .filter(student_id.eq(id_student).and(status.ne("Removed"))) + .filter(org_id.eq(id_org)), + )) + .get_result(conn) + .map_err(|e| { + anyhow!(HtyErr { + code: HtyErrCode::DbErr, + reason: Some(e.to_string()), + }) + }) + } + pub fn delete_all(conn: &mut PgConnection) -> anyhow::Result { match diesel::delete(teacher_student::table).execute(conn) { Ok(num) => Ok(num),