Skip to main content

bat_markets_core/
auth.rs

1use core::fmt;
2use std::env;
3
4use hmac::{Hmac, Mac};
5use sha2::Sha256;
6
7use crate::error::{ErrorKind, MarketError, Result};
8
9type HmacSha256 = Hmac<Sha256>;
10
11/// Signer abstraction for private request authentication.
12pub trait Signer: Send + Sync {
13    fn sign_hex(&self, payload: &[u8]) -> Result<String>;
14}
15
16/// In-memory signer used by tests or controlled environments.
17pub struct MemorySigner {
18    secret: Vec<u8>,
19}
20
21impl MemorySigner {
22    #[must_use]
23    pub fn new(secret: impl AsRef<[u8]>) -> Self {
24        Self {
25            secret: secret.as_ref().to_vec(),
26        }
27    }
28}
29
30impl fmt::Debug for MemorySigner {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("MemorySigner")
33            .field("secret", &"<redacted>")
34            .finish()
35    }
36}
37
38impl Signer for MemorySigner {
39    fn sign_hex(&self, payload: &[u8]) -> Result<String> {
40        let mut mac = HmacSha256::new_from_slice(&self.secret)
41            .map_err(|_| MarketError::new(ErrorKind::ConfigError, "invalid hmac key length"))?;
42        mac.update(payload);
43        Ok(hex::encode(mac.finalize().into_bytes()))
44    }
45}
46
47/// Env-backed signer that reads the secret lazily on every signing call.
48pub struct EnvSigner {
49    secret_var: Box<str>,
50}
51
52impl EnvSigner {
53    #[must_use]
54    pub fn new(secret_var: impl Into<Box<str>>) -> Self {
55        Self {
56            secret_var: secret_var.into(),
57        }
58    }
59}
60
61impl fmt::Debug for EnvSigner {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.debug_struct("EnvSigner")
64            .field("secret_var", &self.secret_var)
65            .field("secret", &"<redacted>")
66            .finish()
67    }
68}
69
70impl Signer for EnvSigner {
71    fn sign_hex(&self, payload: &[u8]) -> Result<String> {
72        let secret = env::var(self.secret_var.as_ref()).map_err(|_| {
73            MarketError::new(
74                ErrorKind::AuthError,
75                format!("missing env secret {}", self.secret_var),
76            )
77        })?;
78        MemorySigner::new(secret).sign_hex(payload)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::{MemorySigner, Signer};
85
86    #[test]
87    fn memory_signer_produces_stable_hmac() {
88        let signer = MemorySigner::new("secret");
89        let signature = signer.sign_hex(b"payload");
90        assert_eq!(
91            signature.as_deref(),
92            Ok("b82fcb791acec57859b989b430a826488ce2e479fdf92326bd0a2e8375a42ba4")
93        );
94    }
95}