io_http/rfc6750/
bearer.rs1use core::fmt;
20
21use alloc::{format, string::String};
22
23use secrecy::{ExposeSecret, SecretString};
24use thiserror::Error;
25
26#[derive(Debug, Error)]
28pub enum HttpAuthBearerError {
29 #[error("Missing `Bearer ` prefix in Authorization value")]
31 MissingPrefix,
32}
33
34#[derive(Clone)]
36pub struct HttpAuthBearer(SecretString);
37
38impl HttpAuthBearer {
39 pub fn new(token: impl Into<String>) -> Self {
41 Self(SecretString::from(token.into()))
42 }
43
44 pub fn to_authorization(&self) -> String {
46 format!("Bearer {}", self.0.expose_secret())
47 }
48
49 pub fn from_authorization(value: &str) -> Result<Self, HttpAuthBearerError> {
51 value
52 .strip_prefix("Bearer ")
53 .ok_or(HttpAuthBearerError::MissingPrefix)
54 .map(|token| Self(SecretString::from(String::from(token))))
55 }
56}
57
58impl ExposeSecret<str> for HttpAuthBearer {
59 fn expose_secret(&self) -> &str {
60 self.0.expose_secret()
61 }
62}
63
64impl fmt::Debug for HttpAuthBearer {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.debug_tuple("HttpAuthBearer")
67 .field(&"[REDACTED]")
68 .finish()
69 }
70}
71
72impl fmt::Display for HttpAuthBearer {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.write_str(self.0.expose_secret())
75 }
76}
77
78impl PartialEq for HttpAuthBearer {
79 fn eq(&self, other: &Self) -> bool {
80 self.0.expose_secret() == other.0.expose_secret()
81 }
82}
83
84impl Eq for HttpAuthBearer {}
85
86#[cfg(test)]
87mod tests {
88 use alloc::{format, string::ToString};
89
90 use secrecy::ExposeSecret;
91
92 use crate::rfc6750::bearer::*;
93
94 #[test]
95 fn to_authorization_rfc_example() {
96 let token = HttpAuthBearer::new("mF_9.B5f-4.1JqM");
97 assert_eq!(token.to_authorization(), "Bearer mF_9.B5f-4.1JqM");
98 }
99
100 #[test]
101 fn to_authorization_has_bearer_prefix() {
102 let token = HttpAuthBearer::new("sometoken");
103 assert!(token.to_authorization().starts_with("Bearer "));
104 }
105
106 #[test]
107 fn from_authorization_roundtrip() {
108 let original = HttpAuthBearer::new("eyJhbGciOiJSUzI1NiJ9.example");
109 let header = original.to_authorization();
110 let parsed = HttpAuthBearer::from_authorization(&header).unwrap();
111 assert_eq!(parsed, original);
112 }
113
114 #[test]
115 fn from_authorization_missing_prefix() {
116 assert!(matches!(
117 HttpAuthBearer::from_authorization("Basic dXNlcjpwYXNz"),
118 Err(HttpAuthBearerError::MissingPrefix)
119 ));
120 }
121
122 #[test]
123 fn from_authorization_jwt_shaped_token() {
124 let value = "Bearer header.payload.signature";
125 let token = HttpAuthBearer::from_authorization(value).unwrap();
126 assert_eq!(token.expose_secret(), "header.payload.signature");
127 }
128
129 #[test]
130 fn display_yields_token_string() {
131 let token = HttpAuthBearer::new("abc123");
132 assert_eq!(token.to_string(), "abc123");
133 }
134
135 #[test]
136 fn debug_redacts_token() {
137 let token = HttpAuthBearer::new("super-secret-token");
138 let debug = format!("{token:?}");
139 assert!(
140 !debug.contains("super-secret-token"),
141 "token must not appear in debug"
142 );
143 assert!(debug.contains("[REDACTED]"));
144 }
145}