backend/config/auth/
user_info.rs

1//! Contains [`UserInfo`] which stores information about the current user.
2
3use actix_http::{HttpMessage, StatusCode};
4use actix_utils::future::{ready, Ready};
5use actix_web::FromRequest;
6use serde::Deserialize;
7use uuid::Uuid;
8
9use crate::error::ServiceError;
10
11use super::claims::Claims;
12
13/// Information about the user extracted from the token provided.
14#[derive(Debug, Clone, Deserialize)]
15pub struct UserInfo {
16    /// The current users id.
17    pub id: Uuid,
18    /// The scopes the current user has.
19    pub scopes: Vec<String>,
20    /// The roles the current user has.
21    pub roles: Vec<Role>,
22}
23
24/// Roles a user can have
25#[derive(Debug, Clone, Deserialize)]
26pub enum Role {
27    /// The user is a member.
28    Member,
29    /// The user has testing privileges
30    Testing,
31    /// The user is an admin
32    Admin,
33}
34
35impl UserInfo {
36    /// Checks if the user is a member.
37    #[must_use]
38    pub fn is_member(&self) -> bool {
39        self.roles.iter().any(|role| matches!(role, Role::Member))
40    }
41
42    /// Check if a user is admin or testing.
43    /// TODO: this check should be refined for production.
44    #[must_use]
45    pub fn is_admin(&self) -> bool {
46        self.roles
47            .iter()
48            .any(|role| matches!(role, Role::Admin | Role::Testing))
49    }
50}
51
52impl Role {
53    /// Convert a role from a string.
54    #[must_use]
55    pub fn from_string(str: &str) -> Option<Self> {
56        match str {
57            "/Member" => Some(Self::Member),
58            "/Testing" => Some(Self::Testing),
59            "/Admin" => Some(Self::Admin),
60            _ => None,
61        }
62    }
63}
64
65// Trait implementations
66
67impl From<Claims> for UserInfo {
68    fn from(value: Claims) -> Self {
69        let roles = value.groups.map_or_else(Vec::new, |groups| {
70            groups
71                .into_iter()
72                .filter_map(|s| Role::from_string(&s))
73                .collect::<Vec<_>>()
74        });
75
76        Self {
77            id: value.sub,
78            scopes: value.scope.split(' ').map(str::to_owned).collect(),
79            roles,
80        }
81    }
82}
83
84impl FromRequest for UserInfo {
85    type Future = Ready<Result<Self, Self::Error>>;
86    type Error = ServiceError;
87
88    fn from_request(
89        req: &actix_web::HttpRequest,
90        _payload: &mut actix_http::Payload,
91    ) -> Self::Future {
92        let extensions = req.extensions();
93        ready({
94            extensions.get::<Self>().map_or_else(
95                || {
96                    Err(ServiceError::new(
97                        StatusCode::INTERNAL_SERVER_ERROR,
98                        &StatusCode::INTERNAL_SERVER_ERROR.to_string(),
99                    ))
100                },
101                |user_info| Ok(user_info.clone()),
102            )
103        })
104    }
105}