From 33642686f99f1f212ea56ac6caea40792e7c4120 Mon Sep 17 00:00:00 2001 From: Bl0ck154 Date: Fri, 17 Jul 2026 21:18:44 +0300 Subject: [PATCH 1/3] feat: default blank account names to email --- src-tauri/src/auth/switcher.rs | 38 ++++++++++++ src-tauri/src/types.rs | 94 +++++++++++++++++++++++++++++- src/components/AddAccountModal.tsx | 19 ++---- 3 files changed, 136 insertions(+), 15 deletions(-) diff --git a/src-tauri/src/auth/switcher.rs b/src-tauri/src/auth/switcher.rs index e213574e..8a777e72 100644 --- a/src-tauri/src/auth/switcher.rs +++ b/src-tauri/src/auth/switcher.rs @@ -142,3 +142,41 @@ pub fn has_active_login() -> Result { None => Ok(false), } } + +#[cfg(test)] +mod tests { + use super::import_from_auth_json_contents; + use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; + use serde_json::json; + + fn auth_json(payload: serde_json::Value, account_id: &str) -> String { + let payload = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap()); + serde_json::json!({ + "tokens": { + "id_token": format!("header.{payload}.signature"), + "access_token": "access", + "refresh_token": "refresh", + "account_id": account_id + } + }) + .to_string() + } + + #[test] + fn import_blank_name_uses_email() { + let account = import_from_auth_json_contents( + &auth_json(json!({"email": "imported@example.com"}), "acct-import"), + "".into(), + ) + .unwrap(); + assert_eq!(account.name, "imported@example.com"); + } + + #[test] + fn import_without_email_uses_account_id_fallback() { + let account = + import_from_auth_json_contents(&auth_json(json!({}), "acct-87654321"), "".into()) + .unwrap(); + assert_eq!(account.name, "ChatGPT account (87654321)"); + } +} diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index de1abfd2..c23d96d3 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -95,11 +95,37 @@ pub struct StoredAccount { } impl StoredAccount { + fn resolved_name( + name: String, + email: Option<&String>, + account_id: Option<&String>, + kind: &str, + ) -> String { + if !name.trim().is_empty() { + return name; + } + if let Some(email) = email.filter(|email| !email.trim().is_empty()) { + return email.clone(); + } + if let Some(account_id) = account_id.filter(|id| !id.trim().is_empty()) { + let suffix: String = account_id + .chars() + .rev() + .take(8) + .collect::() + .chars() + .rev() + .collect(); + return format!("{kind} account ({suffix})"); + } + format!("{kind} account") + } + /// Create a new account with API key authentication pub fn new_api_key(name: String, api_key: String) -> Self { Self { id: Uuid::new_v4().to_string(), - name, + name: Self::resolved_name(name, None, None, "API key"), email: None, plan_type: None, subscription_expires_at: None, @@ -121,6 +147,7 @@ impl StoredAccount { refresh_token: String, account_id: Option, ) -> Self { + let name = Self::resolved_name(name, email.as_ref(), account_id.as_ref(), "ChatGPT"); Self { id: Uuid::new_v4().to_string(), name, @@ -140,6 +167,71 @@ impl StoredAccount { } } +#[cfg(test)] +mod account_name_tests { + use super::StoredAccount; + + #[test] + fn blank_name_defaults_to_email() { + let account = StoredAccount::new_chatgpt( + "".into(), + Some("user@example.com".into()), + None, + None, + "id".into(), + "access".into(), + "refresh".into(), + Some("acct-12345678".into()), + ); + assert_eq!(account.name, "user@example.com"); + } + + #[test] + fn explicit_name_is_preserved() { + let account = StoredAccount::new_chatgpt( + " My Account ".into(), + Some("user@example.com".into()), + None, + None, + "id".into(), + "access".into(), + "refresh".into(), + None, + ); + assert_eq!(account.name, " My Account "); + } + + #[test] + fn missing_email_uses_account_id_suffix() { + let account = StoredAccount::new_chatgpt( + "".into(), + None, + None, + None, + "id".into(), + "access".into(), + "refresh".into(), + Some("acct-12345678".into()), + ); + assert_eq!(account.name, "ChatGPT account (12345678)"); + } + + #[test] + fn missing_email_and_account_id_use_generic_fallback() { + let account = StoredAccount::new_chatgpt( + "".into(), + None, + None, + None, + "id".into(), + "access".into(), + "refresh".into(), + None, + ); + assert_eq!(account.name, "ChatGPT account"); + } +} + /// Authentication mode #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] diff --git a/src/components/AddAccountModal.tsx b/src/components/AddAccountModal.tsx index 9b795dec..eb63e428 100644 --- a/src/components/AddAccountModal.tsx +++ b/src/components/AddAccountModal.tsx @@ -55,15 +55,10 @@ export function AddAccountModal({ }; const handleOAuthLogin = async () => { - if (!name.trim()) { - setError("Please enter an account name"); - return; - } - try { setLoading(true); setError(null); - const info = await onStartOAuth(name.trim()); + const info = await onStartOAuth(name); setAuthUrl(info.auth_url); setOauthPending(true); setLoading(false); @@ -88,10 +83,6 @@ export function AddAccountModal({ }; const handleImportFile = async () => { - if (!name.trim()) { - setError("Please enter an account name"); - return; - } if (!fileSource) { setError("Please select an auth.json file"); return; @@ -100,7 +91,7 @@ export function AddAccountModal({ try { setLoading(true); setError(null); - await onImportFile(fileSource, name.trim()); + await onImportFile(fileSource, name); handleClose(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); @@ -152,16 +143,16 @@ export function AddAccountModal({ {/* Content */}
- {/* Account Name (always shown) */} + {/* Account name is optional; the backend derives one when blank. */}
setName(e.target.value)} - placeholder="e.g., Work Account" + placeholder="Leave blank to use email" className="w-full px-4 py-2.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:border-gray-400 dark:focus:border-gray-500 focus:ring-1 focus:ring-gray-400 dark:focus:ring-gray-500 transition-colors" />
From 1fbba702cac676c4d537cb1c4bc6ade2d067117c Mon Sep 17 00:00:00 2001 From: Bl0ck154 Date: Thu, 23 Jul 2026 08:35:11 +0300 Subject: [PATCH 2/3] fix: normalize explicit account names --- src-tauri/src/types.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index c23d96d3..86fe7c9e 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -101,8 +101,9 @@ impl StoredAccount { account_id: Option<&String>, kind: &str, ) -> String { - if !name.trim().is_empty() { - return name; + let name = name.trim(); + if !name.is_empty() { + return name.to_string(); } if let Some(email) = email.filter(|email| !email.trim().is_empty()) { return email.clone(); @@ -187,7 +188,7 @@ mod account_name_tests { } #[test] - fn explicit_name_is_preserved() { + fn explicit_name_is_trimmed() { let account = StoredAccount::new_chatgpt( " My Account ".into(), Some("user@example.com".into()), @@ -198,7 +199,7 @@ mod account_name_tests { "refresh".into(), None, ); - assert_eq!(account.name, " My Account "); + assert_eq!(account.name, "My Account"); } #[test] From df8f81a403da01b1a084106e50e9e8c5ab04c28f Mon Sep 17 00:00:00 2001 From: Bl0ck154 Date: Fri, 24 Jul 2026 15:34:41 +0300 Subject: [PATCH 3/3] fix: scope account name trimming to add flows --- src-tauri/src/auth/switcher.rs | 11 +++++++++++ src-tauri/src/commands/oauth.rs | 2 +- src-tauri/src/types.rs | 9 ++++----- src/components/AddAccountModal.tsx | 4 ++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/auth/switcher.rs b/src-tauri/src/auth/switcher.rs index 8a777e72..e8f26394 100644 --- a/src-tauri/src/auth/switcher.rs +++ b/src-tauri/src/auth/switcher.rs @@ -96,6 +96,7 @@ pub fn import_from_auth_json_contents( ) -> Result { let auth: AuthDotJson = serde_json::from_str(&content).context("Failed to parse auth.json contents")?; + let account_name = account_name.trim().to_string(); // Determine auth mode and create account if let Some(api_key) = auth.openai_api_key { @@ -172,6 +173,16 @@ mod tests { assert_eq!(account.name, "imported@example.com"); } + #[test] + fn import_explicit_name_is_trimmed() { + let account = import_from_auth_json_contents( + &auth_json(json!({"email": "imported@example.com"}), "acct-import"), + " Imported Account ".into(), + ) + .unwrap(); + assert_eq!(account.name, "Imported Account"); + } + #[test] fn import_without_email_uses_account_id_fallback() { let account = diff --git a/src-tauri/src/commands/oauth.rs b/src-tauri/src/commands/oauth.rs index f4918147..a6e6517c 100644 --- a/src-tauri/src/commands/oauth.rs +++ b/src-tauri/src/commands/oauth.rs @@ -29,7 +29,7 @@ pub async fn start_login(account_name: String) -> Result previous.cancelled.store(true, Ordering::Relaxed); } - let (info, rx, cancelled) = start_oauth_login(account_name) + let (info, rx, cancelled) = start_oauth_login(account_name.trim().to_string()) .await .map_err(|e| e.to_string())?; diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 86fe7c9e..c23d96d3 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -101,9 +101,8 @@ impl StoredAccount { account_id: Option<&String>, kind: &str, ) -> String { - let name = name.trim(); - if !name.is_empty() { - return name.to_string(); + if !name.trim().is_empty() { + return name; } if let Some(email) = email.filter(|email| !email.trim().is_empty()) { return email.clone(); @@ -188,7 +187,7 @@ mod account_name_tests { } #[test] - fn explicit_name_is_trimmed() { + fn explicit_name_is_preserved() { let account = StoredAccount::new_chatgpt( " My Account ".into(), Some("user@example.com".into()), @@ -199,7 +198,7 @@ mod account_name_tests { "refresh".into(), None, ); - assert_eq!(account.name, "My Account"); + assert_eq!(account.name, " My Account "); } #[test] diff --git a/src/components/AddAccountModal.tsx b/src/components/AddAccountModal.tsx index eb63e428..3d868ca9 100644 --- a/src/components/AddAccountModal.tsx +++ b/src/components/AddAccountModal.tsx @@ -58,7 +58,7 @@ export function AddAccountModal({ try { setLoading(true); setError(null); - const info = await onStartOAuth(name); + const info = await onStartOAuth(name.trim()); setAuthUrl(info.auth_url); setOauthPending(true); setLoading(false); @@ -91,7 +91,7 @@ export function AddAccountModal({ try { setLoading(true); setError(null); - await onImportFile(fileSource, name); + await onImportFile(fileSource, name.trim()); handleClose(); } catch (err) { setError(err instanceof Error ? err.message : String(err));