diff options
Diffstat (limited to 'webhook/src/handler')
| -rw-r--r-- | webhook/src/handler/error.rs | 14 | ||||
| -rw-r--r-- | webhook/src/handler/handler.rs | 84 | ||||
| -rw-r--r-- | webhook/src/handler/tests/handler_integration.rs | 35 |
3 files changed, 84 insertions, 49 deletions
diff --git a/webhook/src/handler/error.rs b/webhook/src/handler/error.rs index f998b84..d4fee07 100644 --- a/webhook/src/handler/error.rs +++ b/webhook/src/handler/error.rs @@ -1,4 +1,4 @@ -use hyper::{Body, Response, StatusCode}; +use hyper::{header::ToStrError, Body, Response, StatusCode}; pub struct WebhookError { pub code: StatusCode, @@ -22,3 +22,15 @@ impl Into<Response<Body>> for WebhookError { .unwrap() } } + +impl From<hyper::Error> for WebhookError { + fn from(_: hyper::Error) -> Self { + WebhookError::new(StatusCode::BAD_REQUEST, "invalid request") + } +} + +impl From<ToStrError> for WebhookError { + fn from(_: ToStrError) -> Self { + WebhookError::new(StatusCode::BAD_REQUEST, "invalid request") + } +} diff --git a/webhook/src/handler/handler.rs b/webhook/src/handler/handler.rs index e54b5ee..97300e4 100644 --- a/webhook/src/handler/handler.rs +++ b/webhook/src/handler/handler.rs @@ -1,10 +1,10 @@ use super::error::WebhookError; use super::signature::validate_signature; use crate::config::Config; -use common::discord_models::slash_commands::{Interaction, InteractionRequestType}; use common::log::{debug, error}; use common::nats_crate::Connection; -use common::payloads::CacheData; +use common::payloads::SerializeHelper; +use ed25519_dalek::PublicKey; use hyper::{ body::{to_bytes, Bytes}, service::Service, @@ -19,54 +19,50 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use ed25519_dalek::PublicKey; +use twilight_model::application::interaction::Interaction; +use twilight_model::gateway::event::Event; +use twilight_model::gateway::payload::InteractionCreate; /// Hyper service used to handle the discord webhooks #[derive(Clone)] pub struct HandlerService { pub config: Arc<Config>, pub nats: Arc<Connection>, - pub public_key: Arc<PublicKey> + pub public_key: Arc<PublicKey>, } impl HandlerService { async fn check_request(&self, req: Request<Body>) -> Result<Bytes, WebhookError> { if req.method() == Method::POST { - let headers = req.headers().clone(); - let signature = headers.get("X-Signature-Ed25519"); - let timestamp = headers.get("X-Signature-Timestamp"); - if let (Some(timestamp), Some(signature)) = (timestamp, signature) { - if let Ok(data) = to_bytes(req.into_body()).await { - let contatenated_data = [timestamp.as_bytes().to_vec(), data.to_vec()].concat(); - if let Ok(signature_str) = &signature.to_str() { - if validate_signature( - &self.public_key, - &contatenated_data, - signature_str, - ) { - Ok(data) - } else { - Err(WebhookError::new( - StatusCode::UNAUTHORIZED, - "invalid signature", - )) - } - } else { - Err(WebhookError::new( - StatusCode::BAD_REQUEST, - "failed to read signature", - )) - } - } else { - Err(WebhookError::new( - StatusCode::BAD_REQUEST, - "unable to read body", - )) - } + let signature = if let Some(sig) = req.headers().get("X-Signature-Ed25519") { + sig.to_owned() + } else { + return Err(WebhookError::new( + StatusCode::BAD_REQUEST, + "missing signature header", + )); + }; + + let timestamp = if let Some(timestamp) = req.headers().get("X-Signature-Timestamp") { + timestamp.to_owned() + } else { + return Err(WebhookError::new( + StatusCode::BAD_REQUEST, + "missing timestamp header", + )); + }; + let data = to_bytes(req.into_body()).await?; + + if validate_signature( + &self.public_key, + &[timestamp.as_bytes().to_vec(), data.to_vec()].concat(), + signature.to_str()?, + ) { + Ok(data) } else { Err(WebhookError::new( StatusCode::UNAUTHORIZED, - "missing signature headers", + "invalid signature", )) } } else { @@ -83,8 +79,8 @@ impl HandlerService { let utf8 = from_utf8(&data); match utf8 { Ok(data) => match serde_json::from_str::<Interaction>(data) { - Ok(value) => match value.type_ { - InteractionRequestType::Ping => Ok(Response::builder() + Ok(value) => match value { + Interaction::Ping(_) => Ok(Response::builder() .header("Content-Type", "application/json") .body(serde_json::to_string(&Ping { t: 1 }).unwrap().into()) .unwrap()), @@ -97,9 +93,9 @@ impl HandlerService { node_id: "".to_string(), span: None, }, - data: CacheData::InteractionCreate { - interaction: Box::new(value), - }, + data: SerializeHelper(Event::InteractionCreate( + Box::new(InteractionCreate(value)), + )), }) .unwrap(); @@ -127,10 +123,10 @@ impl HandlerService { Err(error) => { error!("invalid json body: {}", error); Err(WebhookError::new( - StatusCode::BAD_REQUEST, - "invalid json body", + StatusCode::BAD_REQUEST, + "invalid json body", )) - }, + } }, Err(_) => Err(WebhookError::new(StatusCode::BAD_REQUEST, "not utf-8 body")), diff --git a/webhook/src/handler/tests/handler_integration.rs b/webhook/src/handler/tests/handler_integration.rs index 906b347..b5204fb 100644 --- a/webhook/src/handler/tests/handler_integration.rs +++ b/webhook/src/handler/tests/handler_integration.rs @@ -101,7 +101,7 @@ unsafe fn destroy() { #[tokio::test] async fn respond_to_pings() { - let ping = json!({ "type": 1, "id": "0", "application_id": "0", "token": "random token", "version": 1 }).to_string(); + let ping = json!({ "type": 1, "id": "0", "application_id": "0", "token": "random token", "version": 1, "channel_id": "123" }).to_string(); let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); let signature = sign_message(signature_data, &TEST_KEYPAIR); @@ -113,6 +113,7 @@ async fn respond_to_pings() { .header("X-Signature-Timestamp", timestamp) .body(Body::from(ping.clone())) .expect("request builder"); + let client = hyper::client::Client::new(); let result = client.request(req).await.unwrap(); @@ -121,7 +122,7 @@ async fn respond_to_pings() { #[tokio::test] async fn deny_invalid_signatures() { - let ping = json!({ "type": 1, "id": "0", "application_id": "0", "token": "random token", "version": 1 }).to_string(); + let ping = json!({ "type": 1, "id": "0", "application_id": "0", "token": "random token", "version": 1, "channel_id": "123" }).to_string(); let timestamp = "my datetime :)"; let req = Request::builder() @@ -138,7 +139,19 @@ async fn deny_invalid_signatures() { #[tokio::test] async fn response_500_when_no_nats_response() { - let ping = json!({ "type": 2, "id": "0", "application_id": "0", "token": "random token", "version": 1 }).to_string(); + let ping = json!({ + "type": 2, + "id": "0", + "application_id": "0", + "token": "random token", + "version": 1, + "channel_id": "123", + "data": { + "id": "0", + "name": "command" + } + }).to_string(); + let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); let signature = sign_message(signature_data, &TEST_KEYPAIR); @@ -164,7 +177,19 @@ async fn respond_from_nats_response() { nats = SETTINGS.clone().unwrap().nats.into(); } let sub = nats.subscribe("nova.cache.dispatch.interaction").unwrap(); - let ping = json!({ "type": 2, "id": "0", "application_id": "0", "token": "random token", "version": 1 }).to_string(); + let ping = json!({ + "type": 2, + "id": "0", + "application_id": "0", + "token": "random token", + "version": 1, + "channel_id": "123", + "data": { + "id": "0", + "name": "command" + } + }).to_string(); + let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); let signature = sign_message(signature_data, &TEST_KEYPAIR); @@ -182,6 +207,7 @@ async fn respond_from_nats_response() { .header("X-Signature-Timestamp", timestamp) .body(Body::from(ping.clone())) .expect("request builder"); + let client = hyper::client::Client::new(); let result = client.request(req).await.unwrap(); assert_eq!(result.status(), StatusCode::OK); @@ -201,6 +227,7 @@ async fn response_400_when_invalid_json_body() { .header("X-Signature-Timestamp", timestamp) .body(Body::from(ping.clone())) .expect("request builder"); + let client = hyper::client::Client::new(); let result = client.request(req).await.unwrap(); assert_eq!(result.status(), StatusCode::BAD_REQUEST); |
