diff options
| author | Matthieu <matthieu@developershouse.xyz> | 2021-09-17 18:41:56 +0400 |
|---|---|---|
| committer | Matthieu <matthieu@developershouse.xyz> | 2021-09-17 18:41:56 +0400 |
| commit | 469a8fb1ea7b689399c30badbf33fc467531c561 (patch) | |
| tree | 8ea3caf86cc00c8c3b37287b6a7b1774bb5e8895 /webhook/src | |
| parent | ba0170ea15d6ebc1c9049f02adabd1ee4df6de64 (diff) | |
basic com implementation & cache base
Diffstat (limited to 'webhook/src')
| -rw-r--r-- | webhook/src/handle.rs | 174 | ||||
| -rw-r--r-- | webhook/src/handler/handler.rs | 123 | ||||
| -rw-r--r-- | webhook/src/handler/make_service.rs | 28 | ||||
| -rw-r--r-- | webhook/src/handler/mod.rs | 4 | ||||
| -rw-r--r-- | webhook/src/handler/signature.rs | 65 | ||||
| -rw-r--r-- | webhook/src/handler/types.rs | 10 | ||||
| -rw-r--r-- | webhook/src/main.rs | 14 |
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);
}
}
|
