beep_auth/domain/models/
identity.rs

1use serde::{Deserialize, Serialize};
2
3use crate::domain::models::{claims::Claims, client::Client, user::User};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub enum Identity {
7    User(User),
8    Client(Client),
9}
10
11impl Identity {
12    pub fn id(&self) -> &str {
13        match self {
14            Identity::User(u) => &u.id,
15            Identity::Client(c) => &c.id,
16        }
17    }
18
19    pub fn is_user(&self) -> bool {
20        matches!(self, Identity::User(_))
21    }
22
23    pub fn is_client(&self) -> bool {
24        matches!(self, Identity::Client(_))
25    }
26
27    pub fn username(&self) -> &str {
28        match self {
29            Identity::User(u) => &u.username,
30            Identity::Client(c) => &c.client_id,
31        }
32    }
33
34    pub fn roles(&self) -> &[String] {
35        match self {
36            Identity::User(u) => &u.roles,
37            Identity::Client(c) => &c.roles,
38        }
39    }
40
41    pub fn has_role(&self, role: &str) -> bool {
42        self.roles().iter().any(|r| r == role)
43    }
44}
45
46impl From<Claims> for Identity {
47    fn from(claims: Claims) -> Self {
48        if let Some(client_id) = claims.client_id {
49            Identity::Client(Client {
50                id: claims.sub.0,
51                client_id: client_id,
52                roles: Vec::new(),
53                scopes: Vec::new(),
54            })
55        } else {
56            Identity::User(User {
57                id: claims.sub.0,
58                email: claims.email,
59                name: claims.name,
60                roles: Vec::new(),
61                username: claims.preferred_username,
62            })
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use serde_json::json;
70
71    use crate::domain::models::{claims::Claims, identity::Identity};
72
73    fn create_user_claims() -> Claims {
74        Claims {
75            sub: crate::domain::models::claims::Subject("user-123".to_string()),
76            iss: "https://auth.beep.com".to_string(),
77            aud: Some("beep-api".to_string()),
78            email: Some("john.doe@example.com".to_string()),
79            email_verified: true,
80            exp: None,
81            name: Some("John Doe".to_string()),
82            preferred_username: "johndoe".to_string(),
83            given_name: Some("John".to_string()),
84            family_name: Some("Doe".to_string()),
85            scope: "openid profile email".to_string(),
86            client_id: None,
87            extra: {
88                let mut map = serde_json::Map::new();
89                map.insert(
90                    "realm_access".to_string(),
91                    json!({
92                        "roles": ["user", "moderator"]
93                    }),
94                );
95                map
96            },
97        }
98    }
99
100    fn create_service_account_claims() -> Claims {
101        Claims {
102            sub: crate::domain::models::claims::Subject("service-123".to_string()),
103            iss: "https://auth.beep.com".to_string(),
104            aud: Some("beep-api".to_string()),
105            email: None,
106            email_verified: false,
107            name: None,
108            exp: None,
109            preferred_username: "service-account-bot".to_string(),
110            given_name: None,
111            family_name: None,
112            scope: "admin:all read:users write:messages".to_string(),
113            client_id: Some("beep-bot".to_string()),
114            extra: {
115                let mut map = serde_json::Map::new();
116                map.insert(
117                    "realm_access".to_string(),
118                    json!({
119                        "roles": ["service", "bot"]
120                    }),
121                );
122                map
123            },
124        }
125    }
126
127    #[test]
128    fn test_claims_to_identity_user() {
129        let claims = create_user_claims();
130        let identity: Identity = claims.into();
131
132        match identity {
133            Identity::User(user) => {
134                assert_eq!(user.id, "user-123");
135                assert_eq!(user.username, "johndoe");
136                assert_eq!(user.email, Some("john.doe@example.com".to_string()));
137                assert_eq!(user.name, Some("John Doe".to_string()));
138            }
139            Identity::Client(_) => panic!("Expected User, got Client"),
140        }
141    }
142
143    #[test]
144    fn test_claims_to_identity_service_account() {
145        let claims = create_service_account_claims();
146        let identity: Identity = claims.into();
147
148        match identity {
149            Identity::Client(client) => {
150                assert_eq!(client.id, "service-123");
151                assert_eq!(client.client_id, "beep-bot");
152            }
153            Identity::User(_) => panic!("Expected Client, got User"),
154        }
155    }
156}