zino_auth/
user_session.rs

1use super::{AccessKeyId, SessionId};
2use serde::{Deserialize, Serialize};
3use std::str::FromStr;
4use zino_core::{
5    application::{Agent, Application},
6    crypto::Digest,
7};
8
9#[cfg(feature = "jwt")]
10use super::JwtClaims;
11
12#[cfg(feature = "jwt")]
13use zino_core::{error::Error, extension::JsonObjectExt, warn};
14
15/// Role-based user sessions.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct UserSession<U, R = String, T = U> {
18    /// User ID.
19    user_id: U,
20    /// Session ID.
21    session_id: Option<SessionId>,
22    /// Access key ID.
23    access_key_id: Option<AccessKeyId>,
24    /// A list of user roles.
25    roles: Vec<R>,
26    /// Tenant ID.
27    tenant_id: Option<T>,
28}
29
30impl<U, R, T> UserSession<U, R, T> {
31    /// Creates a new instance with empty roles.
32    #[inline]
33    pub fn new(user_id: U, session_id: impl Into<Option<SessionId>>) -> Self {
34        Self {
35            user_id,
36            session_id: session_id.into(),
37            access_key_id: None,
38            roles: Vec::new(),
39            tenant_id: None,
40        }
41    }
42
43    /// Sets the session ID.
44    #[inline]
45    pub fn set_session_id(&mut self, session_id: SessionId) {
46        self.session_id = Some(session_id);
47    }
48
49    /// Sets the access key ID.
50    #[inline]
51    pub fn set_access_key_id(&mut self, access_key_id: AccessKeyId) {
52        if self.session_id.is_none() {
53            let session_id = SessionId::new::<Digest>(Agent::domain(), access_key_id.as_ref());
54            self.session_id = Some(session_id);
55        }
56        self.access_key_id = Some(access_key_id);
57    }
58
59    /// Sets the user roles.
60    #[inline]
61    pub fn set_roles(&mut self, roles: impl Into<Vec<R>>) {
62        self.roles = roles.into();
63    }
64
65    /// Sets the tenant ID.
66    #[inline]
67    pub fn set_tenant_id(&mut self, tenant_id: T) {
68        self.tenant_id = Some(tenant_id);
69    }
70
71    /// Returns the user ID.
72    #[inline]
73    pub fn user_id(&self) -> &U {
74        &self.user_id
75    }
76
77    /// Returns the tenant ID.
78    #[inline]
79    pub fn tenant_id(&self) -> Option<&T> {
80        self.tenant_id.as_ref()
81    }
82
83    /// Returns the session ID.
84    #[inline]
85    pub fn session_id(&self) -> Option<&SessionId> {
86        self.session_id.as_ref()
87    }
88
89    /// Returns the access key ID.
90    #[inline]
91    pub fn access_key_id(&self) -> Option<&AccessKeyId> {
92        self.access_key_id.as_ref()
93    }
94
95    /// Returns the roles.
96    #[inline]
97    pub fn roles(&self) -> &[R] {
98        &self.roles
99    }
100}
101
102impl<U, R, T> UserSession<U, R, T>
103where
104    U: FromStr,
105    R: FromStr,
106    T: FromStr,
107    <U as FromStr>::Err: std::error::Error + Send + 'static,
108{
109    /// Attempts to construct an instance from a `JwtClaims`.
110    #[cfg(feature = "jwt")]
111    pub fn try_from_jwt_claims(claims: JwtClaims) -> Result<Self, Error> {
112        let data = claims.data();
113        let user_id = claims
114            .subject()
115            .map(|s| s.into())
116            .or_else(|| data.parse_string("uid"))
117            .ok_or_else(|| warn!("subject of a JWT token should be specified"))?
118            .parse()?;
119        let mut user_session = Self::new(user_id, None);
120        if let Some(Ok(roles)) = data
121            .parse_array("roles")
122            .or_else(|| data.parse_array("role"))
123        {
124            user_session.set_roles(roles);
125        }
126        if let Some(tenant_id) = data
127            .parse_string("tenant_id")
128            .or_else(|| data.parse_string("tid"))
129            .and_then(|s| s.parse().ok())
130        {
131            user_session.set_tenant_id(tenant_id);
132        }
133        Ok(user_session)
134    }
135}
136
137impl<U, T> UserSession<U, String, T> {
138    /// Returns `true` if the user has a role of `superuser`.
139    #[inline]
140    pub fn is_superuser(&self) -> bool {
141        self.roles() == ["superuser"]
142    }
143
144    /// Returns `true` if the user has a role of `user`.
145    #[inline]
146    pub fn is_user(&self) -> bool {
147        self.roles() == ["user"]
148    }
149
150    /// Returns `true` if the user has a role of `guest`.
151    #[inline]
152    pub fn is_guest(&self) -> bool {
153        self.roles() == ["guest"]
154    }
155
156    /// Returns `true` if the user has a role of `agent`.
157    #[inline]
158    pub fn is_agent(&self) -> bool {
159        self.roles() == ["agent"]
160    }
161
162    /// Returns `true` if the user has a role of `admin`.
163    pub fn is_admin(&self) -> bool {
164        let role = "admin";
165        let role_prefix = format!("{role}:");
166        for r in &self.roles {
167            if r == role || r.starts_with(&role_prefix) {
168                return true;
169            }
170        }
171        false
172    }
173
174    /// Returns `true` if the user has a role of `worker`.
175    pub fn is_worker(&self) -> bool {
176        let role = "worker";
177        let role_prefix = format!("{role}:");
178        for r in &self.roles {
179            if r == role || r.starts_with(&role_prefix) {
180                return true;
181            }
182        }
183        false
184    }
185
186    /// Returns `true` if the user has a role of `auditor`.
187    pub fn is_auditor(&self) -> bool {
188        let role = "auditor";
189        let role_prefix = format!("{role}:");
190        for r in &self.roles {
191            if r == role || r.starts_with(&role_prefix) {
192                return true;
193            }
194        }
195        false
196    }
197
198    /// Returns `true` if the user has one of the roles: `superuser`, `user`,
199    /// `admin`, `worker` and `auditor`.
200    pub fn has_user_role(&self) -> bool {
201        self.is_superuser()
202            || self.is_user()
203            || self.is_admin()
204            || self.is_worker()
205            || self.is_auditor()
206    }
207
208    /// Returns `true` if the user has a role of `superuser` or `admin`.
209    pub fn has_admin_role(&self) -> bool {
210        self.is_superuser() || self.is_admin()
211    }
212
213    /// Returns `true` if the user has a role of `superuser` or `worker`.
214    pub fn has_worker_role(&self) -> bool {
215        self.is_superuser() || self.is_worker()
216    }
217
218    /// Returns `true` if the user has a role of `superuser` or `auditor`.
219    pub fn has_auditor_role(&self) -> bool {
220        self.is_superuser() || self.is_auditor()
221    }
222
223    /// Returns `true` if the user has the specific `role`.
224    pub fn has_role(&self, role: &str) -> bool {
225        let length = role.len();
226        for r in &self.roles {
227            if r == role {
228                return true;
229            } else {
230                let remainder = if r.len() > length {
231                    r.strip_prefix(role)
232                } else {
233                    role.strip_prefix(r.as_str())
234                };
235                if remainder.is_some_and(|s| s.starts_with(':')) {
236                    return true;
237                }
238            }
239        }
240        false
241    }
242
243    /// Returns `true` if the user has any of the specific `roles`.
244    pub fn has_any_roles(&self, roles: &[&str]) -> bool {
245        for role in roles {
246            if self.has_role(role) {
247                return true;
248            }
249        }
250        false
251    }
252
253    /// Returns `true` if the user has all of the specific `roles`.
254    pub fn has_all_roles(&self, roles: &[&str]) -> bool {
255        for role in roles {
256            if !self.has_role(role) {
257                return false;
258            }
259        }
260        true
261    }
262}