use tauri::State; use crate::commands::codex_oauth::CodexOAuthState; use crate::commands::copilot::CopilotAuthState; use crate::commands::xai_oauth::XaiOAuthState; use crate::proxy::providers::codex_oauth_auth::CodexOAuthError; use crate::proxy::providers::copilot_auth::{ CopilotAuthError, GitHubAccount, GitHubDeviceCodeResponse, }; use crate::proxy::providers::xai_oauth_auth::{XaiOAuthAccount, XaiOAuthError}; const AUTH_PROVIDER_GITHUB_COPILOT: &str = "github_copilot"; const AUTH_PROVIDER_CODEX_OAUTH: &str = "codex_oauth"; const AUTH_PROVIDER_XAI_OAUTH: &str = "xai_oauth"; #[derive(Debug, Clone, serde::Serialize)] pub struct ManagedAuthAccount { pub id: String, pub provider: String, pub login: String, pub avatar_url: Option, pub authenticated_at: i64, pub is_default: bool, pub github_domain: String, pub requires_reauth: bool, } #[derive(Debug, Clone, serde::Serialize)] pub struct ManagedAuthStatus { pub provider: String, pub authenticated: bool, pub default_account_id: Option, pub migration_error: Option, pub accounts: Vec, } #[derive(Debug, Clone, serde::Serialize)] pub struct ManagedAuthDeviceCodeResponse { pub provider: String, pub device_code: String, pub user_code: String, pub verification_uri: String, pub expires_in: u64, pub interval: u64, } fn ensure_auth_provider(auth_provider: &str) -> Result<&'static str, String> { match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => Ok(AUTH_PROVIDER_GITHUB_COPILOT), AUTH_PROVIDER_CODEX_OAUTH => Ok(AUTH_PROVIDER_CODEX_OAUTH), AUTH_PROVIDER_XAI_OAUTH => Ok(AUTH_PROVIDER_XAI_OAUTH), _ => Err(format!("Unsupported auth provider: {auth_provider}")), } } fn map_account( provider: &str, account: GitHubAccount, default_account_id: Option<&str>, ) -> ManagedAuthAccount { ManagedAuthAccount { is_default: default_account_id == Some(account.id.as_str()), id: account.id, provider: provider.to_string(), login: account.login, avatar_url: account.avatar_url, authenticated_at: account.authenticated_at, github_domain: account.github_domain, requires_reauth: false, } } fn map_xai_account( account: XaiOAuthAccount, default_account_id: Option<&str>, ) -> ManagedAuthAccount { ManagedAuthAccount { is_default: default_account_id == Some(account.id.as_str()), id: account.id, provider: AUTH_PROVIDER_XAI_OAUTH.to_string(), login: account.login, avatar_url: account.avatar_url, authenticated_at: account.authenticated_at, github_domain: account.github_domain, requires_reauth: account.requires_reauth, } } fn map_device_code_response( provider: &str, response: GitHubDeviceCodeResponse, ) -> ManagedAuthDeviceCodeResponse { ManagedAuthDeviceCodeResponse { provider: provider.to_string(), device_code: response.device_code, user_code: response.user_code, verification_uri: response.verification_uri, expires_in: response.expires_in, interval: response.interval, } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_start_login( auth_provider: String, github_domain: Option, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.read().await; let response = auth_manager .start_device_flow(github_domain.as_deref()) .await .map_err(|e| e.to_string())?; Ok(map_device_code_response(auth_provider, response)) } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.read().await; let response = auth_manager .start_device_flow() .await .map_err(|e| e.to_string())?; Ok(map_device_code_response(auth_provider, response)) } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.read().await; let response = auth_manager .start_device_flow() .await .map_err(|e| e.to_string())?; Ok(map_device_code_response(auth_provider, response)) } _ => unreachable!(), } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_poll_for_account( auth_provider: String, device_code: String, github_domain: Option, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result, String> { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.write().await; match auth_manager .poll_for_token(&device_code, github_domain.as_deref()) .await { Ok(account) => { let default_account_id = auth_manager.get_status().await.default_account_id; Ok(account.map(|account| { map_account(auth_provider, account, default_account_id.as_deref()) })) } Err(CopilotAuthError::AuthorizationPending) => Ok(None), Err(e) => Err(e.to_string()), } } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.write().await; match auth_manager.poll_for_token(&device_code).await { Ok(account) => { let default_account_id = auth_manager.get_status().await.default_account_id; Ok(account.map(|account| { map_account(auth_provider, account, default_account_id.as_deref()) })) } Err(CodexOAuthError::AuthorizationPending) => Ok(None), Err(e) => Err(e.to_string()), } } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.write().await; match auth_manager.poll_for_token(&device_code).await { Ok(account) => { let default_account_id = auth_manager.get_status().await.default_account_id; Ok(account .map(|account| map_xai_account(account, default_account_id.as_deref()))) } Err(XaiOAuthError::AuthorizationPending) => Ok(None), Err(e) => Err(e.to_string()), } } _ => unreachable!(), } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_list_accounts( auth_provider: String, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result, String> { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.read().await; let status = auth_manager.get_status().await; let default_account_id = status.default_account_id.clone(); Ok(status .accounts .into_iter() .map(|account| map_account(auth_provider, account, default_account_id.as_deref())) .collect()) } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.read().await; let status = auth_manager.get_status().await; let default_account_id = status.default_account_id.clone(); Ok(status .accounts .into_iter() .map(|account| map_account(auth_provider, account, default_account_id.as_deref())) .collect()) } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.read().await; let status = auth_manager.get_status().await; let default_account_id = status.default_account_id.clone(); Ok(status .accounts .into_iter() .map(|account| map_xai_account(account, default_account_id.as_deref())) .collect()) } _ => unreachable!(), } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_get_status( auth_provider: String, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.read().await; let status = auth_manager.get_status().await; let default_account_id = status.default_account_id.clone(); Ok(ManagedAuthStatus { provider: auth_provider.to_string(), authenticated: status.authenticated, default_account_id: default_account_id.clone(), migration_error: status.migration_error, accounts: status .accounts .into_iter() .map(|account| { map_account(auth_provider, account, default_account_id.as_deref()) }) .collect(), }) } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.read().await; let status = auth_manager.get_status().await; let default_account_id = status.default_account_id.clone(); Ok(ManagedAuthStatus { provider: auth_provider.to_string(), authenticated: status.authenticated, default_account_id: default_account_id.clone(), migration_error: None, accounts: status .accounts .into_iter() .map(|account| { map_account(auth_provider, account, default_account_id.as_deref()) }) .collect(), }) } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.read().await; let status = auth_manager.get_status().await; let default_account_id = status.default_account_id.clone(); Ok(ManagedAuthStatus { provider: auth_provider.to_string(), authenticated: status.authenticated, default_account_id: default_account_id.clone(), migration_error: None, accounts: status .accounts .into_iter() .map(|account| map_xai_account(account, default_account_id.as_deref())) .collect(), }) } _ => unreachable!(), } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_remove_account( auth_provider: String, account_id: String, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result<(), String> { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.write().await; auth_manager .remove_account(&account_id) .await .map_err(|e| e.to_string()) } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.write().await; auth_manager .remove_account(&account_id) .await .map_err(|e| e.to_string()) } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.write().await; auth_manager .remove_account(&account_id) .await .map_err(|e| e.to_string()) } _ => unreachable!(), } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_set_default_account( auth_provider: String, account_id: String, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result<(), String> { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.write().await; auth_manager .set_default_account(&account_id) .await .map_err(|e| e.to_string()) } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.write().await; auth_manager .set_default_account(&account_id) .await .map_err(|e| e.to_string()) } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.write().await; auth_manager .set_default_account(&account_id) .await .map_err(|e| e.to_string()) } _ => unreachable!(), } } #[tauri::command(rename_all = "camelCase")] pub async fn auth_logout( auth_provider: String, copilot_state: State<'_, CopilotAuthState>, codex_state: State<'_, CodexOAuthState>, xai_state: State<'_, XaiOAuthState>, ) -> Result<(), String> { let auth_provider = ensure_auth_provider(&auth_provider)?; match auth_provider { AUTH_PROVIDER_GITHUB_COPILOT => { let auth_manager = copilot_state.0.write().await; auth_manager.clear_auth().await.map_err(|e| e.to_string()) } AUTH_PROVIDER_CODEX_OAUTH => { let auth_manager = codex_state.0.write().await; auth_manager.clear_auth().await.map_err(|e| e.to_string()) } AUTH_PROVIDER_XAI_OAUTH => { let auth_manager = xai_state.0.write().await; auth_manager.clear_auth().await.map_err(|e| e.to_string()) } _ => unreachable!(), } }