Skip to main content

fabryk_auth/
user.rs

1//! Authenticated user identity and extraction helpers.
2
3/// An authenticated user identity, extracted from a validated token.
4///
5/// Stored in HTTP request extensions by the auth middleware and propagated
6/// through rmcp into MCP tool handler context via `Extension<Parts>`.
7#[derive(Debug, Clone)]
8pub struct AuthenticatedUser {
9    /// The user's email address.
10    pub email: String,
11    /// The user's unique subject identifier (from the `sub` claim).
12    pub subject: String,
13}
14
15/// Extract the `AuthenticatedUser` from HTTP request `Parts`, if present.
16pub fn user_from_parts(parts: &http::request::Parts) -> Option<&AuthenticatedUser> {
17    parts.extensions.get::<AuthenticatedUser>()
18}
19
20/// Extract the user's email from HTTP request `Parts`.
21///
22/// Returns `"anonymous"` if no authenticated user is present (dev mode).
23pub fn email_from_parts(parts: &http::request::Parts) -> &str {
24    parts
25        .extensions
26        .get::<AuthenticatedUser>()
27        .map(|u| u.email.as_str())
28        .unwrap_or("anonymous")
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    fn parts_with_user() -> http::request::Parts {
36        let (mut parts, _body) = http::Request::new(()).into_parts();
37        parts.extensions.insert(AuthenticatedUser {
38            email: "alice@banyan.com".to_string(),
39            subject: "sub_123".to_string(),
40        });
41        parts
42    }
43
44    fn parts_without_user() -> http::request::Parts {
45        let (parts, _body) = http::Request::new(()).into_parts();
46        parts
47    }
48
49    #[test]
50    fn test_user_from_parts_present() {
51        let parts = parts_with_user();
52        let user = user_from_parts(&parts).unwrap();
53        assert_eq!(user.email, "alice@banyan.com");
54        assert_eq!(user.subject, "sub_123");
55    }
56
57    #[test]
58    fn test_user_from_parts_absent() {
59        let parts = parts_without_user();
60        assert!(user_from_parts(&parts).is_none());
61    }
62
63    #[test]
64    fn test_email_from_parts_present() {
65        let parts = parts_with_user();
66        assert_eq!(email_from_parts(&parts), "alice@banyan.com");
67    }
68
69    #[test]
70    fn test_email_from_parts_anonymous() {
71        let parts = parts_without_user();
72        assert_eq!(email_from_parts(&parts), "anonymous");
73    }
74
75    #[test]
76    fn test_authenticated_user_clone() {
77        let user = AuthenticatedUser {
78            email: "bob@banyan.com".to_string(),
79            subject: "sub_456".to_string(),
80        };
81        let cloned = user.clone();
82        assert_eq!(cloned.email, "bob@banyan.com");
83        assert_eq!(cloned.subject, "sub_456");
84    }
85}