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, Serialize};
7use uuid::Uuid;
8
9use crate::{config::app::Mode, 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    /// Optional Mode
23    pub mode: Mode,
24}
25
26/// Roles a user can have
27#[derive(Debug, Clone, Deserialize, Serialize)]
28pub enum Role {
29    /// The user is a member.
30    Member,
31    /// The user has testing privileges
32    Testing,
33    /// The user is an admin
34    Admin,
35}
36
37impl UserInfo {
38    /// Checks if the user is a member.
39    #[must_use]
40    pub fn is_member(&self) -> bool {
41        self.roles.iter().any(|role| matches!(role, Role::Member))
42    }
43
44    /// Check if a user is admin or testing.
45    #[must_use]
46    pub fn is_admin(&self) -> bool {
47        match self.mode {
48            Mode::Production => self.roles.iter().any(|role| matches!(role, Role::Admin)),
49            Mode::Testing => self.roles.iter().any(|role| matches!(role, Role::Admin)),
50        }
51    }
52    #[must_use]
53    pub fn is_testing(&self) -> bool {
54        match self.mode {
55            Mode::Production => false,
56            Mode::Testing => self.roles.iter().any(|role| matches!(role, Role::Testing)),
57        }
58    }
59}
60
61impl Role {
62    /// Convert a role from a string.
63    #[must_use]
64    pub fn from_string(str: &str) -> Option<Self> {
65        match str {
66            "/Member" => Some(Self::Member),
67            "/Testing" => Some(Self::Testing),
68            "/Admin" => Some(Self::Admin),
69            _ => None,
70        }
71    }
72}
73
74// Trait implementations
75
76impl From<(Claims, Mode)> for UserInfo {
77    fn from((value, mode): (Claims, Mode)) -> Self {
78        let roles = value.groups.map_or_else(Vec::new, |groups| {
79            groups
80                .into_iter()
81                .filter_map(|s| Role::from_string(&s))
82                .collect::<Vec<_>>()
83        });
84
85        Self {
86            id: value.sub,
87            scopes: value.scope.split(' ').map(str::to_owned).collect(),
88            roles,
89            mode,
90        }
91    }
92}
93
94impl FromRequest for UserInfo {
95    type Future = Ready<Result<Self, Self::Error>>;
96    type Error = ServiceError;
97
98    fn from_request(
99        req: &actix_web::HttpRequest,
100        _payload: &mut actix_http::Payload,
101    ) -> Self::Future {
102        let extensions = req.extensions();
103        ready({
104            extensions.get::<Self>().map_or_else(
105                || {
106                    Err(ServiceError::new(
107                        StatusCode::INTERNAL_SERVER_ERROR,
108                        &StatusCode::INTERNAL_SERVER_ERROR.to_string(),
109                    ))
110                },
111                |user_info| Ok(user_info.clone()),
112            )
113        })
114    }
115}