palpo_core/error/
auth.rs

1//! Errors that can be sent from the homeserver.
2use std::collections::BTreeMap;
3
4use crate::PrivOwnedStr;
5
6/// Errors in the `WWW-Authenticate` header.
7///
8/// To construct this use `::from_str()`. To get its serialized form, use its
9/// `TryInto<http::HeaderValue>` implementation.
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum AuthenticateError {
14    /// insufficient_scope
15    ///
16    /// Encountered when authentication is handled by OpenID Connect and the current access token
17    /// isn't authorized for the proper scope for this request. It should be paired with a
18    /// `401` status code and a `M_FORBIDDEN` error.
19    InsufficientScope {
20        /// The new scope to request an authorization for.
21        scope: String,
22    },
23
24    #[doc(hidden)]
25    _Custom {
26        errcode: PrivOwnedStr,
27        attributes: AuthenticateAttrs,
28    },
29}
30
31#[doc(hidden)]
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct AuthenticateAttrs(BTreeMap<String, String>);
34
35impl AuthenticateError {
36    /// Construct an `AuthenticateError` from a string.
37    ///
38    /// Returns `None` if the string doesn't contain an error.
39    fn from_str(s: &str) -> Option<Self> {
40        if let Some(val) = s.strip_prefix("Bearer").map(str::trim) {
41            let mut errcode = None;
42            let mut attrs = BTreeMap::new();
43
44            // Split the attributes separated by commas and optionally spaces, then split the keys
45            // and the values, with the values optionally surrounded by double quotes.
46            for (key, value) in val
47                .split(',')
48                .filter_map(|attr| attr.trim().split_once('='))
49                .map(|(key, value)| (key, value.trim_matches('"')))
50            {
51                if key == "error" {
52                    errcode = Some(value);
53                } else {
54                    attrs.insert(key.to_owned(), value.to_owned());
55                }
56            }
57
58            if let Some(errcode) = errcode {
59                let error = if let Some(scope) = attrs.get("scope").filter(|_| errcode == "insufficient_scope") {
60                    AuthenticateError::InsufficientScope {
61                        scope: scope.to_owned(),
62                    }
63                } else {
64                    AuthenticateError::_Custom {
65                        errcode: PrivOwnedStr(errcode.into()),
66                        attributes: AuthenticateAttrs(attrs),
67                    }
68                };
69
70                return Some(error);
71            }
72        }
73
74        None
75    }
76}
77
78impl TryFrom<&AuthenticateError> for http::HeaderValue {
79    type Error = http::header::InvalidHeaderValue;
80
81    fn try_from(error: &AuthenticateError) -> Result<Self, Self::Error> {
82        let s = match error {
83            AuthenticateError::InsufficientScope { scope } => {
84                format!("Bearer error=\"insufficient_scope\", scope=\"{scope}\"")
85            }
86            AuthenticateError::_Custom { errcode, attributes } => {
87                let mut s = format!("Bearer error=\"{}\"", errcode.0);
88
89                for (key, value) in attributes.0.iter() {
90                    s.push_str(&format!(", {key}=\"{value}\""));
91                }
92
93                s
94            }
95        };
96
97        s.try_into()
98    }
99}