Files
AutoJellyProxy/src/auth.rs

391 lines
14 KiB
Rust

use anyhow::{anyhow, Result};
use pbkdf2::{
password_hash::{PasswordHash, PasswordVerifier},
Pbkdf2,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct AuthRequest {
#[serde(rename = "Username")]
pub username: String,
#[serde(rename = "Pw")]
pub pw: String,
}
#[derive(Debug, Serialize)]
pub struct AuthResponse {
#[serde(rename = "User")]
pub user: AuthUser,
#[serde(rename = "SessionInfo")]
pub session_info: SessionInfo,
#[serde(rename = "AccessToken")]
pub access_token: String,
#[serde(rename = "ServerId")]
pub server_id: String,
}
#[derive(Debug, Serialize)]
pub struct AuthUser {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "ServerId")]
pub server_id: String,
#[serde(rename = "Id")]
pub id: String,
#[serde(rename = "HasPassword")]
pub has_password: bool,
#[serde(rename = "HasConfiguredPassword")]
pub has_configured_password: bool,
#[serde(rename = "HasConfiguredEasyPassword")]
pub has_configured_easy_password: bool,
#[serde(rename = "EnableAutoLogin")]
pub enable_auto_login: bool,
#[serde(rename = "LastLoginDate")]
pub last_login_date: Option<String>,
#[serde(rename = "LastActivityDate")]
pub last_activity_date: Option<String>,
#[serde(rename = "Configuration")]
pub configuration: UserConfiguration,
#[serde(rename = "Policy")]
pub policy: UserPolicy,
}
#[derive(Debug, Serialize)]
pub struct UserConfiguration {
#[serde(rename = "PlayDefaultAudioTrack")]
pub play_default_audio_track: bool,
#[serde(rename = "SubtitleLanguagePreference")]
pub subtitle_language_preference: String,
#[serde(rename = "DisplayMissingEpisodes")]
pub display_missing_episodes: bool,
#[serde(rename = "GroupedFolders")]
pub grouped_folders: Vec<String>,
#[serde(rename = "SubtitleMode")]
pub subtitle_mode: String,
#[serde(rename = "DisplayCollectionsView")]
pub display_collections_view: bool,
#[serde(rename = "EnableLocalPassword")]
pub enable_local_password: bool,
#[serde(rename = "OrderedViews")]
pub ordered_views: Vec<String>,
#[serde(rename = "LatestItemsExcludes")]
pub latest_items_excludes: Vec<String>,
#[serde(rename = "MyMediaExcludes")]
pub my_media_excludes: Vec<String>,
#[serde(rename = "HidePlayedInLatest")]
pub hide_played_in_latest: bool,
#[serde(rename = "RememberAudioSelections")]
pub remember_audio_selections: bool,
#[serde(rename = "RememberSubtitleSelections")]
pub remember_subtitle_selections: bool,
#[serde(rename = "EnableNextEpisodeAutoPlay")]
pub enable_next_episode_auto_play: bool,
#[serde(rename = "CastReceiverId")]
pub cast_receiver_id: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct UserPolicy {
#[serde(rename = "IsAdministrator")]
pub is_administrator: bool,
#[serde(rename = "IsHidden")]
pub is_hidden: bool,
#[serde(rename = "EnableCollectionManagement")]
pub enable_collection_management: bool,
#[serde(rename = "EnableSubtitleManagement")]
pub enable_subtitle_management: bool,
#[serde(rename = "EnableLyricManagement")]
pub enable_lyric_management: bool,
#[serde(rename = "IsDisabled")]
pub is_disabled: bool,
#[serde(rename = "MaxParentalRating")]
pub max_parental_rating: Option<i32>,
#[serde(rename = "BlockedTags")]
pub blocked_tags: Vec<String>,
#[serde(rename = "AllowedTags")]
pub allowed_tags: Vec<String>,
#[serde(rename = "EnableUserPreferenceAccess")]
pub enable_user_preference_access: bool,
#[serde(rename = "AccessSchedules")]
pub access_schedules: Vec<String>,
#[serde(rename = "BlockUnratedItems")]
pub block_unrated_items: Vec<String>,
#[serde(rename = "EnableRemoteControlOfOtherUsers")]
pub enable_remote_control_of_other_users: bool,
#[serde(rename = "EnableSharedDeviceControl")]
pub enable_shared_device_control: bool,
#[serde(rename = "EnableRemoteAccess")]
pub enable_remote_access: bool,
#[serde(rename = "EnableLiveTvManagement")]
pub enable_live_tv_management: bool,
#[serde(rename = "EnableLiveTvAccess")]
pub enable_live_tv_access: bool,
#[serde(rename = "EnableMediaPlayback")]
pub enable_media_playback: bool,
#[serde(rename = "EnableAudioPlaybackTranscoding")]
pub enable_audio_playback_transcoding: bool,
#[serde(rename = "EnableVideoPlaybackTranscoding")]
pub enable_video_playback_transcoding: bool,
#[serde(rename = "EnablePlaybackRemuxing")]
pub enable_playback_remuxing: bool,
#[serde(rename = "ForceRemoteSourceTranscoding")]
pub force_remote_source_transcoding: bool,
#[serde(rename = "EnableContentDeletion")]
pub enable_content_deletion: bool,
#[serde(rename = "EnableContentDeletionFromFolders")]
pub enable_content_deletion_from_folders: Vec<String>,
#[serde(rename = "EnableContentDownloading")]
pub enable_content_downloading: bool,
#[serde(rename = "EnableSyncTranscoding")]
pub enable_sync_transcoding: bool,
#[serde(rename = "EnableMediaConversion")]
pub enable_media_conversion: bool,
#[serde(rename = "EnabledDevices")]
pub enabled_devices: Vec<String>,
#[serde(rename = "EnableAllDevices")]
pub enable_all_devices: bool,
#[serde(rename = "EnabledChannels")]
pub enabled_channels: Vec<String>,
#[serde(rename = "EnableAllChannels")]
pub enable_all_channels: bool,
#[serde(rename = "EnabledFolders")]
pub enabled_folders: Vec<String>,
#[serde(rename = "EnableAllFolders")]
pub enable_all_folders: bool,
#[serde(rename = "InvalidLoginAttemptCount")]
pub invalid_login_attempt_count: i32,
#[serde(rename = "LoginAttemptsBeforeLockout")]
pub login_attempts_before_lockout: i32,
#[serde(rename = "MaxActiveSessions")]
pub max_active_sessions: i32,
#[serde(rename = "EnablePublicSharing")]
pub enable_public_sharing: bool,
#[serde(rename = "BlockedMediaFolders")]
pub blocked_media_folders: Vec<String>,
#[serde(rename = "BlockedChannels")]
pub blocked_channels: Vec<String>,
#[serde(rename = "RemoteClientBitrateLimit")]
pub remote_client_bitrate_limit: i32,
#[serde(rename = "AuthenticationProviderId")]
pub authentication_provider_id: String,
#[serde(rename = "PasswordResetProviderId")]
pub password_reset_provider_id: String,
#[serde(rename = "SyncPlayAccess")]
pub sync_play_access: String,
}
#[derive(Debug, Serialize)]
pub struct SessionInfo {
#[serde(rename = "PlayState")]
pub play_state: PlayState,
#[serde(rename = "AdditionalUsers")]
pub additional_users: Vec<String>,
#[serde(rename = "Capabilities")]
pub capabilities: Capabilities,
#[serde(rename = "RemoteEndPoint")]
pub remote_end_point: String,
#[serde(rename = "Id")]
pub id: String,
#[serde(rename = "UserId")]
pub user_id: String,
#[serde(rename = "UserName")]
pub user_name: String,
#[serde(rename = "Client")]
pub client: String,
#[serde(rename = "LastActivityDate")]
pub last_activity_date: String,
#[serde(rename = "LastPlaybackCheckIn")]
pub last_playback_check_in: String,
#[serde(rename = "DeviceName")]
pub device_name: String,
#[serde(rename = "DeviceType")]
pub device_type: String,
#[serde(rename = "NowPlayingItem")]
pub now_playing_item: Option<String>,
#[serde(rename = "DeviceId")]
pub device_id: String,
#[serde(rename = "ApplicationVersion")]
pub application_version: String,
#[serde(rename = "IsActive")]
pub is_active: bool,
#[serde(rename = "SupportsMediaControl")]
pub supports_media_control: bool,
#[serde(rename = "SupportsRemoteControl")]
pub supports_remote_control: bool,
#[serde(rename = "HasCustomDeviceName")]
pub has_custom_device_name: bool,
#[serde(rename = "ServerId")]
pub server_id: String,
#[serde(rename = "SupportedCommands")]
pub supported_commands: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct PlayState {
#[serde(rename = "CanSeek")]
pub can_seek: bool,
#[serde(rename = "IsPaused")]
pub is_paused: bool,
#[serde(rename = "IsMuted")]
pub is_muted: bool,
#[serde(rename = "RepeatMode")]
pub repeat_mode: String,
#[serde(rename = "ShuffleMode")]
pub shuffle_mode: String,
}
#[derive(Debug, Serialize)]
pub struct Capabilities {
#[serde(rename = "PlayableMediaTypes")]
pub playable_media_types: Vec<String>,
#[serde(rename = "SupportedCommands")]
pub supported_commands: Vec<String>,
#[serde(rename = "SupportsMediaControl")]
pub supports_media_control: bool,
#[serde(rename = "SupportsContentUploading")]
pub supports_content_uploading: bool,
#[serde(rename = "SupportsPersistentIdentifier")]
pub supports_persistent_identifier: bool,
#[serde(rename = "SupportsSync")]
pub supports_sync: bool,
}
pub fn verify_password(password: &str, stored_hash: &str) -> Result<bool> {
// Handle PBKDF2-SHA512 format: $PBKDF2-SHA512$iterations=210000$salt$hash
if stored_hash.starts_with("$PBKDF2-SHA512$") {
let parts: Vec<&str> = stored_hash.split('$').collect();
if parts.len() != 5 {
return Err(anyhow!("Invalid password hash format"));
}
let iterations_part = parts[2];
let salt_part = parts[3];
let hash_part = parts[4];
let iterations: u32 = iterations_part
.strip_prefix("iterations=")
.ok_or_else(|| anyhow!("Invalid iterations format"))?
.parse()?;
let salt = hex::decode(salt_part)?;
let expected_hash = hex::decode(hash_part)?;
let mut result = vec![0u8; expected_hash.len()];
pbkdf2::pbkdf2_hmac::<sha2::Sha512>(password.as_bytes(), &salt, iterations, &mut result);
Ok(result == expected_hash)
} else {
// Fallback for other hash formats
match PasswordHash::new(stored_hash) {
Ok(parsed_hash) => Ok(Pbkdf2.verify_password(password.as_bytes(), &parsed_hash).is_ok()),
Err(_) => Ok(false),
}
}
}
pub fn parse_authorization_header(auth_header: &str) -> Option<(String, String, String, String)> {
// Parse MediaBrowser authorization header
// Format: MediaBrowser Client="...", Version="...", DeviceId="...", Device="...", Token="..."
if !auth_header.starts_with("MediaBrowser ") {
return None;
}
let params_part = &auth_header[12..]; // Remove "MediaBrowser "
let mut client = String::new();
let mut version = String::new();
let mut device_id = String::new();
let mut device = String::new();
for param in params_part.split(", ") {
if let Some((key, value)) = param.split_once('=') {
let value = value.trim_matches('"');
match key {
"Client" => client = value.replace('+', " "),
"Version" => version = value.to_string(),
"DeviceId" => device_id = value.to_string(),
"Device" => device = value.replace('+', " "),
_ => {}
}
}
}
if !client.is_empty() && !version.is_empty() && !device_id.is_empty() && !device.is_empty() {
Some((client, version, device_id, device))
} else {
None
}
}
impl Default for UserConfiguration {
fn default() -> Self {
Self {
play_default_audio_track: true,
subtitle_language_preference: String::new(),
display_missing_episodes: false,
grouped_folders: Vec::new(),
subtitle_mode: "Default".to_string(),
display_collections_view: false,
enable_local_password: false,
ordered_views: Vec::new(),
latest_items_excludes: Vec::new(),
my_media_excludes: Vec::new(),
hide_played_in_latest: true,
remember_audio_selections: true,
remember_subtitle_selections: true,
enable_next_episode_auto_play: true,
cast_receiver_id: None,
}
}
}
impl Default for UserPolicy {
fn default() -> Self {
Self {
is_administrator: true,
is_hidden: false,
enable_collection_management: true,
enable_subtitle_management: true,
enable_lyric_management: true,
is_disabled: false,
max_parental_rating: None,
blocked_tags: Vec::new(),
allowed_tags: Vec::new(),
enable_user_preference_access: true,
access_schedules: Vec::new(),
block_unrated_items: Vec::new(),
enable_remote_control_of_other_users: true,
enable_shared_device_control: true,
enable_remote_access: true,
enable_live_tv_management: true,
enable_live_tv_access: true,
enable_media_playback: true,
enable_audio_playback_transcoding: true,
enable_video_playback_transcoding: true,
enable_playback_remuxing: true,
force_remote_source_transcoding: false,
enable_content_deletion: true,
enable_content_deletion_from_folders: Vec::new(),
enable_content_downloading: true,
enable_sync_transcoding: true,
enable_media_conversion: true,
enabled_devices: Vec::new(),
enable_all_devices: true,
enabled_channels: Vec::new(),
enable_all_channels: true,
enabled_folders: Vec::new(),
enable_all_folders: true,
invalid_login_attempt_count: 0,
login_attempts_before_lockout: -1,
max_active_sessions: 0,
enable_public_sharing: true,
blocked_media_folders: Vec::new(),
blocked_channels: Vec::new(),
remote_client_bitrate_limit: 0,
authentication_provider_id: "Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider".to_string(),
password_reset_provider_id: "Jellyfin.Server.Implementations.Users.DefaultPasswordResetProvider".to_string(),
sync_play_access: "CreateAndJoinGroups".to_string(),
}
}
}