Skip to main content

cloud_sdk_reqwest/shared/
auth.rs

1use core::fmt;
2
3use cloud_sdk_sanitization::sanitize_bytes;
4use reqwest::header::HeaderValue;
5use std::vec::Vec;
6
7/// Maximum bearer-token length accepted by the adapter.
8pub const MAX_BEARER_TOKEN_BYTES: usize = 4096;
9
10const BEARER_PREFIX: &[u8] = b"Bearer ";
11
12/// Bearer-token validation error.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum BearerTokenError {
15    /// Tokens must not be empty.
16    Empty,
17    /// Tokens exceed [`MAX_BEARER_TOKEN_BYTES`].
18    TooLong,
19    /// Tokens contain bytes outside the RFC bearer-token alphabet or invalid
20    /// non-trailing padding.
21    InvalidByte,
22    /// Allocation failed while taking owned secret storage.
23    AllocationFailed,
24}
25
26/// Owned bearer authorization value with redacted diagnostics and volatile
27/// cleanup of the adapter-owned bytes.
28pub struct BearerToken {
29    authorization: Vec<u8>,
30}
31
32impl BearerToken {
33    /// Validates and copies a bearer token into adapter-owned secret storage.
34    pub fn new(token: &str) -> Result<Self, BearerTokenError> {
35        if token.is_empty() {
36            return Err(BearerTokenError::Empty);
37        }
38        if token.len() > MAX_BEARER_TOKEN_BYTES {
39            return Err(BearerTokenError::TooLong);
40        }
41        let mut padding = false;
42        for byte in token.bytes() {
43            if byte == b'=' {
44                padding = true;
45            } else if padding || !is_bearer_byte(byte) {
46                return Err(BearerTokenError::InvalidByte);
47            }
48        }
49
50        let capacity = BEARER_PREFIX
51            .len()
52            .checked_add(token.len())
53            .ok_or(BearerTokenError::TooLong)?;
54        let mut authorization = Vec::new();
55        authorization
56            .try_reserve_exact(capacity)
57            .map_err(|_| BearerTokenError::AllocationFailed)?;
58        authorization.extend_from_slice(BEARER_PREFIX);
59        authorization.extend_from_slice(token.as_bytes());
60        Ok(Self { authorization })
61    }
62
63    pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
64        let mut value = HeaderValue::from_bytes(&self.authorization).map_err(|_| ())?;
65        value.set_sensitive(true);
66        Ok(value)
67    }
68
69    #[cfg(all(
70        test,
71        any(feature = "blocking-rustls", feature = "blocking-rustls-fips")
72    ))]
73    pub(crate) fn owned_bytes(&self) -> &[u8] {
74        &self.authorization
75    }
76}
77
78impl Drop for BearerToken {
79    fn drop(&mut self) {
80        sanitize_bytes(&mut self.authorization);
81    }
82}
83
84impl fmt::Debug for BearerToken {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        formatter.write_str("BearerToken([redacted])")
87    }
88}
89
90const fn is_bearer_byte(byte: u8) -> bool {
91    byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
92}