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)]
49#[non_exhaustive]
50pub struct Session {
51    /// Unique session identifier.
52    pub id: String,
53    /// The identity associated with this session.
54    pub identity: Identity,
55    /// When the session expires.
56    pub expires_at: chrono::DateTime<chrono::Utc>,
57}
58
59/// Trait for implementing session persistence.
60#[async_trait]
61pub trait SessionStore: Send + Sync + 'static {
62    /// Load a session by its ID.
63    async fn load_session(&self, id: &str) -> Result<Option<Session>, AuthError>;
64    /// Save or update a session.
65    async fn save_session(&self, session: &Session) -> Result<(), AuthError>;
66    /// Delete a session by its ID.
67    async fn delete_session(&self, id: &str) -> Result<(), AuthError>;
68}
69
70#[async_trait]
71impl<S: crate::store::KvStore<Session>> SessionStore for S {
72    async fn load_session(&self, id: &str) -> Result<Option<Session>, AuthError> {
73        self.get(id)
74            .await
75            .map_err(|e| AuthError::Session(e.to_string()))
76    }
77
78    async fn save_session(&self, session: &Session) -> Result<(), AuthError> {
79        let ttl_secs = (session.expires_at - chrono::Utc::now()).num_seconds();
80        let ttl = std::time::Duration::from_secs(if ttl_secs > 0 { ttl_secs as u64 } else { 0 });
81        self.set(&session.id, session.clone(), ttl)
82            .await
83            .map_err(|e| AuthError::Session(e.to_string()))
84    }
85
86    async fn delete_session(&self, id: &str) -> Result<(), AuthError> {
87        self.delete(id)
88            .await
89            .map_err(|e| AuthError::Session(e.to_string()))
90    }
91}
92
93impl Session {
94    /// Creates a new Session.
95    pub fn new(id: String, identity: Identity, expires_at: chrono::DateTime<chrono::Utc>) -> Self {
96        Self {
97            id,
98            identity,
99            expires_at,
100        }
101    }
102}