use std::time::SystemTime;
use soaprs_core::{BoxFuture, SoapResult};
use crate::SessionId;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Session<P> {
id: SessionId,
principal: P,
created_at: SystemTime,
expires_at: SystemTime,
}
impl<P> Session<P> {
pub fn new(
id: SessionId,
principal: P,
created_at: SystemTime,
expires_at: SystemTime,
) -> SoapResult<Self> {
if expires_at <= created_at {
return Err(soaprs_core::SoapError::validation(
"session expiration must be later than creation",
));
}
Ok(Self {
id,
principal,
created_at,
expires_at,
})
}
pub fn is_expired_at(&self, now: SystemTime) -> bool {
now >= self.expires_at
}
pub const fn id(&self) -> &SessionId {
&self.id
}
pub const fn principal(&self) -> &P {
&self.principal
}
pub const fn created_at(&self) -> SystemTime {
self.created_at
}
pub const fn expires_at(&self) -> SystemTime {
self.expires_at
}
pub fn into_principal(self) -> P {
self.principal
}
}
pub trait SessionStore<P>: Send + Sync
where
P: Send,
{
fn load<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<Option<Session<P>>>>;
fn save(&self, session: Session<P>) -> BoxFuture<'_, SoapResult<()>>;
fn delete<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<()>>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenPair<A, R> {
pub access_token: A,
pub refresh_token: R,
}
pub trait TokenService<P>: Send + Sync
where
P: Send + Sync,
{
type AccessToken: Send;
type RefreshToken: Send;
fn issue(
&self,
principal: &P,
) -> BoxFuture<'_, SoapResult<TokenPair<Self::AccessToken, Self::RefreshToken>>>;
fn refresh(
&self,
refresh_token: Self::RefreshToken,
) -> BoxFuture<'_, SoapResult<TokenPair<Self::AccessToken, Self::RefreshToken>>>;
fn revoke(&self, refresh_token: Self::RefreshToken) -> BoxFuture<'_, SoapResult<()>>;
}
#[cfg(test)]
mod tests {
use std::time::{Duration, UNIX_EPOCH};
use super::Session;
use crate::SessionId;
#[test]
fn sessions_require_forward_expiration_and_use_explicit_time() {
let Some(id) = SessionId::new("session-1").ok() else {
panic!("valid session ID");
};
assert!(Session::new(id.clone(), (), UNIX_EPOCH, UNIX_EPOCH).is_err());
let session = Session::new(id, (), UNIX_EPOCH, UNIX_EPOCH + Duration::from_secs(60));
let Some(session) = session.ok() else {
panic!("valid session");
};
assert!(!session.is_expired_at(UNIX_EPOCH + Duration::from_secs(59)));
assert!(session.is_expired_at(UNIX_EPOCH + Duration::from_secs(60)));
}
}