1use hmac::{Hmac, Mac};
2use sha2::Sha256;
3use base64::{Engine as _, engine::general_purpose::STANDARD};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6type HmacSha256 = Hmac<Sha256>;
7
8pub struct Auth;
9
10impl Auth {
11 pub fn generate_hmac_signature(secret_key: &str, method: &str, path: &str) -> (String, String) {
12 let clean_path = path.split('?').next().unwrap_or(path);
13 let timestamp = SystemTime::now()
14 .duration_since(UNIX_EPOCH)
15 .unwrap()
16 .as_secs()
17 .to_string();
18
19 let message = format!("{};{};{}", timestamp, method, clean_path);
20
21 let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes())
22 .expect("HMAC can take key of any size");
23 mac.update(message.as_bytes());
24 let result = mac.finalize();
25 let signature = STANDARD.encode(result.into_bytes());
26
27 (signature, timestamp)
28 }
29
30 pub fn build_auth_header(key_id: &str, secret_key: &str, method: &str, path: &str) -> String {
31 let (signature, timestamp) = Self::generate_hmac_signature(secret_key, method, path);
32 format!(
33 "MUXI-HMAC key={}, timestamp={}, signature={}",
34 key_id, timestamp, signature
35 )
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn test_generate_hmac_signature() {
45 let (sig, ts) = Auth::generate_hmac_signature("secret456", "GET", "/rpc/status");
46 assert!(!sig.is_empty());
47 assert!(!ts.is_empty());
48 }
49
50 #[test]
51 fn test_build_auth_header() {
52 let header = Auth::build_auth_header("key123", "secret456", "GET", "/path");
53 assert!(header.contains("MUXI-HMAC key="));
54 assert!(header.contains("key123"));
55 assert!(header.contains("timestamp="));
56 assert!(header.contains("signature="));
57 }
58
59 #[test]
60 fn test_signature_strips_query_params() {
61 let (sig1, _) = Auth::generate_hmac_signature("secret", "GET", "/path");
62 let (sig2, _) = Auth::generate_hmac_signature("secret", "GET", "/path?foo=bar");
63 assert!(!sig1.is_empty());
64 assert!(!sig2.is_empty());
65 }
66}