use axum::{
http::{HeaderMap, StatusCode, Uri},
response::Response,
};
use super::dynamo_response;
pub(super) fn validate_auth(headers: &HeaderMap, uri: &Uri, response_ct: &str) -> Option<Response> {
let auth_header = headers.get("authorization").and_then(|v| v.to_str().ok());
let query = uri.query().unwrap_or("");
let has_algorithm_query = query.split('&').any(|p| {
let key = p.split('=').next().unwrap_or("");
key == "X-Amz-Algorithm"
});
if auth_header.is_some() && has_algorithm_query {
let body = serde_json::json!({
"__type": "com.amazon.coral.service#InvalidSignatureException",
"message": "Found both 'X-Amz-Algorithm' as a query-string param and 'Authorization' as HTTP header."
})
.to_string();
return Some(dynamo_response(StatusCode::BAD_REQUEST, response_ct, body));
}
if has_algorithm_query {
let mut missing = Vec::new();
let query_params: Vec<&str> = query
.split('&')
.map(|p| p.split('=').next().unwrap_or(""))
.collect();
let algo_has_value = query.split('&').any(|p| {
let mut parts = p.splitn(2, '=');
let key = parts.next().unwrap_or("");
let val = parts.next().unwrap_or("");
key == "X-Amz-Algorithm" && !val.is_empty()
});
if !algo_has_value {
missing.push("'X-Amz-Algorithm'");
}
for (param, label) in [
("X-Amz-Credential", "'X-Amz-Credential'"),
("X-Amz-Signature", "'X-Amz-Signature'"),
("X-Amz-SignedHeaders", "'X-Amz-SignedHeaders'"),
("X-Amz-Date", "'X-Amz-Date'"),
] {
if !query_params.contains(¶m) {
missing.push(label);
}
}
if !missing.is_empty() {
let parts: Vec<String> = missing
.iter()
.map(|p| format!("AWS query-string parameters must include {p}. "))
.collect();
let msg = format!("{}Re-examine the query-string parameters.", parts.join(""));
let body = serde_json::json!({
"__type": "com.amazon.coral.service#IncompleteSignatureException",
"message": msg
})
.to_string();
return Some(dynamo_response(StatusCode::BAD_REQUEST, response_ct, body));
}
return None;
}
match auth_header {
None => {
let body = serde_json::json!({
"__type": "com.amazon.coral.service#MissingAuthenticationTokenException",
"message": "Request is missing Authentication Token"
})
.to_string();
Some(dynamo_response(StatusCode::BAD_REQUEST, response_ct, body))
}
Some(auth) => {
if !auth.starts_with("AWS4-") {
let body = serde_json::json!({
"__type": "com.amazon.coral.service#MissingAuthenticationTokenException",
"message": "Request is missing Authentication Token"
})
.to_string();
return Some(dynamo_response(StatusCode::BAD_REQUEST, response_ct, body));
}
let has_date = headers.get("x-amz-date").is_some() || headers.get("date").is_some();
let has_credential = auth.contains("Credential=") || auth.contains("credential=");
let has_signature = auth.contains("Signature=") || auth.contains("signature=");
let has_signed_headers =
auth.contains("SignedHeaders=") || auth.contains("signedheaders=");
let mut missing = Vec::new();
if !has_credential {
missing.push("'Credential'");
}
if !has_signature {
missing.push("'Signature'");
}
if !has_signed_headers {
missing.push("'SignedHeaders'");
}
if !has_date {
missing.push("existence of either a 'X-Amz-Date' or a 'Date' header.");
}
if missing.is_empty() {
return None;
}
let mut parts: Vec<String> = missing
.iter()
.map(|p| {
if p.contains("existence of") {
format!("Authorization header requires {p}")
} else {
format!("Authorization header requires {p} parameter.")
}
})
.collect();
parts.push(format!("Authorization={auth}"));
let msg = parts.join(" ");
let body = serde_json::json!({
"__type": "com.amazon.coral.service#IncompleteSignatureException",
"message": msg
})
.to_string();
Some(dynamo_response(StatusCode::BAD_REQUEST, response_ct, body))
}
}
}