use std::time::{Duration, SystemTime};
use huskarl::{
core::serde_utils::time::unix_secs,
grant::core::TokenResponse,
token::{IdToken, RefreshToken},
};
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Clone, Serialize, Deserialize, bon::Builder)]
pub struct SessionState {
#[serde(with = "unix_secs")]
pub token_expiry: SystemTime,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<RefreshToken>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sub: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sid: Option<String>,
#[serde(with = "unix_secs")]
pub created_at: SystemTime,
#[serde(with = "unix_secs")]
pub last_active: SystemTime,
}
impl SessionState {
pub(crate) fn from_completed(
completed: &crate::grant::CompletedLogin,
default_lifetime: Duration,
) -> Self {
let now = SystemTime::now();
let token_response = completed.token_response();
let lifetime = token_response
.raw_token_response()
.expires_in
.map_or(default_lifetime, Duration::from_secs);
let token_expiry = now + lifetime;
let (sub, sid) = match completed.id_token_claims() {
Some(claims) => (
claims.sub.clone(),
claims
.extra
.get("sid")
.and_then(|v| v.as_str())
.map(str::to_owned),
),
None => (None, None),
};
Self {
token_expiry,
refresh_token: token_response.refresh_token().cloned(),
sub,
sid,
created_at: now,
last_active: now,
}
}
#[must_use]
pub fn refreshed(&self, token_response: &TokenResponse, default_lifetime: Duration) -> Self {
let now = SystemTime::now();
let mut new = self.clone();
let lifetime = token_response
.raw_token_response()
.expires_in
.map_or(default_lifetime, Duration::from_secs);
new.token_expiry = now + lifetime;
if let Some(rt) = token_response.refresh_token() {
new.refresh_token = Some(rt.clone());
}
new.last_active = now;
new
}
#[must_use]
pub fn with_activity(&self) -> Self {
let mut new = self.clone();
new.last_active = SystemTime::now();
new
}
}
pub trait Session {
fn state(&self) -> &SessionState;
fn set_state(&mut self, state: SessionState);
fn token_expiry(&self) -> SystemTime {
self.state().token_expiry
}
fn refresh_token(&self) -> Option<&RefreshToken> {
self.state().refresh_token.as_ref()
}
fn id_token(&self) -> Option<&IdToken> {
None
}
fn sub(&self) -> Option<&str> {
self.state().sub.as_deref()
}
fn sid(&self) -> Option<&str> {
self.state().sid.as_deref()
}
fn created_at(&self) -> SystemTime {
self.state().created_at
}
fn last_active(&self) -> SystemTime {
self.state().last_active
}
fn apply_refresh(&mut self, token_response: &TokenResponse, default_lifetime: Duration) {
let new_state = self.state().refreshed(token_response, default_lifetime);
self.set_state(new_state);
}
fn record_activity(&mut self) {
let new_state = self.state().with_activity();
self.set_state(new_state);
}
}