reinhardt_http/
auth_state.rs1use crate::Extensions;
13use crate::extensions::{IsActive, IsAdmin, IsAuthenticated};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
18struct AuthStateMarker;
19
20#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct AuthState {
57 user_id: String,
61
62 is_authenticated: bool,
64
65 is_admin: bool,
67
68 is_active: bool,
70
71 _marker: AuthStateMarker,
73}
74
75impl AuthState {
76 pub fn authenticated(user_id: impl Into<String>, is_admin: bool, is_active: bool) -> Self {
84 Self {
85 user_id: user_id.into(),
86 is_authenticated: true,
87 is_admin,
88 is_active,
89 _marker: AuthStateMarker,
90 }
91 }
92
93 pub fn anonymous() -> Self {
95 Self {
96 user_id: String::new(),
97 is_authenticated: false,
98 is_admin: false,
99 is_active: false,
100 _marker: AuthStateMarker,
101 }
102 }
103
104 pub fn from_extensions(extensions: &Extensions) -> Option<Self> {
119 if let Some(state) = extensions.get::<AuthState>() {
121 return Some(state);
122 }
123 let user_id = extensions
125 .get::<String>()
126 .or_else(|| extensions.get::<uuid::Uuid>().map(|id| id.to_string()))?;
127 let is_authenticated = extensions
128 .get::<IsAuthenticated>()
129 .map(|v| v.0)
130 .unwrap_or(true);
131 let is_admin = extensions.get::<IsAdmin>().map(|v| v.0).unwrap_or(false);
132 let is_active = extensions
133 .get::<IsActive>()
134 .map(|v| v.0)
135 .unwrap_or(is_authenticated);
136 Some(Self {
137 user_id,
138 is_authenticated,
139 is_admin,
140 is_active,
141 _marker: AuthStateMarker,
142 })
143 }
144
145 pub fn user_id(&self) -> &str {
147 &self.user_id
148 }
149
150 pub fn is_authenticated(&self) -> bool {
152 self.is_authenticated
153 }
154
155 pub fn is_admin(&self) -> bool {
157 self.is_admin
158 }
159
160 pub fn is_active(&self) -> bool {
162 self.is_active
163 }
164
165 pub fn is_anonymous(&self) -> bool {
167 !self.is_authenticated
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use rstest::rstest;
175
176 #[test]
177 fn test_authenticated() {
178 let state = AuthState::authenticated("user-123", true, true);
179
180 assert_eq!(state.user_id(), "user-123");
181 assert!(state.is_authenticated());
182 assert!(state.is_admin());
183 assert!(state.is_active());
184 }
185
186 #[test]
187 fn test_anonymous() {
188 let state = AuthState::anonymous();
189
190 assert!(state.user_id().is_empty());
191 assert!(!state.is_authenticated());
192 assert!(!state.is_admin());
193 assert!(!state.is_active());
194 }
195
196 #[rstest]
197 fn test_from_extensions_with_authstate_object() {
198 let extensions = Extensions::new();
200 let state = AuthState::authenticated("user-456", true, true);
201 extensions.insert(state.clone());
202
203 let result = AuthState::from_extensions(&extensions);
205
206 assert_eq!(result, Some(state));
208 let retrieved = result.unwrap();
209 assert_eq!(retrieved.user_id(), "user-456");
210 assert!(retrieved.is_authenticated());
211 assert!(retrieved.is_admin());
212 assert!(retrieved.is_active());
213 }
214
215 #[rstest]
216 fn test_from_extensions_with_legacy_identity_defaults_to_authenticated_active() {
217 let extensions = Extensions::new();
219 extensions.insert("user-789".to_string());
220 extensions.insert(IsAuthenticated(true));
221
222 let result = AuthState::from_extensions(&extensions);
224
225 assert!(result.is_some());
227 let retrieved = result.unwrap();
228 assert_eq!(retrieved.user_id(), "user-789");
229 assert!(retrieved.is_authenticated());
230 assert!(!retrieved.is_admin());
231 assert!(retrieved.is_active());
232 }
233
234 #[rstest]
235 fn test_from_extensions_preserves_explicit_inactive_status() {
236 let extensions = Extensions::new();
238 extensions.insert("user-789".to_string());
239 extensions.insert(IsAuthenticated(true));
240 extensions.insert(IsActive(false));
241
242 let result = AuthState::from_extensions(&extensions);
244
245 let retrieved = result.expect("legacy identity should produce an auth state");
247 assert!(retrieved.is_authenticated());
248 assert!(!retrieved.is_active());
249 }
250
251 #[rstest]
252 fn test_from_extensions_with_uuid_user_id() {
253 let extensions = Extensions::new();
255 let user_id = uuid::Uuid::now_v7();
256 extensions.insert(user_id);
257 extensions.insert(IsAuthenticated(true));
258 extensions.insert(IsActive(true));
259
260 let result = AuthState::from_extensions(&extensions);
262
263 let retrieved = result.expect("UUID identity should produce an auth state");
265 assert_eq!(retrieved.user_id(), user_id.to_string());
266 assert!(retrieved.is_authenticated());
267 assert!(retrieved.is_active());
268 }
269
270 #[rstest]
271 fn test_from_extensions_empty() {
272 let extensions = Extensions::new();
274
275 let result = AuthState::from_extensions(&extensions);
277
278 assert_eq!(result, None);
280 }
281
282 #[rstest]
283 fn test_from_extensions_preserves_admin_and_active() {
284 let extensions = Extensions::new();
286 let state = AuthState::authenticated("admin-user", true, true);
287 extensions.insert(state);
288
289 let result = AuthState::from_extensions(&extensions);
291
292 let retrieved = result.unwrap();
294 assert_eq!(retrieved.user_id(), "admin-user");
295 assert!(retrieved.is_authenticated());
296 assert!(retrieved.is_admin());
297 assert!(retrieved.is_active());
298 }
299}