systemprompt_traits/
auth.rs1use async_trait::async_trait;
12use std::future::Future;
13use systemprompt_identifiers::UserId;
14
15use crate::BoxedSource;
16
17pub type AuthResult<T> = Result<T, AuthProviderError>;
18
19#[derive(Debug, thiserror::Error)]
20#[non_exhaustive]
21pub enum AuthProviderError {
22 #[error("Invalid credentials")]
23 InvalidCredentials,
24
25 #[error("User not found")]
26 UserNotFound,
27
28 #[error("Invalid token")]
29 InvalidToken,
30
31 #[error("Token expired")]
32 TokenExpired,
33
34 #[error("Insufficient permissions")]
35 InsufficientPermissions,
36
37 #[error("Internal error: {0}")]
38 Internal(#[source] BoxedSource),
39}
40
41#[derive(Debug, Clone)]
42pub struct AuthUser {
43 pub id: UserId,
44 pub name: String,
45 pub email: String,
46 pub roles: Vec<String>,
47 pub is_active: bool,
48}
49
50#[derive(Debug, Clone, Default)]
57pub struct FederatedIdentityClaims {
58 pub email: Option<String>,
59 pub email_verified: bool,
60 pub name: Option<String>,
61 pub preferred_username: Option<String>,
62 pub roles: Vec<String>,
63}
64
65#[derive(Debug, Clone, Default)]
74pub enum SenderIdentity {
75 Linked(FederatedIdentityClaims),
76 #[default]
77 Unlinked,
78}
79
80impl SenderIdentity {
81 #[must_use]
82 pub fn claims(&self) -> FederatedIdentityClaims {
83 match self {
84 Self::Linked(claims) => claims.clone(),
85 Self::Unlinked => FederatedIdentityClaims::default(),
86 }
87 }
88}
89
90#[async_trait]
91pub trait UserProvider: Send + Sync {
92 async fn find_by_id(&self, id: &UserId) -> AuthResult<Option<AuthUser>>;
93 async fn find_by_email(&self, email: &str) -> AuthResult<Option<AuthUser>>;
94 async fn find_by_name(&self, name: &str) -> AuthResult<Option<AuthUser>>;
95 async fn create_user(
96 &self,
97 name: &str,
98 email: &str,
99 full_name: Option<&str>,
100 ) -> AuthResult<AuthUser>;
101 async fn create_anonymous(&self, fingerprint: &str) -> AuthResult<AuthUser>;
102 async fn assign_roles(&self, user_id: &UserId, roles: &[String]) -> AuthResult<()>;
103
104 async fn find_or_create_federated(
105 &self,
106 issuer: &str,
107 external_sub: &str,
108 claims: &FederatedIdentityClaims,
109 ) -> AuthResult<UserId>;
110
111 async fn promote_anonymous(&self, source: &UserId, target: &UserId) -> AuthResult<u64>;
112}
113
114pub trait RoleProvider: Send + Sync {
115 fn get_roles(&self, user_id: &UserId) -> impl Future<Output = AuthResult<Vec<String>>> + Send;
116 fn assign_role(
117 &self,
118 user_id: &UserId,
119 role: &str,
120 ) -> impl Future<Output = AuthResult<()>> + Send;
121 fn revoke_role(
122 &self,
123 user_id: &UserId,
124 role: &str,
125 ) -> impl Future<Output = AuthResult<()>> + Send;
126 fn list_users_by_role(
127 &self,
128 role: &str,
129 ) -> impl Future<Output = AuthResult<Vec<AuthUser>>> + Send;
130}