Skip to main content

authkestra_engine/auth/
session.rs

1use crate::auth::error::AuthError;
2use crate::auth::state::Identity;
3use crate::auth::SameSite;
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7/// Configuration for session cookies.
8#[derive(Clone, Debug)]
9pub struct SessionConfig {
10    /// The name of the session cookie.
11    pub cookie_name: String,
12    /// Whether the cookie should only be sent over HTTPS.
13    pub secure: bool,
14    /// Whether the cookie should be inaccessible to client-side scripts.
15    pub http_only: bool,
16    /// The `SameSite` attribute for the cookie.
17    pub same_site: SameSite,
18    /// The path for which the cookie is valid.
19    pub path: String,
20    /// The maximum age of the session.
21    pub max_age: Option<chrono::Duration>,
22    /// Key used to encrypt intermediate OAuth state cookies.
23    /// Must be 32 bytes for AES-256-GCM.
24    pub state_encryption_key: [u8; 32],
25}
26
27impl Default for SessionConfig {
28    fn default() -> Self {
29        let mut key = [0u8; 32];
30        // In a real app, this should be loaded from env.
31        // For default/dev, we use a fixed but "not secure" key or random.
32        // To support horizontal scaling, it MUST be consistent across instances.
33        key.copy_from_slice(b"static_key_change_in_production!");
34
35        Self {
36            cookie_name: "authkestra_session".to_string(),
37            secure: true,
38            http_only: true,
39            same_site: SameSite::Lax,
40            path: "/".to_string(),
41            max_age: Some(chrono::Duration::hours(24)),
42            state_encryption_key: key,
43        }
44    }
45}
46
47/// Represents an active user session.
48#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct Session {
50    /// Unique session identifier.
51    pub id: String,
52    /// The identity associated with this session.
53    pub identity: Identity,
54    /// When the session expires.
55    pub expires_at: chrono::DateTime<chrono::Utc>,
56}
57
58/// Trait for implementing session persistence.
59#[async_trait]
60pub trait SessionStore: Send + Sync + 'static {
61    /// Load a session by its ID.
62    async fn load_session(&self, id: &str) -> Result<Option<Session>, AuthError>;
63    /// Save or update a session.
64    async fn save_session(&self, session: &Session) -> Result<(), AuthError>;
65    /// Delete a session by its ID.
66    async fn delete_session(&self, id: &str) -> Result<(), AuthError>;
67}
68
69#[async_trait]
70impl<S: crate::store::KvStore<Session>> SessionStore for S {
71    async fn load_session(&self, id: &str) -> Result<Option<Session>, AuthError> {
72        self.get(id)
73            .await
74            .map_err(|e| AuthError::Session(e.to_string()))
75    }
76
77    async fn save_session(&self, session: &Session) -> Result<(), AuthError> {
78        let ttl_secs = (session.expires_at - chrono::Utc::now()).num_seconds();
79        let ttl = std::time::Duration::from_secs(if ttl_secs > 0 { ttl_secs as u64 } else { 0 });
80        self.set(&session.id, session.clone(), ttl)
81            .await
82            .map_err(|e| AuthError::Session(e.to_string()))
83    }
84
85    async fn delete_session(&self, id: &str) -> Result<(), AuthError> {
86        self.delete(id)
87            .await
88            .map_err(|e| AuthError::Session(e.to_string()))
89    }
90}