use josekit::jwt::JwtPayload;
use ultimate_common::time::{self, Duration, OffsetDateTime, UtcDateTime};
use crate::error::DataError;
#[derive(Clone, Debug)]
pub struct Session {
user_id: i64,
req_time: OffsetDateTime,
expires_at: OffsetDateTime,
}
impl Session {
pub fn new(user_id: i64, req_time: OffsetDateTime, expires_at: OffsetDateTime) -> Self {
Self { user_id, req_time, expires_at }
}
pub fn new_root() -> Self {
let req_time = time::now();
let expires_at = req_time + Duration::minutes(30);
Self::new(0, req_time, expires_at)
}
pub fn user_id(&self) -> i64 {
self.user_id
}
pub fn req_time(&self) -> &OffsetDateTime {
&self.req_time
}
pub fn with_expires_at(mut self, expires_at: OffsetDateTime) -> Self {
self.expires_at = expires_at;
self
}
pub fn try_from_jwt_payload(payload: &JwtPayload, req_time: Option<OffsetDateTime>) -> Result<Self, DataError> {
let req_time = req_time.unwrap_or_else(time::now);
let sub = payload.subject().ok_or_else(|| DataError::unauthorized("'sub' of jwt missing"))?;
let user_id: i64 = sub.parse().map_err(|_| DataError::unauthorized(format!("<sub:{sub}> invalid")))?;
let expires_at: UtcDateTime = if let Some(st) = payload.expires_at() {
let expires_at = st.into();
if expires_at < time::now_utc() {
return Err(DataError::unauthorized("The token expired"));
}
expires_at
} else {
OffsetDateTime::MAX_UTC
};
Ok(Session::new(user_id, req_time, expires_at.fixed_offset()))
}
}
impl TryFrom<JwtPayload> for Session {
type Error = DataError;
fn try_from(payload: JwtPayload) -> std::result::Result<Self, Self::Error> {
Session::try_from_jwt_payload(&payload, Some(time::now()))
}
}