bitbank_api/private/
auth.rs

1use reqwest::header::HeaderMap;
2
3const ACCESS_KEY: &'static str = "ACCESS-KEY";
4const ACCESS_NONCE: &'static str = "ACCESS-NONCE";
5const ACCESS_SIGNATURE: &'static str = "ACCESS-SIGNATURE";
6
7fn generate_nonce() -> u64 {
8    use std::time::SystemTime;
9    let now = SystemTime::now();
10    let du = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
11    du.as_micros() as u64
12}
13
14#[derive(Debug, Clone)]
15pub struct Credential {
16    pub api_key: String,
17    pub api_secret: String,
18}
19impl Credential {
20    /// Create a credential from the env variables.
21    ///
22    /// - BITBANK_API_KEY
23    /// - BITBANK_API_SECRET
24    pub fn from_env() -> anyhow::Result<Self> {
25        use std::env;
26        let api_key = env::var("BITBANK_API_KEY")?;
27        let api_secret = env::var("BITBANK_API_SECRET")?;
28        Ok(Self {
29            api_key,
30            api_secret,
31        })
32    }
33    fn signature(&self, message: &str) -> String {
34        use crypto::hmac::Hmac;
35        use crypto::mac::Mac;
36        use crypto::sha2::Sha256;
37        use hex::ToHex;
38
39        let mut mac = Hmac::new(Sha256::new(), self.api_secret.as_bytes());
40        mac.input(message.as_bytes());
41        let signature: String = mac.result().code().encode_hex();
42        signature
43    }
44}
45
46pub struct GetAuth {
47    pub path: String,
48    pub params: String,
49}
50impl GetAuth {
51    pub fn create(self, cred: Credential) -> anyhow::Result<HeaderMap> {
52        let mut out = HeaderMap::new();
53        let nonce = generate_nonce();
54        let sig = {
55            let message = format!("{nonce}{}{}", self.path, self.params);
56            cred.signature(&message)
57        };
58        out.insert(ACCESS_KEY, cred.api_key.try_into()?);
59        out.insert(ACCESS_NONCE, nonce.try_into()?);
60        out.insert(ACCESS_SIGNATURE, sig.try_into()?);
61        Ok(out)
62    }
63}
64
65pub struct PostAuth {
66    pub body: String,
67}
68impl PostAuth {
69    pub fn create(self, cred: Credential) -> anyhow::Result<HeaderMap> {
70        let mut out = HeaderMap::new();
71        let nonce = generate_nonce();
72        let sig = {
73            let message = format!("{nonce}{}", self.body);
74            cred.signature(&message)
75        };
76        out.insert(ACCESS_KEY, cred.api_key.try_into()?);
77        out.insert(ACCESS_NONCE, nonce.try_into()?);
78        out.insert(ACCESS_SIGNATURE, sig.try_into()?);
79        Ok(out)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test() -> anyhow::Result<()> {
89        let cred = Credential {
90            api_key: "MY_API_KEY".to_owned(),
91            api_secret: "MY_API_SECRET".to_owned(),
92        };
93        let get = GetAuth {
94            path: "/v1/a/b/c".to_owned(),
95            params: "?a=1&b=2".to_owned(),
96        };
97        let h = get.create(cred.clone())?;
98        dbg!(h);
99
100        let post = PostAuth {
101            body: "{a:1,b:[2,3]}".to_owned(),
102        };
103        let h = post.create(cred)?;
104        dbg!(h);
105
106        Ok(())
107    }
108}