diff --git a/src-tauri/src/auth/mod.rs b/src-tauri/src/auth/mod.rs index e4bc17ca..3f588884 100644 --- a/src-tauri/src/auth/mod.rs +++ b/src-tauri/src/auth/mod.rs @@ -129,6 +129,18 @@ impl AuthManager { Ok(normalized) } + /// Normalize a username before it goes to the server. + /// + /// Only surrounding whitespace is stripped — interior spaces are legal in + /// Jellyfin usernames. Without this, a trailing space from a soft keyboard's + /// autocorrect makes the server report an unknown user, which surfaces as a + /// 401 that looks exactly like a wrong password. + /// + /// TRACES: UR-042 | DR-054 + pub fn normalize_username(username: &str) -> String { + username.trim().to_string() + } + /// Connect to server and get server info pub async fn connect_to_server(&self, server_url: &str) -> Result { let normalized_url = Self::normalize_url(server_url)?; @@ -185,6 +197,7 @@ impl AuthManager { ) -> Result { let url = Self::normalize_url(server_url)?; let endpoint = format!("{}/Users/AuthenticateByName", url); + let username = Self::normalize_username(username); log::info!("[AuthManager] Authenticating user: {}", username); @@ -443,6 +456,26 @@ mod tests { ); } + /// Usernames must be trimmed before they reach the server: the Android soft + /// keyboard appends a trailing space after autocorrect, and Jellyfin then + /// reports an unknown user — a 401 indistinguishable from a wrong password. + #[test] + fn test_normalize_username_trims_whitespace() { + assert_eq!(AuthManager::normalize_username("duncan "), "duncan"); + assert_eq!(AuthManager::normalize_username(" duncan"), "duncan"); + assert_eq!(AuthManager::normalize_username(" duncan "), "duncan"); + assert_eq!(AuthManager::normalize_username("duncan\n"), "duncan"); + } + + /// Interior spaces are legal in Jellyfin usernames and must survive. + #[test] + fn test_normalize_username_preserves_interior_spaces() { + assert_eq!( + AuthManager::normalize_username(" duncan tourolle "), + "duncan tourolle" + ); + } + /// Test URL normalization - real world case #[test] fn test_normalize_url_real_world_case() {