use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tauri::State; use crate::services::{ auth::{AuthService, SetPasswordsPayload}, SecurityService, }; use crate::state::AppState; use super::response::IpcResponse; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthStatusResponse { pub permission: String, pub has_admin_password: bool, pub has_points_password: bool, pub has_recovery_string: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoginResponse { pub permission: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SetPasswordsResponse { #[serde(skip_serializing_if = "Option::is_none")] pub recovery_string: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthConfig { pub platform_id: String, pub platform_secret: String, pub callback_url: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthTokenResponse { pub access_token: String, pub refresh_token: String, pub token_type: String, pub expires_in: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthUserInfo { pub user_id: String, pub email: String, pub name: String, pub github_username: Option, pub permission: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthIntrospectResponse { pub active: bool, #[serde(skip_serializing_if = "Option::is_none")] pub scope: Option, #[serde(skip_serializing_if = "Option::is_none")] pub client_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub username: Option, #[serde(skip_serializing_if = "Option::is_none")] pub token_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub exp: Option, #[serde(skip_serializing_if = "Option::is_none")] pub iat: Option, #[serde(skip_serializing_if = "Option::is_none")] pub sub: Option, #[serde(skip_serializing_if = "Option::is_none")] pub aud: Option, #[serde(skip_serializing_if = "Option::is_none")] pub iss: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthAuthorizationUrlResponse { pub url: String, pub state: String, } fn get_iv_hex() -> String { SecurityService::generate_iv_hex() } #[tauri::command] pub async fn auth_get_status( sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let state_guard = state.read(); let db_conn = state_guard.db.read().clone(); let mut settings = state_guard.settings.write(); settings.attach_db(db_conn); settings.initialize().await?; let mut permissions = state_guard.permissions.write(); let status = AuthService::get_status(&settings, sender_id, &mut permissions); let response = AuthStatusResponse { permission: status.permission, has_admin_password: status.has_admin_password, has_points_password: status.has_points_password, has_recovery_string: status.has_recovery_string, }; Ok(IpcResponse::success(response)) } #[tauri::command] pub async fn auth_login( password: String, sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let sender = sender_id.unwrap_or(0); let iv_hex = get_iv_hex(); let result = { let state_guard = state.read(); let db_conn = state_guard.db.read().clone(); let mut settings = state_guard.settings.write(); settings.attach_db(db_conn); settings.initialize().await?; let security = state_guard.security.read(); let mut permissions = state_guard.permissions.write(); AuthService::login( &mut settings, &security, &mut permissions, sender, &password, &iv_hex, ) .await }; if result.success { Ok(IpcResponse::success(LoginResponse { permission: result.permission.unwrap_or_else(|| "view".to_string()), })) } else { Ok(IpcResponse::failure_with_type( &result.message.unwrap_or_else(|| "Login failed".to_string()), )) } } #[tauri::command] pub async fn auth_logout( sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let default_permission = { let state_guard = state.read(); let mut permissions = state_guard.permissions.write(); if let Some(id) = sender_id { let level = AuthService::logout(&mut permissions, id); level.as_str().to_string() } else { permissions.get_default_permission().as_str().to_string() } }; Ok(IpcResponse::success(LoginResponse { permission: default_permission, })) } #[tauri::command] pub async fn auth_set_passwords( admin_password: Option, points_password: Option, sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let sender = sender_id.unwrap_or(0); let iv_hex = get_iv_hex(); let payload = SetPasswordsPayload { admin_password, points_password, }; let result = { let state_guard = state.read(); let db_conn = state_guard.db.read().clone(); let mut settings = state_guard.settings.write(); settings.attach_db(db_conn); settings.initialize().await?; let security = state_guard.security.read(); let mut permissions = state_guard.permissions.write(); AuthService::set_passwords( &mut settings, &security, &mut permissions, sender, payload, &iv_hex, ) .await }; if result.success { Ok(IpcResponse::success(SetPasswordsResponse { recovery_string: result.recovery_string, })) } else { Ok(IpcResponse::failure_with_type( &result .message .unwrap_or_else(|| "Failed to set passwords".to_string()), )) } } #[tauri::command] pub async fn auth_generate_recovery( sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let sender = sender_id.unwrap_or(0); let iv_hex = get_iv_hex(); let result = { let state_guard = state.read(); let db_conn = state_guard.db.read().clone(); let mut settings = state_guard.settings.write(); settings.attach_db(db_conn); settings.initialize().await?; let security = state_guard.security.read(); let mut permissions = state_guard.permissions.write(); AuthService::generate_recovery(&mut settings, &security, &mut permissions, sender, &iv_hex) .await }; if result.success { Ok(IpcResponse::success(SetPasswordsResponse { recovery_string: result.recovery_string, })) } else { Ok(IpcResponse::failure_with_type( &result .message .unwrap_or_else(|| "Failed to generate recovery".to_string()), )) } } #[tauri::command] pub async fn auth_reset_by_recovery( recovery_string: String, sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let sender = sender_id.unwrap_or(0); let iv_hex = get_iv_hex(); let result = { let state_guard = state.read(); let db_conn = state_guard.db.read().clone(); let mut settings = state_guard.settings.write(); settings.attach_db(db_conn); settings.initialize().await?; let security = state_guard.security.read(); let mut permissions = state_guard.permissions.write(); AuthService::reset_by_recovery( &mut settings, &security, &mut permissions, sender, &recovery_string, &iv_hex, ) .await }; if result.success { Ok(IpcResponse::success(SetPasswordsResponse { recovery_string: result.recovery_string, })) } else { Ok(IpcResponse::failure_with_type( &result .message .unwrap_or_else(|| "Recovery failed".to_string()), )) } } #[tauri::command] pub async fn auth_clear_all( sender_id: Option, state: State<'_, Arc>>, ) -> Result, String> { let sender = sender_id.unwrap_or(0); let result = { let state_guard = state.read(); let db_conn = state_guard.db.read().clone(); let mut settings = state_guard.settings.write(); settings.attach_db(db_conn); settings.initialize().await?; let mut permissions = state_guard.permissions.write(); AuthService::clear_all(&mut settings, &mut permissions, sender).await }; match result { Ok(()) => Ok(IpcResponse::success(())), Err(e) => Ok(IpcResponse::error(&e)), } } #[tauri::command] pub async fn oauth_get_authorization_url( platform_id: String, callback_url: String, state: Option, ) -> Result, String> { let state = state.unwrap_or_else(|| { use rand::Rng; let mut rng = rand::thread_rng(); let random_bytes: Vec = (0..32).map(|_| rng.gen()).collect(); base64::Engine::encode( &base64::engine::general_purpose::URL_SAFE_NO_PAD, &random_bytes, ) }); let url = format!( "https://sectl.top/oauth/authorize?client_id={}&redirect_uri={}&response_type=code&state={}", platform_id, urlencoding::encode(&callback_url), urlencoding::encode(&state) ); Ok(IpcResponse::success(OAuthAuthorizationUrlResponse { url, state, })) } #[tauri::command] pub async fn oauth_exchange_code( code: String, platform_id: String, platform_secret: String, callback_url: String, ) -> Result, String> { println!( "[OAuth] 换取令牌 - code: {}, platform_id: {}, callback_url: {}", code, platform_id, callback_url ); let client = reqwest::Client::new(); let params = [ ("grant_type", "authorization_code"), ("code", &code), ("client_id", &platform_id), ("client_secret", &platform_secret), ("redirect_uri", &callback_url), ]; println!("[OAuth] 请求参数:{:?}", params); let response = client .post("https://sectl.top/api/oauth/token") .form(¶ms) .send() .await .map_err(|e| format!("Request failed: {}", e))?; println!("[OAuth] 响应状态:{}", response.status()); let response_text = response .text() .await .map_err(|e| format!("Failed to read response: {}", e))?; println!("[OAuth] 响应内容:{}", response_text); if !response_text.is_empty() && response_text.contains("error") { return Ok(IpcResponse::error(&response_text)); } let token_response: OAuthTokenResponse = serde_json::from_str(&response_text) .map_err(|e| format!("Failed to parse response: {}", e))?; Ok(IpcResponse::success(token_response)) } #[tauri::command] pub async fn oauth_revoke_token( token: String, token_type_hint: Option, platform_id: String, platform_secret: String, ) -> Result, String> { let client = reqwest::Client::new(); let mut payload = serde_json::json!({ "token": token, "client_id": platform_id, "client_secret": platform_secret }); if let Some(hint) = token_type_hint { payload["token_type_hint"] = serde_json::json!(hint); } let response = client .post("https://sectl.top/api/oauth/revoke") .json(&payload) .send() .await .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { let error_text = response .text() .await .unwrap_or_else(|_| "Unknown error".to_string()); return Ok(IpcResponse::error(&error_text)); } Ok(IpcResponse::success(())) } #[tauri::command] pub async fn oauth_introspect_token( token: String, platform_id: String, platform_secret: String, ) -> Result, String> { let client = reqwest::Client::new(); let response = client .post("https://sectl.top/api/oauth/introspect") .json(&serde_json::json!({ "token": token, "client_id": platform_id, "client_secret": platform_secret })) .send() .await .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { let error_text = response .text() .await .unwrap_or_else(|_| "Unknown error".to_string()); return Ok(IpcResponse::error(&error_text)); } let introspect_response: OAuthIntrospectResponse = response .json() .await .map_err(|e| format!("Failed to parse response: {}", e))?; Ok(IpcResponse::success(introspect_response)) } #[tauri::command] pub async fn oauth_get_user_info( access_token: String, ) -> Result, String> { let client = reqwest::Client::new(); let response = client .get("https://sectl.top/api/oauth/userinfo") .header("Authorization", format!("Bearer {}", access_token)) .send() .await .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { let error_text = response .text() .await .unwrap_or_else(|_| "Unknown error".to_string()); return Ok(IpcResponse::error(&error_text)); } let user_info: OAuthUserInfo = response .json() .await .map_err(|e| format!("Failed to parse response: {}", e))?; Ok(IpcResponse::success(user_info)) } #[tauri::command] pub async fn oauth_refresh_token( refresh_token: String, platform_id: String, platform_secret: String, ) -> Result, String> { let client = reqwest::Client::new(); let response = client .post("https://sectl.top/api/oauth/token") .json(&serde_json::json!({ "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": platform_id, "client_secret": platform_secret })) .send() .await .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { let error_text = response .text() .await .unwrap_or_else(|_| "Unknown error".to_string()); return Ok(IpcResponse::error(&error_text)); } let token_response: OAuthTokenResponse = response .json() .await .map_err(|e| format!("Failed to parse response: {}", e))?; Ok(IpcResponse::success(token_response)) }