use std::pin::Pin;
use std::sync::Arc;
use crate::authn::event::AuthEvent;
pub trait DeviceEventSink: Send + Sync + 'static {
fn emit<'a>(
&'a self,
event: AuthEvent,
) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoopDeviceEventSink;
impl DeviceEventSink for NoopDeviceEventSink {
fn emit<'a>(
&'a self,
event: AuthEvent,
) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
tracing::trace!(
target: "axess::device::events",
event_type = ?event.event_type,
"NoopDeviceEventSink: device event discarded",
);
Box::pin(async {})
}
}
pub struct IdentityStoreEventSink<S>
where
S: crate::authn::store::IdentityStore,
{
store: Arc<S>,
}
impl<S> IdentityStoreEventSink<S>
where
S: crate::authn::store::IdentityStore,
{
pub fn new(store: S) -> Self {
Self {
store: Arc::new(store),
}
}
}
impl<S> DeviceEventSink for IdentityStoreEventSink<S>
where
S: crate::authn::store::IdentityStore,
{
fn emit<'a>(
&'a self,
event: AuthEvent,
) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
let store = self.store.clone();
Box::pin(async move {
if let Err(e) = store.record_event(event).await {
tracing::warn!(error = %e, "device-event sink: record_event failed");
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::authn::event::{AuthEventBuilder, AuthEventType};
use std::sync::Mutex;
pub(crate) struct CapturingSink {
pub(crate) events: Mutex<Vec<AuthEvent>>,
}
impl CapturingSink {
pub(crate) fn new() -> Self {
Self {
events: Mutex::new(Vec::new()),
}
}
}
impl DeviceEventSink for CapturingSink {
fn emit<'a>(
&'a self,
event: AuthEvent,
) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
self.events.lock().unwrap().push(event);
})
}
}
#[tokio::test]
async fn noop_sink_is_a_silent_drop() {
let sink = NoopDeviceEventSink;
let event = AuthEventBuilder::success(AuthEventType::DeviceFirstSeen).build();
sink.emit(event).await;
}
#[tokio::test]
async fn capturing_sink_records_every_emit() {
let sink = CapturingSink::new();
sink.emit(AuthEventBuilder::success(AuthEventType::DeviceFirstSeen).build())
.await;
sink.emit(AuthEventBuilder::success(AuthEventType::DeviceRevoked).build())
.await;
let events = sink.events.lock().unwrap();
assert_eq!(events.len(), 2);
assert_eq!(events[0].event_type, AuthEventType::DeviceFirstSeen);
assert_eq!(events[1].event_type, AuthEventType::DeviceRevoked);
}
}