1#![warn(missing_docs)]
15
16pub use authkestra_core::ErasedOAuthFlow;
17use authkestra_core::{
18 error::AuthError, state::Identity, CredentialsProvider, OAuthProvider, UserMapper,
19};
20#[cfg(feature = "session")]
21pub use authkestra_session::{Session, SessionConfig, SessionStore};
22
23#[cfg(not(feature = "session"))]
24#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
25pub struct SessionConfig {
27 pub secure: bool,
29 pub max_age: Option<chrono::Duration>,
31}
32
33#[cfg(not(feature = "session"))]
34impl Default for SessionConfig {
35 fn default() -> Self {
36 Self {
37 secure: true,
38 max_age: Some(chrono::Duration::minutes(15)),
39 }
40 }
41}
42
43pub use chrono;
44
45#[cfg(feature = "session")]
47pub trait SessionStoreState: Send + Sync + 'static {
48 fn get_store(&self) -> Arc<dyn SessionStore>;
50}
51
52#[cfg(feature = "session")]
53impl SessionStoreState for Configured<Arc<dyn SessionStore>> {
54 fn get_store(&self) -> Arc<dyn SessionStore> {
55 self.0.clone()
56 }
57}
58
59#[cfg(feature = "token")]
60use authkestra_token::TokenManager;
61
62#[cfg(feature = "token")]
63pub trait TokenManagerState: Send + Sync + 'static {
65 fn get_manager(&self) -> Arc<TokenManager>;
67}
68
69#[cfg(feature = "token")]
70impl TokenManagerState for Configured<Arc<TokenManager>> {
71 fn get_manager(&self) -> Arc<TokenManager> {
72 self.0.clone()
73 }
74}
75
76use std::collections::HashMap;
77use std::sync::Arc;
78
79pub mod client_credentials_flow;
81pub mod device_flow;
83pub mod oauth2;
85
86pub use client_credentials_flow::ClientCredentialsFlow;
87pub use device_flow::{DeviceAuthorizationResponse, DeviceFlow};
88pub use oauth2::OAuth2Flow;
89
90#[derive(Clone, Default)]
92pub struct Missing;
93
94#[derive(Clone)]
96pub struct Configured<T>(pub T);
97
98pub struct Authkestra<S = Missing, T = Missing> {
100 pub providers: HashMap<String, Arc<dyn ErasedOAuthFlow>>,
102 #[cfg(feature = "session")]
104 pub session_store: S,
105 #[cfg(feature = "session")]
107 pub session_config: SessionConfig,
108 #[cfg(feature = "token")]
110 pub token_manager: T,
111 #[cfg(all(not(feature = "session"), not(feature = "token")))]
113 pub _marker: std::marker::PhantomData<(S, T)>,
114 #[cfg(all(feature = "session", not(feature = "token")))]
116 pub _marker: std::marker::PhantomData<T>,
117 #[cfg(all(not(feature = "session"), feature = "token"))]
119 pub _marker: std::marker::PhantomData<S>,
120}
121
122impl<S, T> Clone for Authkestra<S, T>
123where
124 S: Clone,
125 T: Clone,
126{
127 fn clone(&self) -> Self {
128 Self {
129 providers: self.providers.clone(),
130 #[cfg(feature = "session")]
131 session_store: self.session_store.clone(),
132 #[cfg(feature = "session")]
133 session_config: self.session_config.clone(),
134 #[cfg(feature = "token")]
135 token_manager: self.token_manager.clone(),
136 #[cfg(any(not(feature = "session"), not(feature = "token")))]
137 _marker: std::marker::PhantomData,
138 }
139 }
140}
141
142impl Authkestra<Missing, Missing> {
143 pub fn builder() -> AuthkestraBuilder<Missing, Missing> {
145 AuthkestraBuilder::default()
146 }
147}
148
149#[cfg(feature = "session")]
150impl<T> Authkestra<Configured<Arc<dyn SessionStore>>, T> {
151 pub async fn create_session(&self, identity: Identity) -> Result<Session, AuthError> {
153 let session_duration = self
154 .session_config
155 .max_age
156 .unwrap_or(chrono::Duration::hours(24));
157 let session = Session {
158 id: uuid::Uuid::new_v4().to_string(),
159 identity,
160 expires_at: chrono::Utc::now() + session_duration,
161 };
162
163 self.session_store
164 .0
165 .save_session(&session)
166 .await
167 .map_err(|e| AuthError::Session(e.to_string()))?;
168
169 Ok(session)
170 }
171}
172
173#[cfg(feature = "token")]
174impl<S> Authkestra<S, Configured<Arc<TokenManager>>> {
175 pub fn issue_token(
177 &self,
178 identity: Identity,
179 expires_in_secs: u64,
180 ) -> Result<String, AuthError> {
181 self.token_manager
182 .0
183 .issue_user_token(identity, expires_in_secs, None)
184 .map_err(|e| AuthError::Token(e.to_string()))
185 }
186}
187
188pub struct AuthkestraBuilder<S, T> {
190 providers: HashMap<String, Arc<dyn ErasedOAuthFlow>>,
191 #[cfg(feature = "session")]
192 session_store: S,
193 #[cfg(feature = "session")]
194 session_config: SessionConfig,
195 #[cfg(feature = "token")]
196 token_manager: T,
197 #[cfg(all(not(feature = "session"), not(feature = "token")))]
199 pub _marker: std::marker::PhantomData<(S, T)>,
200 #[cfg(all(feature = "session", not(feature = "token")))]
202 pub _marker: std::marker::PhantomData<T>,
203 #[cfg(all(not(feature = "session"), feature = "token"))]
205 pub _marker: std::marker::PhantomData<S>,
206}
207
208impl Default for AuthkestraBuilder<Missing, Missing> {
209 fn default() -> Self {
210 Self {
211 providers: HashMap::new(),
212 #[cfg(feature = "session")]
213 session_store: Missing,
214 #[cfg(feature = "session")]
215 session_config: SessionConfig::default(),
216 #[cfg(feature = "token")]
217 token_manager: Missing,
218 #[cfg(any(not(feature = "session"), not(feature = "token")))]
219 _marker: std::marker::PhantomData,
220 }
221 }
222}
223
224impl<S, T> AuthkestraBuilder<S, T> {
225 pub fn provider<P, M>(mut self, flow: OAuth2Flow<P, M>) -> Self
227 where
228 P: OAuthProvider + 'static,
229 M: UserMapper + 'static,
230 {
231 let id = flow.provider_id();
232 self.providers.insert(id, Arc::new(flow));
233 self
234 }
235
236 #[cfg(feature = "session")]
238 pub fn session_store(
239 self,
240 store: Arc<dyn SessionStore>,
241 ) -> AuthkestraBuilder<Configured<Arc<dyn SessionStore>>, T> {
242 AuthkestraBuilder {
243 providers: self.providers,
244 session_store: Configured(store),
245 session_config: self.session_config,
246 #[cfg(feature = "token")]
247 token_manager: self.token_manager,
248 #[cfg(any(not(feature = "session"), not(feature = "token")))]
249 _marker: std::marker::PhantomData,
250 }
251 }
252
253 #[cfg(feature = "token")]
255 pub fn token_manager(
256 self,
257 manager: Arc<TokenManager>,
258 ) -> AuthkestraBuilder<S, Configured<Arc<TokenManager>>> {
259 AuthkestraBuilder {
260 providers: self.providers,
261 #[cfg(feature = "session")]
262 session_store: self.session_store,
263 #[cfg(feature = "session")]
264 session_config: self.session_config,
265 token_manager: Configured(manager),
266 #[cfg(any(not(feature = "session"), not(feature = "token")))]
267 _marker: std::marker::PhantomData,
268 }
269 }
270
271 #[cfg(feature = "token")]
273 pub fn jwt_secret(self, secret: &[u8]) -> AuthkestraBuilder<S, Configured<Arc<TokenManager>>> {
274 self.token_manager(Arc::new(TokenManager::new(secret, None)))
275 }
276
277 pub fn build(self) -> Authkestra<S, T> {
279 Authkestra {
280 providers: self.providers,
281 #[cfg(feature = "session")]
282 session_store: self.session_store,
283 #[cfg(feature = "session")]
284 session_config: self.session_config,
285 #[cfg(feature = "token")]
286 token_manager: self.token_manager,
287 #[cfg(any(not(feature = "session"), not(feature = "token")))]
288 _marker: std::marker::PhantomData,
289 }
290 }
291}
292
293#[cfg(feature = "session")]
294impl<S, T> AuthkestraBuilder<S, T> {
295 pub fn session_config(mut self, config: SessionConfig) -> Self {
297 self.session_config = config;
298 self
299 }
300}
301
302#[cfg(feature = "token")]
303impl<S> AuthkestraBuilder<S, Configured<Arc<TokenManager>>> {
304 pub fn jwt_issuer(self, issuer: impl Into<String>) -> Self {
308 let manager = Arc::new((*self.token_manager.0).clone().with_issuer(issuer.into()));
309 self.token_manager(manager)
310 }
311}
312
313#[cfg(feature = "session")]
315pub trait HasSessionStore {
316 fn session_store(&self) -> Arc<dyn SessionStore>;
318}
319
320#[cfg(feature = "session")]
321impl<T> HasSessionStore for Authkestra<Configured<Arc<dyn SessionStore>>, T> {
322 fn session_store(&self) -> Arc<dyn SessionStore> {
323 self.session_store.0.clone()
324 }
325}
326
327#[cfg(feature = "token")]
329pub trait HasTokenManager {
330 fn token_manager(&self) -> Arc<TokenManager>;
332}
333
334#[cfg(feature = "token")]
335impl<S> HasTokenManager for Authkestra<S, Configured<Arc<TokenManager>>> {
336 fn token_manager(&self) -> Arc<TokenManager> {
337 self.token_manager.0.clone()
338 }
339}
340
341#[cfg(feature = "session")]
343pub type HasSessionStoreMarker = Configured<Arc<dyn SessionStore>>;
344pub type NoSessionStoreMarker = Missing;
346
347#[cfg(feature = "token")]
348pub type HasTokenManagerMarker = Configured<Arc<TokenManager>>;
350pub type NoTokenManagerMarker = Missing;
352
353#[cfg(feature = "session")]
358pub type StatefullAuthkestra = Authkestra<HasSessionStoreMarker, NoTokenManagerMarker>;
359
360#[cfg(feature = "token")]
361pub type StatelessAuthkestra = Authkestra<NoSessionStoreMarker, HasTokenManagerMarker>;
366
367pub struct CredentialsFlow<P: CredentialsProvider, M: UserMapper = ()> {
369 provider: P,
370 mapper: Option<M>,
371}
372
373impl<P: CredentialsProvider> CredentialsFlow<P, ()> {
374 pub fn new(provider: P) -> Self {
376 Self {
377 provider,
378 mapper: None,
379 }
380 }
381}
382
383impl<P: CredentialsProvider, M: UserMapper> CredentialsFlow<P, M> {
384 pub fn with_mapper(provider: P, mapper: M) -> Self {
386 Self {
387 provider,
388 mapper: Some(mapper),
389 }
390 }
391
392 pub async fn authenticate(
394 &self,
395 creds: P::Credentials,
396 ) -> Result<(Identity, Option<M::LocalUser>), AuthError> {
397 let identity = self.provider.authenticate(creds).await?;
398
399 let local_user = if let Some(mapper) = &self.mapper {
400 Some(mapper.map_user(&identity).await?)
401 } else {
402 None
403 };
404
405 Ok((identity, local_user))
406 }
407}