Skip to main content

systemprompt_traits/sso/
events.rs

1//! Side-effect contracts the SSO driver calls out to: the sign-in audit sink
2//! and the assertion replay guard.
3//!
4//! Both are dispatched as `Arc<dyn …>` (several sinks fan out from one
5//! driver), so they use `#[async_trait]`; native `async fn` in traits is not
6//! yet `dyn`-compatible.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use std::fmt::Debug;
12use std::net::IpAddr;
13
14use async_trait::async_trait;
15use chrono::{DateTime, Utc};
16use systemprompt_identifiers::{SessionId, UserId};
17
18use super::{GatedIdentity, SsoError};
19
20/// A completed sign-in: the identity was admitted and a session minted.
21#[derive(Debug, Clone, Copy)]
22pub struct SsoLoginSuccess<'a> {
23    pub provider_id: &'a str,
24    pub issuer: &'a str,
25    pub user_id: &'a UserId,
26    pub session_id: &'a SessionId,
27    pub identity: &'a GatedIdentity,
28    pub created: bool,
29    pub ip: Option<IpAddr>,
30}
31
32/// A refused sign-in, with the audit reason and whatever identity was known
33/// when it was refused.
34#[derive(Debug, Clone, Copy)]
35pub struct SsoLoginRefused<'a> {
36    pub provider_id: &'a str,
37    pub issuer: &'a str,
38    pub reason: &'static str,
39    pub email: Option<&'a str>,
40    pub external_sub: Option<&'a str>,
41    pub ip: Option<IpAddr>,
42}
43
44/// Receives every SSO sign-in outcome. A sink must not fail the sign-in: it
45/// logs its own errors. Held as `Arc<dyn AuthEventSink>` so several sinks fan
46/// out from one driver.
47#[async_trait]
48pub trait AuthEventSink: Send + Sync + Debug {
49    async fn on_login_success(&self, event: &SsoLoginSuccess<'_>);
50
51    async fn on_login_refused(&self, event: &SsoLoginRefused<'_>);
52
53    async fn on_logout(&self, _user_id: &UserId, _session_id: &str) {}
54}
55
56/// Single-use marker for `IdP` assertions, authoritative across instances.
57/// Held as `Arc<dyn ReplayGuard>` by the driver.
58#[async_trait]
59pub trait ReplayGuard: Send + Sync + Debug {
60    async fn claim(
61        &self,
62        scope: &str,
63        id: &str,
64        expires_at: DateTime<Utc>,
65    ) -> Result<bool, SsoError>;
66}