use secrecy::SecretString;
use crate::domain::error::WireResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AuthMethod {
#[default]
Bearer,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AuthSpec {
pub service_key: String,
#[serde(default)]
pub method: AuthMethod,
}
impl AuthSpec {
pub fn bearer(service_key: impl Into<String>) -> Self {
Self {
service_key: service_key.into(),
method: AuthMethod::Bearer,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum AuthMaterial {
Bearer(SecretString),
}
pub trait AuthResolver: Send + Sync {
fn resolve(&self, spec: &AuthSpec) -> WireResult<Option<AuthMaterial>>;
}
#[cfg(test)]
mod tests {
use super::*;
use secrecy::ExposeSecret;
#[test]
fn auth_method_default_is_bearer() {
assert_eq!(AuthMethod::default(), AuthMethod::Bearer);
}
#[test]
fn auth_method_serde_roundtrip() {
let json = serde_json::to_string(&AuthMethod::Bearer).unwrap();
assert_eq!(json, "\"bearer\"", "snake_case rename");
let back: AuthMethod = serde_json::from_str(&json).unwrap();
assert_eq!(back, AuthMethod::Bearer);
}
#[test]
fn auth_spec_bearer_constructor_sets_method() {
let spec = AuthSpec::bearer("github-alt");
assert_eq!(spec.service_key, "github-alt");
assert_eq!(spec.method, AuthMethod::Bearer);
}
#[test]
fn auth_spec_serde_roundtrip() {
let spec = AuthSpec::bearer("notion");
let json = serde_json::to_string(&spec).unwrap();
let back: AuthSpec = serde_json::from_str(&json).unwrap();
assert_eq!(back, spec);
}
#[test]
fn auth_spec_method_defaults_when_omitted_from_json() {
let json = r#"{"service_key": "slack"}"#;
let spec: AuthSpec = serde_json::from_str(json).unwrap();
assert_eq!(spec.service_key, "slack");
assert_eq!(spec.method, AuthMethod::Bearer);
}
#[test]
fn auth_material_debug_does_not_leak_secret() {
let material = AuthMaterial::Bearer(SecretString::from("super-secret-token".to_string()));
let debug_str = format!("{material:?}");
assert!(
!debug_str.contains("super-secret-token"),
"Debug output must not contain the raw secret, got: {debug_str}"
);
}
#[test]
fn auth_material_bearer_exposes_secret_only_via_expose_secret() {
let material = AuthMaterial::Bearer(SecretString::from("tok-123".to_string()));
let AuthMaterial::Bearer(secret) = material;
assert_eq!(secret.expose_secret(), "tok-123");
}
}