use axum::http::{HeaderMap, HeaderName};
use serde::Serialize;
use super::secret::generate_secret;
use super::verify::{authorize_basic, authorize_bearer, constant_time_eq};
use crate::native::error::AdminUiError;
#[derive(Clone, Debug, Serialize)]
pub enum PublicAuthMode {
Basic,
Bearer,
Header,
None,
}
impl PublicAuthMode {
pub(crate) fn as_str(&self) -> &'static str {
match self {
Self::Basic => "basic",
Self::Bearer => "bearer",
Self::Header => "header",
Self::None => "none",
}
}
}
#[derive(Clone, Debug)]
pub enum AdminAuthConfig {
Basic {
username: String,
password: String,
generated_password: bool,
},
Bearer {
token: String,
generated_token: bool,
},
Header {
header_name: HeaderName,
token: String,
generated_token: bool,
},
None,
}
#[derive(Clone, Debug, Serialize)]
pub struct AdminAuthSummary {
pub mode: PublicAuthMode,
pub username: Option<String>,
pub header_name: Option<String>,
pub generated_secret: bool,
}
impl AdminAuthConfig {
pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
Self::Basic {
username: username.into(),
password: password.into(),
generated_password: false,
}
}
pub fn basic_with_random_password(username: impl Into<String>) -> Self {
Self::Basic {
username: username.into(),
password: generate_secret(),
generated_password: true,
}
}
pub fn bearer(token: impl Into<String>, generated_token: bool) -> Self {
Self::Bearer {
token: token.into(),
generated_token,
}
}
pub fn header(
header_name: impl AsRef<str>,
token: impl Into<String>,
generated_token: bool,
) -> Result<Self, AdminUiError> {
let header_name = HeaderName::from_bytes(header_name.as_ref().as_bytes())
.map_err(|_| AdminUiError::InvalidHeaderName(header_name.as_ref().to_string()))?;
Ok(Self::Header {
header_name,
token: token.into(),
generated_token,
})
}
pub fn summary(&self) -> AdminAuthSummary {
match self {
Self::Basic {
username,
generated_password,
..
} => AdminAuthSummary {
mode: PublicAuthMode::Basic,
username: Some(username.clone()),
header_name: None,
generated_secret: *generated_password,
},
Self::Bearer {
generated_token, ..
} => AdminAuthSummary {
mode: PublicAuthMode::Bearer,
username: None,
header_name: None,
generated_secret: *generated_token,
},
Self::Header {
header_name,
generated_token,
..
} => AdminAuthSummary {
mode: PublicAuthMode::Header,
username: None,
header_name: Some(header_name.as_str().to_string()),
generated_secret: *generated_token,
},
Self::None => AdminAuthSummary {
mode: PublicAuthMode::None,
username: None,
header_name: None,
generated_secret: false,
},
}
}
pub fn secret_for_display(&self) -> Option<&str> {
match self {
Self::Basic { password, .. } => Some(password),
Self::Bearer { token, .. } | Self::Header { token, .. } => Some(token),
Self::None => None,
}
}
pub(crate) fn is_authorized(&self, headers: &HeaderMap) -> bool {
match self {
Self::None => true,
Self::Basic {
username, password, ..
} => authorize_basic(headers, username, password),
Self::Bearer { token, .. } => authorize_bearer(headers, token),
Self::Header {
header_name, token, ..
} => headers
.get(header_name)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| constant_time_eq(value.as_bytes(), token.as_bytes())),
}
}
}