darkwing/server/dtos/entities/proxy_dto.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
//! Proxy data transfer objects and related functionality
//!
//! This module provides types and implementations for handling proxy
//! configuration data, including conversion between encrypted database records
//! and decrypted runtime representations. It supports various proxy types,
//! optional authentication, and IP change functionality.
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use crate::{
database::browser_profile::{Proxy, ProxyType},
server::{
error::{AppResult, Error},
services::database_encryption_services::DynDatabaseEncryption,
},
};
/// Represents the complete proxy configuration data with decrypted sensitive
/// fields
///
/// This struct contains all the necessary information to configure a proxy
/// connection, including optional authentication credentials and IP change
/// functionality.
#[derive(Debug, Clone)]
pub struct ProxyFullData {
/// Unique identifier for the proxy configuration
pub id: u64,
/// Type of proxy (e.g. HTTP, SOCKS5)
pub proxy_type: ProxyType,
/// Hostname or IP address of the proxy server
pub host: String,
/// Optional username for proxy authentication
pub login: Option<String>,
/// Optional password for proxy authentication
pub password: Option<String>,
/// Port number that the proxy server listens on
pub port: String,
/// Optional URL endpoint for triggering IP address rotation
pub change_ip_url: Option<String>,
}
impl ProxyFullData {
/// Creates a new `ProxyFullData` instance from an encrypted `Proxy` record
///
/// # Arguments
///
/// * `proxy` - The encrypted proxy record from the database
/// * `database_encryption` - Service for decrypting sensitive proxy data
///
/// # Returns
///
/// Returns `AppResult<Self>` which is Ok if all decryption operations succeed
///
/// # Errors
///
/// Returns an error if:
/// - Encrypted data cannot be decoded as UTF-8
/// - Decryption operations fail
/// - Required fields are missing or invalid
pub async fn new(
proxy: Proxy,
database_encryption: DynDatabaseEncryption,
) -> AppResult<Self> {
let id = proxy.id;
let login = match proxy.loginCrypt {
Some(login) => {
database_encryption
.decrypt_string(
proxy.cryptoKeyId as usize,
String::from_utf8(login).map_err(|_| {
Error::ConfigForming(
"Login cannot be decoded as its value is not valid UTF-8"
.to_string(),
)
})?,
)
.await?
}
None => proxy.deletedLogin,
};
let login = login.trim().to_string();
let login = if login.is_empty() { None } else { Some(login) };
let password = match proxy.passwordCrypt {
Some(password) => {
database_encryption
.decrypt_string(
proxy.cryptoKeyId as usize,
String::from_utf8(password).map_err(|_| {
Error::ConfigForming(
"Password cannot be decoded as its value is not valid UTF-8"
.to_string(),
)
})?,
)
.await?
}
None => proxy.password,
};
let password = password.trim().to_string();
let password = if password.is_empty() {
None
} else {
Some(password)
};
let port = match proxy.portCrypt {
Some(port) => {
database_encryption
.decrypt_string(
proxy.cryptoKeyId as usize,
String::from_utf8(port).map_err(|_| {
Error::ConfigForming(
"Port cannot be decoded as its value is not valid UTF-8"
.to_string(),
)
})?,
)
.await?
}
None => proxy
.deletedPort
.ok_or(Error::ConfigForming("Port is not set".to_string()))?,
};
let port = port.trim().to_string();
let change_ip_url = match proxy.changeIpUrlCrypt {
Some(change_ip_url) => Some(
database_encryption
.decrypt_string(
proxy.cryptoKeyId as usize,
String::from_utf8(change_ip_url).map_err(|_| {
Error::DatabaseDecoding(
"changeIpUrl".to_string(),
"value is not valid UTF-8".to_string(),
)
})?,
)
.await?,
),
None => proxy.changeIpUrl,
};
let change_ip_url = change_ip_url.and_then(|url| {
if url.is_empty() {
None
} else {
Some(url.trim().to_string())
}
});
Ok(Self {
id,
proxy_type: proxy.r#type,
host: proxy.host.trim().to_string(),
login,
password,
port,
change_ip_url,
})
}
/// Formats the proxy configuration as a standard proxy URL string
///
/// Generates a URL in the format: `protocol://[user[:pass]@]host:port`
/// where the authentication part is included only if credentials are present
///
/// # Returns
///
/// Returns a String containing the formatted proxy URL
fn as_url(&self) -> String {
match (&self.login, &self.password) {
(Some(login), None) => format!(
"{}://{}@{}:{}",
self.proxy_type, login, self.host, self.port
),
(Some(login), Some(password)) if !login.is_empty() => {
format!(
"{}://{}:{}@{}:{}",
self.proxy_type, login, password, self.host, self.port
)
}
(None, Some(password)) => format!(
"{}://:{}@{}:{}",
self.proxy_type, password, self.host, self.port
),
_ => format!("{}://{}:{}", self.proxy_type, self.host, self.port),
}
}
/// Formats the proxy configuration as a URL with base64-encoded credentials
///
/// Similar to `as_url()`, but when both login and password are present,
/// they are combined and base64-encoded in the format:
/// `protocol://base64_encoded_creds:base64@host:port`
///
/// # Returns
///
/// Returns a String containing the proxy URL with base64-encoded credentials
/// if applicable, otherwise returns a standard proxy URL
pub fn as_base64_url(&self) -> String {
match (&self.login, &self.password) {
(Some(login), Some(password)) => {
let encoded_pair = URL_SAFE
.encode(format!("{}:{}", login, password))
.replace("=", "");
format!(
"{}://{}:base64@{}:{}",
self.proxy_type, encoded_pair, self.host, self.port
)
}
_ => self.as_url(),
}
}
/// Creates a mock proxy configuration for testing purposes
///
/// # Returns
///
/// Returns a `ProxyFullData` instance configured with localhost HTTP proxy
/// settings
pub fn get_mock() -> Self {
Self {
id: 0,
proxy_type: ProxyType::Http,
host: "127.0.0.1".to_string(),
port: "8080".to_string(),
login: None,
password: None,
change_ip_url: None,
}
}
}