use web_time::SystemTime;
use topcoat_core::{context::Cx, error::Result};
use crate::{TokenHash, config, state, token::Token, token_store};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Session {
pub token_hash: TokenHash,
pub expires_at: SystemTime,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rotation {
pub revoked: TokenHash,
pub session: Session,
}
pub async fn start(cx: &Cx) -> Result<Session> {
let token = Token::random();
let session = Session {
token_hash: token.hash(),
expires_at: expires_at(cx),
};
token_store(cx)
.write(cx, token.clone(), config(cx).lifetime)
.await?;
state(cx).set(Some(token)).await;
Ok(session)
}
pub async fn stop(cx: &Cx) -> Result<Option<TokenHash>> {
let hash = state(cx).token(cx).await?.map(|token| token.hash());
token_store(cx).delete(cx).await?;
state(cx).set(None).await;
Ok(hash)
}
pub async fn refresh(cx: &Cx) -> Result<Option<Session>> {
let Some(token) = state(cx).token(cx).await? else {
return Ok(None);
};
let session = Session {
token_hash: token.hash(),
expires_at: expires_at(cx),
};
token_store(cx)
.write(cx, token, config(cx).lifetime)
.await?;
Ok(Some(session))
}
pub async fn rotate(cx: &Cx) -> Result<Option<Rotation>> {
let Some(old) = state(cx).token(cx).await? else {
return Ok(None);
};
let token = Token::random();
let rotation = Rotation {
revoked: old.hash(),
session: Session {
token_hash: token.hash(),
expires_at: expires_at(cx),
},
};
token_store(cx)
.write(cx, token.clone(), config(cx).lifetime)
.await?;
state(cx).set(Some(token)).await;
Ok(Some(rotation))
}
pub async fn token_hash(cx: &Cx) -> Result<Option<TokenHash>> {
Ok(state(cx).token(cx).await?.map(|token| token.hash()))
}
fn expires_at(cx: &Cx) -> SystemTime {
SystemTime::now() + config(cx).lifetime
}