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
26pub struct BearerToken {
29 authorization: Vec<u8>,
30}
31
32impl BearerToken {
33 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(
72 feature = "blocking-rustls",
73 feature = "blocking-rustls-webpki-roots",
74 feature = "blocking-rustls-fips"
75 )
76 ))]
77 pub(crate) fn owned_bytes(&self) -> &[u8] {
78 &self.authorization
79 }
80}
81
82impl Drop for BearerToken {
83 fn drop(&mut self) {
84 sanitize_bytes(&mut self.authorization);
85 }
86}
87
88impl fmt::Debug for BearerToken {
89 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90 formatter.write_str("BearerToken([redacted])")
91 }
92}
93
94const fn is_bearer_byte(byte: u8) -> bool {
95 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
96}