1use std::collections::{HashMap, HashSet};
6
7use crate::{AuthError, AuthResult, UserContext};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ResourceAction {
12 pub resource: String,
14
15 pub action: String,
17}
18
19impl ResourceAction {
20 pub fn new(resource: impl Into<String>, action: impl Into<String>) -> Self {
22 Self {
23 resource: resource.into(),
24 action: action.into(),
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct AuthGuardConfig {
32 pub skip_paths: HashSet<String>,
34
35 pub optional: bool,
37
38 pub required_roles: Vec<String>,
40
41 pub required_permissions: Vec<String>,
43
44 pub require_all_roles: bool,
46
47 pub require_all_permissions: bool,
49
50 pub required_resource_actions: Vec<ResourceAction>,
52
53 pub auth_context: HashMap<String, serde_json::Value>,
55}
56
57impl Default for AuthGuardConfig {
58 fn default() -> Self {
59 Self {
60 skip_paths: ["/health", "/metrics"]
61 .iter()
62 .map(|s| s.to_string())
63 .collect(),
64 optional: false,
65 required_roles: Vec::new(),
66 required_permissions: Vec::new(),
67 require_all_roles: false,
68 require_all_permissions: false,
69 required_resource_actions: Vec::new(),
70 auth_context: HashMap::new(),
71 }
72 }
73}
74
75pub trait AuthGuard {
77 fn should_skip_path(&self, path: &str) -> bool;
79
80 fn is_optional(&self) -> bool;
82
83 fn validate_user(&self, user: &UserContext) -> AuthResult<()>;
85
86 fn validate_user_with_rbac(&self, user: &UserContext) -> AuthResult<()> {
88 self.validate_user(user)?;
90
91 if !self.config().required_resource_actions.is_empty() {
93 self.validate_resource_actions(user)?;
94 }
95
96 Ok(())
97 }
98
99 fn validate_resource_actions(&self, user: &UserContext) -> AuthResult<()> {
101 for resource_action in &self.config().required_resource_actions {
102 let permission_key = format!("{}.{}", resource_action.resource, resource_action.action);
103
104 if !user.permissions.contains(&permission_key) {
105 return Err(AuthError::insufficient_permissions(format!(
106 "User lacks permission for {}.{}",
107 resource_action.resource, resource_action.action
108 )));
109 }
110 }
111
112 Ok(())
113 }
114
115 fn config(&self) -> &AuthGuardConfig;
117}
118
119#[derive(Debug, Clone)]
121pub struct RequireAuth {
122 config: AuthGuardConfig,
123}
124
125impl RequireAuth {
126 pub fn new() -> Self {
128 Self {
129 config: AuthGuardConfig::default(),
130 }
131 }
132
133 pub fn with_config(config: AuthGuardConfig) -> Self {
135 Self { config }
136 }
137
138 pub fn skip_path<S: Into<String>>(mut self, path: S) -> Self {
140 self.config.skip_paths.insert(path.into());
141 self
142 }
143
144 pub fn skip_paths<I, S>(mut self, paths: I) -> Self
146 where
147 I: IntoIterator<Item = S>,
148 S: Into<String>,
149 {
150 for path in paths {
151 self.config.skip_paths.insert(path.into());
152 }
153 self
154 }
155
156 pub fn require_role<S: Into<String>>(mut self, role: S) -> Self {
158 self.config.required_roles.push(role.into());
159 self
160 }
161
162 pub fn require_roles<I, S>(mut self, roles: I) -> Self
164 where
165 I: IntoIterator<Item = S>,
166 S: Into<String>,
167 {
168 self.config
169 .required_roles
170 .extend(roles.into_iter().map(|r| r.into()));
171 self
172 }
173
174 pub fn require_permission<S: Into<String>>(mut self, permission: S) -> Self {
176 self.config.required_permissions.push(permission.into());
177 self
178 }
179
180 pub fn require_permissions<I, S>(mut self, permissions: I) -> Self
182 where
183 I: IntoIterator<Item = S>,
184 S: Into<String>,
185 {
186 self.config
187 .required_permissions
188 .extend(permissions.into_iter().map(|p| p.into()));
189 self
190 }
191
192 pub fn require_all_roles(mut self) -> Self {
194 self.config.require_all_roles = true;
195 self
196 }
197
198 pub fn require_all_permissions(mut self) -> Self {
200 self.config.require_all_permissions = true;
201 self
202 }
203
204 pub fn require_resource_action(
206 mut self,
207 resource: impl Into<String>,
208 action: impl Into<String>,
209 ) -> Self {
210 self.config
211 .required_resource_actions
212 .push(ResourceAction::new(resource, action));
213 self
214 }
215
216 pub fn require_resource_actions<I>(mut self, resource_actions: I) -> Self
218 where
219 I: IntoIterator<Item = (String, String)>,
220 {
221 for (resource, action) in resource_actions {
222 self.config
223 .required_resource_actions
224 .push(ResourceAction::new(resource, action));
225 }
226 self
227 }
228
229 pub fn with_context<K, V>(mut self, key: K, value: V) -> Self
231 where
232 K: Into<String>,
233 V: Into<serde_json::Value>,
234 {
235 self.config.auth_context.insert(key.into(), value.into());
236 self
237 }
238
239 pub fn with_contexts<I, K, V>(mut self, contexts: I) -> Self
241 where
242 I: IntoIterator<Item = (K, V)>,
243 K: Into<String>,
244 V: Into<serde_json::Value>,
245 {
246 for (key, value) in contexts {
247 self.config.auth_context.insert(key.into(), value.into());
248 }
249 self
250 }
251}
252
253impl Default for RequireAuth {
254 fn default() -> Self {
255 Self::new()
256 }
257}
258
259impl AuthGuard for RequireAuth {
260 fn should_skip_path(&self, path: &str) -> bool {
261 self.config.skip_paths.contains(path)
262 }
263
264 fn is_optional(&self) -> bool {
265 false }
267
268 fn validate_user(&self, user: &UserContext) -> AuthResult<()> {
269 if !self.config.required_roles.is_empty() {
271 let has_required_roles = if self.config.require_all_roles {
272 self.config
274 .required_roles
275 .iter()
276 .all(|role| user.roles.contains(role))
277 } else {
278 self.config
280 .required_roles
281 .iter()
282 .any(|role| user.roles.contains(role))
283 };
284
285 if !has_required_roles {
286 return Err(AuthError::insufficient_permissions(format!(
287 "User lacks required roles: {:?}",
288 self.config.required_roles
289 )));
290 }
291 }
292
293 if !self.config.required_permissions.is_empty() {
295 let has_required_permissions = if self.config.require_all_permissions {
296 self.config
298 .required_permissions
299 .iter()
300 .all(|perm| user.permissions.contains(perm))
301 } else {
302 self.config
304 .required_permissions
305 .iter()
306 .any(|perm| user.permissions.contains(perm))
307 };
308
309 if !has_required_permissions {
310 return Err(AuthError::insufficient_permissions(format!(
311 "User lacks required permissions: {:?}",
312 self.config.required_permissions
313 )));
314 }
315 }
316
317 Ok(())
318 }
319
320 fn config(&self) -> &AuthGuardConfig {
321 &self.config
322 }
323}
324
325#[derive(Debug, Clone)]
327pub struct OptionalAuth {
328 config: AuthGuardConfig,
329}
330
331impl OptionalAuth {
332 pub fn new() -> Self {
334 let mut config = AuthGuardConfig::default();
335 config.optional = true;
336
337 Self { config }
338 }
339
340 pub fn with_config(mut config: AuthGuardConfig) -> Self {
342 config.optional = true;
343 Self { config }
344 }
345
346 pub fn skip_path<S: Into<String>>(mut self, path: S) -> Self {
348 self.config.skip_paths.insert(path.into());
349 self
350 }
351
352 pub fn skip_paths<I, S>(mut self, paths: I) -> Self
354 where
355 I: IntoIterator<Item = S>,
356 S: Into<String>,
357 {
358 for path in paths {
359 self.config.skip_paths.insert(path.into());
360 }
361 self
362 }
363}
364
365impl Default for OptionalAuth {
366 fn default() -> Self {
367 Self::new()
368 }
369}
370
371impl AuthGuard for OptionalAuth {
372 fn should_skip_path(&self, path: &str) -> bool {
373 self.config.skip_paths.contains(path)
374 }
375
376 fn is_optional(&self) -> bool {
377 true }
379
380 fn validate_user(&self, _user: &UserContext) -> AuthResult<()> {
381 Ok(())
383 }
384
385 fn config(&self) -> &AuthGuardConfig {
386 &self.config
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use chrono::Utc;
394 use std::collections::HashMap;
395
396 fn create_test_user() -> UserContext {
397 UserContext {
398 user_id: "123".to_string(),
399 username: "test@example.com".to_string(),
400 roles: vec!["user".to_string(), "moderator".to_string()],
401 permissions: vec!["read".to_string(), "write".to_string()],
402 auth_provider: "test".to_string(),
403 authenticated_at: Utc::now(),
404 expires_at: None,
405 additional_data: HashMap::new(),
406 }
407 }
408
409 #[test]
410 fn test_require_auth_creation() {
411 let guard = RequireAuth::new();
412 assert!(!guard.is_optional());
413 assert!(guard.should_skip_path("/health"));
414 assert!(guard.should_skip_path("/metrics"));
415 assert!(!guard.should_skip_path("/protected"));
416 }
417
418 #[test]
419 fn test_require_auth_custom_skip_paths() {
420 let guard = RequireAuth::new()
421 .skip_path("/public")
422 .skip_paths(["/docs", "/swagger"]);
423
424 assert!(guard.should_skip_path("/public"));
425 assert!(guard.should_skip_path("/docs"));
426 assert!(guard.should_skip_path("/swagger"));
427 assert!(!guard.should_skip_path("/private"));
428 }
429
430 #[test]
431 fn test_require_auth_role_validation_any() {
432 let user = create_test_user();
433
434 let guard = RequireAuth::new().require_role("user");
436 assert!(guard.validate_user(&user).is_ok());
437
438 let guard = RequireAuth::new().require_role("moderator");
440 assert!(guard.validate_user(&user).is_ok());
441
442 let guard = RequireAuth::new().require_role("admin");
444 assert!(guard.validate_user(&user).is_err());
445
446 let guard = RequireAuth::new().require_roles(["admin", "moderator"]);
448 assert!(guard.validate_user(&user).is_ok());
449 }
450
451 #[test]
452 fn test_require_auth_role_validation_all() {
453 let user = create_test_user();
454
455 let guard = RequireAuth::new()
457 .require_roles(["user", "moderator"])
458 .require_all_roles();
459 assert!(guard.validate_user(&user).is_ok());
460
461 let guard = RequireAuth::new()
463 .require_roles(["user", "admin"])
464 .require_all_roles();
465 assert!(guard.validate_user(&user).is_err());
466 }
467
468 #[test]
469 fn test_require_auth_permission_validation_any() {
470 let user = create_test_user();
471
472 let guard = RequireAuth::new().require_permission("read");
474 assert!(guard.validate_user(&user).is_ok());
475
476 let guard = RequireAuth::new().require_permission("delete");
478 assert!(guard.validate_user(&user).is_err());
479
480 let guard = RequireAuth::new().require_permissions(["delete", "write"]);
482 assert!(guard.validate_user(&user).is_ok());
483 }
484
485 #[test]
486 fn test_require_auth_permission_validation_all() {
487 let user = create_test_user();
488
489 let guard = RequireAuth::new()
491 .require_permissions(["read", "write"])
492 .require_all_permissions();
493 assert!(guard.validate_user(&user).is_ok());
494
495 let guard = RequireAuth::new()
497 .require_permissions(["read", "delete"])
498 .require_all_permissions();
499 assert!(guard.validate_user(&user).is_err());
500 }
501
502 #[test]
503 fn test_optional_auth_creation() {
504 let guard = OptionalAuth::new();
505 assert!(guard.is_optional());
506 assert!(guard.should_skip_path("/health"));
507 assert!(guard.should_skip_path("/metrics"));
508 }
509
510 #[test]
511 fn test_optional_auth_validation() {
512 let user = create_test_user();
513 let guard = OptionalAuth::new();
514
515 assert!(guard.validate_user(&user).is_ok());
517 }
518
519 #[test]
520 fn test_optional_auth_custom_skip_paths() {
521 let guard = OptionalAuth::new()
522 .skip_path("/api")
523 .skip_paths(["/v1", "/v2"]);
524
525 assert!(guard.should_skip_path("/api"));
526 assert!(guard.should_skip_path("/v1"));
527 assert!(guard.should_skip_path("/v2"));
528 assert!(!guard.should_skip_path("/protected"));
529 }
530
531 #[test]
532 fn test_resource_action_creation() {
533 let resource_action = ResourceAction::new("users", "create");
534 assert_eq!(resource_action.resource, "users");
535 assert_eq!(resource_action.action, "create");
536 }
537
538 #[test]
539 fn test_require_auth_rbac_resource_actions() {
540 let mut user = create_test_user();
541 user.permissions = vec![
542 "users.create".to_string(),
543 "articles.read".to_string(),
544 "articles.edit".to_string(),
545 ];
546
547 let guard = RequireAuth::new().require_resource_action("users", "create");
549 assert!(guard.validate_user_with_rbac(&user).is_ok());
550
551 let guard = RequireAuth::new().require_resource_action("users", "delete");
553 assert!(guard.validate_user_with_rbac(&user).is_err());
554
555 let guard = RequireAuth::new().require_resource_actions([
557 ("users".to_string(), "create".to_string()),
558 ("articles".to_string(), "read".to_string()),
559 ]);
560 assert!(guard.validate_user_with_rbac(&user).is_ok());
561
562 let guard = RequireAuth::new().require_resource_actions([
564 ("users".to_string(), "create".to_string()),
565 ("users".to_string(), "delete".to_string()),
566 ]);
567 assert!(guard.validate_user_with_rbac(&user).is_err());
568 }
569
570 #[test]
571 fn test_require_auth_with_context() {
572 let guard = RequireAuth::new()
573 .with_context("owner", "user123")
574 .with_contexts([
575 ("department".to_string(), serde_json::json!("engineering")),
576 ("role_level".to_string(), serde_json::json!(5)),
577 ]);
578
579 assert_eq!(guard.config().auth_context.get("owner").unwrap(), "user123");
580 assert_eq!(
581 guard.config().auth_context.get("department").unwrap(),
582 "engineering"
583 );
584 assert_eq!(
585 guard.config().auth_context.get("role_level").unwrap(),
586 &serde_json::json!(5)
587 );
588 }
589
590 #[test]
591 fn test_validate_resource_actions() {
592 let mut user = create_test_user();
593 user.permissions = vec!["users.create".to_string(), "articles.read".to_string()];
594
595 let guard = RequireAuth::new()
596 .require_resource_action("users", "create")
597 .require_resource_action("articles", "read");
598
599 assert!(guard.validate_resource_actions(&user).is_ok());
601
602 let guard = RequireAuth::new()
604 .require_resource_action("users", "create")
605 .require_resource_action("users", "delete");
606
607 assert!(guard.validate_resource_actions(&user).is_err());
609 }
610}