Skip to main content

authkestra_engine/
engine.rs

1use 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/// Marker for a missing component in the typestate pattern.
9#[derive(Clone, Default, Debug)]
10pub struct Missing;
11
12/// Marker for a configured component in the typestate pattern.
13#[derive(Clone, Debug)]
14pub struct Configured<T>(pub T);
15
16/// Trait for the session store state in the `Engine`.
17pub trait SessionStoreState: Send + Sync + Clone {
18    /// Returns the session store if configured.
19    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
28/// Trait for the token manager state in the `Engine`.
29pub trait TokenManagerState: Send + Sync + Clone {
30    /// Returns the token manager if configured.
31    #[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
42/// The central orchestrator for Authkestra.
43///
44/// `Engine` ties together authentication methods, session management, and flows.
45/// It is constructed using the [`EngineBuilder`] which uses the Typestate pattern
46/// to ensure that certain methods are only available when the necessary components are configured.
47pub struct Engine<S = Missing, T = Missing> {
48    /// Map of registered OAuth providers.
49    pub providers: HashMap<String, Arc<dyn ErasedOAuthFlow>>,
50    /// The session storage backend.
51    pub session_store: S,
52    /// Configuration for session cookies.
53    pub session_config: SessionConfig,
54    /// Manager for JWT signing and verification.
55    #[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    /// Start building a new `Engine`.
77    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
88/// A builder for configuring and creating an [`Engine`] instance.
89pub 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    /// Register an OAuth provider flow.
99    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    /// Set the session store.
109    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    /// Set the token manager.
123    #[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    /// Set the JWT secret for the default token manager.
137    #[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    /// Set the session configuration.
143    pub fn session_config(mut self, config: SessionConfig) -> Self {
144        self.session_config = config;
145        self
146    }
147
148    /// Build the `Engine`.
149    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
160// Methods available only when a session store is present
161impl<T> Engine<Configured<Arc<dyn SessionStore>>, T> {
162    /// Get the session store.
163    pub fn session_store(&self) -> Arc<dyn SessionStore> {
164        self.session_store.0.clone()
165    }
166
167    /// Create a new session for the given identity.
168    #[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    /// Get the token manager.
199    pub fn token_manager(&self) -> Arc<TokenManager> {
200        self.token_manager.0.clone()
201    }
202
203    /// Issue a JWT for the given identity.
204    #[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
224/// Trait for Authkestra instances that have a session store configured.
225pub trait HasSessionStore {
226    /// Returns the session store.
227    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/// Trait for Authkestra instances that have a token manager configured.
237#[cfg(feature = "token")]
238pub trait HasTokenManager {
239    /// Returns the token manager.
240    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}