use stateknot_core::BoxFuture;
use stateknot_runtime::AgentServiceCaller;
use std::fmt;
use thiserror::Error;
use zeroize::Zeroizing;
#[derive(Clone)]
pub struct AgentHttpCredential(Zeroizing<String>);
impl AgentHttpCredential {
pub const MAX_BYTES: usize = 8192;
pub fn new(value: impl Into<String>) -> Result<Self, AgentHttpAuthenticationError> {
let value = Zeroizing::new(value.into());
if value.is_empty() || value.starts_with('=') || value.len() > Self::MAX_BYTES {
return Err(AgentHttpAuthenticationError::Unauthenticated);
}
let mut padding = false;
for byte in value.bytes() {
if byte == b'=' {
padding = true;
} else if padding
|| !(byte.is_ascii_alphanumeric()
|| matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/'))
{
return Err(AgentHttpAuthenticationError::Unauthenticated);
}
}
Ok(Self(value))
}
#[must_use]
pub fn expose_secret(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Debug for AgentHttpCredential {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("AgentHttpCredential([REDACTED])")
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AgentHttpOperation {
Submit,
Read,
Cancel,
InspectHost,
}
impl AgentHttpOperation {
const fn bit(self) -> u8 {
match self {
Self::Submit => 1,
Self::Read => 2,
Self::Cancel => 4,
Self::InspectHost => 8,
}
}
}
#[derive(Clone, Debug)]
pub struct AgentHttpPrincipal {
caller: AgentServiceCaller,
permissions: u8,
}
impl AgentHttpPrincipal {
#[must_use]
pub fn new(
caller: AgentServiceCaller,
operations: impl IntoIterator<Item = AgentHttpOperation>,
) -> Self {
Self {
caller,
permissions: operations.into_iter().fold(0, |bits, op| bits | op.bit()),
}
}
#[must_use]
pub const fn caller(&self) -> &AgentServiceCaller {
&self.caller
}
#[must_use]
pub const fn allows(&self, operation: AgentHttpOperation) -> bool {
self.permissions & operation.bit() != 0
}
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum AgentHttpAuthenticationError {
#[error("agent_http.unauthenticated")]
Unauthenticated,
#[error("agent_http.unavailable")]
Unavailable,
}
pub trait AgentHttpAuthenticator: Send + Sync + 'static {
fn authenticate(
&self,
credential: AgentHttpCredential,
) -> BoxFuture<'_, Result<AgentHttpPrincipal, AgentHttpAuthenticationError>>;
}