Skip to main content

reinhardt_testkit/auth/
identity.rs

1use super::traits::ForceLoginUser;
2
3/// Type-erased session identity, decoupled from the user's concrete type.
4///
5/// Holds the minimal fields that `CookieSessionAuthMiddleware` reads from
6/// `SessionData.data`: `user_id`, `is_staff`, `is_superuser`.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct SessionIdentity {
9	/// User ID stored as `"user_id"` in session data.
10	pub user_id: String,
11	/// Staff flag stored as `"is_staff"` in session data.
12	pub is_staff: bool,
13	/// Superuser flag stored as `"is_superuser"` in session data.
14	pub is_superuser: bool,
15}
16
17impl SessionIdentity {
18	/// Extract identity from any user implementing [`ForceLoginUser`].
19	pub fn from_user(user: &impl ForceLoginUser) -> Self {
20		Self {
21			user_id: user.session_user_id(),
22			is_staff: user.session_is_staff(),
23			is_superuser: user.session_is_superuser(),
24		}
25	}
26
27	/// Convert to `SessionData` for `AsyncSessionBackend`.
28	///
29	/// Creates a `SessionData` with the fields that `CookieSessionAuthMiddleware`
30	/// expects: `user_id`, `is_staff`, `is_superuser`.
31	#[cfg(native)]
32	pub fn to_session_data(
33		&self,
34		session_id: &str,
35		ttl: std::time::Duration,
36	) -> reinhardt_middleware::session::SessionData {
37		use std::collections::HashMap;
38
39		use serde_json::json;
40
41		let mut session = reinhardt_middleware::session::SessionData::new(ttl);
42		session.id = session_id.to_string();
43		session.data = HashMap::from([
44			("user_id".into(), json!(self.user_id)),
45			("is_staff".into(), json!(self.is_staff)),
46			("is_superuser".into(), json!(self.is_superuser)),
47		]);
48		session
49	}
50}
51
52#[cfg(test)]
53mod tests {
54	use super::*;
55	use rstest::*;
56
57	struct StubUser {
58		id: String,
59		staff: bool,
60		superuser: bool,
61	}
62
63	impl ForceLoginUser for StubUser {
64		fn session_user_id(&self) -> String {
65			self.id.clone()
66		}
67		fn session_is_staff(&self) -> bool {
68			self.staff
69		}
70		fn session_is_superuser(&self) -> bool {
71			self.superuser
72		}
73	}
74
75	#[rstest]
76	fn from_user_extracts_all_fields() {
77		// Arrange
78		let user = StubUser {
79			id: "abc-123".into(),
80			staff: true,
81			superuser: false,
82		};
83
84		// Act
85		let identity = SessionIdentity::from_user(&user);
86
87		// Assert
88		assert_eq!(
89			identity,
90			SessionIdentity {
91				user_id: "abc-123".into(),
92				is_staff: true,
93				is_superuser: false,
94			}
95		);
96	}
97
98	#[rstest]
99	fn from_user_default_flags() {
100		let user = StubUser {
101			id: "u1".into(),
102			staff: false,
103			superuser: false,
104		};
105		let identity = SessionIdentity::from_user(&user);
106		assert!(!identity.is_staff);
107		assert!(!identity.is_superuser);
108	}
109
110	#[cfg(native)]
111	mod session_data_tests {
112		use std::time::Duration;
113
114		use serde_json::json;
115
116		use super::*;
117
118		#[rstest]
119		fn to_session_data_sets_correct_keys() {
120			// Arrange
121			let identity = SessionIdentity {
122				user_id: "user-42".into(),
123				is_staff: true,
124				is_superuser: false,
125			};
126
127			// Act
128			let data = identity.to_session_data("sess-001", Duration::from_secs(1800));
129
130			// Assert
131			assert_eq!(data.id, "sess-001");
132			assert_eq!(data.data.get("user_id").unwrap(), &json!("user-42"));
133			assert_eq!(data.data.get("is_staff").unwrap(), &json!(true));
134			assert_eq!(data.data.get("is_superuser").unwrap(), &json!(false));
135			assert!(data.expires_at > data.created_at);
136		}
137	}
138}