@jpgilldev / steid

steid/src/domain/session.rs
2.3 KBRaw
1use std::time::SystemTime;
2
3use super::UserId;
4
5/// The hash of a session token, as the application records it.
6///
7/// Opaque and hex-encoded. The raw token exists only on the client — Topcoat issues it
8/// and hands us the hash — so a dumped `sessions` table contains nothing presentable.
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct SessionTokenHash(String);
11
12impl SessionTokenHash {
13 /// Wraps an already-computed hash.
14 pub fn from_trusted(value: impl Into<String>) -> Self {
15 Self(value.into())
16 }
17
18 pub fn as_str(&self) -> &str {
19 &self.0
20 }
21}
22
23/// An authenticated session.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Session {
26 pub token_hash: SessionTokenHash,
27 pub user_id: UserId,
28 pub expires_at: SystemTime,
29}
30
31impl Session {
32 pub fn new(token_hash: SessionTokenHash, user_id: UserId, expires_at: SystemTime) -> Self {
33 Self {
34 token_hash,
35 user_id,
36 expires_at,
37 }
38 }
39
40 /// Whether the session has passed its expiry.
41 ///
42 /// Checked on every read rather than trusted to a cleanup job, so a session that
43 /// outlives the sweep still stops working on time.
44 pub fn is_expired_at(&self, now: SystemTime) -> bool {
45 now >= self.expires_at
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use std::time::Duration;
52
53 use super::*;
54
55 fn session(expires_at: SystemTime) -> Session {
56 Session::new(
57 SessionTokenHash::from_trusted("abc123"),
58 UserId::generate(),
59 expires_at,
60 )
61 }
62
63 #[test]
64 fn a_future_expiry_is_not_expired() {
65 let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000);
66 let session = session(now + Duration::from_secs(60));
67
68 assert!(!session.is_expired_at(now));
69 }
70
71 #[test]
72 fn a_past_expiry_is_expired() {
73 let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000);
74 let session = session(now - Duration::from_secs(1));
75
76 assert!(session.is_expired_at(now));
77 }
78
79 #[test]
80 fn expiry_is_inclusive_at_the_boundary() {
81 let now = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000);
82 let session = session(now);
83
84 assert!(
85 session.is_expired_at(now),
86 "a session should not survive the instant it expires"
87 );
88 }
89}