use crate::{
auth::application::{
request::login_request::LoginRequest, response::login_response::LoginResponse,
},
core::domain::value_object::base_value_object::ValueObject,
ProxmoxAuth, ProxmoxCSRFToken, ProxmoxConnection, ProxmoxError, ProxmoxResult, ProxmoxTicket,
ValidationError,
};
use reqwest::{
header::{HeaderMap, ACCEPT, CONTENT_TYPE},
Client, StatusCode,
};
use std::backtrace::Backtrace;
pub struct LoginService {
http_client: Client,
default_headers: HeaderMap,
}
impl LoginService {
pub fn new() -> Self {
let mut default_headers = HeaderMap::new();
default_headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
default_headers.insert(ACCEPT, "application/json".parse().unwrap());
Self {
http_client: Client::new(),
default_headers,
}
}
pub async fn execute(&self, connection: &ProxmoxConnection) -> ProxmoxResult<ProxmoxAuth> {
let url = self.build_login_url(connection).await?;
let request = self.build_login_request(connection).await?;
let response = self.send_request(&url, &request).await?;
match response.status() {
StatusCode::OK => self.handle_successful_login(response).await,
StatusCode::UNAUTHORIZED => Err(ProxmoxError::Authentication(
"Invalid credentials provided".to_string(),
)),
StatusCode::BAD_REQUEST => Err(ProxmoxError::Validation {
source: ValidationError::Field {
field: "request".to_string(),
message: "Invalid request format".to_string(),
},
backtrace: Backtrace::capture(),
}),
StatusCode::NOT_FOUND => Err(ProxmoxError::Connection(
"Login endpoint not found".to_string(),
)),
StatusCode::SERVICE_UNAVAILABLE => Err(ProxmoxError::Connection(
"Proxmox service is currently unavailable".to_string(),
)),
status => Err(ProxmoxError::Connection(format!(
"Unexpected response status: {}",
status
))),
}
}
async fn build_login_url(&self, connection: &ProxmoxConnection) -> ProxmoxResult<String> {
let url = connection
.proxmox_url()
.with_path("/api2/json/access/ticket")
.await?
.as_inner()
.await;
Ok(url)
}
async fn build_login_request(
&self,
connection: &ProxmoxConnection,
) -> ProxmoxResult<LoginRequest> {
Ok(LoginRequest {
username: connection.proxmox_username().as_inner().await,
password: connection.proxmox_password().as_inner().await,
realm: connection.proxmox_realm().as_inner().await,
})
}
async fn send_request(
&self,
url: &str,
request: &LoginRequest,
) -> ProxmoxResult<reqwest::Response> {
self.http_client
.post(url)
.headers(self.default_headers.clone())
.json(request)
.send()
.await
.map_err(|e| ProxmoxError::Connection(e.to_string()))
}
async fn handle_successful_login(
&self,
response: reqwest::Response,
) -> ProxmoxResult<ProxmoxAuth> {
let login_response = response.json::<LoginResponse>().await.map_err(|e| {
ProxmoxError::Connection(format!("Failed to parse login response: {}", e))
})?;
let ticket = ProxmoxTicket::new(login_response.ticket).await?;
let csrf_token = ProxmoxCSRFToken::new(login_response.csrf_token).await?;
ProxmoxAuth::new(ticket, Some(csrf_token)).await
}
}
impl Default for LoginService {
fn default() -> Self {
Self::new()
}
}