use std::sync::Arc;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
pub mod bounds {
pub const MAX_SUBJECT_LEN: usize = 256;
pub const MAX_OPERATION_LEN: usize = 50;
pub const MAX_ERROR_MESSAGE_LEN: usize = 1024;
pub const MAX_CONTEXT_LEN: usize = 2048;
pub const MAX_ENTRIES_IN_MEMORY: usize = 10_000;
pub const BYTES_PER_ENTRY: usize = 4096;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum AuditEventType {
JwtValidation,
JwtRefresh,
OidcCredentialAccess,
OidcTokenExchange,
SessionTokenCreated,
SessionTokenValidation,
SessionTokenRevoked,
CsrfStateGenerated,
CsrfStateValidated,
OauthStart,
OauthCallback,
AuthSuccess,
AuthFailure,
AuthorizationDenied,
AdminSqlExecution,
}
impl AuditEventType {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
AuditEventType::JwtValidation => "jwt_validation",
AuditEventType::JwtRefresh => "jwt_refresh",
AuditEventType::OidcCredentialAccess => "oidc_credential_access",
AuditEventType::OidcTokenExchange => "oidc_token_exchange",
AuditEventType::SessionTokenCreated => "session_token_created",
AuditEventType::SessionTokenValidation => "session_token_validation",
AuditEventType::SessionTokenRevoked => "session_token_revoked",
AuditEventType::CsrfStateGenerated => "csrf_state_generated",
AuditEventType::CsrfStateValidated => "csrf_state_validated",
AuditEventType::OauthStart => "oauth_start",
AuditEventType::OauthCallback => "oauth_callback",
AuditEventType::AuthSuccess => "auth_success",
AuditEventType::AuthFailure => "auth_failure",
AuditEventType::AuthorizationDenied => "authorization_denied",
AuditEventType::AdminSqlExecution => "admin_sql_execution",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SecretType {
JwtToken,
SessionToken,
ClientSecret,
RefreshToken,
AuthorizationCode,
StateToken,
CsrfToken,
AdminToken,
}
impl SecretType {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
SecretType::JwtToken => "jwt_token",
SecretType::SessionToken => "session_token",
SecretType::ClientSecret => "client_secret",
SecretType::RefreshToken => "refresh_token",
SecretType::AuthorizationCode => "authorization_code",
SecretType::StateToken => "state_token",
SecretType::CsrfToken => "csrf_token",
SecretType::AdminToken => "admin_token",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
pub event_type: AuditEventType,
pub secret_type: SecretType,
pub subject: Option<String>,
pub operation: String,
pub success: bool,
pub error_message: Option<String>,
pub context: Option<String>,
pub chain_hash: Option<String>,
}
pub trait AuditLogger: Send + Sync {
fn log_entry(&self, entry: AuditEntry);
fn log_success(
&self,
event_type: AuditEventType,
secret_type: SecretType,
subject: Option<String>,
operation: &str,
) {
self.log_entry(AuditEntry {
event_type,
secret_type,
subject,
operation: operation.to_string(),
success: true,
error_message: None,
context: None,
chain_hash: None,
});
}
fn log_failure(
&self,
event_type: AuditEventType,
secret_type: SecretType,
subject: Option<String>,
operation: &str,
error: &str,
) {
self.log_entry(AuditEntry {
event_type,
secret_type,
subject,
operation: operation.to_string(),
success: false,
error_message: Some(error.to_string()),
context: None,
chain_hash: None,
});
}
}
pub struct StructuredAuditLogger;
impl StructuredAuditLogger {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl Default for StructuredAuditLogger {
fn default() -> Self {
Self::new()
}
}
impl AuditLogger for StructuredAuditLogger {
fn log_entry(&self, entry: AuditEntry) {
if entry.success {
info!(
event_type = entry.event_type.as_str(),
secret_type = entry.secret_type.as_str(),
subject = ?entry.subject,
operation = entry.operation,
context = ?entry.context,
"Security event: successful operation"
);
} else {
warn!(
event_type = entry.event_type.as_str(),
secret_type = entry.secret_type.as_str(),
subject = ?entry.subject,
operation = entry.operation,
error = ?entry.error_message,
context = ?entry.context,
"Security event: failed operation"
);
}
}
}
pub static AUDIT_LOGGER: std::sync::OnceLock<Arc<dyn AuditLogger>> = std::sync::OnceLock::new();
pub fn init_audit_logger(logger: Arc<dyn AuditLogger>) {
let _ = AUDIT_LOGGER.set(logger);
}
pub fn get_audit_logger() -> Arc<dyn AuditLogger> {
AUDIT_LOGGER.get_or_init(|| Arc::new(StructuredAuditLogger::new())).clone()
}
pub trait AuditExt<T, E> {
fn audit_log(
self,
event_type: AuditEventType,
secret_type: SecretType,
subject: Option<String>,
operation: &str,
) -> Result<T, E>;
}
impl<T, E: std::fmt::Display> AuditExt<T, E> for Result<T, E> {
fn audit_log(
self,
event_type: AuditEventType,
secret_type: SecretType,
subject: Option<String>,
operation: &str,
) -> Result<T, E> {
let logger = get_audit_logger();
match &self {
Ok(_) => logger.log_success(event_type, secret_type, subject, operation),
Err(e) => {
logger.log_failure(event_type, secret_type, subject, operation, &e.to_string());
},
}
self
}
}