nextcloud_auth/basic/
basic_auth.rs1use std::fmt;
2
3use base64::Engine as _;
4use base64::prelude::BASE64_STANDARD;
5use sdk_core::{AuthProvider, Headers};
6
7use super::AppPassword;
8use crate::SecretString;
9
10#[derive(Clone)]
16pub struct BasicAuth {
17 credentials: AppPassword,
18}
19
20impl BasicAuth {
21 pub fn new(user: impl Into<String>, password: SecretString) -> Self {
23 Self {
24 credentials: AppPassword::new(user, password),
25 }
26 }
27
28 pub const fn from_app_password(credentials: AppPassword) -> Self {
30 Self { credentials }
31 }
32
33 pub const fn credentials(&self) -> &AppPassword {
35 &self.credentials
36 }
37
38 pub fn header_value(&self) -> String {
40 let encoded = BASE64_STANDARD.encode(self.encoded_pair());
41 format!("Basic {encoded}")
42 }
43
44 fn encoded_pair(&self) -> Vec<u8> {
45 let mut pair = Vec::with_capacity(self.credentials.user().len() + 1);
46 pair.extend_from_slice(self.credentials.user().as_bytes());
47 pair.push(b':');
48 pair.extend_from_slice(self.credentials.password().expose().as_bytes());
49 pair
50 }
51}
52
53impl AuthProvider for BasicAuth {
54 fn authenticate(&self, headers: &mut Headers) {
55 headers.set("Authorization", self.header_value());
56 }
57}
58
59impl fmt::Debug for BasicAuth {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.debug_struct("BasicAuth")
62 .field("credentials", &self.credentials)
63 .finish()
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use base64::Engine as _;
70 use base64::prelude::BASE64_STANDARD;
71 use sdk_core::{AuthProvider, Headers};
72
73 use super::BasicAuth;
74 use crate::SecretString;
75
76 fn basic_auth() -> BasicAuth {
77 BasicAuth::new("alice", SecretString::new("app-password"))
78 }
79
80 #[test]
81 fn header_value_matches_independent_encoding() {
82 let provider = BasicAuth::new("alice", SecretString::new("app-password"));
83 let expected = BASE64_STANDARD.encode(b"alice:app-password");
84 assert_eq!(provider.header_value(), format!("Basic {expected}"));
85 }
86
87 #[test]
88 fn authenticate_sets_the_authorization_header() {
89 let mut headers = Headers::new();
90 basic_auth().authenticate(&mut headers);
91 assert_eq!(
92 headers.get("Authorization"),
93 Some("Basic YWxpY2U6YXBwLXBhc3N3b3Jk")
94 );
95 }
96
97 #[test]
98 fn authenticate_replaces_an_existing_authorization_header() {
99 let mut headers = Headers::new().with(sdk_core::Header::new("authorization", "stale"));
100 basic_auth().authenticate(&mut headers);
101 assert_eq!(headers.len(), 1);
102 assert_eq!(
103 headers.get("Authorization"),
104 Some("Basic YWxpY2U6YXBwLXBhc3N3b3Jk")
105 );
106 }
107
108 #[test]
109 fn debug_redacts_both_the_password_and_the_encoded_pair() {
110 let rendered = format!("{:?}", basic_auth());
111 assert!(!rendered.contains("app-password"));
112 assert!(!rendered.contains("YWxpY2U6YXBwLXBhc3N3b3Jk"));
113 assert!(rendered.contains("<redacted>"));
114 }
115}