ferrosonic 0.3.1

A terminal-based Subsonic music client with bit-perfect audio playback
//! Subsonic authentication helpers

use rand::Rng;

/// Generate authentication parameters for Subsonic API requests
///
/// Subsonic uses token-based authentication:
/// - salt: random string
/// - token: md5(password + salt)
pub fn generate_auth_params(password: &str) -> (String, String) {
    let salt = generate_salt();
    let token = generate_token(password, &salt);
    (salt, token)
}

/// Generate a random salt string
fn generate_salt() -> String {
    let mut rng = rand::thread_rng();
    (0..16)
        .map(|_| {
            let idx = rng.gen_range(0..36);
            if idx < 10 {
                (b'0' + idx) as char
            } else {
                (b'a' + idx - 10) as char
            }
        })
        .collect()
}

/// Generate authentication token: md5(password + salt)
fn generate_token(password: &str, salt: &str) -> String {
    let input = format!("{}{}", password, salt);
    let digest = md5::compute(input.as_bytes());
    format!("{:x}", digest)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_token() {
        // Test with known values
        let token = generate_token("sesame", "c19b2d");
        assert_eq!(token, "26719a1196d2a940705a59634eb18eab");
    }

    #[test]
    fn test_generate_salt_length() {
        let salt = generate_salt();
        assert_eq!(salt.len(), 16);
    }

    #[test]
    fn test_auth_params() {
        let (salt, token) = generate_auth_params("password");
        assert_eq!(salt.len(), 16);
        assert_eq!(token.len(), 32); // MD5 hex is 32 chars
    }
}