You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
4.6 KiB
188 lines
4.6 KiB
//! Web server state
|
|
use super::*;
|
|
use std::{
|
|
collections::HashMap,
|
|
sync::Arc,
|
|
pin::Pin,
|
|
task::Context,
|
|
task::Poll,
|
|
|
|
fmt,error,
|
|
};
|
|
use tokio::{
|
|
sync::{
|
|
RwLock,
|
|
RwLockWriteGuard,
|
|
RwLockReadGuard,
|
|
|
|
Notify,
|
|
},
|
|
time::{
|
|
self,
|
|
DelayQueue,
|
|
delay_queue,
|
|
},
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub struct AuthContainer
|
|
{
|
|
active_req: HashMap<Uuid, (auth::AuthRequest, delay_queue::Key)>,
|
|
timeouts: DelayQueue<Uuid>,
|
|
}
|
|
|
|
pub struct AuthPurge<'a, F = fn(auth::AuthRequest)>(&'a mut AuthContainer, F);
|
|
|
|
impl<'a, F> Future for AuthPurge<'a, F>
|
|
where F: FnMut(auth::AuthRequest) + 'a + Unpin
|
|
{
|
|
type Output = Result<(), time::Error>;
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
let this = self.get_mut();
|
|
while let Some(res) = futures::ready!(this.0.timeouts.poll_expired(cx)) {
|
|
let ent = res?;
|
|
let _ = this.0.active_req.remove(ent.get_ref()).map(|x| x.0).map(&mut this.1);
|
|
}
|
|
|
|
Poll::Ready(Ok(()))
|
|
}
|
|
}
|
|
|
|
impl AuthContainer
|
|
{
|
|
/// Gerate a new empty auth token container
|
|
fn new() -> Self
|
|
{
|
|
Self {
|
|
active_req: HashMap::new(),
|
|
timeouts: DelayQueue::new(),
|
|
}
|
|
}
|
|
/// Returns a future that purges expired entries, running the provided closure on them.
|
|
///
|
|
/// The future will yield if:
|
|
/// * The stire is not empty
|
|
/// * There are non-expired entries in the store
|
|
pub fn purge_and_then<'a, F: FnMut(auth::AuthRequest) +Unpin +'a>(&'a mut self, and_then: F) -> AuthPurge<'a, F>
|
|
{
|
|
AuthPurge(self, and_then)
|
|
}
|
|
/// Returns a future that purges expired entries. See `purge_and_then`.
|
|
#[inline] pub fn purge(&mut self) -> AuthPurge<'_>
|
|
{
|
|
AuthPurge(self, std::mem::drop)
|
|
}
|
|
/// Purge all expired entries.
|
|
#[inline] pub fn purge_now(&mut self)
|
|
{
|
|
self.purge().now_or_never();
|
|
}
|
|
/// Purge all expired entries, running the provided closure on them.
|
|
pub fn purge_now_and_then<'a, F: FnMut(auth::AuthRequest) +Unpin+'a>(&'a mut self, and_then: F)
|
|
{
|
|
self.purge_and_then(and_then).now_or_never();
|
|
}
|
|
|
|
/// Insert a request into the store, setting it to expire once its ttl is up.
|
|
pub fn insert_req(&mut self, req: auth::AuthRequest)
|
|
{
|
|
self.purge_now();
|
|
|
|
let k = self.timeouts.insert(req.id, req.ttl());
|
|
self.active_req.insert(req.id, (req, k));
|
|
}
|
|
|
|
/// Attempt to retrieve a value from the store by its ID.
|
|
///
|
|
/// # Notes
|
|
/// `AuthCacheError::Timeout` will only be returned if the request we're trying to extract has timed out *and not yet been removed* yet by an earlier, potentially unrelated, call to `handle_req` *or* `insert_req` (or an explicit purge).
|
|
/// If an error is returned you cannot rely on the accuracy of the error kind.
|
|
pub fn handle_req(&mut self, id: Uuid) -> Result<auth::AuthRequest, AuthCacheError>
|
|
{
|
|
let mut timed_out=false;
|
|
self.purge_now_and_then(|other| if other.id==id { timed_out = true; });
|
|
|
|
if timed_out {
|
|
Err(AuthCacheError::Timeout)
|
|
} else {
|
|
self.active_req.remove(&id).ok_or(AuthCacheError::Removed).map(|(v, k)| {
|
|
self.timeouts.remove(&k);
|
|
v
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[non_exhaustive]
|
|
pub enum AuthCacheError
|
|
{
|
|
Removed,
|
|
Timeout,
|
|
}
|
|
impl error::Error for AuthCacheError{}
|
|
impl fmt::Display for AuthCacheError
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
match self {
|
|
Self::Removed => write!(f, "id was not present"),
|
|
Self::Timeout => write!(f, "id timed out as we read it"),
|
|
}
|
|
}
|
|
}
|
|
|
|
use session::*;
|
|
|
|
#[derive(Debug)]
|
|
pub struct State
|
|
{
|
|
backend: RwLock<ServerState>,
|
|
|
|
auth_tokens: RwLock<AuthContainer>,
|
|
//TODO: user auths, public keys, hashed passwords, etc.
|
|
|
|
sessions: RwLock<Sessions>,
|
|
settings: Settings,
|
|
}
|
|
|
|
impl State
|
|
{
|
|
/// Create state from a backend state and server settings
|
|
pub fn new(backend: ServerState, settings: Settings) -> Self
|
|
{
|
|
Self {
|
|
auth_tokens: RwLock::new(AuthContainer::new()),
|
|
sessions: RwLock::new(Sessions::new()),
|
|
backend: RwLock::new(backend),
|
|
settings,
|
|
}
|
|
}
|
|
|
|
/// The session container
|
|
pub fn sessions(&self) -> &RwLock<Sessions>
|
|
{
|
|
&self.sessions
|
|
}
|
|
|
|
/// The web server settings
|
|
pub fn cfg(&self) -> &Settings
|
|
{
|
|
&self.settings
|
|
}
|
|
|
|
/// Get a write reference to the auth container
|
|
pub async fn auth_tokens(&self) -> RwLockWriteGuard<'_, AuthContainer>
|
|
{
|
|
self.auth_tokens.write().await
|
|
}
|
|
|
|
/// Get a read reference to the auth container.
|
|
///
|
|
/// Typically only useful for debugging/logging.
|
|
pub async fn auth_tokens_ref(&self) -> RwLockReadGuard<'_, AuthContainer>
|
|
{
|
|
self.auth_tokens.read().await
|
|
}
|
|
}
|