systemprompt_users/repository/
sso_login_events.rs1use std::net::IpAddr;
11use std::sync::Arc;
12
13use chrono::{DateTime, Utc};
14use sqlx::PgPool;
15use systemprompt_database::DbPool;
16use systemprompt_identifiers::UserId;
17
18use crate::error::Result;
19
20const MAX_LIST: i64 = 500;
21
22#[derive(Debug, Clone, Copy)]
23pub struct NewSsoLoginEvent<'a> {
24 pub provider_id: &'a str,
25 pub issuer: &'a str,
26 pub outcome: &'a str,
27 pub reason: Option<&'a str>,
28 pub user_id: Option<&'a UserId>,
29 pub email: Option<&'a str>,
30 pub external_sub: Option<&'a str>,
31 pub ip: Option<IpAddr>,
32 pub groups_count: Option<i32>,
33}
34
35#[derive(Debug, Clone)]
36pub struct SsoLoginEvent {
37 pub id: i64,
38 pub occurred_at: DateTime<Utc>,
39 pub provider_id: String,
40 pub issuer: String,
41 pub outcome: String,
42 pub reason: Option<String>,
43 pub user_id: Option<UserId>,
44 pub email: Option<String>,
45 pub external_sub: Option<String>,
46 pub ip: Option<String>,
47 pub groups_count: Option<i32>,
48}
49
50#[derive(Debug, Clone)]
51pub struct SsoLoginEventRepository {
52 pool: Arc<PgPool>,
53 write_pool: Arc<PgPool>,
54}
55
56impl SsoLoginEventRepository {
57 pub fn new(db: &DbPool) -> Self {
58 Self {
59 pool: db.pool(),
60 write_pool: db.write_pool(),
61 }
62 }
63
64 pub async fn insert_sso_login_event(&self, event: &NewSsoLoginEvent<'_>) -> Result<()> {
65 sqlx::query!(
66 "INSERT INTO sso_login_events
67 (provider_id, issuer, outcome, reason, user_id, email, external_sub, ip,
68 groups_count)
69 VALUES ($1, $2, $3, $4, $5, $6, $7, CAST($8::TEXT AS INET), $9)",
70 event.provider_id,
71 event.issuer,
72 event.outcome,
73 event.reason,
74 event.user_id.map(UserId::as_str),
75 event.email,
76 event.external_sub,
77 event.ip.map(|ip| ip.to_string()),
78 event.groups_count
79 )
80 .execute(&*self.write_pool)
81 .await?;
82 Ok(())
83 }
84
85 pub async fn list_recent_sso_login_events(&self, limit: i64) -> Result<Vec<SsoLoginEvent>> {
86 let rows = sqlx::query!(
87 r#"SELECT id, occurred_at, provider_id, issuer, outcome, reason, user_id, email,
88 external_sub, host(ip) AS "ip?", groups_count
89 FROM sso_login_events
90 ORDER BY occurred_at DESC, id DESC
91 LIMIT $1"#,
92 limit.clamp(1, MAX_LIST)
93 )
94 .fetch_all(&*self.pool)
95 .await?;
96
97 Ok(rows
98 .into_iter()
99 .map(|r| SsoLoginEvent {
100 id: r.id,
101 occurred_at: r.occurred_at,
102 provider_id: r.provider_id,
103 issuer: r.issuer,
104 outcome: r.outcome,
105 reason: r.reason,
106 user_id: r.user_id.map(UserId::new),
107 email: r.email,
108 external_sub: r.external_sub,
109 ip: r.ip,
110 groups_count: r.groups_count,
111 })
112 .collect())
113 }
114}