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(false);
131 let is_admin = extensions.get::<IsAdmin>().map(|v| v.0).unwrap_or(false);
132 let is_active = extensions.get::<IsActive>().map(|v| v.0).unwrap_or(false);
133 Some(Self {
134 user_id,
135 is_authenticated,
136 is_admin,
137 is_active,
138 _marker: AuthStateMarker,
139 })
140 }
141
142 pub fn user_id(&self) -> &str {
144 &self.user_id
145 }
146
147 pub fn is_authenticated(&self) -> bool {
149 self.is_authenticated
150 }
151
152 pub fn is_admin(&self) -> bool {
154 self.is_admin
155 }
156
157 pub fn is_active(&self) -> bool {
159 self.is_active
160 }
161
162 pub fn is_anonymous(&self) -> bool {
164 !self.is_authenticated
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use rstest::rstest;
172
173 #[test]
174 fn test_authenticated() {
175 let state = AuthState::authenticated("user-123", true, true);
176
177 assert_eq!(state.user_id(), "user-123");
178 assert!(state.is_authenticated());
179 assert!(state.is_admin());
180 assert!(state.is_active());
181 }
182
183 #[test]
184 fn test_anonymous() {
185 let state = AuthState::anonymous();
186
187 assert!(state.user_id().is_empty());
188 assert!(!state.is_authenticated());
189 assert!(!state.is_admin());
190 assert!(!state.is_active());
191 }
192
193 #[rstest]
194 fn test_from_extensions_with_authstate_object() {
195 let extensions = Extensions::new();
197 let state = AuthState::authenticated("user-456", true, true);
198 extensions.insert(state.clone());
199
200 let result = AuthState::from_extensions(&extensions);
202
203 assert_eq!(result, Some(state));
205 let retrieved = result.unwrap();
206 assert_eq!(retrieved.user_id(), "user-456");
207 assert!(retrieved.is_authenticated());
208 assert!(retrieved.is_admin());
209 assert!(retrieved.is_active());
210 }
211
212 #[rstest]
213 fn test_from_extensions_with_legacy_identity_defaults_to_unauthenticated_inactive() {
214 let extensions = Extensions::new();
216 extensions.insert("user-789".to_string());
217
218 let result = AuthState::from_extensions(&extensions);
220
221 assert!(result.is_some());
223 let retrieved = result.unwrap();
224 assert_eq!(retrieved.user_id(), "user-789");
225 assert!(!retrieved.is_authenticated());
226 assert!(!retrieved.is_admin());
227 assert!(!retrieved.is_active());
228 }
229
230 #[rstest]
231 fn test_from_extensions_preserves_explicit_inactive_status() {
232 let extensions = Extensions::new();
234 extensions.insert("user-789".to_string());
235 extensions.insert(IsAuthenticated(true));
236 extensions.insert(IsActive(false));
237
238 let result = AuthState::from_extensions(&extensions);
240
241 let retrieved = result.expect("legacy identity should produce an auth state");
243 assert!(retrieved.is_authenticated());
244 assert!(!retrieved.is_active());
245 }
246
247 #[rstest]
248 fn test_from_extensions_with_uuid_user_id() {
249 let extensions = Extensions::new();
251 let user_id = uuid::Uuid::now_v7();
252 extensions.insert(user_id);
253 extensions.insert(IsAuthenticated(true));
254 extensions.insert(IsActive(true));
255
256 let result = AuthState::from_extensions(&extensions);
258
259 let retrieved = result.expect("UUID identity should produce an auth state");
261 assert_eq!(retrieved.user_id(), user_id.to_string());
262 assert!(retrieved.is_authenticated());
263 assert!(retrieved.is_active());
264 }
265
266 #[rstest]
267 fn test_from_extensions_empty() {
268 let extensions = Extensions::new();
270
271 let result = AuthState::from_extensions(&extensions);
273
274 assert_eq!(result, None);
276 }
277
278 #[rstest]
279 fn test_from_extensions_preserves_admin_and_active() {
280 let extensions = Extensions::new();
282 let state = AuthState::authenticated("admin-user", true, true);
283 extensions.insert(state);
284
285 let result = AuthState::from_extensions(&extensions);
287
288 let retrieved = result.unwrap();
290 assert_eq!(retrieved.user_id(), "admin-user");
291 assert!(retrieved.is_authenticated());
292 assert!(retrieved.is_admin());
293 assert!(retrieved.is_active());
294 }
295}