kamu_snap_crypto_actix/
lib.rs1#![forbid(unsafe_code)]
16
17use actix_web::http::{Method as ActixMethod, header::HeaderMap as ActixHeaderMap};
18use kamu_snap_crypto::Signature;
19use kamu_snap_crypto::snap_bi::{ServiceStringToSign, verify_service};
20
21const X_SIGNATURE: &str = "X-SIGNATURE";
22const X_TIMESTAMP: &str = "X-TIMESTAMP";
23const AUTHORIZATION: &str = "Authorization";
24
25pub fn verify_request(
31 method: &ActixMethod,
32 path: &str,
33 headers: &ActixHeaderMap,
34 body: &[u8],
35 client_secret: &str,
36) -> kamu_snap_crypto::Result<()> {
37 let signature_b64 = header_str(headers, X_SIGNATURE)?;
38 let timestamp = header_str(headers, X_TIMESTAMP)?;
39 let authorization = header_str(headers, AUTHORIZATION)?;
40 let access_token = authorization.strip_prefix("Bearer ").unwrap_or(authorization);
41
42 let http_method = http::Method::from_bytes(method.as_str().as_bytes())
43 .map_err(|e| kamu_snap_crypto::Error::Webhook(format!("invalid HTTP method: {e}")))?;
44
45 let parts = ServiceStringToSign { method: &http_method, path, access_token, body, timestamp };
46
47 let sig = Signature::from_base64(signature_b64)?;
48 verify_service(client_secret.as_bytes(), &parts, &sig)
49}
50
51fn header_str<'a>(headers: &'a ActixHeaderMap, name: &'static str) -> kamu_snap_crypto::Result<&'a str> {
52 headers
53 .get(name)
54 .ok_or_else(|| kamu_snap_crypto::Error::Webhook(format!("missing header: {name}")))?
55 .to_str()
56 .map_err(|e| kamu_snap_crypto::Error::Webhook(format!("non-ASCII header {name}: {e}")))
57}