#[non_exhaustive]
#[derive(Clone, PartialEq, Eq)]
pub enum Credentials {
Basic {
username: String,
password: String,
},
Digest {
username: String,
password: String,
},
Bearer {
token: String,
},
}
impl Credentials {
pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
Credentials::Digest {
username: username.into(),
password: password.into(),
}
}
pub fn bearer(token: impl Into<String>) -> Self {
Credentials::Bearer {
token: token.into(),
}
}
pub(crate) fn username_password(&self) -> Option<(&str, &str)> {
match self {
Credentials::Basic { username, password }
| Credentials::Digest { username, password } => Some((username, password)),
Credentials::Bearer { .. } => None,
}
}
}
impl core::fmt::Debug for Credentials {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Credentials::Basic { username, .. } => f
.debug_struct("Credentials::Basic")
.field("username", username)
.field("password", &"***")
.finish(),
Credentials::Digest { username, .. } => f
.debug_struct("Credentials::Digest")
.field("username", username)
.field("password", &"***")
.finish(),
Credentials::Bearer { .. } => f
.debug_struct("Credentials::Bearer")
.field("token", &"***")
.finish(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_debug_redacts_password_but_keeps_username() {
let creds = Credentials::Basic {
username: "admin".to_string(),
password: "s3cr3t-password".to_string(),
};
let debug = format!("{creds:?}");
assert!(!debug.contains("s3cr3t-password"), "leaked: {debug}");
assert!(
debug.contains("admin"),
"username should be visible: {debug}"
);
assert!(debug.contains("***"), "expected redaction marker: {debug}");
}
#[test]
fn digest_debug_redacts_password_but_keeps_username() {
let creds = Credentials::Digest {
username: "camera-user".to_string(),
password: "hunter2-super-secret".to_string(),
};
let debug = format!("{creds:?}");
assert!(!debug.contains("hunter2-super-secret"), "leaked: {debug}");
assert!(
debug.contains("camera-user"),
"username should be visible: {debug}"
);
assert!(debug.contains("***"), "expected redaction marker: {debug}");
}
#[test]
fn bearer_debug_redacts_token() {
let creds = Credentials::bearer("super-secret-bearer-token-xyz");
let debug = format!("{creds:?}");
assert!(
!debug.contains("super-secret-bearer-token-xyz"),
"leaked: {debug}"
);
assert!(debug.contains("***"), "expected redaction marker: {debug}");
}
}