rust_webx_core/auth.rs
1//! Authentication and authorization traits for the LRWF framework.
2//!
3//! Provides:
4//! - `IClaims`: Extracted auth claims (JWT or other token types).
5//! - `IAuthenticationHandler`: Authenticate an HTTP request and produce claims.
6//! - `IAuthorizationPolicy`: Check if authenticated claims can access a resource.
7
8use crate::error::Result;
9use crate::http::IHttpContext;
10use std::collections::HashMap;
11
12/// Claims extracted from an authentication token (JWT, etc.).
13///
14/// Stored in `IHttpContext` extensions via `IClaimsExt`.
15pub trait IClaims: Send + Sync {
16 /// The user / subject identifier.
17 fn subject(&self) -> &str;
18
19 /// Roles assigned to the user.
20 fn roles(&self) -> &[String];
21
22 /// Permissions assigned to the user.
23 fn permissions(&self) -> &[String];
24
25 /// Raw claims map (key-value pairs from the token).
26 fn claims(&self) -> &HashMap<String, String>;
27
28 /// Clone the claims into a new boxed trait object.
29 fn clone_box(&self) -> Box<dyn IClaims>;
30
31 // ── Convenience helpers (default implementations) ──
32
33 /// Check whether a specific role is assigned.
34 ///
35 /// ```ignore
36 /// if claims.has_role("admin") { ... }
37 /// ```
38 fn has_role(&self, role: &str) -> bool {
39 self.roles().iter().any(|r| r == role)
40 }
41
42 /// Alias for `subject()` — returns the user identifier.
43 fn get_userid(&self) -> &str {
44 self.subject()
45 }
46
47 /// Read the display name from the raw claims map (key `"name"`).
48 /// Returns `None` when absent.
49 fn get_username(&self) -> Option<&str> {
50 self.claims().get("name").map(|s| s.as_str())
51 }
52
53 /// Read the tenant identifier from the raw claims map (key `"tenant_id"`).
54 /// Returns `None` when absent.
55 fn get_tenantid(&self) -> Option<&str> {
56 self.claims()
57 .get("tenant_id")
58 .or_else(|| self.claims().get("tenant"))
59 .map(|s| s.as_str())
60 }
61}
62
63impl Clone for Box<dyn IClaims> {
64 fn clone(&self) -> Self {
65 self.clone_box()
66 }
67}
68
69/// Authentication scheme interface.
70///
71/// Implementations read credentials from the HTTP context (e.g., JWT bearer token,
72/// API key header, cookie) and return claims or `None` if unauthenticated.
73#[async_trait::async_trait]
74pub trait IAuthenticationHandler: Send + Sync {
75 /// Authenticate the request and return claims, or `None` if not authenticated.
76 ///
77 /// Uses `&mut dyn IHttpContext` so the returned future is `Send`
78 /// (required by `tokio::spawn` — `&dyn IHttpContext` is `!Send`
79 /// because `IHttpContext` is not `Sync`).
80 async fn authenticate(&self, ctx: &mut dyn IHttpContext) -> Result<Option<Box<dyn IClaims>>>;
81}
82
83/// Authorization policy that checks whether an authenticated user
84/// can access a given resource.
85///
86/// The `resource_key` is the original route pattern string (e.g., `"/api/users/{id}"`),
87/// enabling dynamic identity-authorization-data systems to match routes directly.
88#[async_trait::async_trait]
89pub trait IAuthorizationPolicy: Send + Sync {
90 /// Check if the authenticated user can access the resource.
91 ///
92 /// * `claims` — the user's authentication claims.
93 /// * `resource_key` — the original route pattern string.
94 /// * `method` — the HTTP method.
95 ///
96 /// Returns `Ok(())` if authorized, or an `Err` if forbidden.
97 async fn authorize(&self, claims: &dyn IClaims, resource_key: &str, method: &str)
98 -> Result<()>;
99}
100
101/// Dynamic authorizer interface — pluggable authorization for protected routes.
102///
103/// Implement this trait and register it in the DI container:
104///
105/// ```ignore
106/// svc.singleton::<dyn IDynamicAuthorizer>(|_| Arc::new(MyAuthorizer::default()))
107/// ```
108///
109/// The framework automatically detects registered `IDynamicAuthorizer` implementations
110/// and invokes them on every route that has `#[authorize]`.
111/// If no implementations are registered, authorization is pass-through (no dynamic checks).
112///
113/// # Method
114///
115/// * `authorize` — receives the user's claims, matched route pattern, and HTTP method.
116/// Returns `Ok(())` if allowed, or `Err` with details if denied.
117#[async_trait::async_trait]
118pub trait IDynamicAuthorizer: Send + Sync {
119 /// Validate whether the authenticated user can access the resource.
120 async fn authorize(
121 &self,
122 claims: &dyn IClaims,
123 route_pattern: &str,
124 method: &str,
125 ) -> Result<()>;
126}