diff --git a/src/main.rs b/src/main.rs index 6f94157..abbbcdb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -#![cfg_attr(nightly, feature(never_type))] +#![cfg_attr(nightly, feature(never_type))] +#![feature(entry_insert)] #![allow(dead_code)] #![allow(unused_imports)] diff --git a/src/server/state.rs b/src/server/state.rs index 8b87f13..640feb8 100644 --- a/src/server/state.rs +++ b/src/server/state.rs @@ -6,4 +6,5 @@ use super::*; pub struct ServerState { root: data::Datamap, + userspace: user::Userspace, } diff --git a/src/server/web/mod.rs b/src/server/web/mod.rs index 90a88aa..32f5094 100644 --- a/src/server/web/mod.rs +++ b/src/server/web/mod.rs @@ -27,6 +27,8 @@ pub struct Config } pub mod settings; + +mod session; mod state; mod source; diff --git a/src/server/web/session.rs b/src/server/web/session.rs new file mode 100644 index 0000000..8220cab --- /dev/null +++ b/src/server/web/session.rs @@ -0,0 +1,133 @@ +//! Handles sessions and maintaining them +use super::*; +use tokio::{ + time::{ + self, + DelayQueue, + delay_queue, + }, + sync::{ + RwLock, + RwLockReadGuard, + RwLockWriteGuard, + }, +}; +use std::collections::{ + HashMap, +}; +use std::{ + task::{Context,Poll}, + pin::Pin, +}; + +use server::user::UserID; +id_type!(pub SessionID: "A unique session id"); + +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct Session +{ + id: Option, // `None` for uncoupled + + users: Vec, +} + +impl Session +{ + fn couple_to_new(self) -> Self + { + self.couple_to(SessionID::id_new()) + } + #[inline] fn couple_to(self, id: SessionID) -> Self + { + Self { + id: Some(id), + ..self + } + } + + /// See if this session object is coupled. If it is, return the session's ID. + #[inline] pub fn coupled(&self) -> Option<&SessionID> + { + self.id.as_ref() + } +} + + +pub struct SessionPurge<'a, F = fn(Session)>(&'a mut Sessions, F); + +impl<'a, F> Future for SessionPurge<'a, F> +where F: FnMut(Session) + 'a + Unpin +{ + type Output = Result<(), time::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + while let Some(res) = futures::ready!(this.0.expire.poll_expired(cx)) { + let ent = res?; + let _ = this.0.ids.remove(ent.get_ref()).map(|x| x.0).map(&mut this.1); + } + + Poll::Ready(Ok(())) + } +} + + +#[derive(Debug)] +pub struct Sessions +{ + ids: HashMap, + expire: DelayQueue, + + ttl_range: (u64, u64), + + rewrite_needed: !, +} + +impl Sessions +{ + pub fn purge(&mut self) -> SessionPurge<'_> + { + SessionPurge(self, std::mem::drop) + } + pub fn purge_now(&mut self) + { + self.purge().now_or_never(); + } + + pub fn new_session_id(&mut self, ses: Session) -> SessionID + { + self.purge_now(); + + let ses = ses.couple_to_new(); + let id = ses.coupled().unwrap().clone(); + let k = self.expire.insert(id.clone(), time::Duration::from_millis(self.ttl_range.jitter())); + self.ids.insert(id.clone(), (ses, k)); + id + } + pub fn new_session(&mut self, ses: Session) -> &mut Session + { + self.purge_now(); + + let ses = ses.couple_to_new(); + let id = ses.coupled().unwrap().clone(); + let k = self.expire.insert(id.clone(), time::Duration::from_millis(self.ttl_range.jitter())); + + &mut self.ids.entry(id).insert((ses, k)).into_mut().0 + } + + pub fn session(&mut self, id: &SessionID) -> Option<&mut Session> + { + /////////TODO: `Session` and `Sessions` need a whole rework. We can't be returning references that borrow this container, it will block all other consumers wanting to get their own session reference. Rewrite using `Arc`s instead of `Session` s. + self.purge_now(); + self.ids.get_mut(id).map(|(ses, _)| ses) + } + + pub fn new(cfg: &settings::Settings) -> Sessions + { + Self{ + ids: HashMap::new(), + expire: DelayQueue::new(), + + ttl_range: cfg.auth_token_ttl_millis, + } + } +} diff --git a/src/server/web/state.rs b/src/server/web/state.rs index d1aed71..3540a92 100644 --- a/src/server/web/state.rs +++ b/src/server/web/state.rs @@ -14,6 +14,8 @@ use tokio::{ RwLock, RwLockWriteGuard, RwLockReadGuard, + + Notify, }, time::{ self, @@ -130,6 +132,8 @@ impl fmt::Display for AuthCacheError } } +use session::*; + #[derive(Debug)] pub struct State { @@ -137,6 +141,8 @@ pub struct State auth_tokens: RwLock, //TODO: user auths, public keys, hashed passwords, etc. + + logged_in: Arc<(RwLock>, Notify)>, settings: Settings, } @@ -147,10 +153,25 @@ impl State { Self { auth_tokens: RwLock::new(AuthContainer::new()), - + logged_in: Arc::new((RwLock::new(Box::new(Sessions::new(&settings))), Notify::new())), backend: RwLock::new(backend), settings, - } + }.with_detatched_purger() + } + fn with_detatched_purger(self) -> Self + { + let logged_in = Arc::clone(&self.logged_in); + tokio::spawn(async move { + while Arc::strong_count(&logged_in) > 1 { + logged_in.1.notified().await; + // We're now allowed to perform cleanup. + { + let mut sessions = logged_in.0.write().await; + sessions.purge_now(); + } + } + }); + self } /// The web server settings