From 72e409f952e54a957454c7fe2965ed735eda5fe7 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 22 Jul 2024 07:12:35 +0100 Subject: [PATCH] WIP --- crates/config/src/server.rs | 8 ++ .../server-actor/src/handlers/notify_user.rs | 88 ++++++++++++++----- .../handlers/receive_valid_server_config.rs | 22 ----- crates/server-actor/src/lib.rs | 3 - 4 files changed, 73 insertions(+), 48 deletions(-) diff --git a/crates/config/src/server.rs b/crates/config/src/server.rs index 8ea97ab..199fd43 100644 --- a/crates/config/src/server.rs +++ b/crates/config/src/server.rs @@ -243,3 +243,11 @@ pub struct OutboundWebhook { url: String, secret: String, } +impl OutboundWebhook { + pub fn url(&self) -> &str { + self.url.as_ref() + } + pub fn secret(&self) -> Secret { + Secret::new(self.secret.clone()) + } +} diff --git a/crates/server-actor/src/handlers/notify_user.rs b/crates/server-actor/src/handlers/notify_user.rs index cfb8d6f..118cd42 100644 --- a/crates/server-actor/src/handlers/notify_user.rs +++ b/crates/server-actor/src/handlers/notify_user.rs @@ -1,39 +1,81 @@ // use actix::prelude::*; -use git_next_config::server::NotificationType; +use git_next_config::server::{Notification, NotificationType}; use git_next_repo_actor::messages::NotifyUser; +use secrecy::ExposeSecret; +use standardwebhooks::Webhook; +use tracing::Instrument; use crate::ServerActor; impl Handler for ServerActor { type Result = (); - fn handle(&mut self, msg: NotifyUser, _ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: NotifyUser, ctx: &mut Self::Context) -> Self::Result { let Some(server_config) = &self.server_config else { return; }; - let notification = &server_config.notification(); - match notification.r#type() { - NotificationType::None => { /* do nothing */ } - NotificationType::Webhook => { - let message_id = format!("msg_{}", ulid::Ulid::new()); - let timestamp = time::OffsetDateTime::now_utc(); //.unix_timestamp().to_string(); - let payload = msg.as_json(timestamp).to_string(); - let timestamp = timestamp.unix_timestamp(); - let to_sign = format!("{message_id}.{timestamp}.{payload}"); - tracing::info!(?to_sign, ""); - let Some(webhook) = self.webhook.as_ref() else { - tracing::warn!("Invalid notification configuration - can't sent notification"); - return; - }; - #[allow(clippy::expect_used)] - let signature = webhook - .sign(&message_id, timestamp, payload.as_ref()) - .expect("signature"); - tracing::info!(?signature, ""); - // TODO: (#95) should notify user - // send post to notification webhook url + let notification_config = server_config.notification().clone(); + let net = self.net.clone(); + async move { + match notification_config.r#type() { + NotificationType::None => { /* do nothing */ } + NotificationType::Webhook => send_webhook(msg, notification_config, net).await, } } + .in_current_span() + .into_actor(self) + .wait(ctx); } } + +async fn send_webhook( + msg: NotifyUser, + notification_config: Notification, + net: kxio::network::Network, +) { + let Some(webhook_config) = notification_config.webhook() else { + tracing::warn!("Invalid notification configuration (config) - can't sent notification"); + return; + }; + let Ok(webhook) = Webhook::new(webhook_config.secret().expose_secret()) else { + tracing::warn!("Invalid notification configuration (signer) - can't sent notification"); + return; + }; + do_send_webhook(msg, webhook, webhook_config, net).await; +} + +async fn do_send_webhook( + msg: NotifyUser, + webhook: Webhook, + webhook_config: &git_next_config::server::OutboundWebhook, + net: kxio::network::Network, +) { + let message_id = format!("msg_{}", ulid::Ulid::new()); + let timestamp = time::OffsetDateTime::now_utc(); + let payload = msg.as_json(timestamp); + let timestamp = timestamp.unix_timestamp(); + let to_sign = format!("{message_id}.{timestamp}.{payload}"); + tracing::info!(?to_sign, ""); + #[allow(clippy::expect_used)] + let signature = webhook + .sign(&message_id, timestamp, payload.to_string().as_ref()) + .expect("signature"); + tracing::info!(?signature, ""); + let url = webhook_config.url(); + use kxio::network::{NetRequest, NetUrl, RequestBody, ResponseType}; + let net_url = NetUrl::new(url.to_string()); + let request = NetRequest::post(net_url) + .body(RequestBody::Json(payload)) + .header("webhook-id", &message_id) + .header("webhook-timestamp", ×tamp.to_string()) + .header("webhook-signature", &signature) + .response_type(ResponseType::None) + .build(); + net.post_json::<()>(request).await.map_or_else( + |err| { + tracing::warn!(?err, "sending webhook"); + }, + |_| (), + ); +} diff --git a/crates/server-actor/src/handlers/receive_valid_server_config.rs b/crates/server-actor/src/handlers/receive_valid_server_config.rs index 4a8a32e..f2fd8b4 100644 --- a/crates/server-actor/src/handlers/receive_valid_server_config.rs +++ b/crates/server-actor/src/handlers/receive_valid_server_config.rs @@ -1,7 +1,5 @@ use actix::prelude::*; -use git_next_config::server::NotificationType; use git_next_webhook_actor::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter}; -use standardwebhooks::Webhook; use crate::{ messages::{ReceiveValidServerConfig, ValidServerConfig}, @@ -21,26 +19,6 @@ impl Handler for ServerActor { if let Some(webhook_actor_addr) = self.webhook_actor_addr.take() { webhook_actor_addr.do_send(ShutdownWebhook); } - match server_config.notification().r#type() { - NotificationType::None => { /* do nothing */ } - NotificationType::Webhook => { - // Create webhook signer - use secrecy::ExposeSecret; - let webhook = server_config - .notification() - .webhook_secret() - .map(|secret| Webhook::new(secret.expose_secret())) - .transpose() - .map_err(|e| { - tracing::error!( - "Invalid notification webhook secret (will not send notifications): {e}" - ) - }) - .ok() - .flatten(); - self.webhook = webhook; - } - } self.generation.inc(); // Webhook Server tracing::info!("Starting Webhook Server..."); diff --git a/crates/server-actor/src/lib.rs b/crates/server-actor/src/lib.rs index d55b7df..2bca343 100644 --- a/crates/server-actor/src/lib.rs +++ b/crates/server-actor/src/lib.rs @@ -14,7 +14,6 @@ use git_next_repo_actor::messages::NotifyUser; use git_next_repo_actor::{messages::CloneRepo, RepoActor}; use git_next_webhook_actor as webhook; use kxio::{fs::FileSystem, network::Network}; -use standardwebhooks::Webhook; use std::{ collections::BTreeMap, path::PathBuf, @@ -54,7 +53,6 @@ pub struct ServerActor { repository_factory: Box, sleep_duration: std::time::Duration, repo_actors: BTreeMap<(ForgeAlias, RepoAlias), Addr>, - webhook: Option, // testing message_log: Option>>>, @@ -80,7 +78,6 @@ impl ServerActor { repository_factory: repo, sleep_duration, repo_actors: BTreeMap::new(), - webhook: None, message_log: None, } }