use sfr_core as sc;
use crate::error::ResponseError;
use axum::body::{Body, Bytes};
use axum::extract::FromRequest;
use axum::http::{header, Method, Request};
use sc::{Slack, SlashCommandBody};
pub struct SlackSlashCommand(pub SlashCommandBody);
const HEADER_KEY_SIGNATURE: &str = "X-Slack-Signature";
const HEADER_KEY_TIMESTAMP: &str = "X-Slack-Request-Timestamp";
const CONTENT_TYPE_VALUE_APPLICATION_WWW_FORM_URLENCODED: &str =
"application/x-www-form-urlencoded";
impl<S> FromRequest<S> for SlackSlashCommand
where
S: Send + Sync,
{
type Rejection = ResponseError;
async fn from_request(req: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
let slack = req
.extensions()
.get::<Slack>()
.ok_or(ResponseError::Unknown)?
.clone();
if req.method() != Method::POST {
return Err(ResponseError::MethodNotAllowed);
}
let signature = take_header(&req, HEADER_KEY_SIGNATURE)?.to_string();
let timestamp = take_header(&req, HEADER_KEY_TIMESTAMP)?.to_string();
if !has_content_type(&req, CONTENT_TYPE_VALUE_APPLICATION_WWW_FORM_URLENCODED) {
return Err(ResponseError::InvalidHeader(String::from("Content-Type")));
}
let body = Bytes::from_request(req, state)
.await
.map_err(|_| ResponseError::ReadBody)?;
slack
.validate_request(&signature, ×tamp, body.as_ref())
.map_err(|e| {
ResponseError::InvalidHeader(format!("error in signature validation: {:?}", e))
})?;
let value: SlashCommandBody = serde_urlencoded::from_bytes(&body)
.map_err(|e| ResponseError::DeserializeBody(Box::new(e)))?;
slack
.verification_token(&value)
.map_err(|_| ResponseError::InvalidToken)?;
Ok(SlackSlashCommand(value))
}
}
fn take_header<'a>(req: &'a Request<Body>, name: &'a str) -> Result<&'a str, ResponseError> {
let value = req
.headers()
.get(name)
.and_then(|x| x.to_str().ok())
.ok_or_else(|| ResponseError::InvalidHeader(format!("`{name}` is not found")))?;
Ok(value)
}
fn has_content_type(req: &Request<Body>, content_type: &str) -> bool {
let Ok(value) = take_header(req, header::CONTENT_TYPE.as_str()) else {
return false;
};
value.starts_with(content_type)
}