use std::{collections::HashMap, str::FromStr};
use chrono::Utc;
use jsonwebtoken::{crypto::sign, Algorithm, EncodingKey};
use reqwest::{
header::{HeaderName, HeaderValue},
Method, Request, Url,
};
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
#[derive(Debug, Deserialize, Serialize)]
pub struct Signature {
pub access_key: String,
pub algorithm: String,
pub request_time: String,
pub sign: String,
pub signed_headers: String,
}
#[derive(Debug, Serialize, Ord, Eq, PartialOrd, PartialEq)]
struct SignHeader {
pub key: String,
pub value: String,
}
pub fn signature(
value: &mut Signature,
sign_headers: HashMap<String, String>,
secret_access_key: String,
seconds_offset: u64,
) -> Result<String> {
let request_time = value.request_time.parse::<i64>().map_err(Error::any)?;
let now = Utc::now().timestamp();
if now.abs_diff(request_time).gt(&seconds_offset) {
return Err(Error::Invalid("time span exceeds threshold".to_string()));
}
let algorithm = Algorithm::from_str(&value.algorithm).map_err(Error::any)?;
value.sign = Default::default();
let message = serde_json::to_string(&value).map_err(Error::any)?;
let mut sort_headers = Vec::new();
for h in value
.signed_headers
.split(';')
.collect::<Vec<&str>>()
.iter()
{
match sign_headers.get_key_value(*h) {
Some((k, v)) => sort_headers.push(SignHeader {
key: k.to_owned(),
value: v.to_owned(),
}),
None => return Err(Error::Invalid(format!("lack {}", *h))),
}
}
sort_headers.sort();
let signature = serde_json::to_string(&sort_headers).map_err(Error::any)?;
let sign_result = sign(
[message, signature].join(".").as_bytes(),
&EncodingKey::from_secret(secret_access_key.as_bytes()),
algorithm,
)
.map_err(Error::any)?;
Ok(sign_result)
}
pub fn query(
value: &mut Signature,
sign_headers: HashMap<String, String>,
secret_access_key: String,
seconds_offset: u64,
) -> Result<String> {
value.sign = signature(
value,
sign_headers.clone(),
secret_access_key,
seconds_offset,
)?;
let prefix = serde_urlencoded::to_string(value).map_err(Error::any)?;
let suffix = serde_urlencoded::to_string(sign_headers).map_err(Error::any)?;
Ok([prefix, suffix].join("&"))
}
pub fn request(
method: Method,
url: Url,
value: &mut Signature,
sign_headers: HashMap<String, String>,
secret_access_key: String,
) -> Result<Request> {
value.sign = signature(value, sign_headers.clone(), secret_access_key, 15)?;
let mut req = Request::new(method, url);
req.headers_mut().append(
HeaderName::from_static("access_key"),
HeaderValue::from_str(&value.access_key).map_err(Error::any)?,
);
req.headers_mut().append(
HeaderName::from_static("algorithm"),
HeaderValue::from_str(&value.algorithm).map_err(Error::any)?,
);
req.headers_mut().append(
HeaderName::from_static("request_time"),
HeaderValue::from_str(&value.request_time).map_err(Error::any)?,
);
req.headers_mut().append(
HeaderName::from_static("sign"),
HeaderValue::from_str(&value.sign).map_err(Error::any)?,
);
req.headers_mut().append(
HeaderName::from_static("signed_headers"),
HeaderValue::from_str(&value.signed_headers).map_err(Error::any)?,
);
for (k, v) in sign_headers.iter() {
req.headers_mut().append(
HeaderName::from_str(k).map_err(Error::any)?,
HeaderValue::from_str(v.as_str()).map_err(Error::any)?,
);
}
Ok(req)
}