rustical_store/auth/
principal.rs

1use crate::{Secret, auth::PrincipalType};
2use axum::{
3    body::Body,
4    extract::{FromRequestParts, OptionalFromRequestParts},
5    response::{IntoResponse, Response},
6};
7use chrono::{DateTime, Utc};
8use derive_more::Display;
9use http::{HeaderValue, StatusCode, header};
10use serde::{Deserialize, Serialize};
11use std::convert::Infallible;
12
13#[derive(Debug, Clone, Deserialize, Serialize)]
14pub struct AppToken {
15    pub id: String,
16    pub name: String,
17    pub token: Secret<String>,
18    pub created_at: Option<DateTime<Utc>>,
19}
20
21#[derive(Debug, Clone, Deserialize, Serialize)]
22#[serde(deny_unknown_fields)]
23pub struct Principal {
24    pub id: String,
25    pub displayname: Option<String>,
26    #[serde(default)]
27    pub principal_type: PrincipalType,
28    #[serde(skip_serializing)]
29    pub password: Option<Secret<String>>,
30    #[serde(default)]
31    pub memberships: Vec<String>,
32}
33
34impl Principal {
35    /// Returns true if the user is either
36    /// - the principal itself
37    /// - has full access to the prinicpal (is member)
38    #[must_use]
39    pub fn is_principal(&self, principal: &str) -> bool {
40        if self.id == principal {
41            return true;
42        }
43        self.memberships
44            .iter()
45            .any(|membership| membership == principal)
46    }
47
48    /// Returns all principals the user implements
49    pub fn memberships(&self) -> Vec<&str> {
50        let mut memberships: Vec<_> = self.memberships.iter().map(String::as_ref).collect();
51        memberships.push(self.id.as_str());
52        memberships
53    }
54
55    pub fn memberships_without_self(&self) -> Vec<&str> {
56        self.memberships.iter().map(String::as_str).collect()
57    }
58}
59
60impl rustical_dav::Principal for Principal {
61    fn get_id(&self) -> &str {
62        &self.id
63    }
64}
65
66#[derive(Clone, Debug, Display)]
67pub struct UnauthorizedError;
68
69impl IntoResponse for UnauthorizedError {
70    fn into_response(self) -> axum::response::Response {
71        let mut resp = Response::builder().status(StatusCode::UNAUTHORIZED);
72        resp.headers_mut().unwrap().insert(
73            header::WWW_AUTHENTICATE,
74            HeaderValue::from_static(r#"Basic realm="RustiCal", charset="UTF-8""#),
75        );
76        resp.body(Body::empty()).unwrap()
77    }
78}
79
80impl<S: Send + Sync + Clone> FromRequestParts<S> for Principal {
81    type Rejection = UnauthorizedError;
82
83    async fn from_request_parts(
84        parts: &mut http::request::Parts,
85        _state: &S,
86    ) -> Result<Self, Self::Rejection> {
87        parts
88            .extensions
89            .get::<Self>()
90            .cloned()
91            .ok_or(UnauthorizedError)
92    }
93}
94
95impl<S: Send + Sync + Clone> OptionalFromRequestParts<S> for Principal {
96    type Rejection = Infallible;
97
98    async fn from_request_parts(
99        parts: &mut http::request::Parts,
100        _state: &S,
101    ) -> Result<Option<Self>, Self::Rejection> {
102        Ok(parts.extensions.get::<Self>().cloned())
103    }
104}