cloud_sdk_reqwest/shared/
auth.rs1use core::fmt;
2
3use cloud_sdk_sanitization::sanitize_bytes;
4use reqwest::header::HeaderValue;
5use std::vec::Vec;
6
7pub const MAX_BEARER_TOKEN_BYTES: usize = 4096;
9
10const BEARER_PREFIX: &[u8] = b"Bearer ";
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum BearerTokenError {
15 Empty,
17 TooLong,
19 InvalidByte,
22 AllocationFailed,
24}
25
26impl_static_error!(BearerTokenError,
27 Self::Empty => "bearer token is empty",
28 Self::TooLong => "bearer token exceeds the length limit",
29 Self::InvalidByte => "bearer token contains an invalid byte",
30 Self::AllocationFailed => "bearer-token allocation failed",
31);
32
33pub struct BearerToken {
36 authorization: Vec<u8>,
37}
38
39impl BearerToken {
40 pub fn new(token: &str) -> Result<Self, BearerTokenError> {
42 if token.is_empty() {
43 return Err(BearerTokenError::Empty);
44 }
45 if token.len() > MAX_BEARER_TOKEN_BYTES {
46 return Err(BearerTokenError::TooLong);
47 }
48 let mut padding = false;
49 for byte in token.bytes() {
50 if byte == b'=' {
51 padding = true;
52 } else if padding || !is_bearer_byte(byte) {
53 return Err(BearerTokenError::InvalidByte);
54 }
55 }
56
57 let capacity = BEARER_PREFIX
58 .len()
59 .checked_add(token.len())
60 .ok_or(BearerTokenError::TooLong)?;
61 let mut authorization = Vec::new();
62 authorization
63 .try_reserve_exact(capacity)
64 .map_err(|_| BearerTokenError::AllocationFailed)?;
65 authorization.extend_from_slice(BEARER_PREFIX);
66 authorization.extend_from_slice(token.as_bytes());
67 Ok(Self { authorization })
68 }
69
70 pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
71 let mut value = HeaderValue::from_bytes(&self.authorization).map_err(|_| ())?;
72 value.set_sensitive(true);
73 Ok(value)
74 }
75
76 #[cfg(all(
77 test,
78 any(
79 feature = "blocking-rustls",
80 feature = "blocking-rustls-webpki-roots",
81 feature = "blocking-rustls-fips"
82 )
83 ))]
84 pub(crate) fn owned_bytes(&self) -> &[u8] {
85 &self.authorization
86 }
87}
88
89impl Drop for BearerToken {
90 fn drop(&mut self) {
91 sanitize_bytes(&mut self.authorization);
92 }
93}
94
95impl fmt::Debug for BearerToken {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str("BearerToken([redacted])")
98 }
99}
100
101const fn is_bearer_byte(byte: u8) -> bool {
102 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
103}