]> git.puffer.fish Git - matthieu/nova.git/commitdiff
add more tests for the webhook
authorMatthieu <matthieu@developershouse.xyz>
Wed, 13 Oct 2021 09:09:35 +0000 (13:09 +0400)
committerMatthieu <matthieu@developershouse.xyz>
Wed, 13 Oct 2021 09:09:35 +0000 (13:09 +0400)
webhook/Cargo.toml
webhook/src/handler/error.rs [new file with mode: 0644]
webhook/src/handler/handler.rs
webhook/src/handler/mod.rs
webhook/src/handler/signature.rs
webhook/src/handler/tests/handler.rs
webhook/src/handler/tests/handler_integration.rs [new file with mode: 0644]
webhook/src/handler/tests/mod.rs
webhook/src/handler/tests/signature.rs [new file with mode: 0644]
webhook/src/handler/tests/utils.rs [new file with mode: 0644]
webhook/src/main.rs

index ba0b2f88bf0fa90153d9de23c91f177de238c5bd..7e73c436a119d55bf93cdb5ef5aee62232949d7f 100644 (file)
@@ -12,6 +12,7 @@ libsodium-sys = "0.2.7"
 hex = "0.4.3"
 serde_json = { version = "1.0" }
 libc = "0.2.101"
+lazy_static = "1.4.0"
 
 [[bin]]
 name = "webhook"
diff --git a/webhook/src/handler/error.rs b/webhook/src/handler/error.rs
new file mode 100644 (file)
index 0000000..ccec59f
--- /dev/null
@@ -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()
+    }
+}
index b3dc8a6b808f8fc1ea549ba907dd4e00ea010dda..bcce81db2824fc58a7c8dabafd4dd73c2d9f4b9f 100644 (file)
@@ -1,14 +1,22 @@
+use super::error::WebhookError;
 use super::{signature::validate_signature, types::Interaction};
 use crate::config::Config;
+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 common::log::{error, info, trace};
-use common::nats_crate::Connection;
 use serde::{Deserialize, Serialize};
-use std::{future::Future, io::{Error, ErrorKind}, pin::Pin, str::from_utf8, sync::Arc, task::{Context, Poll}, time::Duration};
+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)]
@@ -18,7 +26,7 @@ pub struct HandlerService {
 }
 
 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");
@@ -34,25 +42,79 @@ 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,
-                            "failed to read signature",
-                        ))
+                        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) => 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());
+                            }
+                            _ => {
+                                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),
         }
     }
 }
@@ -74,58 +136,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,
-                                },
-                                operation: "".to_string(),
-                                data: value,
-                            })
-                            .unwrap();
+            let response = clone.process_request(req).await;
 
-                            match self_clone
-                                .nats
-                                .request_timeout("nova.cache.dispatch.interaction", payload, Duration::from_secs(2))
-                            {
-                                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())
             }
         })
     }
index 598906bad4ae61ab10eb68efa9099af93d03d9c1..a437dd540e2b483a27cc591bd61ede38e0614569 100644 (file)
@@ -2,4 +2,7 @@ pub mod make_service;
 mod signature;
 mod handler;
 mod types;
+mod error;
+
+#[cfg(test)]
 pub mod tests;
\ No newline at end of file
index 5af6b631b24ce2ec5f3014d0ebddf8713ce38b83..b98af516234e45b4c20f5dee428fadb07bd3775c 100644 (file)
@@ -29,37 +29,3 @@ pub fn validate_signature(hex_public_key: &str, data: &Vec<u8>, hex_signature: &
     }
     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))
-    }
-}
index 3e5ccd0921cfd18a76860ef5ac62520589519bd2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,174 +0,0 @@
-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);
-}
-
-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);
-}
-
-#[tokio::test]
-async fn respond_to_pings_and_deny_invalid() {
-    use crate::start;
-    use common::config::test_init;
-    use common::config::Settings;
-    use common::log::info;
-    use common::testcontainers::images::generic::GenericImage;
-    use common::testcontainers::Docker;
-    use hyper::{Body, Method, Request};
-    use libsodium_sys::sodium_init;
-    use serde_json::json;
-    use std::time::Duration;
-
-    test_init();
-
-    unsafe {
-        if sodium_init() < 0 {
-            panic!("libsodium init error!");
-        }
-    }
-
-    let (private_key, secret_key) = generate_keypair();
-    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, secret_key);
-
-    // start nats
-    let docker = common::testcontainers::clients::Cli::default();
-    let image = GenericImage::new("nats");
-    let node = docker.run(image);
-    node.start();
-    let port = node.get_host_port(4222).unwrap();
-
-    let settings: Settings<crate::config::Config> = 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: private_key,
-                client_id: 0,
-            },
-        },
-        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),
-        },
-    };
-
-    let nats: common::nats_crate::Connection = settings.nats.clone().into();
-    // start the server
-    tokio::task::spawn(start(settings));
-    tokio::time::sleep(Duration::from_secs(1)).await;
-
-    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);
-
-    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);
-
-    // setup nats mock listener
-    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, secret_key);
-
-    // we must timeout
-    let req = Request::builder()
-        .method(Method::POST)
-        .uri("http://localhost:5003/")
-        .header("X-Signature-Ed25519", signature.clone())
-        .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);
-
-     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.clone())
-        .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);
-}
diff --git a/webhook/src/handler/tests/handler_integration.rs b/webhook/src/handler/tests/handler_integration.rs
new file mode 100644 (file)
index 0000000..6aee0bd
--- /dev/null
@@ -0,0 +1,213 @@
+use std::time::Duration;
+
+use crate::{
+    config::Config,
+    handler::tests::utils::{generate_keypair, sign_message},
+    start,
+};
+use common::{config::test_init, nats_crate::Connection};
+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;
+
+lazy_static! {
+    static ref DOCKER: Cli = Cli::default();
+
+    static ref NATS_CONTAINER: Container<'static, Cli, GenericImage> = {
+        test_init();
+        let image: GenericImage = GenericImage::new("nats");
+        let container = DOCKER.run(image);
+        container.start();
+        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 _ = 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 _ = 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);
+}
index ef7d850d185bbd43a15e6d9bf8142395a8607100..589ad521d615e34affc7459dbfa7433bbb42b7e1 100644 (file)
@@ -1 +1,4 @@
-pub mod handler;
\ No newline at end of file
+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 (file)
index 0000000..475e446
--- /dev/null
@@ -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 (file)
index 0000000..f8cdac2
--- /dev/null
@@ -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
index b213e9d724f08a0d801f80707814f7f823dd4096..eef47514f4d41b078da0c693760b5ed2d7a158bc 100644 (file)
@@ -15,6 +15,7 @@ async fn main() {
 }\r
 \r
 async fn start(settings: Settings<Config>) {\r
+    \r
     let addr = format!(\r
         "{}:{}",\r
         settings.config.server.address, settings.config.server.port\r
@@ -36,6 +37,6 @@ async fn start(settings: Settings<Config>) {
     });\r
 \r
     if let Err(e) = server.await {\r
-        panic!("server error: {}", e);\r
+        error!("server error: {}", e);\r
     }\r
 }\r