From db075f2ca1f7e7efc2be38b367303bc39b9eb587 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 11 Apr 2023 01:45:56 +0200 Subject: [PATCH] feat(backend): add instance events webhook --- README.md | 2 +- backend/src/main.rs | 2 +- backend/windmill-api/src/oauth2.rs | 13 ++---- backend/windmill-api/src/webhook_util.rs | 45 +++++++++++++++++-- backend/windmill-api/src/workspaces.rs | 30 +++++-------- frontend/src/lib/components/Path.svelte | 18 ++++++-- .../lib/components/icons/WindmillIcon.svelte | 6 +-- 7 files changed, 77 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index ea3642aec3..e291d2dd0a 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ it being synced automatically everyday. | INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server | | WHITELIST_WORKSPACES | None | Whitelist of workspaces this worker takes job from | Worker | | BLACKLIST_WORKSPACES | None | Blacklist of workspaces this worker takes job from | Worker | -| NEW_USER_WEBHOOK | None | Webhook to notify of a new user added, signup/invite. Can hook back to windmill to send emails | Server | +| INSTANCE_EVENTS_WEBHOOK | None | Webhook to notify of events such as new user added, signup/invite. Can hook back to windmill to send emails | Server | ## Run a local dev setup diff --git a/backend/src/main.rs b/backend/src/main.rs index 412cc7dbbc..31ce62cf50 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -112,7 +112,7 @@ Windmill Community Edition {GIT_VERSION} "INCLUDE_HEADERS", "WHITELIST_WORKSPACES", "BLACKLIST_WORKSPACES", - "NEW_USER_WEBHOOK", + "INSTANCE_EVENTS_WEBHOOK", "CLOUD_HOSTED", ]); diff --git a/backend/windmill-api/src/oauth2.rs b/backend/windmill-api/src/oauth2.rs index fb6c697029..861c37a8e8 100644 --- a/backend/windmill-api/src/oauth2.rs +++ b/backend/windmill-api/src/oauth2.rs @@ -32,7 +32,8 @@ use windmill_audit::{audit_log, ActionKind}; use windmill_common::users::username_to_permissioned_as; use windmill_common::utils::{not_found_if_none, now_from_db}; -use crate::users::{truncate_token, Authed, NEW_USER_WEBHOOK}; +use crate::users::{truncate_token, Authed}; +use crate::webhook_util::{InstanceEvent, WebhookShared}; use crate::workspaces::invite_user_to_all_auto_invite_worspaces; use crate::{ db::{UserDB, DB}, @@ -815,6 +816,7 @@ async fn login_callback( Path(client_name): Path, cookies: Cookies, Extension(db): Extension, + Extension(webhook): Extension, Json(callback): Json, ) -> error::Result { let client_w_config = &OAUTH_CLIENTS @@ -940,14 +942,7 @@ async fn login_callback( } tx.commit().await?; - if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() { - let _ = HTTP_CLIENT - .post(&new_user_webhook) - .json(&serde_json::json!({"email" : &email, "event": "oauth_signup"})) - .send() - .await - .map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string())); - } + webhook.send_instance_event(InstanceEvent::UserSignupOAuth { email: email.clone() }); Ok("Successfully logged in".to_string()) } else { diff --git a/backend/windmill-api/src/webhook_util.rs b/backend/windmill-api/src/webhook_util.rs index e4c76b17c7..f312448701 100644 --- a/backend/windmill-api/src/webhook_util.rs +++ b/backend/windmill-api/src/webhook_util.rs @@ -13,6 +13,26 @@ lazy_static::lazy_static! { "Histogram of webhook requests made" ) .unwrap(); + + pub static ref INSTANCE_EVENTS_WEBHOOK: Option = std::env::var("INSTANCE_EVENTS_WEBHOOK").ok(); + +} + +pub enum WebhookPayload { + WorkspaceEvent(String, WebhookMessage), + InstanceEvent(InstanceEvent), +} + +#[derive(Serialize)] +#[serde(tag = "type")] +pub enum InstanceEvent { + UserSignupOAuth { email: String }, + UserAdded { email: String }, + // UserDeleted { email: String }, + // UserDeletedWorkspace { workspace: String, email: String }, + UserAddedWorkspace { workspace: String, email: String }, + UserInvitedWorkspace { workspace: String, email: String }, + UserJoinedWorkspace { workspace: String, email: String, username: String }, } #[derive(Serialize)] @@ -46,12 +66,12 @@ pub enum WebhookMessage { #[derive(Clone)] pub struct WebhookShared { - pub channel: mpsc::UnboundedSender<(String, WebhookMessage)>, + pub channel: mpsc::UnboundedSender, } impl WebhookShared { pub fn new(mut shutdown_rx: tokio::sync::broadcast::Receiver<()>, db: DB) -> Self { - let (tx, mut rx) = mpsc::unbounded_channel::<(String, WebhookMessage)>(); + let (tx, mut rx) = mpsc::unbounded_channel::(); let _process = tokio::spawn(async move { let client = reqwest::Client::builder() // TODO: investigate pool timeouts and such if TCP load is high @@ -66,7 +86,7 @@ impl WebhookShared { biased; _ = shutdown_rx.recv() => break, r = rx.recv() => match r { - Some((workspace_id, message)) => { + Some(WebhookPayload::WorkspaceEvent(workspace_id, message)) => { let url_guard = match cache.get(&workspace_id).await { Some(guard) => { guard @@ -96,6 +116,13 @@ impl WebhookShared { drop(url_guard); } }, + Some(WebhookPayload::InstanceEvent(event)) => { + if *METRICS_ENABLED { Some(WEBHOOK_REQUEST_COUNT.start_timer()) } else { None }; + let r = client.post(INSTANCE_EVENTS_WEBHOOK.as_ref().unwrap()).json(&event).send().await; + if let Err(e) = r { + tracing::error!("Error sending instance event: {}", e); + } + }, None => break, }, _ = futures::future::poll_fn(|cx| cache_purge_interval.poll_tick(cx)) => { @@ -110,6 +137,16 @@ impl WebhookShared { } pub fn send_message(&self, workspace_id: String, message: WebhookMessage) { - let _ = self.channel.send((workspace_id.clone(), message)); + let _ = self.channel.send(WebhookPayload::WorkspaceEvent( + workspace_id.clone(), + message, + )); + } + + pub fn send_instance_event(&self, event: InstanceEvent) { + if INSTANCE_EVENTS_WEBHOOK.is_none() { + return; + } + let _ = self.channel.send(WebhookPayload::InstanceEvent(event)); } } diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 1b9d410396..41d3810fa0 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -16,10 +16,10 @@ use crate::{ db::{UserDB, DB}, folders::Folder, resources::{Resource, ResourceType}, - users::{Authed, WorkspaceInvite, NEW_USER_WEBHOOK, VALID_USERNAME}, + users::{Authed, WorkspaceInvite, VALID_USERNAME}, utils::require_super_admin, variables::build_crypt, - HTTP_CLIENT, + webhook_util::{InstanceEvent, WebhookShared}, }; #[cfg(feature = "enterprise")] use axum::response::Redirect; @@ -943,6 +943,7 @@ pub async fn invite_user_to_all_auto_invite_worspaces(db: &DB, email: &str) -> R async fn invite_user( Authed { username, is_admin, .. }: Authed, Extension(db): Extension, + Extension(webhook): Extension, Path(w_id): Path, Json(mut nu): Json, ) -> Result<(StatusCode, String)> { @@ -966,14 +967,10 @@ async fn invite_user( tx.commit().await?; - if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() { - let _ = &HTTP_CLIENT - .post(&new_user_webhook) - .json(&serde_json::json!({"email" : &nu.email, "event": "workspace_invite"})) - .send() - .await - .map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string())); - } + webhook.send_instance_event(InstanceEvent::UserInvitedWorkspace { + email: nu.email.clone(), + workspace: w_id, + }); Ok(( StatusCode::CREATED, @@ -984,6 +981,7 @@ async fn invite_user( async fn add_user( Authed { username, is_admin, .. }: Authed, Extension(db): Extension, + Extension(webhook): Extension, Path(w_id): Path, Json(mut nu): Json, ) -> Result<(StatusCode, String)> { @@ -1022,14 +1020,10 @@ async fn add_user( tx.commit().await?; - if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() { - let _ = HTTP_CLIENT - .post(&new_user_webhook) - .json(&serde_json::json!({"email" : &nu.email, "event": "workspace_add"})) - .send() - .await - .map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string())); - } + webhook.send_instance_event(InstanceEvent::UserAddedWorkspace { + workspace: w_id.clone(), + email: nu.email.clone(), + }); Ok(( StatusCode::CREATED, diff --git a/frontend/src/lib/components/Path.svelte b/frontend/src/lib/components/Path.svelte index 67ff79b6ca..2c1aa6b900 100644 --- a/frontend/src/lib/components/Path.svelte +++ b/frontend/src/lib/components/Path.svelte @@ -317,16 +317,28 @@ > -
+
- -
diff --git a/frontend/src/lib/components/icons/WindmillIcon.svelte b/frontend/src/lib/components/icons/WindmillIcon.svelte index fb537c189e..a2ae1a2f13 100644 --- a/frontend/src/lib/components/icons/WindmillIcon.svelte +++ b/frontend/src/lib/components/icons/WindmillIcon.svelte @@ -8,9 +8,9 @@