use osproxy_core::{EndpointKind, ErrorCode};
use thiserror::Error;
use crate::principal::Principal;
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct ClientCredentials {
pub bearer_token: Option<String>,
pub client_cert_subject: Option<String>,
}
impl ClientCredentials {
#[must_use]
pub fn bearer(token: impl Into<String>) -> Self {
Self {
bearer_token: Some(token.into()),
client_cert_subject: None,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bearer_token.is_none() && self.client_cert_subject.is_none()
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Action {
pub endpoint: EndpointKind,
pub logical_index: String,
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum AuthError {
#[error("missing credentials")]
MissingCredentials,
#[error("invalid credentials")]
InvalidCredentials,
#[error("not authorized for the requested action")]
Unauthorized,
}
impl AuthError {
#[must_use]
pub fn code(&self) -> ErrorCode {
match self {
Self::MissingCredentials | Self::InvalidCredentials => ErrorCode::AuthFailed,
Self::Unauthorized => ErrorCode::Unauthorized,
}
}
#[must_use]
pub fn http_status(&self) -> u16 {
match self {
Self::MissingCredentials | Self::InvalidCredentials => 401,
Self::Unauthorized => 403,
}
}
}
pub trait Authenticator: Send + Sync + 'static {
fn authenticate(
&self,
creds: &ClientCredentials,
) -> impl std::future::Future<Output = Result<Principal, AuthError>> + Send;
}
pub trait Authorizer: Send + Sync + 'static {
fn authorize(
&self,
principal: &Principal,
action: &Action,
) -> impl std::future::Future<Output = Result<(), AuthError>> + Send;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auth_errors_map_to_codes_and_statuses() {
assert_eq!(AuthError::MissingCredentials.code(), ErrorCode::AuthFailed);
assert_eq!(AuthError::MissingCredentials.http_status(), 401);
assert_eq!(AuthError::Unauthorized.code(), ErrorCode::Unauthorized);
assert_eq!(AuthError::Unauthorized.http_status(), 403);
}
#[test]
fn credentials_helpers() {
assert!(ClientCredentials::default().is_empty());
let c = ClientCredentials::bearer("t");
assert!(!c.is_empty());
assert_eq!(c.bearer_token.as_deref(), Some("t"));
}
}