From 743b6b33c941574bfff459177bdb3e1ea80d1940 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 30 Jun 2026 20:44:25 +0000 Subject: [PATCH] Switch to vscode + fetch bot_name with token --- .devcontainer/devcontainer.json | 6 +- .vscode/settings.json | 3 + .zed/settings.json | 11 ---- Cargo.lock | 99 +-------------------------------- Cargo.toml | 6 +- src/api.rs | 15 ++--- src/bot.rs | 45 ++++++++------- src/env.rs | 3 - src/gitea.rs | 16 ++++++ src/main.rs | 25 +++++++-- 10 files changed, 75 insertions(+), 154 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 .zed/settings.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 328bc9f..a0fc52a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,8 +12,12 @@ "containerEnv": { "SHELL": "/bin/bash" }, + "customizations": { + "vscode": { + "extensions": ["fill-labs.dependi", "rust-lang.rust-analyzer", "tamasfe.even-better-toml"] + } + }, "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/herald,type=bind", "workspaceFolder": "/workspaces/herald", - "runArgs": ["--userns=keep-id", "--security-opt", "label=disable"], "appPort": [3000] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..660eb93 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.check.command": "clippy" +} \ No newline at end of file diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index d569920..0000000 --- a/.zed/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "lsp": { - "rust-analyzer": { - "initialization_options": { - "check": { - "command": "clippy" - } - } - } - } -} diff --git a/Cargo.lock b/Cargo.lock index f998bb0..fc3fa28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,15 +280,6 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -[[package]] -name = "block-buffer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" -dependencies = [ - "hybrid-array", -] - [[package]] name = "block2" version = "0.6.2" @@ -341,18 +332,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "cmov" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" - -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "convert_case" version = "0.10.0" @@ -378,33 +357,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] - [[package]] name = "darling" version = "0.20.11" @@ -523,18 +475,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "ctutils", -] - [[package]] name = "dispatch2" version = "0.3.1" @@ -794,15 +734,13 @@ dependencies = [ "dotenvy", "futures-util", "hex", - "hmac", "openrouter-rs", "reqwest 0.12.28", + "ring", "sentry", "sentry-anyhow", "serde", "serde_json", - "sha2", - "subtle", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -819,15 +757,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest", -] - [[package]] name = "hostname" version = "0.4.2" @@ -895,15 +824,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hybrid-array" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" -dependencies = [ - "typenum", -] - [[package]] name = "hyper" version = "1.9.0" @@ -2268,17 +2188,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -2701,12 +2610,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - [[package]] name = "uname" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 2e9845f..4192d13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,6 @@ tracing-subscriber = { version = "0.3", features=["env-filter"] } axum = "0.8" anyhow = { version = "1.0", features = ["backtrace"] } thiserror = "2.0" -hmac = "0.13" -sha2 = "0.11" +ring = "0.17" hex = "0.4" -subtle = "2.6" -bytes = "1.11" +bytes = "1.1" diff --git a/src/api.rs b/src/api.rs index 36b13d0..13e3505 100644 --- a/src/api.rs +++ b/src/api.rs @@ -4,12 +4,10 @@ use axum::http::Request; use axum::response::IntoResponse; use axum::routing::{get, post}; use axum::{Json, Router}; -use hmac::{Hmac, KeyInit, Mac}; use reqwest::StatusCode; +use ring::hmac; use sentry::integrations::tower::{NewSentryLayer, SentryHttpLayer}; use serde_json::Value; -use sha2::Sha256; -use subtle::ConstantTimeEq; use tower::ServiceBuilder; use tower_http::trace::TraceLayer; use tracing::{info, instrument}; @@ -105,7 +103,7 @@ where }); }); - let webhook = parse_webhook(&type_header, &app_state.config.bot_name, &body_bytes)?; + let webhook = parse_webhook(&type_header, &app_state.bot.name(), &body_bytes)?; Ok(WebhookExtract(webhook)) } } @@ -137,12 +135,7 @@ fn parse_webhook(header: &str, bot_name: &str, body_bytes: &[u8]) -> Result Result<(), AppError> { let sig_header_decoded = hex::decode(sig_header).map_err(|_| AppError::WebHookSigHeaderInvalidErr)?; - let mut mac = Hmac::::new_from_slice(secret_key).map_err(anyhow::Error::from)?; + let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key); - mac.update(body); - - let generated_hmac = mac.finalize().into_bytes(); - bool::from(generated_hmac.ct_eq(&sig_header_decoded)) - .then_some(()) - .ok_or(AppError::WebHookSigHeaderInvalidErr) + hmac::verify(&key, body, &sig_header_decoded).map_err(|_| AppError::WebHookSigHeaderInvalidErr) } diff --git a/src/bot.rs b/src/bot.rs index 22b10c2..55e0cab 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,10 +1,9 @@ use crate::{ - env::EnvConfig, gitea::{GiteaAPI, WebhookType}, open_router::OpenRouterClient, }; use serde::Deserialize; -use std::{collections::HashSet, sync::Arc, time::Duration}; +use std::{collections::HashSet, sync::Arc}; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; use tracing::{error, info, instrument}; @@ -26,33 +25,37 @@ pub struct ReviewItem { #[derive(Clone)] pub struct Bot { - config: EnvConfig, + bot_name: String, gitea_api: GiteaAPI, open_router_client: OpenRouterClient, http_client: reqwest::Client, max_concurrent: usize, + open_router_model: String, actions_handled: Arc>>, } impl Bot { - pub fn new(config: EnvConfig) -> anyhow::Result { - let gitea_timeout = config.gitea_timeout; - let open_router_timeout = config.open_router_timeout; - - Ok(Self { - gitea_api: GiteaAPI::new(&config.gitea_url, &config.gitea_token, gitea_timeout)?, - open_router_client: OpenRouterClient::new( - &config.open_router_api_key, - &config.open_router_model, - open_router_timeout, - )?, - max_concurrent: config.bot_max_concurrent, - config, + pub fn new( + bot_name: String, + gitea_api: GiteaAPI, + open_router_client: OpenRouterClient, + http_client: reqwest::Client, + max_concurrent: usize, + open_router_model: String, + ) -> Self { + Self { + bot_name, + gitea_api, + open_router_client, + http_client, + max_concurrent, + open_router_model, actions_handled: Arc::new(Mutex::new(HashSet::new())), - http_client: reqwest::Client::builder() - .timeout(Duration::from_secs(gitea_timeout)) - .build()?, - }) + } + } + + pub fn name(&self) -> String { + self.bot_name.clone() } pub async fn start( @@ -111,7 +114,7 @@ impl Bot { &self.gitea_api, &self.open_router_client, &self.http_client, - &self.config.open_router_model, + &self.open_router_model, review_payload, ), } diff --git a/src/env.rs b/src/env.rs index 855b58b..4a558a6 100644 --- a/src/env.rs +++ b/src/env.rs @@ -7,7 +7,6 @@ pub struct EnvConfig { pub open_router_api_key: String, pub open_router_model: String, pub open_router_timeout: u64, - pub bot_name: String, pub bot_max_concurrent: usize, pub gitea_url: String, pub gitea_token: String, @@ -16,7 +15,6 @@ pub struct EnvConfig { pub fn load_config() -> anyhow::Result { let http_port = try_get_env("HTTP_PORT")?.parse()?; - let bot_name = try_get_env("BOT_NAME")?; let webhook_secret = try_get_env("WEBHOOK_SIG_HEADER_SECRET")?; let open_router_api_key = try_get_env("OPEN_ROUTER_API_KEY")?; let open_router_model = try_get_env("OPEN_ROUTER_MODEL")?; @@ -29,7 +27,6 @@ pub fn load_config() -> anyhow::Result { Ok(EnvConfig { http_port, webhook_secret, - bot_name, open_router_api_key, open_router_model, open_router_timeout, diff --git a/src/gitea.rs b/src/gitea.rs index e405ca1..6959692 100644 --- a/src/gitea.rs +++ b/src/gitea.rs @@ -29,6 +29,21 @@ impl GiteaAPI { }) } + #[instrument(skip(self))] + pub async fn get_authorized_user(&self) -> anyhow::Result { + let url = format!("{}/api/v1/user", self.base_url); + let res = self.client.get(url).send().await?; + + if !res.status().is_success() { + return Err(anyhow::anyhow!( + "Failed to get authorized user: {}", + res.status() + )); + } + + res.json::().await.map_err(anyhow::Error::from) + } + #[instrument(skip(self))] pub async fn comment( &self, @@ -192,6 +207,7 @@ pub struct Comment { #[derive(Deserialize, Debug)] pub struct User { pub id: u64, + pub login: String, } #[derive(Deserialize, Debug)] diff --git a/src/main.rs b/src/main.rs index 52d1ed6..34d92fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; - -use crate::{bot::Bot, gitea::WebhookType, state::AppState}; +use crate::{bot::Bot, gitea::{GiteaAPI, WebhookType}, open_router::OpenRouterClient, state::AppState}; use dotenvy::dotenv; use tokio::signal::unix::{SignalKind, signal}; @@ -51,17 +49,34 @@ fn main() -> anyhow::Result<()> { async fn run() -> anyhow::Result<()> { let config = env::load_config()?; + let gitea_api = GiteaAPI::new(&config.gitea_url, &config.gitea_token, config.gitea_timeout)?; + let gitea_user = gitea_api.get_authorized_user().await?; + info!( port = config.http_port, model = %config.open_router_model, gitea_url = %config.gitea_url, - bot_name = %config.bot_name, + bot_name = %gitea_user.login, "Starting Herald" ); + let open_router_client = OpenRouterClient::new( + &config.open_router_api_key, + &config.open_router_model, + config.open_router_timeout, + )?; + let shutdown = CancellationToken::new(); - let bot = Bot::new(config.clone())?; + let bot = Bot::new( + gitea_user.login, + gitea_api, + open_router_client, + reqwest::Client::new(), + config.bot_max_concurrent, + config.open_router_model.clone(), + ); + let (tx, rx) = tokio::sync::mpsc::channel::(config.bot_max_concurrent * 2); let app_state = AppState { bot_tx: tx,