Skip to main content

cloud_sdk_reqwest/shared/
auth.rs

1use core::fmt;
2
3use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
4use reqwest::header::HeaderValue;
5#[cfg(test)]
6use std::sync::{
7    Arc,
8    atomic::{AtomicUsize, Ordering},
9};
10use std::vec::Vec;
11
12/// Maximum bearer-token length accepted by the adapter.
13pub const MAX_BEARER_TOKEN_BYTES: usize = 4096;
14
15const BEARER_PREFIX: &[u8] = b"Bearer ";
16
17/// Bearer-token validation error.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum BearerTokenError {
20    /// Tokens must not be empty.
21    Empty,
22    /// Tokens exceed [`MAX_BEARER_TOKEN_BYTES`].
23    TooLong,
24    /// Tokens contain bytes outside the RFC bearer-token alphabet or invalid
25    /// non-trailing padding.
26    InvalidByte,
27    /// Allocation failed while taking owned secret storage.
28    AllocationFailed,
29}
30
31impl_static_error!(BearerTokenError,
32    Self::Empty => "bearer token is empty",
33    Self::TooLong => "bearer token exceeds the length limit",
34    Self::InvalidByte => "bearer token contains an invalid byte",
35    Self::AllocationFailed => "bearer-token allocation failed",
36);
37
38/// Owned bearer authorization value with redacted diagnostics and volatile
39/// cleanup of the adapter-owned bytes.
40pub struct BearerToken {
41    authorization: Vec<u8>,
42    #[cfg(test)]
43    drop_probe: Option<Arc<AtomicUsize>>,
44}
45
46impl BearerToken {
47    /// Validates and copies a bearer token into adapter-owned secret storage.
48    pub fn new(token: &str) -> Result<Self, BearerTokenError> {
49        Self::from_bytes(token.as_bytes())
50    }
51
52    /// Consumes a mutable bearer-token source and clears the complete source
53    /// buffer on success or failure.
54    pub fn from_mut_bytes(token: &mut [u8]) -> Result<Self, BearerTokenError> {
55        let result = Self::from_bytes(token);
56        sanitize_bytes(token);
57        result
58    }
59
60    /// Consumes guarded bearer-token storage, which clears its complete source
61    /// buffer when this function returns on success or failure.
62    pub fn from_secret_buffer(token: SecretBuffer<'_>) -> Result<Self, BearerTokenError> {
63        Self::from_bytes(token.as_slice())
64    }
65
66    fn from_bytes(token: &[u8]) -> Result<Self, BearerTokenError> {
67        if token.is_empty() {
68            return Err(BearerTokenError::Empty);
69        }
70        if token.len() > MAX_BEARER_TOKEN_BYTES {
71            return Err(BearerTokenError::TooLong);
72        }
73        let mut padding = false;
74        for byte in token.iter().copied() {
75            if byte == b'=' {
76                padding = true;
77            } else if padding || !is_bearer_byte(byte) {
78                return Err(BearerTokenError::InvalidByte);
79            }
80        }
81
82        let capacity = BEARER_PREFIX
83            .len()
84            .checked_add(token.len())
85            .ok_or(BearerTokenError::TooLong)?;
86        let mut authorization = Vec::new();
87        authorization
88            .try_reserve_exact(capacity)
89            .map_err(|_| BearerTokenError::AllocationFailed)?;
90        authorization.extend_from_slice(BEARER_PREFIX);
91        authorization.extend_from_slice(token);
92        Ok(Self {
93            authorization,
94            #[cfg(test)]
95            drop_probe: None,
96        })
97    }
98
99    pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
100        let mut value = HeaderValue::from_bytes(&self.authorization).map_err(|_| ())?;
101        value.set_sensitive(true);
102        Ok(value)
103    }
104
105    #[cfg(test)]
106    pub(crate) fn owned_bytes(&self) -> &[u8] {
107        &self.authorization
108    }
109
110    #[cfg(test)]
111    pub(crate) fn with_drop_probe(
112        token: &str,
113        probe: Arc<AtomicUsize>,
114    ) -> Result<Self, BearerTokenError> {
115        let mut value = Self::new(token)?;
116        value.drop_probe = Some(probe);
117        Ok(value)
118    }
119}
120
121impl Drop for BearerToken {
122    fn drop(&mut self) {
123        sanitize_bytes(&mut self.authorization);
124        #[cfg(test)]
125        if let Some(probe) = &self.drop_probe {
126            probe.fetch_add(1, Ordering::SeqCst);
127        }
128    }
129}
130
131impl fmt::Debug for BearerToken {
132    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133        formatter.write_str("BearerToken([redacted])")
134    }
135}
136
137const fn is_bearer_byte(byte: u8) -> bool {
138    byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
139}