2025-02-25 19:07:26 +01:00
|
|
|
use crate::api::model::app_state::AppState;
|
2025-02-06 20:31:51 +01:00
|
|
|
use crate::m3u_filter_error::{create_m3u_filter_error_result, info_err, M3uFilterError, M3uFilterErrorKind};
|
2025-02-25 19:07:26 +01:00
|
|
|
use crate::model::config::Config;
|
2025-02-26 13:46:23 +01:00
|
|
|
use crate::repository::user_repository::{backup_api_user_db_file, get_api_user_db_path, load_api_user, merge_api_user};
|
2025-02-06 20:31:51 +01:00
|
|
|
use crate::utils::file::config_reader;
|
2025-02-25 19:07:26 +01:00
|
|
|
use chrono::Local;
|
2023-12-02 20:47:26 +01:00
|
|
|
use enum_iterator::Sequence;
|
2025-03-23 12:35:31 +01:00
|
|
|
use log::debug;
|
|
|
|
|
use std::cmp::PartialEq;
|
|
|
|
|
use std::collections::HashSet;
|
2025-02-25 19:07:26 +01:00
|
|
|
use std::fmt::Display;
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::str::FromStr;
|
2025-03-23 12:35:31 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
|
|
|
|
pub enum UserConnectionPermission {
|
|
|
|
|
Exhausted,
|
|
|
|
|
Allowed,
|
|
|
|
|
GracePeriod,
|
|
|
|
|
}
|
2023-10-09 00:17:54 +02:00
|
|
|
|
2024-11-04 18:46:56 +01:00
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq)]
|
|
|
|
|
pub enum ProxyType {
|
2023-12-02 20:47:26 +01:00
|
|
|
#[serde(rename = "reverse")]
|
|
|
|
|
Reverse,
|
|
|
|
|
#[serde(rename = "redirect")]
|
|
|
|
|
Redirect,
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-10 10:13:50 +02:00
|
|
|
impl Default for ProxyType {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::Redirect
|
2023-12-02 20:47:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-01 11:14:04 +01:00
|
|
|
impl ProxyType {
|
|
|
|
|
const REVERSE: &'static str = "reverse";
|
|
|
|
|
const REDIRECT: &'static str = "redirect";
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-21 15:22:35 +02:00
|
|
|
impl Display for ProxyType {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2025-02-06 10:49:34 +01:00
|
|
|
write!(f, "{}", match self {
|
2025-02-06 20:31:51 +01:00
|
|
|
Self::Reverse => Self::REVERSE,
|
|
|
|
|
Self::Redirect => Self::REDIRECT,
|
2025-03-12 14:24:51 +01:00
|
|
|
})
|
2023-12-02 20:47:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for ProxyType {
|
|
|
|
|
type Err = M3uFilterError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
|
2025-02-06 20:31:51 +01:00
|
|
|
match s {
|
|
|
|
|
Self::REVERSE => Ok(Self::Reverse),
|
|
|
|
|
Self::REDIRECT => Ok(Self::Redirect),
|
|
|
|
|
_ => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown ProxyType: {}", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq)]
|
|
|
|
|
pub enum ProxyUserStatus {
|
|
|
|
|
Active, // The account is in good standing and can stream content
|
|
|
|
|
Expired, // The account can no longer access content unless it is renewed.
|
|
|
|
|
Banned, // The account is temporarily or permanently disabled. Typically used for users who violate terms of service or abuse the system.
|
|
|
|
|
Trial, // The account is marked as a trial account.
|
|
|
|
|
Disabled, // The account is inactive or deliberately disabled by the administrator.
|
|
|
|
|
Pending,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for ProxyUserStatus {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::Active
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ProxyUserStatus {
|
|
|
|
|
const ACTIVE: &'static str = "Active";
|
|
|
|
|
const EXPIRED: &'static str = "Expired";
|
|
|
|
|
const BANNED: &'static str = "Banned";
|
|
|
|
|
const TRIAL: &'static str = "Trial";
|
|
|
|
|
const DISABLED: &'static str = "Disabled";
|
|
|
|
|
const PENDING: &'static str = "Pending";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Display for ProxyUserStatus {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}", match self {
|
|
|
|
|
Self::Active => Self::ACTIVE,
|
|
|
|
|
Self::Expired => Self::EXPIRED,
|
|
|
|
|
Self::Banned => Self::BANNED,
|
|
|
|
|
Self::Trial => Self::TRIAL,
|
|
|
|
|
Self::Disabled => Self::DISABLED,
|
|
|
|
|
Self::Pending => Self::PENDING,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FromStr for ProxyUserStatus {
|
|
|
|
|
type Err = M3uFilterError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
|
|
|
|
|
match s {
|
|
|
|
|
Self::EXPIRED => Ok(Self::Expired),
|
|
|
|
|
Self::BANNED => Ok(Self::Banned),
|
|
|
|
|
Self::TRIAL => Ok(Self::Trial),
|
|
|
|
|
Self::DISABLED => Ok(Self::Disabled),
|
|
|
|
|
Self::PENDING => Ok(Self::Pending),
|
|
|
|
|
_ => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown ProxyType: {}", s)
|
2023-12-02 20:47:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 16:10:50 +02:00
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
2024-11-04 18:46:56 +01:00
|
|
|
pub struct ProxyUserCredentials {
|
2024-04-22 10:28:14 +02:00
|
|
|
pub username: String,
|
|
|
|
|
pub password: String,
|
2025-01-21 10:47:28 +01:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2024-04-22 10:28:14 +02:00
|
|
|
pub token: Option<String>,
|
2023-12-02 20:47:26 +01:00
|
|
|
#[serde(default = "ProxyType::default")]
|
2024-01-29 12:10:29 +01:00
|
|
|
pub proxy: ProxyType,
|
2025-01-21 10:47:28 +01:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2024-01-29 12:10:29 +01:00
|
|
|
pub server: Option<String>,
|
2025-01-21 10:47:28 +01:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2024-08-23 16:37:48 +02:00
|
|
|
pub epg_timeshift: Option<String>,
|
2025-02-06 20:31:51 +01:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub created_at: Option<i64>,
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub exp_date: Option<i64>,
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2025-02-06 22:06:24 +01:00
|
|
|
pub max_connections: Option<u32>,
|
2025-02-06 20:31:51 +01:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub status: Option<ProxyUserStatus>,
|
2023-10-23 09:58:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-03 01:47:41 +02:00
|
|
|
impl ProxyUserCredentials {
|
2025-03-22 14:24:42 +01:00
|
|
|
pub fn prepare(&mut self) {
|
|
|
|
|
self.trim();
|
2024-04-22 10:28:14 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-23 09:58:36 +02:00
|
|
|
pub fn matches_token(&self, token: &str) -> bool {
|
2024-04-22 10:28:14 +02:00
|
|
|
if let Some(tkn) = &self.token {
|
2023-10-23 09:58:36 +02:00
|
|
|
return tkn.eq(token);
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn matches(&self, username: &str, password: &str) -> bool {
|
2024-04-22 10:28:14 +02:00
|
|
|
self.username.eq(username) && self.password.eq(password)
|
2023-10-23 09:58:36 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-26 16:23:32 +02:00
|
|
|
pub fn trim(&mut self) {
|
|
|
|
|
self.username = self.username.trim().to_string();
|
|
|
|
|
self.password = self.password.trim().to_string();
|
|
|
|
|
match &self.token {
|
|
|
|
|
None => {}
|
|
|
|
|
Some(tkn) => {
|
|
|
|
|
self.token = Some(tkn.trim().to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-06 20:31:51 +01:00
|
|
|
|
2025-02-24 22:13:07 +01:00
|
|
|
pub fn validate(&self) -> Result<(), M3uFilterError> {
|
|
|
|
|
if self.username.is_empty() {
|
|
|
|
|
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Username required".to_string()));
|
|
|
|
|
}
|
|
|
|
|
if self.password.is_empty() {
|
|
|
|
|
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Password required".to_string()));
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-16 17:08:00 +01:00
|
|
|
pub fn has_permissions(&self, app_state: &AppState) -> bool {
|
2025-02-07 11:30:37 +01:00
|
|
|
if app_state.config.user_access_control {
|
|
|
|
|
if let Some(exp_date) = self.exp_date.as_ref() {
|
|
|
|
|
let now = Local::now();
|
|
|
|
|
if (exp_date - now.timestamp()) < 0 {
|
2025-02-13 19:14:01 +01:00
|
|
|
debug!("User access denied, expired: {}", self.username);
|
2025-02-07 11:30:37 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-02-06 22:06:24 +01:00
|
|
|
}
|
2025-03-23 12:35:31 +01:00
|
|
|
|
2025-02-07 11:30:37 +01:00
|
|
|
if let Some(status) = &self.status {
|
2025-02-14 17:51:33 +01:00
|
|
|
if !matches!(status, ProxyUserStatus::Active | ProxyUserStatus::Trial) {
|
|
|
|
|
debug!("User access denied, status invalid: {status} for user: {}", self.username);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-02-06 20:31:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
2025-03-16 17:08:00 +01:00
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn permission_denied(&self, app_state: &AppState) -> bool {
|
|
|
|
|
!self.has_permissions(app_state)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-03-23 12:35:31 +01:00
|
|
|
pub async fn connection_permission(&self, app_state: &AppState) -> UserConnectionPermission {
|
2025-03-16 17:08:00 +01:00
|
|
|
if app_state.config.user_access_control {
|
2025-03-23 12:35:31 +01:00
|
|
|
|
|
|
|
|
// we allow requests with max connection reached, but we should block streaming after grace period
|
|
|
|
|
if let Some(&max_connections) = self.max_connections.as_ref() {
|
|
|
|
|
if max_connections > 0 {
|
|
|
|
|
let current_connections = app_state.get_active_connections_for_user(&self.username).await;
|
|
|
|
|
if current_connections == max_connections + 1 {
|
|
|
|
|
return UserConnectionPermission::GracePeriod;
|
|
|
|
|
}
|
|
|
|
|
if current_connections > max_connections {
|
|
|
|
|
debug!("User access denied, too many connections: {}", self.username);
|
|
|
|
|
return UserConnectionPermission::Exhausted;
|
|
|
|
|
}
|
2025-03-16 17:08:00 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-23 12:35:31 +01:00
|
|
|
UserConnectionPermission::Allowed
|
2025-03-16 17:08:00 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-23 12:35:31 +01:00
|
|
|
|
2025-03-16 17:08:00 +01:00
|
|
|
#[inline]
|
2025-03-23 12:35:31 +01:00
|
|
|
pub async fn connections_exhausted(&self, app_state: &Arc<AppState>) -> bool {
|
|
|
|
|
self.connection_permission(app_state).await == UserConnectionPermission::Exhausted
|
2025-03-16 17:08:00 +01:00
|
|
|
}
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
2024-11-04 18:46:56 +01:00
|
|
|
pub struct TargetUser {
|
2023-10-08 19:54:59 +02:00
|
|
|
pub target: String,
|
2024-05-03 01:47:41 +02:00
|
|
|
pub credentials: Vec<ProxyUserCredentials>,
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TargetUser {
|
2024-12-01 10:07:24 +01:00
|
|
|
pub fn get_target_name(
|
|
|
|
|
&self,
|
|
|
|
|
username: &str,
|
|
|
|
|
password: &str,
|
|
|
|
|
) -> Option<(&ProxyUserCredentials, &str)> {
|
|
|
|
|
self.credentials
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|c| c.matches(username, password))
|
2023-10-25 09:57:44 +02:00
|
|
|
.map(|credentials| (credentials, self.target.as_str()))
|
2023-10-23 09:58:36 +02:00
|
|
|
}
|
2024-05-03 01:47:41 +02:00
|
|
|
pub fn get_target_name_by_token(&self, token: &str) -> Option<(&ProxyUserCredentials, &str)> {
|
2024-12-01 10:07:24 +01:00
|
|
|
self.credentials
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|c| c.matches_token(token))
|
2023-10-25 09:57:44 +02:00
|
|
|
.map(|credentials| (credentials, self.target.as_str()))
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
2024-11-04 18:46:56 +01:00
|
|
|
pub struct ApiProxyServerInfo {
|
2024-01-29 12:10:29 +01:00
|
|
|
pub name: String,
|
2023-10-09 00:17:54 +02:00
|
|
|
pub protocol: String,
|
2024-01-29 12:10:29 +01:00
|
|
|
pub host: String,
|
2025-01-30 17:23:41 +01:00
|
|
|
pub port: String,
|
2023-10-09 00:17:54 +02:00
|
|
|
pub timezone: String,
|
|
|
|
|
pub message: String,
|
2025-01-30 17:23:41 +01:00
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub path: Option<String>,
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-05 17:26:33 +01:00
|
|
|
impl ApiProxyServerInfo {
|
2023-10-27 08:56:32 +02:00
|
|
|
pub fn is_valid(&mut self) -> bool {
|
|
|
|
|
self.protocol = self.protocol.trim().to_string();
|
|
|
|
|
if self.protocol.is_empty() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-01-29 12:10:29 +01:00
|
|
|
self.host = self.host.trim().to_string();
|
|
|
|
|
if self.host.is_empty() {
|
2023-10-27 08:56:32 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-01-30 17:23:41 +01:00
|
|
|
self.port = self.port.trim().to_string();
|
|
|
|
|
if self.port.is_empty() {
|
|
|
|
|
if self.protocol == "http" {
|
|
|
|
|
self.port = "80".to_string();
|
|
|
|
|
} else {
|
|
|
|
|
self.port = "443".to_string();
|
|
|
|
|
}
|
|
|
|
|
} else if self.port.parse::<u16>().is_err() {
|
2023-10-27 08:56:32 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-01-30 17:23:41 +01:00
|
|
|
|
2023-10-27 08:56:32 +02:00
|
|
|
self.timezone = self.timezone.trim().to_string();
|
|
|
|
|
if self.timezone.is_empty() {
|
|
|
|
|
self.timezone = "UTC".to_string();
|
|
|
|
|
}
|
|
|
|
|
if self.message.is_empty() {
|
|
|
|
|
self.message = "Welcome to m3u-filter".to_string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
2024-04-01 16:49:12 +02:00
|
|
|
|
2024-11-04 18:46:56 +01:00
|
|
|
pub fn get_base_url(&self) -> String {
|
2025-01-30 17:23:41 +01:00
|
|
|
let base_url = if self.port.is_empty() {
|
|
|
|
|
format!("{}://{}", self.protocol, self.host)
|
2024-12-01 10:07:24 +01:00
|
|
|
} else {
|
2025-01-30 17:23:41 +01:00
|
|
|
format!("{}://{}:{}", self.protocol, self.host, self.port)
|
2024-12-01 10:07:24 +01:00
|
|
|
};
|
2025-01-30 17:23:41 +01:00
|
|
|
|
|
|
|
|
match &self.path {
|
|
|
|
|
None => base_url,
|
|
|
|
|
Some(path) => format!("{base_url}/{}", path.trim_matches('/'))
|
2024-04-01 16:49:12 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-27 08:56:32 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 00:17:54 +02:00
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
2024-11-04 18:46:56 +01:00
|
|
|
pub struct ApiProxyConfig {
|
2024-01-29 12:10:29 +01:00
|
|
|
pub server: Vec<ApiProxyServerInfo>,
|
2023-10-08 19:54:59 +02:00
|
|
|
pub user: Vec<TargetUser>,
|
2025-02-25 19:07:26 +01:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub use_user_db: bool,
|
|
|
|
|
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-09 00:17:54 +02:00
|
|
|
impl ApiProxyConfig {
|
2025-02-25 19:07:26 +01:00
|
|
|
// we have the option to store user in the config file or in the user_db
|
|
|
|
|
// When we switch from one to other we need to migrate the existing data.
|
|
|
|
|
fn migrate_api_user(&mut self, cfg: &Config, errors: &mut Vec<String>) {
|
|
|
|
|
if self.use_user_db {
|
|
|
|
|
// we have user defined in config file.
|
|
|
|
|
// we migrate them to the db and delete them from the config file
|
|
|
|
|
if !&self.user.is_empty() {
|
|
|
|
|
if let Err(err) = merge_api_user(cfg, &self.user) {
|
|
|
|
|
errors.push(err.to_string());
|
|
|
|
|
} else {
|
|
|
|
|
let api_proxy_file = cfg.t_api_proxy_file_path.as_str();
|
|
|
|
|
let backup_dir = cfg.backup_dir.as_ref().unwrap().as_str();
|
|
|
|
|
self.user = vec![];
|
2025-02-26 13:46:23 +01:00
|
|
|
if let Err(err) = config_reader::save_api_proxy(api_proxy_file, backup_dir, self) {
|
2025-02-25 19:07:26 +01:00
|
|
|
errors.push(format!("Error saving api proxy file: {err}"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
match load_api_user(cfg) {
|
|
|
|
|
Ok(users) => {
|
|
|
|
|
self.user = users;
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
println!("{err}");
|
2025-02-26 13:46:23 +01:00
|
|
|
errors.push(err.to_string());
|
2025-03-23 12:35:31 +01:00
|
|
|
}
|
2025-02-25 19:07:26 +01:00
|
|
|
};
|
2024-01-29 12:10:29 +01:00
|
|
|
} else {
|
2025-02-25 19:07:26 +01:00
|
|
|
let user_db_path = get_api_user_db_path(cfg);
|
|
|
|
|
if user_db_path.exists() {
|
|
|
|
|
// we cant have user defined in db file.
|
|
|
|
|
// we need to load them and save them into the config file
|
|
|
|
|
if let Ok(stored_users) = load_api_user(cfg) {
|
|
|
|
|
for stored_user in stored_users {
|
|
|
|
|
if let Some(target_user) = self.user.iter_mut().find(|t| t.target == stored_user.target) {
|
2025-02-26 13:46:23 +01:00
|
|
|
for stored_credential in &stored_user.credentials {
|
|
|
|
|
if !target_user.credentials.iter().any(|c| c.username == stored_credential.username) {
|
2025-02-25 19:07:26 +01:00
|
|
|
target_user.credentials.push(stored_credential.clone());
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.user.push(stored_user);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let api_proxy_file = cfg.t_api_proxy_file_path.as_str();
|
|
|
|
|
let backup_dir = cfg.backup_dir.as_ref().unwrap().as_str();
|
2025-02-26 13:46:23 +01:00
|
|
|
if let Err(err) = config_reader::save_api_proxy(api_proxy_file, backup_dir, self) {
|
2025-02-25 19:07:26 +01:00
|
|
|
errors.push(format!("Error saving api proxy file: {err}"));
|
2024-01-29 12:10:29 +01:00
|
|
|
} else {
|
2025-02-26 13:46:23 +01:00
|
|
|
backup_api_user_db_file(cfg, &user_db_path);
|
2025-02-25 19:07:26 +01:00
|
|
|
let _ = fs::remove_file(&user_db_path);
|
2024-01-29 12:10:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-25 19:07:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn prepare_server_config(&mut self, errors: &mut Vec<String>) {
|
|
|
|
|
let mut name_set = HashSet::new();
|
|
|
|
|
for server in &self.server {
|
|
|
|
|
if server.name.trim().is_empty() {
|
|
|
|
|
errors.push("Server info name is empty ".to_owned());
|
|
|
|
|
} else if name_set.contains(server.name.as_str()) {
|
|
|
|
|
errors.push(format!("Non unique server info name found {}", &server.name));
|
|
|
|
|
} else {
|
|
|
|
|
name_set.insert(server.name.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-22 14:24:42 +01:00
|
|
|
fn prepare_target_user(&mut self, errors: &mut Vec<String>) {
|
2025-02-25 19:07:26 +01:00
|
|
|
let mut usernames = HashSet::new();
|
|
|
|
|
let mut tokens = HashSet::new();
|
2023-10-23 09:58:36 +02:00
|
|
|
for target_user in &mut self.user {
|
|
|
|
|
for user in &mut target_user.credentials {
|
2025-03-22 14:24:42 +01:00
|
|
|
user.prepare();
|
2023-10-09 00:17:54 +02:00
|
|
|
if usernames.contains(&user.username) {
|
2023-10-12 19:17:25 +02:00
|
|
|
errors.push(format!("Non unique username found {}", &user.username));
|
2023-10-09 00:17:54 +02:00
|
|
|
} else {
|
|
|
|
|
usernames.insert(user.username.to_string());
|
|
|
|
|
}
|
2023-10-23 09:58:36 +02:00
|
|
|
if let Some(token) = &user.token {
|
|
|
|
|
if token.is_empty() {
|
2024-05-10 12:01:48 +02:00
|
|
|
user.token = None;
|
2023-10-23 09:58:36 +02:00
|
|
|
} else if tokens.contains(token) {
|
|
|
|
|
errors.push(format!("Non unique token found {}", &user.username));
|
|
|
|
|
} else {
|
|
|
|
|
tokens.insert(token.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-29 12:10:29 +01:00
|
|
|
|
|
|
|
|
if let Some(server_info_name) = &user.server {
|
2025-02-25 19:07:26 +01:00
|
|
|
if !&self.server.iter()
|
2024-12-01 10:07:24 +01:00
|
|
|
.any(|server_info| server_info.name.eq(server_info_name))
|
|
|
|
|
{
|
|
|
|
|
errors.push(format!(
|
|
|
|
|
"No server info with name {} found for user {}",
|
|
|
|
|
server_info_name, &user.username
|
|
|
|
|
));
|
2024-01-29 12:10:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-09 00:17:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-25 19:07:26 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-22 14:24:42 +01:00
|
|
|
pub fn prepare(&mut self, cfg: &Config) -> Result<(), M3uFilterError> {
|
2025-02-25 19:07:26 +01:00
|
|
|
let mut errors = Vec::new();
|
|
|
|
|
if self.server.is_empty() {
|
|
|
|
|
errors.push("No server info defined".to_string());
|
|
|
|
|
} else {
|
|
|
|
|
self.prepare_server_config(&mut errors);
|
|
|
|
|
}
|
2025-03-22 14:24:42 +01:00
|
|
|
self.prepare_target_user(&mut errors);
|
2025-02-25 19:07:26 +01:00
|
|
|
self.migrate_api_user(cfg, &mut errors);
|
2023-10-12 19:17:25 +02:00
|
|
|
if errors.is_empty() {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
2024-12-23 11:30:33 +01:00
|
|
|
Err(info_err!(errors.join("\n")))
|
2023-10-12 19:17:25 +02:00
|
|
|
}
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-01 10:07:24 +01:00
|
|
|
pub fn get_target_name(
|
|
|
|
|
&self,
|
|
|
|
|
username: &str,
|
|
|
|
|
password: &str,
|
|
|
|
|
) -> Option<(ProxyUserCredentials, String)> {
|
2023-10-08 19:54:59 +02:00
|
|
|
for target_user in &self.user {
|
2024-12-01 10:07:24 +01:00
|
|
|
if let Some((credentials, target_name)) =
|
|
|
|
|
target_user.get_target_name(username, password)
|
|
|
|
|
{
|
2023-10-26 16:23:32 +02:00
|
|
|
return Some((credentials.clone(), target_name.to_string()));
|
2023-10-08 19:54:59 +02:00
|
|
|
};
|
|
|
|
|
}
|
2024-12-05 19:54:34 +01:00
|
|
|
debug!("Could not find any target for user {username}");
|
2023-10-08 19:54:59 +02:00
|
|
|
None
|
2023-10-07 16:10:50 +02:00
|
|
|
}
|
2023-10-23 09:58:36 +02:00
|
|
|
|
2024-05-03 01:47:41 +02:00
|
|
|
pub fn get_target_name_by_token(&self, token: &str) -> Option<(ProxyUserCredentials, String)> {
|
2023-10-23 09:58:36 +02:00
|
|
|
for target_user in &self.user {
|
2023-10-25 09:57:44 +02:00
|
|
|
if let Some((credentials, target_name)) = target_user.get_target_name_by_token(token) {
|
2023-10-26 16:23:32 +02:00
|
|
|
return Some((credentials.clone(), target_name.to_string()));
|
2023-10-23 09:58:36 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
2024-12-30 12:40:22 +01:00
|
|
|
|
2025-02-06 20:31:51 +01:00
|
|
|
pub fn get_user_credentials(&self, username: &str) -> Option<ProxyUserCredentials> {
|
2024-12-30 12:40:22 +01:00
|
|
|
let result = self.user.iter()
|
|
|
|
|
.flat_map(|target_user| &target_user.credentials)
|
|
|
|
|
.find(|credential| credential.username == username)
|
|
|
|
|
.cloned();
|
2025-02-22 17:11:16 +01:00
|
|
|
if result.is_none() && username != "test" {
|
|
|
|
|
debug!("Could not find any user {username}");
|
2024-12-30 12:40:22 +01:00
|
|
|
}
|
|
|
|
|
result
|
|
|
|
|
}
|
2024-12-01 10:07:24 +01:00
|
|
|
}
|