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:
parent
ca5bbea0d4
commit
b978f9a4de
4 changed files with 49 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -169,6 +169,7 @@ dependencies = [
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"http",
|
"http",
|
||||||
"rand",
|
"rand",
|
||||||
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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(®ister.password);
|
let hashed = hash_secret(®ister.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) => {
|
||||||
|
|
|
@ -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("_")
|
||||||
|
|
Loading…
Reference in a new issue