systemprompt_traits/
auth.rs1use async_trait::async_trait;
17use std::future::Future;
18use systemprompt_identifiers::UserId;
19
20use crate::BoxedSource;
21
22pub type AuthResult<T> = Result<T, AuthProviderError>;
23
24#[derive(Debug, thiserror::Error)]
25#[non_exhaustive]
26pub enum AuthProviderError {
27 #[error("Invalid credentials")]
28 InvalidCredentials,
29
30 #[error("User not found")]
31 UserNotFound,
32
33 #[error("Invalid token")]
34 InvalidToken,
35
36 #[error("Token expired")]
37 TokenExpired,
38
39 #[error("Insufficient permissions")]
40 InsufficientPermissions,
41
42 #[error("Service accounts cannot sign in interactively")]
43 ServiceAccountInteractiveLogin,
44
45 #[error("Internal error: {0}")]
46 Internal(#[source] BoxedSource),
47}
48
49#[derive(Debug, Clone)]
50pub struct AuthUser {
51 pub id: UserId,
52 pub name: String,
53 pub email: String,
54 pub roles: Vec<String>,
55 pub is_active: bool,
56 pub is_service_account: bool,
57}
58
59impl AuthUser {
60 pub const fn ensure_interactive(&self) -> AuthResult<()> {
61 if self.is_service_account {
62 return Err(AuthProviderError::ServiceAccountInteractiveLogin);
63 }
64 Ok(())
65 }
66}
67
68#[derive(Debug, Clone, Default)]
75pub struct FederatedIdentityClaims {
76 pub email: Option<String>,
77 pub email_verified: bool,
78 pub name: Option<String>,
79 pub preferred_username: Option<String>,
80 pub roles: Vec<String>,
81}
82
83#[derive(Debug, Clone, Default)]
92pub enum SenderIdentity {
93 Linked(FederatedIdentityClaims),
94 #[default]
95 Unlinked,
96}
97
98impl SenderIdentity {
99 #[must_use]
100 pub fn claims(&self) -> FederatedIdentityClaims {
101 match self {
102 Self::Linked(claims) => claims.clone(),
103 Self::Unlinked => FederatedIdentityClaims::default(),
104 }
105 }
106}
107
108#[async_trait]
109pub trait UserProvider: Send + Sync {
110 async fn find_by_id(&self, id: &UserId) -> AuthResult<Option<AuthUser>>;
111 async fn find_by_email(&self, email: &str) -> AuthResult<Option<AuthUser>>;
112 async fn find_by_name(&self, name: &str) -> AuthResult<Option<AuthUser>>;
113 async fn create_user(
114 &self,
115 name: &str,
116 email: &str,
117 full_name: Option<&str>,
118 ) -> AuthResult<AuthUser>;
119 async fn create_anonymous(&self, fingerprint: &str) -> AuthResult<AuthUser>;
120 async fn assign_roles(&self, user_id: &UserId, roles: &[String]) -> AuthResult<()>;
121
122 async fn find_or_create_federated(
123 &self,
124 issuer: &str,
125 external_sub: &str,
126 claims: &FederatedIdentityClaims,
127 ) -> AuthResult<UserId>;
128
129 async fn promote_anonymous(&self, source: &UserId, target: &UserId) -> AuthResult<u64>;
130}
131
132pub trait RoleProvider: Send + Sync {
133 fn get_roles(&self, user_id: &UserId) -> impl Future<Output = AuthResult<Vec<String>>> + Send;
134 fn assign_role(
135 &self,
136 user_id: &UserId,
137 role: &str,
138 ) -> impl Future<Output = AuthResult<()>> + Send;
139 fn revoke_role(
140 &self,
141 user_id: &UserId,
142 role: &str,
143 ) -> impl Future<Output = AuthResult<()>> + Send;
144 fn list_users_by_role(
145 &self,
146 role: &str,
147 ) -> impl Future<Output = AuthResult<Vec<AuthUser>>> + Send;
148}