1use reqwest::RequestBuilder;
16
17use crate::error::{Error, Result};
18
19pub trait Auth: Sync + Send {
20 fn wrap(&self, builder: RequestBuilder) -> Result<RequestBuilder>;
21 fn can_reload(&self) -> bool {
22 false
23 }
24 fn username(&self) -> String;
25}
26
27#[derive(Clone)]
28pub struct BasicAuth {
29 username: String,
30 password: SensitiveString,
31}
32
33impl BasicAuth {
34 pub fn new(username: impl ToString, password: impl ToString) -> Self {
35 Self {
36 username: username.to_string(),
37 password: SensitiveString(password.to_string()),
38 }
39 }
40}
41
42impl Auth for BasicAuth {
43 fn wrap(&self, builder: RequestBuilder) -> Result<RequestBuilder> {
44 Ok(builder.basic_auth(&self.username, Some(self.password.inner())))
45 }
46
47 fn username(&self) -> String {
48 self.username.clone()
49 }
50}
51
52#[derive(Clone)]
53pub struct AccessTokenAuth {
54 token: SensitiveString,
55}
56
57impl AccessTokenAuth {
58 pub fn new(token: impl ToString) -> Self {
59 Self {
60 token: SensitiveString::from(token.to_string()),
61 }
62 }
63}
64
65impl Auth for AccessTokenAuth {
66 fn wrap(&self, builder: RequestBuilder) -> Result<RequestBuilder> {
67 Ok(builder.bearer_auth(self.token.inner()))
68 }
69
70 fn username(&self) -> String {
71 "token".to_string()
72 }
73}
74
75#[derive(Clone)]
76pub struct AccessTokenFileAuth {
77 token_file: String,
78}
79
80impl AccessTokenFileAuth {
81 pub fn new(token_file: impl ToString) -> Self {
82 let token_file = token_file.to_string();
83 Self { token_file }
84 }
85}
86
87impl Auth for AccessTokenFileAuth {
88 fn wrap(&self, builder: RequestBuilder) -> Result<RequestBuilder> {
89 let token = std::fs::read_to_string(&self.token_file).map_err(|e| {
90 Error::IO(format!(
91 "cannot read access token from file {}: {}",
92 self.token_file, e
93 ))
94 })?;
95 Ok(builder.bearer_auth(token.trim()))
96 }
97
98 fn can_reload(&self) -> bool {
99 true
100 }
101
102 fn username(&self) -> String {
103 "token".to_string()
104 }
105}
106
107#[derive(::serde::Deserialize, ::serde::Serialize)]
108#[serde(from = "String", into = "String")]
109#[derive(Clone, Default, PartialEq, Eq)]
110pub struct SensitiveString(String);
111
112impl From<String> for SensitiveString {
113 fn from(value: String) -> Self {
114 Self(value)
115 }
116}
117
118impl From<&str> for SensitiveString {
119 fn from(value: &str) -> Self {
120 Self(value.to_string())
121 }
122}
123
124impl From<SensitiveString> for String {
125 fn from(value: SensitiveString) -> Self {
126 value.0
127 }
128}
129
130impl std::fmt::Display for SensitiveString {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(f, "**REDACTED**")
133 }
134}
135
136impl std::fmt::Debug for SensitiveString {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 write!(f, "\"**REDACTED**\"")
140 }
141}
142
143impl SensitiveString {
144 #[must_use]
145 pub fn inner(&self) -> &str {
146 self.0.as_str()
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn serialization() {
156 let json_value = "\"foo\"";
157 let value: SensitiveString = serde_json::from_str(json_value).unwrap();
158 let result: String = serde_json::to_string(&value).unwrap();
159 assert_eq!(result, json_value);
160 }
161
162 #[test]
163 fn hide_content() {
164 let value = SensitiveString("hello world".to_string());
165 let display = format!("{value}");
166 assert_eq!(display, "**REDACTED**");
167 let debug = format!("{value:?}");
168 assert_eq!(debug, "\"**REDACTED**\"");
169 }
170}