1use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use rustauth_core::context::AuthContext;
8
9use crate::options::{ScimAuditEvent, ScimAuditEventKind, ScimAuditSeverity, ScimOptions};
10
11type AuditEventFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
12
13#[derive(Clone)]
15pub struct ScimAuditEventResolver {
16 resolver: Arc<dyn Fn(ScimAuditEvent) -> AuditEventFuture + Send + Sync>,
17}
18
19impl ScimAuditEventResolver {
20 pub fn new<F, Fut>(resolver: F) -> Self
22 where
23 F: Fn(ScimAuditEvent) -> Fut + Send + Sync + 'static,
24 Fut: Future<Output = ()> + Send + 'static,
25 {
26 Self {
27 resolver: Arc::new(move |event| Box::pin(resolver(event))),
28 }
29 }
30
31 pub async fn resolve(&self, event: ScimAuditEvent) {
33 (self.resolver)(event).await;
34 }
35}
36
37impl std::fmt::Debug for ScimAuditEventResolver {
38 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 formatter.write_str("ScimAuditEventResolver(..)")
40 }
41}
42
43impl PartialEq for ScimAuditEventResolver {
44 fn eq(&self, _other: &Self) -> bool {
45 false
46 }
47}
48
49pub(crate) async fn emit(context: &AuthContext, options: &ScimOptions, event: ScimAuditEvent) {
50 log(context, &event);
51 if let Some(resolver) = &options.audit_event {
52 resolver.resolve(event).await;
53 }
54}
55
56fn log(context: &AuthContext, event: &ScimAuditEvent) {
57 let message = match event.kind {
58 ScimAuditEventKind::TokenGenerated => "scim token generated",
59 ScimAuditEventKind::UserProvisioned => "scim user provisioned",
60 ScimAuditEventKind::UserDeprovisioned => "scim user deprovisioned",
61 ScimAuditEventKind::BulkFailed => "scim bulk operation failed",
62 ScimAuditEventKind::BulkRolledBack => "scim atomic bulk rolled back",
63 };
64 let mut args = Vec::new();
65 if let Some(provider_id) = event.provider_id.as_deref() {
66 args.push(provider_id);
67 }
68 if let Some(user_id) = event.user_id.as_deref() {
69 args.push(user_id);
70 }
71 if let Some(organization_id) = event.organization_id.as_deref() {
72 args.push(organization_id);
73 }
74 if let Some(reason) = event.reason.as_deref() {
75 args.push(reason);
76 }
77 match event.severity {
78 ScimAuditSeverity::Info => context.logger.info(message, &args),
79 ScimAuditSeverity::Warn => context.logger.warn(message, &args),
80 ScimAuditSeverity::Error => context.logger.error(message, &args),
81 }
82}