rama_net/user/credentials/
bearer.rs

1use rama_core::error::{ErrorContext, OpaqueError};
2use std::borrow::Cow;
3
4#[cfg(feature = "http")]
5use rama_http_types::HeaderValue;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8/// Bearer credentials.
9pub struct Bearer(Cow<'static, str>);
10
11impl Bearer {
12    /// Try to create a [`Bearer`] from a header string.
13    pub fn try_from_header_str(value: impl AsRef<str>) -> Result<Self, OpaqueError> {
14        let value = value.as_ref();
15
16        if value.len() <= BEARER_SCHEME.len() + 1 {
17            return Err(OpaqueError::from_display("invalid bearer scheme length"));
18        }
19        if !value.as_bytes()[..BEARER_SCHEME.len()].eq_ignore_ascii_case(BEARER_SCHEME.as_bytes()) {
20            return Err(OpaqueError::from_display("invalid bearer scheme"));
21        }
22
23        let bytes = &value.as_bytes()[BEARER_SCHEME.len() + 1..];
24
25        let non_space_pos = bytes
26            .iter()
27            .position(|b| *b != b' ')
28            .ok_or_else(|| OpaqueError::from_display("missing space separator in bearer str"))?;
29        let bytes = &bytes[non_space_pos..];
30
31        let s =
32            std::str::from_utf8(bytes).context("turn scheme-trimmed bearer back into utf-8 str")?;
33        Self::try_from_clear_str(s.to_owned())
34    }
35
36    /// Try to create a [`Bearer`] from a [`&'static str`][str] or [`String`].
37    pub fn try_from_clear_str(s: impl Into<Cow<'static, str>>) -> Result<Self, OpaqueError> {
38        let s = s.into();
39        if s.is_empty() {
40            return Err(OpaqueError::from_display(
41                "empty str cannot be used as Bearer",
42            ));
43        }
44        if s.as_bytes().iter().any(|b| *b < 32 || *b >= 127) {
45            return Err(OpaqueError::from_display(
46                "string contains non visible ASCII characters",
47            ));
48        }
49
50        Ok(Self(s))
51    }
52
53    /// Serialize this [`Bearer`] credential as a header string.
54    pub fn as_header_string(&self) -> String {
55        format!("{BEARER_SCHEME} {}", self.0)
56    }
57
58    #[cfg(feature = "http")]
59    /// View this [`Bearer`] as a [`HeaderValue`].
60    pub fn as_header_value(&self) -> HeaderValue {
61        let encoded = self.as_header_string();
62        // we validate the inner value upon creation
63        HeaderValue::from_str(&encoded).expect("inner value should always be valid")
64    }
65
66    /// Serialize this [`Bearer`] credential as a clear (not encoded) string.
67    pub fn as_clear_string(&self) -> String {
68        self.0.to_string()
69    }
70
71    /// View the token part as a `&str`.
72    pub fn token(&self) -> &str {
73        &self.0
74    }
75}
76
77/// Http Credentail scheme for basic credentails
78pub const BEARER_SCHEME: &str = "Bearer";
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn bearer_parse_empty() {
86        let value = Bearer::try_from_header_str("");
87        assert!(value.is_err());
88    }
89
90    #[test]
91    fn bearer_clear_text_empty() {
92        let value = Bearer::try_from_clear_str("");
93        assert!(value.is_err());
94    }
95
96    #[test]
97    fn bearer_header() {
98        let auth = Bearer::try_from_header_str("Bearer 123abc").unwrap();
99        assert_eq!(auth.token(), "123abc");
100        assert_eq!("Bearer 123abc", auth.as_header_string());
101    }
102
103    #[test]
104    fn bearer_clear() {
105        let auth = Bearer::try_from_clear_str("foobar".to_owned()).unwrap();
106        assert_eq!(auth.token(), "foobar");
107        assert_eq!("foobar", auth.as_clear_string());
108    }
109}