Refine room AI streaming logic, update TOTP auth error handling, and adjust user 2FA migration order. Remove unused service exports.
141 lines
3.4 KiB
Rust
141 lines
3.4 KiB
Rust
use hmac::{Hmac, Mac, KeyInit};
|
|
use sha2::Sha256;
|
|
use std::time::Duration;
|
|
|
|
type HmacSha256 = Hmac<Sha256>;
|
|
|
|
/// Signs a payload body using HMAC-SHA256 with the given secret.
|
|
/// Returns the "X-Hub-Signature-256" header value.
|
|
pub fn sign_payload(body: &[u8], secret: &str) -> Option<String> {
|
|
if secret.is_empty() {
|
|
return None;
|
|
}
|
|
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).ok()?;
|
|
mac.update(body);
|
|
let bytes = mac.finalize().into_bytes();
|
|
Some(format!(
|
|
"sha256={}",
|
|
bytes.iter().map(|b| format!("{:02x}", b)).collect::<String>()
|
|
))
|
|
}
|
|
|
|
/// Payload sent for a push event webhook.
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct PushPayload<'a> {
|
|
#[serde(rename = "ref")]
|
|
pub r#ref: &'a str,
|
|
pub before: &'a str,
|
|
pub after: &'a str,
|
|
pub repository: RepositoryPayload<'a>,
|
|
pub pusher: PusherPayload<'a>,
|
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
pub commits: Vec<CommitPayload<'a>>,
|
|
}
|
|
|
|
/// Payload sent for a tag push event webhook.
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct TagPushPayload<'a> {
|
|
#[serde(rename = "ref")]
|
|
pub r#ref: &'a str,
|
|
pub before: &'a str,
|
|
pub after: &'a str,
|
|
pub repository: RepositoryPayload<'a>,
|
|
pub pusher: PusherPayload<'a>,
|
|
}
|
|
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct RepositoryPayload<'a> {
|
|
pub id: i64,
|
|
pub name: &'a str,
|
|
pub full_name: &'a str,
|
|
pub namespace: &'a str,
|
|
pub default_branch: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct PusherPayload<'a> {
|
|
pub name: &'a str,
|
|
pub email: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct CommitPayload<'a> {
|
|
pub id: &'a str,
|
|
pub message: &'a str,
|
|
pub author: AuthorPayload<'a>,
|
|
}
|
|
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct AuthorPayload<'a> {
|
|
pub name: &'a str,
|
|
pub email: &'a str,
|
|
}
|
|
|
|
/// A configured webhook destination.
|
|
#[derive(Debug, Clone)]
|
|
pub struct WebhookTarget {
|
|
pub id: i64,
|
|
pub url: String,
|
|
pub secret: Option<String>,
|
|
pub content_type: String,
|
|
pub events: WebhookEvents,
|
|
pub active: bool,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct WebhookEvents {
|
|
pub push: bool,
|
|
pub tag_push: bool,
|
|
pub pull_request: bool,
|
|
pub issue_comment: bool,
|
|
pub release: bool,
|
|
}
|
|
|
|
/// Dispatches a webhook HTTP POST request.
|
|
pub async fn deliver(
|
|
client: &reqwest::Client,
|
|
url: &str,
|
|
secret: Option<&str>,
|
|
content_type: &str,
|
|
body: &[u8],
|
|
) -> Result<(), DispatchError> {
|
|
let mut req = client
|
|
.post(url)
|
|
.header("Content-Type", content_type)
|
|
.header("User-Agent", "Code-Git-Hook/1.0")
|
|
.header("X-Webhook-Event", "push")
|
|
.timeout(Duration::from_secs(10))
|
|
.body(body.to_vec());
|
|
|
|
if let Some(secret) = secret {
|
|
if let Some(sig) = sign_payload(body, secret) {
|
|
req = req.header("X-Hub-Signature-256", sig);
|
|
}
|
|
}
|
|
|
|
let resp = req.send().await.map_err(|e| {
|
|
if e.is_timeout() {
|
|
DispatchError::Timeout
|
|
} else if e.is_connect() {
|
|
DispatchError::ConnectionFailed
|
|
} else {
|
|
DispatchError::RequestFailed(e.to_string())
|
|
}
|
|
})?;
|
|
|
|
let status = resp.status();
|
|
if status.is_success() {
|
|
Ok(())
|
|
} else {
|
|
Err(DispatchError::HttpError(status.as_u16()))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum DispatchError {
|
|
Timeout,
|
|
ConnectionFailed,
|
|
RequestFailed(String),
|
|
HttpError(u16),
|
|
}
|