summaryrefslogtreecommitdiff
path: root/webhook/src
diff options
context:
space:
mode:
Diffstat (limited to 'webhook/src')
-rw-r--r--webhook/src/handle.rs174
-rw-r--r--webhook/src/handler/handler.rs123
-rw-r--r--webhook/src/handler/make_service.rs28
-rw-r--r--webhook/src/handler/mod.rs4
-rw-r--r--webhook/src/handler/signature.rs65
-rw-r--r--webhook/src/handler/types.rs10
-rw-r--r--webhook/src/main.rs14
7 files changed, 237 insertions, 181 deletions
diff --git a/webhook/src/handle.rs b/webhook/src/handle.rs
deleted file mode 100644
index b87d9a5..0000000
--- a/webhook/src/handle.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-use hyper::service::Service;
-use hyper::{body::to_bytes, HeaderMap};
-use hyper::{Body, Method, Request, Response, StatusCode};
-use libsodium_sys::crypto_sign_ed25519_verify_detached;
-use log::info;
-use serde_json::Value;
-use std::future;
-use std::future::Future;
-use std::pin::Pin;
-use std::str::from_utf8;
-use std::task::{Context, Poll};
-use serde::{Deserialize, Serialize};
-use crate::config::Config;
-use nats::{Connection, connect};
-
-pub fn validate_signature(b64_public_key: &str, data: &Vec<u8>, b64_signature: &str) -> bool {
- // First, we need to check if the signature & private key is valid base64.
- let signature_result = hex::decode(b64_signature);
- let public_key_result = hex::decode(b64_public_key);
-
- if signature_result.is_ok() && public_key_result.is_ok() {
- // Since we now have the signatures in u8 vectors. We will initialize all the
- // parameters for the ffi call to sodium.
- let signature_pointer = signature_result.unwrap();
- let private_key_pointer = public_key_result.unwrap();
-
- let data_pointer = data.as_ptr();
- let data_len = data.len() as u64;
-
- // A ffi call is considered unsafe by the Rust compiler
- // we assume all the parameters are correct for the call
- unsafe {
- // If the signature is valid, sodium will return 0
- return crypto_sign_ed25519_verify_detached(
- signature_pointer.as_ptr(),
- data_pointer,
- data_len,
- private_key_pointer.as_ptr(),
- ) == 0;
- }
- }
- false
-}
-
-fn get_signature(headers: &HeaderMap) -> Option<(&str, &str)> {
- let signature = headers.get("X-Signature-Ed25519");
- let timestamp = headers.get("X-Signature-Timestamp");
-
- if signature.is_some() && timestamp.is_some() {
- return Some((
- signature.unwrap().to_str().unwrap(),
- timestamp.unwrap().to_str().unwrap(),
- ));
- }
- None
-}
-
-pub struct HandlerService {
- pub config: Config,
- pub nats: Box<Connection>
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct Ping {
- #[serde(rename = "type")]
- t: i32
-}
-
-impl Service<Request<Body>> for HandlerService {
- type Response = Response<Body>;
- type Error = hyper::Error;
- type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
-
- fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
- Poll::Ready(Ok(()))
- }
-
- fn call(&mut self, req: Request<Body>) -> Self::Future {
- if req.method() == Method::POST {
- let public_key = self.config.discord.public_key.clone();
- let nats = self.nats.clone();
- return Box::pin(async move {
- let headers = req.headers().clone();
- if let Some((signature, timestamp)) = get_signature(&headers) {
- if let Ok(data) = to_bytes(req.into_body()).await {
- let contatenated_data = [timestamp.as_bytes().to_vec(), data.to_vec()].concat();
-
- if validate_signature(public_key.as_str(), &contatenated_data, signature) {
- let d: Value = serde_json::from_str(from_utf8(&data).unwrap()).unwrap();
- let t = d.get("type").unwrap().as_i64().unwrap();
-
- if t == 1 {
- return Ok(Response::builder().header("Content-Type", "application/json").body(serde_json::to_string(&Ping {
- t: 1
- }).unwrap().into()).unwrap());
- } else {
- info!("Handled event");
- nats.publish(&format!("nova.dispatch.interaction_raw"), data).unwrap();
- return Ok(Response::builder().header("Content-Type", "application/json").body(serde_json::to_string(&Ping {
- t: 5
- }).unwrap().into()).unwrap());
- }
- } else {
- Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("signature verification failed".into()).unwrap())
- }
- } else {
- Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("failed to read body".into()).unwrap())
- }
- } else {
- Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("no signature specified".into()).unwrap())
- }
- });
- } else {
- return Box::pin(async {
- Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("bad method".into()).unwrap())
- });
- }
- }
-}
-
-pub struct MakeSvc {
- pub settings: Config,
- pub nats: Box<Connection>,
-}
-
-impl<T> Service<T> for MakeSvc {
- type Response = HandlerService;
- type Error = std::io::Error;
- type Future = future::Ready<Result<Self::Response, Self::Error>>;
-
- fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
- Ok(()).into()
- }
-
- fn call(&mut self, _: T) -> Self::Future {
- future::ready(Ok(HandlerService {
- config: self.settings.clone(),
- nats: self.nats.clone(),
- }))
- }
-}
-
-#[cfg(test)]
-mod test {
- use crate::handle::validate_signature;
-
- #[test]
- fn validate_signature_test() {
- let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002";
- let public_key = "eefe0c24473737cb2035232e3b4eb91c206f0a14684168f3503f7d8316058d6f";
- let content = "message de test incroyable".as_bytes().to_vec();
- assert!(validate_signature(public_key, &content, signature))
- }
-
- #[test]
- fn validate_signature_reverse_test() {
- let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002";
- let public_key = "c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044";
- let content = "ceci est un test qui ne fonctionnera pas!"
- .as_bytes()
- .to_vec();
- assert!(!validate_signature(public_key, &content, signature))
- }
-
- #[test]
- fn invalid_hex() {
- let signature = "zzz";
- let public_key = "zzz";
- let content = "ceci est un test qui ne fonctionnera pas!"
- .as_bytes()
- .to_vec();
- assert!(!validate_signature(public_key, &content, signature))
- }
-}
diff --git a/webhook/src/handler/handler.rs b/webhook/src/handler/handler.rs
new file mode 100644
index 0000000..923c650
--- /dev/null
+++ b/webhook/src/handler/handler.rs
@@ -0,0 +1,123 @@
+use super::{signature::validate_signature, types::Interaction};
+use crate::config::Config;
+use hyper::{Body, Method, Request, Response, StatusCode, body::{to_bytes, Bytes}, service::Service};
+use log::{error, info, trace};
+use nats::Connection;
+use serde::{Deserialize, Serialize};
+use std::{future::Future, io::{Error, ErrorKind}, pin::Pin, str::from_utf8, sync::Arc, task::{Context, Poll}};
+
+/// Hyper service used to handle the discord webhooks
+#[derive(Clone)]
+pub struct HandlerService {
+ pub config: Config,
+ pub nats: Arc<Connection>,
+}
+
+impl HandlerService {
+ async fn check_request(&self, req: Request<Body>) -> Result<Bytes, Error> {
+ 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.config.discord.public_key,
+ &contatenated_data,
+ signature_str,
+ ) {
+ Ok(data)
+ } else {
+ Err(Error::new(
+ ErrorKind::InvalidData,
+ "invalid signature specified",
+ ))
+ }
+ } else {
+ Err(Error::new(
+ ErrorKind::BrokenPipe,
+ "failed to read signature",
+ ))
+ }
+ } else {
+ Err(Error::new(ErrorKind::BrokenPipe, "unable to read body"))
+ }
+ } else {
+ Err(Error::new(ErrorKind::InvalidData, "missing headers"))
+ }
+ } else {
+ Err(Error::new(ErrorKind::InvalidData, "invalid method"))
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Ping {
+ #[serde(rename = "type")]
+ t: i32
+}
+
+/// Implementation of the service
+impl Service<Request<Body>> for HandlerService {
+ type Response = Response<Body>;
+ type Error = hyper::Error;
+ type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
+
+ fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, req: Request<Body>) -> Self::Future {
+ let self_clone = self.clone();
+
+ Box::pin(async move {
+ match self_clone.check_request(req).await {
+ Ok(data) => {
+ let value: Interaction = serde_json::from_str(from_utf8(&data).unwrap()).unwrap();
+ trace!("received value: {:?}", value);
+
+ match value.t {
+ 1 => {
+ info!("sending pong");
+ // a ping must be responded with another ping
+ return Ok(Response::builder().header("Content-Type", "application/json").body(serde_json::to_string(&Ping {
+ t: 1
+ }).unwrap().into()).unwrap());
+ },
+ _ => {
+ let payload = serde_json::to_string(&common::payloads::CachePayload {
+ tracing: (),
+ data: value,
+ }).unwrap();
+
+ match self_clone.nats.request("nova.cache.dispatch.interaction", payload) {
+ Ok(response) => {
+ Ok(
+ Response::builder()
+ .header("Content-Type", "application/json")
+ .body(from_utf8(&response.data).unwrap().to_string().into())
+ .unwrap()
+ )
+ },
+ Err(error) => {
+ error!("failed to request nats: {}", error);
+ Ok(
+ Response::builder()
+ .status(500)
+ .body("an internal server error occured".to_string().into())
+ .unwrap()
+ )
+ }
+ }
+ },
+ }
+ },
+ Err(error) => {
+ Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body(error.to_string().into()).unwrap())
+ }
+ }
+ })
+ }
+}
diff --git a/webhook/src/handler/make_service.rs b/webhook/src/handler/make_service.rs
new file mode 100644
index 0000000..96b203d
--- /dev/null
+++ b/webhook/src/handler/make_service.rs
@@ -0,0 +1,28 @@
+use std::{future::{Ready, ready}, sync::Arc, task::{Context, Poll}};
+use hyper::service::Service;
+use nats::Connection;
+use crate::config::Config;
+use super::handler::HandlerService;
+
+
+pub struct MakeSvc {
+ pub settings: Config,
+ pub nats: Arc<Connection>,
+}
+
+impl<T> Service<T> for MakeSvc {
+ type Response = HandlerService;
+ type Error = std::io::Error;
+ type Future = Ready<Result<Self::Response, Self::Error>>;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Ok(()).into()
+ }
+
+ fn call(&mut self, _: T) -> Self::Future {
+ ready(Ok(HandlerService {
+ config: self.settings.clone(),
+ nats: self.nats.clone(),
+ }))
+ }
+}
diff --git a/webhook/src/handler/mod.rs b/webhook/src/handler/mod.rs
new file mode 100644
index 0000000..490c580
--- /dev/null
+++ b/webhook/src/handler/mod.rs
@@ -0,0 +1,4 @@
+pub mod make_service;
+mod signature;
+mod handler;
+mod types; \ No newline at end of file
diff --git a/webhook/src/handler/signature.rs b/webhook/src/handler/signature.rs
new file mode 100644
index 0000000..5af6b63
--- /dev/null
+++ b/webhook/src/handler/signature.rs
@@ -0,0 +1,65 @@
+use libsodium_sys::crypto_sign_ed25519_verify_detached;
+
+/// Checks the signature of a given data using the hex signature and the public key.
+pub fn validate_signature(hex_public_key: &str, data: &Vec<u8>, hex_signature: &str) -> bool {
+ // First, we need to check if the signature & private key is valid base64.
+ let signature_result = hex::decode(hex_signature);
+ let public_key_result = hex::decode(hex_public_key);
+
+ if signature_result.is_ok() && public_key_result.is_ok() {
+ // Since we now have the signatures in u8 vectors. We will initialize all the
+ // parameters for the ffi call to sodium.
+ let signature_pointer = signature_result.unwrap();
+ let private_key_pointer = public_key_result.unwrap();
+
+ let data_pointer = data.as_ptr();
+ let data_len = data.len() as u64;
+
+ // A ffi call is considered unsafe by the Rust compiler
+ // we assume all the parameters are correct for the call
+ unsafe {
+ // If the signature is valid, sodium will return 0
+ return crypto_sign_ed25519_verify_detached(
+ signature_pointer.as_ptr(),
+ data_pointer,
+ data_len,
+ private_key_pointer.as_ptr(),
+ ) == 0;
+ }
+ }
+ false
+}
+
+#[cfg(test)]
+mod test {
+ use crate::handler::signature::validate_signature;
+
+
+ #[test]
+ fn validate_signature_test() {
+ let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002";
+ let public_key = "eefe0c24473737cb2035232e3b4eb91c206f0a14684168f3503f7d8316058d6f";
+ let content = "message de test incroyable".as_bytes().to_vec();
+ assert!(validate_signature(public_key, &content, signature))
+ }
+
+ #[test]
+ fn validate_signature_reverse_test() {
+ let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002";
+ let public_key = "c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044";
+ let content = "ceci est un test qui ne fonctionnera pas!"
+ .as_bytes()
+ .to_vec();
+ assert!(!validate_signature(public_key, &content, signature))
+ }
+
+ #[test]
+ fn invalid_hex() {
+ let signature = "zzz";
+ let public_key = "zzz";
+ let content = "ceci est un test qui ne fonctionnera pas!"
+ .as_bytes()
+ .to_vec();
+ assert!(!validate_signature(public_key, &content, signature))
+ }
+}
diff --git a/webhook/src/handler/types.rs b/webhook/src/handler/types.rs
new file mode 100644
index 0000000..4cba12a
--- /dev/null
+++ b/webhook/src/handler/types.rs
@@ -0,0 +1,10 @@
+use serde::{Serialize, Deserialize};
+use serde_json::Value;
+
+#[derive(Debug, Deserialize, Clone, Default, Serialize)]
+pub struct Interaction {
+ #[serde(rename = "type")]
+ pub t: i16,
+ pub data: Option<Value>,
+}
+
diff --git a/webhook/src/main.rs b/webhook/src/main.rs
index b9fefc5..c127c2c 100644
--- a/webhook/src/main.rs
+++ b/webhook/src/main.rs
@@ -1,10 +1,10 @@
-use std::net::ToSocketAddrs;
-mod handle;
+use std::{net::ToSocketAddrs, sync::Arc};
+mod handler;
mod config;
+use crate::handler::make_service::MakeSvc;
use hyper::Server;
-use log::info;
-use handle::MakeSvc;
+use log::{info, error};
use common::config::Settings;
use crate::config::Config;
@@ -22,13 +22,13 @@ async fn main() {
"Starting server on {}:{}",
settings.config.server.address, settings.config.server.port
);
- let nats = Box::new(nats::connect("localhost").unwrap());
+
let server = Server::bind(&addr).serve(MakeSvc {
settings: settings.config.clone(),
- nats
+ nats: Arc::new(settings.nats.into()),
});
if let Err(e) = server.await {
- eprintln!("server error: {}", e);
+ error!("server error: {}", e);
}
}