Add register notification webhook (#764)

I find it super motivating when people use my stuff, so this makes it
_even easier_ to know when someone new signs up!
This commit is contained in:
Ellie Huxtable 2023-03-07 22:09:19 +00:00 committed by GitHub
parent ca5bbea0d4
commit b978f9a4de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 3 deletions

1
Cargo.lock generated
View File

@ -169,6 +169,7 @@ dependencies = [
"fs-err", "fs-err",
"http", "http",
"rand", "rand",
"reqwest",
"serde", "serde",
"serde_json", "serde_json",
"sodiumoxide", "sodiumoxide",

View File

@ -35,3 +35,7 @@ fs-err = "2.9"
chronoutil = "0.2.3" chronoutil = "0.2.3"
tower = "0.4" tower = "0.4"
tower-http = { version = "0.3", features = ["trace"] } tower-http = { version = "0.3", features = ["trace"] }
reqwest = { version = "0.11", features = [
"json",
"rustls-tls-native-roots",
], default-features = false }

View File

@ -1,4 +1,6 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap;
use std::time::Duration;
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
@ -6,7 +8,7 @@ use axum::{
}; };
use http::StatusCode; use http::StatusCode;
use sodiumoxide::crypto::pwhash::argon2id13; use sodiumoxide::crypto::pwhash::argon2id13;
use tracing::{debug, error, instrument}; use tracing::{debug, error, info, instrument};
use uuid::Uuid; use uuid::Uuid;
use super::{ErrorResponse, ErrorResponseStatus, RespExt}; use super::{ErrorResponse, ErrorResponseStatus, RespExt};
@ -16,6 +18,8 @@ use crate::{
router::AppState, router::AppState,
}; };
use reqwest::header::CONTENT_TYPE;
use atuin_common::api::*; use atuin_common::api::*;
pub fn verify_str(secret: &str, verify: &str) -> bool { pub fn verify_str(secret: &str, verify: &str) -> bool {
@ -32,6 +36,30 @@ pub fn verify_str(secret: &str, verify: &str) -> bool {
} }
} }
// Try to send a Discord webhook once - if it fails, we don't retry. "At most once", and best effort.
// Don't return the status because if this fails, we don't really care.
async fn send_register_hook(url: &str, username: String, registered: String) {
let hook = HashMap::from([
("username", username),
("content", format!("{registered} has just signed up!")),
]);
let client = reqwest::Client::new();
let resp = client
.post(url)
.timeout(Duration::new(5, 0))
.header(CONTENT_TYPE, "application/json")
.json(&hook)
.send()
.await;
match resp {
Ok(_) => info!("register webhook sent ok!"),
Err(e) => error!("failed to send register webhook: {}", e),
}
}
#[instrument(skip_all, fields(user.username = username.as_str()))] #[instrument(skip_all, fields(user.username = username.as_str()))]
pub async fn get<DB: Database>( pub async fn get<DB: Database>(
Path(username): Path<String>, Path(username): Path<String>,
@ -71,8 +99,8 @@ pub async fn register<DB: Database>(
let hashed = hash_secret(&register.password); let hashed = hash_secret(&register.password);
let new_user = NewUser { let new_user = NewUser {
email: register.email, email: register.email.clone(),
username: register.username, username: register.username.clone(),
password: hashed, password: hashed,
}; };
@ -94,6 +122,16 @@ pub async fn register<DB: Database>(
token: (&token).into(), token: (&token).into(),
}; };
if let Some(url) = &state.settings.register_webhook_url {
// Could probs be run on another thread, but it's ok atm
send_register_hook(
url,
state.settings.register_webhook_username.clone(),
register.username,
)
.await;
}
match db.add_session(&new_session).await { match db.add_session(&new_session).await {
Ok(_) => Ok(Json(RegisterResponse { session: token })), Ok(_) => Ok(Json(RegisterResponse { session: token })),
Err(e) => { Err(e) => {

View File

@ -15,6 +15,8 @@ pub struct Settings {
pub db_uri: String, pub db_uri: String,
pub open_registration: bool, pub open_registration: bool,
pub max_history_length: usize, pub max_history_length: usize,
pub register_webhook_url: Option<String>,
pub register_webhook_username: String,
} }
impl Settings { impl Settings {
@ -37,6 +39,7 @@ impl Settings {
.set_default("open_registration", false)? .set_default("open_registration", false)?
.set_default("max_history_length", 8192)? .set_default("max_history_length", 8192)?
.set_default("path", "")? .set_default("path", "")?
.set_default("register_webhook_username", "")?
.add_source( .add_source(
Environment::with_prefix("atuin") Environment::with_prefix("atuin")
.prefix_separator("_") .prefix_separator("_")