parse_rust_server/
sessions.rs1use std::collections::HashMap;
16use std::sync::RwLock;
17
18use parse_rust_core::object_id::random_string;
19
20const REVOCABLE_PREFIX: &str = "r:";
22
23#[derive(Default)]
24pub struct SessionStore {
25 by_token: RwLock<HashMap<String, String>>,
27}
28
29impl SessionStore {
30 pub fn create(&self, user_object_id: &str) -> String {
32 let token = format!("{REVOCABLE_PREFIX}{}", random_string(32));
33 if let Ok(mut map) = self.by_token.write() {
34 map.insert(token.clone(), user_object_id.to_string());
35 }
36 token
37 }
38
39 pub fn user_for(&self, token: &str) -> Option<String> {
41 self.by_token.read().ok()?.get(token).cloned()
42 }
43
44 pub fn revoke(&self, token: &str) -> bool {
46 self.by_token
47 .write()
48 .map(|mut m| m.remove(token).is_some())
49 .unwrap_or(false)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn tokens_carry_the_revocable_prefix() {
59 let s = SessionStore::default();
60 let t = s.create("u1");
61 assert!(
62 t.starts_with("r:"),
63 "clients treat the r: prefix as meaningful: {t}"
64 );
65 assert_eq!(t.len(), 2 + 32);
66 }
67
68 #[test]
69 fn a_token_resolves_to_its_user_and_only_its_user() {
70 let s = SessionStore::default();
71 let a = s.create("alice");
72 let b = s.create("bob");
73 assert_eq!(s.user_for(&a).as_deref(), Some("alice"));
74 assert_eq!(s.user_for(&b).as_deref(), Some("bob"));
75 assert_ne!(a, b);
76 }
77
78 #[test]
79 fn an_unknown_token_resolves_to_nobody() {
80 let s = SessionStore::default();
81 assert_eq!(s.user_for("r:nonsense"), None);
82 assert_eq!(s.user_for(""), None);
83 }
84
85 #[test]
86 fn revoking_removes_the_token() {
87 let s = SessionStore::default();
88 let t = s.create("u1");
89 assert!(s.revoke(&t));
90 assert_eq!(
91 s.user_for(&t),
92 None,
93 "a revoked token must resolve to nobody"
94 );
95 assert!(!s.revoke(&t), "revoking twice reports the second as a miss");
96 }
97}