systemprompt_users/services/
sso_events.rs1use async_trait::async_trait;
9use systemprompt_traits::sso::{AuthEventSink, SsoLoginRefused, SsoLoginSuccess};
10
11use crate::repository::{NewSsoLoginEvent, SsoLoginEventRepository};
12
13pub const OUTCOME_SUCCESS: &str = "success";
14pub const OUTCOME_REFUSED: &str = "refused";
15
16#[derive(Debug, Clone)]
17pub struct PostgresAuthEventSink {
18 repo: SsoLoginEventRepository,
19}
20
21impl PostgresAuthEventSink {
22 pub const fn new(repo: SsoLoginEventRepository) -> Self {
23 Self { repo }
24 }
25
26 async fn write(&self, event: NewSsoLoginEvent<'_>) {
27 if let Err(e) = self.repo.insert_sso_login_event(&event).await {
28 tracing::error!(
29 provider_id = event.provider_id,
30 outcome = event.outcome,
31 error = %e,
32 "failed to record SSO login event"
33 );
34 }
35 }
36}
37
38#[async_trait]
39impl AuthEventSink for PostgresAuthEventSink {
40 async fn on_login_success(&self, event: &SsoLoginSuccess<'_>) {
41 let groups_count = i32::try_from(event.identity.groups.len()).unwrap_or(i32::MAX);
42 self.write(NewSsoLoginEvent {
43 provider_id: event.provider_id,
44 issuer: event.issuer,
45 outcome: OUTCOME_SUCCESS,
46 reason: None,
47 user_id: Some(event.user_id),
48 email: Some(event.identity.email.as_str()),
49 external_sub: Some(event.identity.external_sub.as_str()),
50 ip: event.ip,
51 groups_count: Some(groups_count),
52 })
53 .await;
54 }
55
56 async fn on_login_refused(&self, event: &SsoLoginRefused<'_>) {
57 self.write(NewSsoLoginEvent {
58 provider_id: event.provider_id,
59 issuer: event.issuer,
60 outcome: OUTCOME_REFUSED,
61 reason: Some(event.reason),
62 user_id: None,
63 email: event.email,
64 external_sub: event.external_sub,
65 ip: event.ip,
66 groups_count: None,
67 })
68 .await;
69 }
70}