-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
funds-manager: middleware: Add symmetric key auth for withdrawal
- Loading branch information
Showing
5 changed files
with
172 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
//! Middleware for the funds manager server | ||
|
||
use crate::error::ApiError; | ||
use crate::Server; | ||
use bytes::Bytes; | ||
use hmac::{Hmac, Mac}; | ||
use serde::de::DeserializeOwned; | ||
use sha2::Sha256; | ||
use std::sync::Arc; | ||
use warp::Filter; | ||
|
||
/// The header key for the HMAC signature | ||
const X_SIGNATURE_HEADER: &str = "X-Signature"; | ||
|
||
/// Add HMAC authentication to a route | ||
pub(crate) fn with_hmac_auth( | ||
server: Arc<Server>, | ||
) -> impl Filter<Extract = (Bytes,), Error = warp::Rejection> + Clone { | ||
warp::any() | ||
.and(warp::any().map(move || server.clone())) | ||
.and(warp::header::optional::<String>(X_SIGNATURE_HEADER)) | ||
.and(warp::method()) | ||
.and(warp::path::full()) | ||
.and(warp::header::headers_cloned()) | ||
.and(warp::body::bytes()) | ||
.and_then(verify_hmac) | ||
} | ||
|
||
/// Verify the HMAC signature | ||
async fn verify_hmac( | ||
server: Arc<Server>, | ||
signature: Option<String>, | ||
method: warp::http::Method, | ||
path: warp::path::FullPath, | ||
headers: warp::http::HeaderMap, | ||
body: Bytes, | ||
) -> Result<Bytes, warp::Rejection> { | ||
// Unwrap the key and signature | ||
let hmac_key = match &server.hmac_key { | ||
Some(hmac_key) => hmac_key, | ||
None => return Ok(body), // Auth is disabled, allow the request | ||
}; | ||
|
||
let signature = match signature { | ||
Some(sig) => sig, | ||
None => { | ||
return Err(warp::reject::custom(ApiError::Unauthenticated( | ||
"Missing signature".to_string(), | ||
))) | ||
}, | ||
}; | ||
|
||
// Construct the MAC | ||
let mut mac = Hmac::<Sha256>::new_from_slice(hmac_key) | ||
.map_err(|_| warp::reject::custom(ApiError::InternalError("HMAC error".to_string())))?; | ||
|
||
// Update with method, path, headers and body in order | ||
mac.update(method.as_str().as_bytes()); | ||
mac.update(path.as_str().as_bytes()); | ||
add_headers_to_hmac(&mut mac, &headers); | ||
mac.update(&body); | ||
|
||
// Check the signature | ||
let expected = mac.finalize().into_bytes(); | ||
let provided = hex::decode(signature) | ||
.map_err(|_| warp::reject::custom(ApiError::BadRequest("Invalid signature".to_string())))?; | ||
if expected.as_slice() != provided.as_slice() { | ||
return Err(warp::reject::custom(ApiError::Unauthenticated( | ||
"Invalid signature".to_string(), | ||
))); | ||
} | ||
|
||
Ok(body) | ||
} | ||
|
||
/// Hash headers into an HMAC | ||
fn add_headers_to_hmac(mac: &mut Hmac<Sha256>, headers: &warp::http::HeaderMap) { | ||
let mut sorted_headers: Vec<(String, String)> = headers | ||
.iter() | ||
.map(|(k, v)| (k.as_str().to_lowercase(), v.to_str().unwrap_or("").to_string())) | ||
.collect(); | ||
sorted_headers.sort_by(|a, b| a.0.cmp(&b.0)); | ||
|
||
for (key, value) in sorted_headers { | ||
// Exclude the signature header itself | ||
if key.to_lowercase() != X_SIGNATURE_HEADER.to_lowercase() { | ||
mac.update(key.as_bytes()); | ||
mac.update(value.as_bytes()); | ||
} | ||
} | ||
} | ||
|
||
/// Extract a JSON body from a request | ||
#[allow(clippy::needless_pass_by_value)] | ||
pub fn with_json_body<T: DeserializeOwned + Send>(body: Bytes) -> Result<T, warp::Rejection> { | ||
serde_json::from_slice(&body) | ||
.map_err(|e| warp::reject::custom(ApiError::BadRequest(format!("Invalid JSON: {}", e)))) | ||
} | ||
|
||
/// Identity map for a handler's middleware, used to chain together `map`s and | ||
/// `and_then`s | ||
pub async fn identity<T>(res: T) -> T { | ||
res | ||
} |