From d9e1e256e9cbc991c82d38abd494a4f9f26537c9 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 16 Aug 2026 11:31:34 +0200 Subject: [PATCH] fix(auth): trim the username before authenticating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The login form guarded on `username.trim()` but sent the raw value, so a trailing space from a soft keyboard reached the server verbatim. Jellyfin reports that as an unknown user, which surfaces as a 401 indistinguishable from a wrong password — the user is certain of their credentials and the app insists otherwise. Normalising in AuthManager rather than the form keeps it on the path every caller uses, alongside normalize_url. Only surrounding whitespace is stripped; interior spaces are legal in Jellyfin usernames. --- src-tauri/src/auth/mod.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) 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() {