authkestra_engine/
engine.rs1use crate::auth::session::{Session, SessionConfig, SessionStore};
2use crate::auth::{AuthError, ErasedOAuthFlow, Identity};
3#[cfg(feature = "token")]
4use crate::token::TokenManager;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8#[derive(Clone, Default, Debug)]
10pub struct Missing;
11
12#[derive(Clone, Debug)]
14pub struct Configured<T>(pub T);
15
16pub trait SessionStoreState: Send + Sync + Clone {
18 fn get_store(&self) -> Arc<dyn SessionStore>;
20}
21
22impl SessionStoreState for Configured<Arc<dyn SessionStore>> {
23 fn get_store(&self) -> Arc<dyn SessionStore> {
24 self.0.clone()
25 }
26}
27
28pub trait TokenManagerState: Send + Sync + Clone {
30 #[cfg(feature = "token")]
32 fn get_manager(&self) -> Arc<TokenManager>;
33}
34
35#[cfg(feature = "token")]
36impl TokenManagerState for Configured<Arc<TokenManager>> {
37 fn get_manager(&self) -> Arc<TokenManager> {
38 self.0.clone()
39 }
40}
41
42pub struct Engine<S = Missing, T = Missing> {
48 pub providers: HashMap<String, Arc<dyn ErasedOAuthFlow>>,
50 pub session_store: S,
52 pub session_config: SessionConfig,
54 #[cfg(feature = "token")]
56 pub token_manager: T,
57}
58
59impl<S, T> Clone for Engine<S, T>
60where
61 S: Clone,
62 T: Clone,
63{
64 fn clone(&self) -> Self {
65 Self {
66 providers: self.providers.clone(),
67 session_store: self.session_store.clone(),
68 session_config: self.session_config.clone(),
69 #[cfg(feature = "token")]
70 token_manager: self.token_manager.clone(),
71 }
72 }
73}
74
75impl Engine<Missing, Missing> {
76 pub fn builder() -> EngineBuilder<Missing, Missing> {
78 EngineBuilder {
79 providers: HashMap::new(),
80 session_store: Missing,
81 session_config: SessionConfig::default(),
82 #[cfg(feature = "token")]
83 token_manager: Missing,
84 }
85 }
86}
87
88pub struct EngineBuilder<S = Missing, T = Missing> {
90 providers: HashMap<String, Arc<dyn ErasedOAuthFlow>>,
91 session_store: S,
92 session_config: SessionConfig,
93 #[cfg(feature = "token")]
94 token_manager: T,
95}
96
97impl<S, T> EngineBuilder<S, T> {
98 pub fn provider<F>(mut self, flow: F) -> Self
100 where
101 F: ErasedOAuthFlow + 'static,
102 {
103 let id = flow.provider_id();
104 self.providers.insert(id, Arc::new(flow));
105 self
106 }
107
108 pub fn session_store(
110 self,
111 store: Arc<dyn SessionStore>,
112 ) -> EngineBuilder<Configured<Arc<dyn SessionStore>>, T> {
113 EngineBuilder {
114 providers: self.providers,
115 session_store: Configured(store),
116 session_config: self.session_config,
117 #[cfg(feature = "token")]
118 token_manager: self.token_manager,
119 }
120 }
121
122 #[cfg(feature = "token")]
124 pub fn token_manager(
125 self,
126 manager: Arc<TokenManager>,
127 ) -> EngineBuilder<S, Configured<Arc<TokenManager>>> {
128 EngineBuilder {
129 providers: self.providers,
130 session_store: self.session_store,
131 session_config: self.session_config,
132 token_manager: Configured(manager),
133 }
134 }
135
136 #[cfg(feature = "token")]
138 pub fn jwt_secret(self, secret: &[u8]) -> EngineBuilder<S, Configured<Arc<TokenManager>>> {
139 self.token_manager(Arc::new(TokenManager::new(secret, None)))
140 }
141
142 pub fn session_config(mut self, config: SessionConfig) -> Self {
144 self.session_config = config;
145 self
146 }
147
148 pub fn build(self) -> Engine<S, T> {
150 Engine {
151 providers: self.providers,
152 session_store: self.session_store,
153 session_config: self.session_config,
154 #[cfg(feature = "token")]
155 token_manager: self.token_manager,
156 }
157 }
158}
159
160impl<T> Engine<Configured<Arc<dyn SessionStore>>, T> {
162 pub fn session_store(&self) -> Arc<dyn SessionStore> {
164 self.session_store.0.clone()
165 }
166
167 #[tracing::instrument(skip(self, identity), fields(user_id = %identity.external_id))]
169 pub async fn create_session(&self, identity: Identity) -> Result<Session, AuthError> {
170 let session_duration = self
171 .session_config
172 .max_age
173 .unwrap_or(chrono::Duration::hours(24));
174 let session = Session {
175 id: uuid::Uuid::new_v4().to_string(),
176 identity,
177 expires_at: chrono::Utc::now() + session_duration,
178 };
179
180 tracing::debug!(session_id = %session.id, "creating new session");
181
182 self.session_store
183 .0
184 .save_session(&session)
185 .await
186 .map_err(|e| {
187 tracing::error!(error = %e, "failed to save session");
188 AuthError::Session(e.to_string())
189 })?;
190
191 tracing::info!(session_id = %session.id, "session created successfully");
192 Ok(session)
193 }
194}
195
196#[cfg(feature = "token")]
197impl<S> Engine<S, Configured<Arc<TokenManager>>> {
198 pub fn token_manager(&self) -> Arc<TokenManager> {
200 self.token_manager.0.clone()
201 }
202
203 #[tracing::instrument(skip(self, identity), fields(user_id = %identity.external_id))]
205 pub fn issue_token(
206 &self,
207 identity: Identity,
208 expires_in_secs: u64,
209 ) -> Result<String, AuthError> {
210 tracing::debug!("issuing token for user");
211 self.token_manager
212 .0
213 .issue_user_token(identity, expires_in_secs, None, None)
214 .map_err(|e| {
215 tracing::error!(error = %e, "failed to issue token");
216 AuthError::Token(e.to_string())
217 })
218 .inspect(|_| {
219 tracing::info!("token issued successfully");
220 })
221 }
222}
223
224pub trait HasSessionStore {
226 fn session_store(&self) -> Arc<dyn SessionStore>;
228}
229
230impl<T> HasSessionStore for Engine<Configured<Arc<dyn SessionStore>>, T> {
231 fn session_store(&self) -> Arc<dyn SessionStore> {
232 self.session_store.0.clone()
233 }
234}
235
236#[cfg(feature = "token")]
238pub trait HasTokenManager {
239 fn token_manager(&self) -> Arc<TokenManager>;
241}
242
243#[cfg(feature = "token")]
244impl<S> HasTokenManager for Engine<S, Configured<Arc<TokenManager>>> {
245 fn token_manager(&self) -> Arc<TokenManager> {
246 self.token_manager.0.clone()
247 }
248}