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
50                .roles
51                .iter()
52                .any(|role| matches!(role, Role::Admin | Role::Testing)),
53        }
54    }
55}
56
57impl Role {
58    /// Convert a role from a string.
59    #[must_use]
60    pub fn from_string(str: &str) -> Option<Self> {
61        match str {
62            "/Member" => Some(Self::Member),
63            "/Testing" => Some(Self::Testing),
64            "/Admin" => Some(Self::Admin),
65            _ => None,
66        }
67    }
68}
69
70// Trait implementations
71
72impl From<(Claims, Mode)> for UserInfo {
73    fn from((value, mode): (Claims, Mode)) -> Self {
74        let roles = value.groups.map_or_else(Vec::new, |groups| {
75            groups
76                .into_iter()
77                .filter_map(|s| Role::from_string(&s))
78                .collect::<Vec<_>>()
79        });
80
81        Self {
82            id: value.sub,
83            scopes: value.scope.split(' ').map(str::to_owned).collect(),
84            roles,
85            mode,
86        }
87    }
88}
89
90impl FromRequest for UserInfo {
91    type Future = Ready<Result<Self, Self::Error>>;
92    type Error = ServiceError;
93
94    fn from_request(
95        req: &actix_web::HttpRequest,
96        _payload: &mut actix_http::Payload,
97    ) -> Self::Future {
98        let extensions = req.extensions();
99        ready({
100            extensions.get::<Self>().map_or_else(
101                || {
102                    Err(ServiceError::new(
103                        StatusCode::INTERNAL_SERVER_ERROR,
104                        &StatusCode::INTERNAL_SERVER_ERROR.to_string(),
105                    ))
106                },
107                |user_info| Ok(user_info.clone()),
108            )
109        })
110    }
111}