1use std::time::SystemTime;
4
5use soaprs_core::{BoxFuture, SoapResult};
6
7use crate::SessionId;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Session<P> {
12 id: SessionId,
14 principal: P,
16 created_at: SystemTime,
18 expires_at: SystemTime,
20}
21
22impl<P> Session<P> {
23 pub fn new(
25 id: SessionId,
26 principal: P,
27 created_at: SystemTime,
28 expires_at: SystemTime,
29 ) -> SoapResult<Self> {
30 if expires_at <= created_at {
31 return Err(soaprs_core::SoapError::validation(
32 "session expiration must be later than creation",
33 ));
34 }
35 Ok(Self {
36 id,
37 principal,
38 created_at,
39 expires_at,
40 })
41 }
42
43 pub fn is_expired_at(&self, now: SystemTime) -> bool {
45 now >= self.expires_at
46 }
47
48 pub const fn id(&self) -> &SessionId {
50 &self.id
51 }
52
53 pub const fn principal(&self) -> &P {
55 &self.principal
56 }
57
58 pub const fn created_at(&self) -> SystemTime {
60 self.created_at
61 }
62
63 pub const fn expires_at(&self) -> SystemTime {
65 self.expires_at
66 }
67
68 pub fn into_principal(self) -> P {
70 self.principal
71 }
72}
73
74pub trait SessionStore<P>: Send + Sync
76where
77 P: Send,
78{
79 fn load<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<Option<Session<P>>>>;
81
82 fn save(&self, session: Session<P>) -> BoxFuture<'_, SoapResult<()>>;
84
85 fn delete<'a>(&'a self, id: &'a SessionId) -> BoxFuture<'a, SoapResult<()>>;
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct TokenPair<A, R> {
92 pub access_token: A,
94 pub refresh_token: R,
96}
97
98pub trait TokenService<P>: Send + Sync
100where
101 P: Send + Sync,
102{
103 type AccessToken: Send;
105 type RefreshToken: Send;
107
108 fn issue(
110 &self,
111 principal: &P,
112 ) -> BoxFuture<'_, SoapResult<TokenPair<Self::AccessToken, Self::RefreshToken>>>;
113
114 fn refresh(
116 &self,
117 refresh_token: Self::RefreshToken,
118 ) -> BoxFuture<'_, SoapResult<TokenPair<Self::AccessToken, Self::RefreshToken>>>;
119
120 fn revoke(&self, refresh_token: Self::RefreshToken) -> BoxFuture<'_, SoapResult<()>>;
122}
123
124#[cfg(test)]
125mod tests {
126 use std::time::{Duration, UNIX_EPOCH};
127
128 use super::Session;
129 use crate::SessionId;
130
131 #[test]
132 fn sessions_require_forward_expiration_and_use_explicit_time() {
133 let Some(id) = SessionId::new("session-1").ok() else {
134 panic!("valid session ID");
135 };
136 assert!(Session::new(id.clone(), (), UNIX_EPOCH, UNIX_EPOCH).is_err());
137 let session = Session::new(id, (), UNIX_EPOCH, UNIX_EPOCH + Duration::from_secs(60));
138 let Some(session) = session.ok() else {
139 panic!("valid session");
140 };
141 assert!(!session.is_expired_at(UNIX_EPOCH + Duration::from_secs(59)));
142 assert!(session.is_expired_at(UNIX_EPOCH + Duration::from_secs(60)));
143 }
144}