Files
el/ui/vessels/el-auth/src/session.rs
T

106 lines
2.9 KiB
Rust

//! Session provider — server-side sessions stored in memory.
//!
//! In production, sessions are stored in Redis or Engram (configured via
//! `session_store = "redis"` or `session_store = "engram"` in `el.toml`).
//! This implementation uses in-memory storage for simplicity and testing.
use crate::{AuthContext, AuthError, AuthProvider, AuthResult, AuthUser, RoleRegistry};
use std::{
collections::HashMap,
sync::Mutex,
time::{Duration, Instant},
};
struct SessionEntry {
context: AuthContext,
created_at: Instant,
ttl: Duration,
}
impl SessionEntry {
fn is_expired(&self) -> bool {
self.created_at.elapsed() > self.ttl
}
}
/// In-memory session store.
pub struct SessionProvider {
sessions: Mutex<HashMap<String, SessionEntry>>,
pub ttl: Duration,
}
impl SessionProvider {
pub fn new() -> Self {
Self {
sessions: Mutex::new(HashMap::new()),
ttl: Duration::from_secs(3600),
}
}
pub fn with_ttl(mut self, seconds: u64) -> Self {
self.ttl = Duration::from_secs(seconds);
self
}
fn generate_session_id() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0);
format!("sess-{:016x}", nanos as u64 ^ 0x7b5e3f1a2c4d6890)
}
/// Count active (non-expired) sessions.
pub fn active_session_count(&self) -> usize {
let sessions = self.sessions.lock().expect("session lock poisoned");
sessions.values().filter(|s| !s.is_expired()).count()
}
}
impl Default for SessionProvider {
fn default() -> Self {
Self::new()
}
}
impl AuthProvider for SessionProvider {
fn name(&self) -> &'static str {
"session"
}
fn verify(&self, session_id: &str) -> AuthResult<AuthContext> {
let mut sessions = self.sessions.lock().expect("session lock poisoned");
// Clean expired sessions
sessions.retain(|_, v| !v.is_expired());
sessions
.get(session_id)
.filter(|s| !s.is_expired())
.map(|s| s.context.clone())
.ok_or(AuthError::SessionNotFound)
}
fn issue(&self, user: AuthUser, _role_registry: &RoleRegistry) -> AuthResult<String> {
let session_id = Self::generate_session_id();
let ctx = AuthContext::authenticated(user, Vec::new(), &session_id);
let entry = SessionEntry {
context: ctx,
created_at: Instant::now(),
ttl: self.ttl,
};
self.sessions
.lock()
.expect("session lock poisoned")
.insert(session_id.clone(), entry);
Ok(session_id)
}
fn revoke(&self, session_id: &str) -> AuthResult<()> {
self.sessions
.lock()
.expect("session lock poisoned")
.remove(session_id);
Ok(())
}
}