use std::sync::Arc;
pub trait AuditLogger: Send + Sync + 'static {
fn log_auth_event(&self, event: &AuthAuditEvent);
}
#[derive(Clone)]
pub struct AuditLoggerHandle {
inner: Arc<dyn AuditLogger>,
}
impl AuditLoggerHandle {
#[must_use]
pub fn new(logger: impl AuditLogger) -> Self {
Self {
inner: Arc::new(logger),
}
}
#[must_use]
pub fn shared(logger: Arc<dyn AuditLogger>) -> Self {
Self { inner: logger }
}
#[must_use]
pub fn logger(&self) -> &dyn AuditLogger {
self.inner.as_ref()
}
}
impl std::fmt::Debug for AuditLoggerHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AuditLoggerHandle").finish_non_exhaustive()
}
}
pub trait IntoAuditLoggerHandle {
fn into_audit_logger_handle(self) -> AuditLoggerHandle;
}
impl<T> IntoAuditLoggerHandle for T
where
T: AuditLogger,
{
fn into_audit_logger_handle(self) -> AuditLoggerHandle {
AuditLoggerHandle::new(self)
}
}
impl<T> IntoAuditLoggerHandle for Arc<T>
where
T: AuditLogger,
{
fn into_audit_logger_handle(self) -> AuditLoggerHandle {
let inner: Arc<dyn AuditLogger> = self;
AuditLoggerHandle::shared(inner)
}
}
impl IntoAuditLoggerHandle for Arc<dyn AuditLogger> {
fn into_audit_logger_handle(self) -> AuditLoggerHandle {
AuditLoggerHandle::shared(self)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AuthAuditEvent {
pub(crate) request_id: String,
pub(crate) client_ip: String,
pub(crate) auth_method: Option<String>,
pub(crate) subject: Option<String>,
pub(crate) outcome: AuthAuditOutcome,
}
impl AuthAuditEvent {
#[must_use]
pub fn new(
request_id: impl Into<String>,
client_ip: impl Into<String>,
outcome: AuthAuditOutcome,
) -> Self {
Self {
request_id: request_id.into(),
client_ip: client_ip.into(),
auth_method: None,
subject: None,
outcome,
}
}
#[must_use]
pub fn with_auth_method(mut self, method: impl Into<String>) -> Self {
self.auth_method = Some(method.into());
self
}
#[must_use]
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
#[must_use]
pub fn request_id(&self) -> &str {
&self.request_id
}
#[must_use]
pub fn client_ip(&self) -> &str {
&self.client_ip
}
#[must_use]
pub fn auth_method(&self) -> Option<&str> {
self.auth_method.as_deref()
}
#[must_use]
pub fn subject(&self) -> Option<&str> {
self.subject.as_deref()
}
#[must_use]
pub const fn outcome(&self) -> &AuthAuditOutcome {
&self.outcome
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AuthAuditOutcome {
Success,
Anonymous,
Denied {
reason: String,
},
Error {
error: String,
},
}
#[derive(Debug, Default)]
pub struct TracingAuditLogger;
impl AuditLogger for TracingAuditLogger {
fn log_auth_event(&self, event: &AuthAuditEvent) {
match &event.outcome {
AuthAuditOutcome::Success => {
tracing::info!(
request_id = %event.request_id,
client_ip = %event.client_ip,
auth_method = ?event.auth_method,
subject = ?event.subject,
"auth.success"
);
}
AuthAuditOutcome::Anonymous => {
tracing::debug!(
request_id = %event.request_id,
client_ip = %event.client_ip,
"auth.anonymous"
);
}
AuthAuditOutcome::Denied { reason } => {
tracing::warn!(
request_id = %event.request_id,
client_ip = %event.client_ip,
auth_method = ?event.auth_method,
subject = ?event.subject,
reason = %reason,
"auth.denied"
);
}
AuthAuditOutcome::Error { error } => {
tracing::error!(
request_id = %event.request_id,
client_ip = %event.client_ip,
error = %error,
"auth.error"
);
}
}
}
}