use std::{
sync::Arc,
time::{Duration, Instant},
};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize)]
pub struct Ticket<'a> {
#[serde(rename = "username")]
user: &'a str,
realm: &'a str,
password: &'a str,
}
impl<'a> Ticket<'a> {
pub fn new(user: &'a str, realm: &'a str, password: &'a str) -> Self {
Self {
user,
realm,
password,
}
}
}
#[derive(Debug, Deserialize)]
pub struct TicketResponse {
#[allow(unused)]
pub username: String,
#[serde(rename = "CSRFPreventionToken")]
pub csrf_token: Option<String>,
#[serde(rename = "clustername")]
#[allow(unused)]
pub cluster_name: Option<String>,
#[serde(rename = "ticket")]
pub auth_ticket: Option<String>,
}
#[derive(Clone, Debug)]
pub struct AuthState {
inner: Arc<RwLock<Inner>>,
}
impl AuthState {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(Inner::new())),
}
}
pub fn set_api_token(&self, user: &str, realm: &str, token_id: &str, token: &str) {
self.inner.write().api_token = Some(format!("{user}@{realm}!{token_id}={token}"));
}
pub fn set_csrf(&self, ticket: String, csrf: String) {
let mut inner = self.inner.write();
inner.auth_ticket = Some(ticket);
inner.csrf_token = Some(csrf);
inner.auth_ticket_refresh_time = Instant::now() + Duration::from_secs(60 * 60);
}
pub fn should_refresh(&self) -> bool {
Instant::now()
.checked_duration_since(self.inner.read().auth_ticket_refresh_time)
.is_some()
}
pub fn auth_ticket(&self) -> Option<String> {
self.inner.read().auth_ticket.clone()
}
pub fn api_token(&self) -> Option<String> {
self.inner.read().api_token.clone()
}
pub fn headers(&self) -> impl Iterator<Item = (&str, String)> + '_ {
let inner = self.inner.read();
let cookie = inner
.auth_ticket
.as_ref()
.map(|v| ("Cookie", format!("PVEAuthCookie={v}")));
let csrf = inner
.csrf_token
.as_ref()
.map(|v| ("CSRFPreventionToken", v.to_string()));
let api_token = inner
.api_token
.as_ref()
.map(|v| ("Authorization", format!("PVEAPIToken={v}")));
cookie.into_iter().chain(csrf).chain(api_token)
}
}
#[derive(Debug)]
struct Inner {
auth_ticket: Option<String>,
auth_ticket_refresh_time: Instant,
csrf_token: Option<String>,
api_token: Option<String>,
}
impl Inner {
pub fn new() -> Self {
Self {
auth_ticket: None,
auth_ticket_refresh_time: Instant::now(),
csrf_token: None,
api_token: None,
}
}
}