summaryrefslogtreecommitdiff
path: root/webhook
diff options
context:
space:
mode:
Diffstat (limited to 'webhook')
-rw-r--r--webhook/BUILD6
-rw-r--r--webhook/Cargo.toml7
-rw-r--r--webhook/cargo/BUILD.bazel21
-rw-r--r--webhook/src/config.rs2
-rw-r--r--webhook/src/handler/error.rs24
-rw-r--r--webhook/src/handler/handler.rs166
-rw-r--r--webhook/src/handler/make_service.rs15
-rw-r--r--webhook/src/handler/mod.rs6
-rw-r--r--webhook/src/handler/signature.rs57
-rw-r--r--webhook/src/handler/tests/handler.rs (renamed from webhook/README.md)0
-rw-r--r--webhook/src/handler/tests/handler_integration.rs234
-rw-r--r--webhook/src/handler/tests/mod.rs4
-rw-r--r--webhook/src/handler/tests/signature.rs30
-rw-r--r--webhook/src/handler/tests/utils.rs46
-rw-r--r--webhook/src/handler/types.rs1
-rw-r--r--webhook/src/main.rs27
16 files changed, 507 insertions, 139 deletions
diff --git a/webhook/BUILD b/webhook/BUILD
index f29da5f..4340909 100644
--- a/webhook/BUILD
+++ b/webhook/BUILD
@@ -7,18 +7,18 @@ test_suite(name = "tests")
rust_binary(
name = "webhook",
srcs = glob(["src/**"]),
- deps = all_crate_deps() + ["//common/rust:common"],
visibility = ["//visibility:public"],
+ deps = all_crate_deps() + ["//common/rust:common"],
)
rust_test(
name = "webhook_test",
- crate = ":webhook"
+ crate = ":webhook",
)
rust_image(
name = "image",
+ base = "//bazel:base",
binary = ":webhook",
visibility = ["//visibility:public"],
- base = "//bazel:base",
)
diff --git a/webhook/Cargo.toml b/webhook/Cargo.toml
index 045c710..7e73c43 100644
--- a/webhook/Cargo.toml
+++ b/webhook/Cargo.toml
@@ -6,14 +6,13 @@ edition = "2018"
[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
-log = { version = "0.4", features = ["std"] }
-config = "0.11"
+common = { path = "../common/rust" }
serde = { version = "1.0.8", features = ["derive"] }
libsodium-sys = "0.2.7"
hex = "0.4.3"
serde_json = { version = "1.0" }
-common = { path = "../common/rust" }
-nats = "0.15.2"
+libc = "0.2.101"
+lazy_static = "1.4.0"
[[bin]]
name = "webhook"
diff --git a/webhook/cargo/BUILD.bazel b/webhook/cargo/BUILD.bazel
index e035ab6..4569cc1 100644
--- a/webhook/cargo/BUILD.bazel
+++ b/webhook/cargo/BUILD.bazel
@@ -13,15 +13,6 @@ licenses([
# Aliased targets
alias(
- name = "config",
- actual = "@raze__config__0_11_0//:config",
- tags = [
- "cargo-raze",
- "manual",
- ],
-)
-
-alias(
name = "hex",
actual = "@raze__hex__0_4_3//:hex",
tags = [
@@ -40,8 +31,8 @@ alias(
)
alias(
- name = "libsodium_sys",
- actual = "@raze__libsodium_sys__0_2_7//:libsodium_sys",
+ name = "lazy_static",
+ actual = "@raze__lazy_static__1_4_0//:lazy_static",
tags = [
"cargo-raze",
"manual",
@@ -49,8 +40,8 @@ alias(
)
alias(
- name = "log",
- actual = "@raze__log__0_4_14//:log",
+ name = "libc",
+ actual = "@raze__libc__0_2_101//:libc",
tags = [
"cargo-raze",
"manual",
@@ -58,8 +49,8 @@ alias(
)
alias(
- name = "nats",
- actual = "@raze__nats__0_15_2//:nats",
+ name = "libsodium_sys",
+ actual = "@raze__libsodium_sys__0_2_7//:libsodium_sys",
tags = [
"cargo-raze",
"manual",
diff --git a/webhook/src/config.rs b/webhook/src/config.rs
index eead97b..a054d33 100644
--- a/webhook/src/config.rs
+++ b/webhook/src/config.rs
@@ -16,4 +16,4 @@ pub struct Discord {
pub struct Config {
pub server: ServerSettings,
pub discord: Discord,
-} \ No newline at end of file
+}
diff --git a/webhook/src/handler/error.rs b/webhook/src/handler/error.rs
new file mode 100644
index 0000000..ccec59f
--- /dev/null
+++ b/webhook/src/handler/error.rs
@@ -0,0 +1,24 @@
+use hyper::{Body, Error, Response, StatusCode};
+
+pub struct WebhookError {
+ pub code: StatusCode,
+ pub message: String,
+}
+
+impl WebhookError {
+ pub fn new(code: StatusCode, message: &str) -> WebhookError {
+ WebhookError {
+ code,
+ message: message.to_string(),
+ }
+ }
+}
+
+impl Into<Response<Body>> for WebhookError {
+ fn into(self) -> Response<Body> {
+ Response::builder()
+ .status(self.code)
+ .body(self.message.into())
+ .unwrap()
+ }
+}
diff --git a/webhook/src/handler/handler.rs b/webhook/src/handler/handler.rs
index b993aaa..4af2ba6 100644
--- a/webhook/src/handler/handler.rs
+++ b/webhook/src/handler/handler.rs
@@ -1,20 +1,32 @@
+use super::error::WebhookError;
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 common::log::{debug, error, info};
+use common::nats_crate::Connection;
+use hyper::{
+ body::{to_bytes, Bytes},
+ service::Service,
+ Body, Method, Request, Response, StatusCode,
+};
use serde::{Deserialize, Serialize};
-use std::{future::Future, io::{Error, ErrorKind}, pin::Pin, str::from_utf8, sync::Arc, task::{Context, Poll}};
+use std::{
+ future::Future,
+ pin::Pin,
+ str::from_utf8,
+ sync::Arc,
+ task::{Context, Poll},
+ time::Duration,
+};
/// Hyper service used to handle the discord webhooks
#[derive(Clone)]
pub struct HandlerService {
- pub config: Config,
+ pub config: Arc<Config>,
pub nats: Arc<Connection>,
}
impl HandlerService {
- async fn check_request(&self, req: Request<Body>) -> Result<Bytes, Error> {
+ 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");
@@ -30,25 +42,96 @@ impl HandlerService {
) {
Ok(data)
} else {
- Err(Error::new(
- ErrorKind::InvalidData,
- "invalid signature specified",
+ Err(WebhookError::new(
+ StatusCode::UNAUTHORIZED,
+ "invalid signature",
))
}
} else {
- Err(Error::new(
- ErrorKind::BrokenPipe,
+ Err(WebhookError::new(
+ StatusCode::BAD_REQUEST,
"failed to read signature",
))
}
} else {
- Err(Error::new(ErrorKind::BrokenPipe, "unable to read body"))
+ Err(WebhookError::new(
+ StatusCode::BAD_REQUEST,
+ "unable to read body",
+ ))
}
} else {
- Err(Error::new(ErrorKind::InvalidData, "missing headers"))
+ Err(WebhookError::new(
+ StatusCode::UNAUTHORIZED,
+ "missing signature headers",
+ ))
}
} else {
- Err(Error::new(ErrorKind::InvalidData, "invalid method"))
+ Err(WebhookError::new(StatusCode::NOT_FOUND, "not found"))
+ }
+ }
+
+ async fn process_request(
+ &mut self,
+ req: Request<Body>,
+ ) -> Result<Response<Body>, WebhookError> {
+ match self.check_request(req).await {
+ Ok(data) => {
+ let utf8 = from_utf8(&data);
+ match utf8 {
+ Ok(data) => match serde_json::from_str::<Interaction>(data) {
+ Ok(value) => {
+ if 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());
+ } else {
+ debug!("calling nats");
+ // this should hopefully not fail ?
+ let payload =
+ serde_json::to_string(&common::payloads::CachePayload {
+ tracing: common::payloads::Tracing {
+ node_id: "".to_string(),
+ span: None,
+ },
+ operation: "".to_string(),
+ data: value,
+ })
+ .unwrap();
+
+ match self.nats.request_timeout(
+ "nova.cache.dispatch.interaction",
+ payload,
+ Duration::from_secs(2),
+ ) {
+ Ok(response) => Ok(Response::builder()
+ .header("Content-Type", "application/json")
+ .body(Body::from(response.data))
+ .unwrap()),
+
+ Err(error) => {
+ error!("failed to request nats: {}", error);
+ Err(WebhookError::new(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "failed to request nats",
+ ))
+ }
+ }
+ }
+ }
+
+ Err(_) => Err(WebhookError::new(
+ StatusCode::BAD_REQUEST,
+ "invalid json body",
+ )),
+ },
+
+ Err(_) => Err(WebhookError::new(StatusCode::BAD_REQUEST, "not utf-8 body")),
+ }
+ }
+ Err(error) => Err(error),
}
}
}
@@ -56,7 +139,7 @@ impl HandlerService {
#[derive(Debug, Serialize, Deserialize)]
pub struct Ping {
#[serde(rename = "type")]
- t: i32
+ t: i32,
}
/// Implementation of the service
@@ -70,56 +153,13 @@ impl Service<Request<Body>> for HandlerService {
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
- let self_clone = self.clone();
-
+ let mut 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: common::payloads::Tracing {
- node_id: "".to_string(),
- span: None,
- },
- data: value,
- }).unwrap();
+ let response = clone.process_request(req).await;
- 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())
- }
+ match response {
+ Ok(r) => Ok(r),
+ Err(e) => Ok(e.into()),
}
})
}
diff --git a/webhook/src/handler/make_service.rs b/webhook/src/handler/make_service.rs
index 96b203d..deeb2fe 100644
--- a/webhook/src/handler/make_service.rs
+++ b/webhook/src/handler/make_service.rs
@@ -1,12 +1,15 @@
-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;
-
+use crate::config::Config;
+use hyper::service::Service;
+use common::nats_crate::Connection;
+use std::{
+ future::{ready, Ready},
+ sync::Arc,
+ task::{Context, Poll},
+};
pub struct MakeSvc {
- pub settings: Config,
+ pub settings: Arc<Config>,
pub nats: Arc<Connection>,
}
diff --git a/webhook/src/handler/mod.rs b/webhook/src/handler/mod.rs
index 490c580..a437dd5 100644
--- a/webhook/src/handler/mod.rs
+++ b/webhook/src/handler/mod.rs
@@ -1,4 +1,8 @@
pub mod make_service;
mod signature;
mod handler;
-mod types; \ No newline at end of file
+mod types;
+mod error;
+
+#[cfg(test)]
+pub mod tests; \ No newline at end of file
diff --git a/webhook/src/handler/signature.rs b/webhook/src/handler/signature.rs
index 5af6b63..c12f9e8 100644
--- a/webhook/src/handler/signature.rs
+++ b/webhook/src/handler/signature.rs
@@ -1,11 +1,30 @@
+use common::prometheus::{Counter, HistogramVec, labels, opts, register_counter, register_histogram_vec};
use libsodium_sys::crypto_sign_ed25519_verify_detached;
+lazy_static::lazy_static! {
+ static ref SIGNATURE_TIME_HISTOGRAM: HistogramVec = register_histogram_vec!(
+ "nova_webhook_signature_time",
+ "The time taken by the signature verification",
+ &["signature"]
+ ).unwrap();
+
+ static ref SIGNATURE_BODY_COUNTER: Counter = register_counter!(opts!(
+ "nova_webhook_",
+ "",
+ labels! {"handler" => "webhook_main"}
+ )).unwrap();
+}
+
/// 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 {
+ SIGNATURE_BODY_COUNTER.inc();
+ let timer = SIGNATURE_TIME_HISTOGRAM.with_label_values(&["webhook_main"]).start_timer();
+
// 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);
+ let mut result = false;
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.
@@ -19,7 +38,7 @@ pub fn validate_signature(hex_public_key: &str, data: &Vec<u8>, hex_signature: &
// 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(
+ result = crypto_sign_ed25519_verify_detached(
signature_pointer.as_ptr(),
data_pointer,
data_len,
@@ -27,39 +46,7 @@ pub fn validate_signature(hex_public_key: &str, data: &Vec<u8>, hex_signature: &
) == 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))
- }
+ timer.observe_duration();
+ result
}
diff --git a/webhook/README.md b/webhook/src/handler/tests/handler.rs
index e69de29..e69de29 100644
--- a/webhook/README.md
+++ b/webhook/src/handler/tests/handler.rs
diff --git a/webhook/src/handler/tests/handler_integration.rs b/webhook/src/handler/tests/handler_integration.rs
new file mode 100644
index 0000000..fd0d67f
--- /dev/null
+++ b/webhook/src/handler/tests/handler_integration.rs
@@ -0,0 +1,234 @@
+use std::time::Duration;
+
+use crate::{
+ config::Config,
+ handler::tests::utils::{generate_keypair, sign_message},
+ start,
+};
+use common::{config::test_init, nats_crate::Connection, testcontainers::{Image, images::generic::WaitFor}};
+use common::{
+ config::Settings,
+ log::info,
+ testcontainers::{clients::Cli, images::generic::GenericImage, Container, Docker},
+};
+use hyper::{Body, Method, Request};
+use lazy_static::{__Deref, lazy_static};
+use serde_json::json;
+
+#[cfg(all(unix, target_arch = "x86_64"))]
+const fn nats_image<'a>() -> &'a str {
+ return "amd64/nats";
+}
+
+#[cfg(all(unix, target_arch = "aarch64"))]
+const fn nats_image<'a>() -> &'a str {
+ return "arm64v8/nats";
+}
+
+#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
+const fn nats_image<'a>() -> &'a str {
+ return "winamd64/nats";
+}
+
+lazy_static! {
+ static ref DOCKER: Cli = Cli::default();
+
+ static ref NATS_CONTAINER: Container<'static, Cli, GenericImage> = {
+ test_init();
+
+ let image: GenericImage = GenericImage::new(nats_image())
+ .with_wait_for(WaitFor::message_on_stderr("Server is ready"));
+
+ let container = DOCKER.run(image);
+ container.start();
+ container.image().wait_until_ready(&container);
+ container.get_host_port(4222).unwrap();
+ container
+ };
+
+
+ static ref KEYPAIR: (String, [u8; 64]) = {
+ generate_keypair()
+ };
+
+ static ref SETTINGS: Settings<Config> = {
+ let port = NATS_CONTAINER.get_host_port(4222).unwrap();
+ common::config::Settings {
+ config: crate::config::Config {
+ server: crate::config::ServerSettings {
+ port: 5003,
+ address: "0.0.0.0".to_string(),
+ },
+ discord: crate::config::Discord {
+ public_key: KEYPAIR.0.clone(),
+ client_id: 0,
+ },
+ },
+ redis: common::redis::RedisConfiguration {
+ url: "".to_string(),
+ },
+ monitoring: common::monitoring::MonitoringConfiguration {
+ enabled: false,
+ address: None,
+ port: None,
+ },
+ nats: common::nats::NatsConfiguration {
+ client_cert: None,
+ root_cert: None,
+ jetstream_api_prefix: None,
+ max_reconnects: None,
+ reconnect_buffer_size: None,
+ tls: None,
+ client_name: None,
+ tls_required: None,
+ host: format!("localhost:{}", port),
+ },
+ }
+ };
+
+ static ref TASK: () = {
+ std::thread::spawn(|| {
+ let r = tokio::runtime::Runtime::new().unwrap();
+ r.spawn(async { start(SETTINGS.clone()).await });
+ loop {}
+ });
+ std::thread::sleep(Duration::from_secs(1));
+ };
+}
+
+#[tokio::test]
+async fn respond_to_pings() {
+ let _ = NATS_CONTAINER.deref();
+ let _ = TASK.deref();
+ let ping = json!({ "type": 1 }).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, KEYPAIR.1);
+
+ let req = Request::builder()
+ .method(Method::POST)
+ .uri("http://localhost:5003/")
+ .header("X-Signature-Ed25519", signature)
+ .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!(result.status() == 200);
+}
+
+#[tokio::test]
+async fn deny_invalid_signatures() {
+ let _ = NATS_CONTAINER.deref();
+ let _ = TASK.deref();
+ let ping = json!({ "type": 1 }).to_string();
+ let timestamp = "my datetime :)";
+
+ let req = Request::builder()
+ .method(Method::POST)
+ .uri("http://localhost:5003/")
+ .header("X-Signature-Ed25519", "inva&lid signature :)")
+ .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!(result.status() == 401);
+}
+
+#[tokio::test]
+async fn response_500_when_no_nats_response() {
+ let _ = NATS_CONTAINER.deref();
+ let _ = TASK.deref();
+ let ping = json!({ "type": 0 }).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, KEYPAIR.1);
+
+ // we must timeout
+ let req = Request::builder()
+ .method(Method::POST)
+ .uri("http://localhost:5003/")
+ .header("X-Signature-Ed25519", signature)
+ .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!(result.status() == 500);
+}
+
+#[tokio::test]
+async fn respond_from_nats_response() {
+ let _ = NATS_CONTAINER.deref();
+ let _ = TASK.deref();
+ let nats: Connection = SETTINGS.clone().nats.into();
+ let sub = nats.subscribe("nova.cache.dispatch.interaction").unwrap();
+ let ping = json!({ "type": 0 }).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, KEYPAIR.1);
+
+ sub.with_handler(move |msg| {
+ info!("Received {}", &msg);
+ msg.respond("ok :)").unwrap();
+ Ok(())
+ });
+
+ let req = Request::builder()
+ .method(Method::POST)
+ .uri("http://localhost:5003/")
+ .header("X-Signature-Ed25519", signature)
+ .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!(result.status() == 200);
+}
+
+#[tokio::test]
+async fn response_400_when_invalid_json_body() {
+ let _ = NATS_CONTAINER.deref();
+ let _ = TASK.deref();
+ let ping = "{".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, KEYPAIR.1);
+
+ let req = Request::builder()
+ .method(Method::POST)
+ .uri("http://localhost:5003/")
+ .header("X-Signature-Ed25519", signature)
+ .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!(result.status() == 400);
+}
+
+#[tokio::test]
+async fn response_400_when_invalid_utf8_body() {
+ let _ = NATS_CONTAINER.deref();
+ let _ = TASK.deref();
+ // invalid 2 octet sequence
+ let ping = vec![0xc3, 0x28];
+
+ let timestamp = "my datetime :)";
+ let signature_data = [timestamp.as_bytes().to_vec(), ping.to_vec()].concat();
+ let signature = sign_message(signature_data, KEYPAIR.1);
+
+ let req = Request::builder()
+ .method(Method::POST)
+ .uri("http://localhost:5003/")
+ .header("X-Signature-Ed25519", signature)
+ .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!(result.status() == 400);
+}
diff --git a/webhook/src/handler/tests/mod.rs b/webhook/src/handler/tests/mod.rs
new file mode 100644
index 0000000..589ad52
--- /dev/null
+++ b/webhook/src/handler/tests/mod.rs
@@ -0,0 +1,4 @@
+pub mod handler_integration;
+pub mod signature;
+pub mod utils;
+pub mod handler;
diff --git a/webhook/src/handler/tests/signature.rs b/webhook/src/handler/tests/signature.rs
new file mode 100644
index 0000000..475e446
--- /dev/null
+++ b/webhook/src/handler/tests/signature.rs
@@ -0,0 +1,30 @@
+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))
+} \ No newline at end of file
diff --git a/webhook/src/handler/tests/utils.rs b/webhook/src/handler/tests/utils.rs
new file mode 100644
index 0000000..f8cdac2
--- /dev/null
+++ b/webhook/src/handler/tests/utils.rs
@@ -0,0 +1,46 @@
+pub fn generate_keypair() -> (
+ String,
+ [u8; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize],
+) {
+ use libsodium_sys::crypto_sign_ed25519_keypair;
+ let pk_s: String;
+
+ let mut pk = [0; libsodium_sys::crypto_sign_ed25519_PUBLICKEYBYTES as usize];
+ let mut sk = [0; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize];
+
+ let pk_p = pk.as_mut_ptr();
+ let sk_p = sk.as_mut_ptr();
+
+ // generate keypair
+ unsafe {
+ if crypto_sign_ed25519_keypair(pk_p, sk_p) < 0 {
+ panic!("keypair generation failed!");
+ }
+ };
+
+ pk_s = hex::encode(pk);
+ return (pk_s, sk);
+}
+
+pub fn sign_message(
+ msg: Vec<u8>,
+ sk: [u8; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize],
+) -> String {
+ use libc::c_ulonglong;
+ use libsodium_sys::crypto_sign_ed25519_detached;
+
+ let len = msg.len();
+ let mut signature_len: c_ulonglong = 0;
+ let mut str = [0; 64];
+ unsafe {
+ crypto_sign_ed25519_detached(
+ str.as_mut_ptr(),
+ &mut signature_len,
+ msg.as_ptr(),
+ len as u64,
+ sk.as_ptr(),
+ );
+ };
+
+ return hex::encode(str);
+} \ No newline at end of file
diff --git a/webhook/src/handler/types.rs b/webhook/src/handler/types.rs
index 4cba12a..4fc5b68 100644
--- a/webhook/src/handler/types.rs
+++ b/webhook/src/handler/types.rs
@@ -7,4 +7,3 @@ pub struct Interaction {
pub t: i16,
pub data: Option<Value>,
}
-
diff --git a/webhook/src/main.rs b/webhook/src/main.rs
index c127c2c..98e5f13 100644
--- a/webhook/src/main.rs
+++ b/webhook/src/main.rs
@@ -1,30 +1,37 @@
use std::{net::ToSocketAddrs, sync::Arc};
-mod handler;
mod config;
+mod handler;
use crate::handler::make_service::MakeSvc;
-use hyper::Server;
-use log::{info, error};
-use common::config::Settings;
use crate::config::Config;
+use common::config::Settings;
+use common::log::{error, info};
+use hyper::Server;
#[tokio::main]
async fn main() {
let settings: Settings<Config> = Settings::new("webhook").unwrap();
+ start(settings).await;
+}
- let addr = format!("{}:{}", settings.config.server.address, settings.config.server.port)
- .to_socket_addrs()
- .unwrap()
- .next()
- .unwrap();
+async fn start(settings: Settings<Config>) {
+ let addr = format!(
+ "{}:{}",
+ settings.config.server.address, settings.config.server.port
+ )
+ .to_socket_addrs()
+ .unwrap()
+ .next()
+ .unwrap();
info!(
"Starting server on {}:{}",
settings.config.server.address, settings.config.server.port
);
+ let config = Arc::new(settings.config);
let server = Server::bind(&addr).serve(MakeSvc {
- settings: settings.config.clone(),
+ settings: config,
nats: Arc::new(settings.nats.into()),
});