fix(auth): trim the username before authenticating

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.
This commit is contained in:
2026-08-16 11:31:34 +02:00
parent 42868fc2e6
commit d9e1e256e9
+33
View File
@@ -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<ServerInfo, String> {
let normalized_url = Self::normalize_url(server_url)?;
@@ -185,6 +197,7 @@ impl AuthManager {
) -> Result<AuthResult, String> {
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() {