mirror of
https://codeberg.org/Mo8it/git-webhook-client
synced 2025-03-15 00:18:09 +00:00
99 lines
2.7 KiB
Rust
99 lines
2.7 KiB
Rust
|
#[macro_use]
|
||
|
extern crate rocket;
|
||
|
|
||
|
use rocket::data::{self, Data, FromData, Limits, Outcome, ToByteUnit};
|
||
|
use rocket::http::Status;
|
||
|
use rocket::outcome::Outcome::{Failure, Success};
|
||
|
use rocket::request::{self, Request};
|
||
|
|
||
|
use hmac::{Hmac, Mac};
|
||
|
use sha2::Sha256;
|
||
|
|
||
|
use serde::{Deserialize, Serialize};
|
||
|
use std::fs::File;
|
||
|
use std::io::BufReader;
|
||
|
|
||
|
const CONFIG_FILENAME: &str = "config.json";
|
||
|
|
||
|
struct SignatureDataGuard;
|
||
|
|
||
|
fn is_valid_signature(received_signature: &str, payload: &Vec<u8>) -> bool {
|
||
|
let config_file = File::open(CONFIG_FILENAME).unwrap();
|
||
|
let config_reader = BufReader::new(config_file);
|
||
|
let config: Config = serde_json::from_reader(config_reader).unwrap();
|
||
|
|
||
|
type HmacSha256 = Hmac<Sha256>;
|
||
|
let mut mac = HmacSha256::new_from_slice(config.secret.as_bytes()).unwrap();
|
||
|
mac.update(payload);
|
||
|
let expected_signature = mac.finalize().into_bytes();
|
||
|
|
||
|
received_signature.as_bytes()[..] == expected_signature[..]
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
enum Error {
|
||
|
PayloadTooLarge,
|
||
|
MissingSignature,
|
||
|
MoreThatOneSignature,
|
||
|
InvalidSignature,
|
||
|
Io(std::io::Error),
|
||
|
}
|
||
|
|
||
|
#[rocket::async_trait]
|
||
|
impl<'r> FromData<'r> for SignatureDataGuard {
|
||
|
type Error = Error;
|
||
|
|
||
|
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> {
|
||
|
let payload = match data.open(256.bytes()).into_bytes().await {
|
||
|
Ok(payload) if payload.is_complete() => payload.into_inner(),
|
||
|
Ok(_) => return Failure((Status::PayloadTooLarge, Error::PayloadTooLarge)),
|
||
|
Err(e) => return Failure((Status::InternalServerError, Error::Io(e))),
|
||
|
};
|
||
|
|
||
|
let mut received_signatures = req.headers().get("X-GITEA-SIGNATURE");
|
||
|
let received_signature = match received_signatures.next() {
|
||
|
Some(signature) => signature,
|
||
|
None => return Outcome::Failure((Status::BadRequest, Error::MissingSignature)),
|
||
|
};
|
||
|
|
||
|
if received_signatures.next().is_some() {
|
||
|
return Outcome::Failure((Status::BadRequest, Error::MoreThatOneSignature));
|
||
|
}
|
||
|
|
||
|
if !is_valid_signature(received_signature, &payload) {
|
||
|
return Outcome::Failure((Status::BadRequest, Error::InvalidSignature));
|
||
|
}
|
||
|
|
||
|
Outcome::Success(SignatureDataGuard)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Deserialize)]
|
||
|
struct Hook {
|
||
|
repo_url: String,
|
||
|
commands: Vec<String>,
|
||
|
}
|
||
|
|
||
|
#[derive(Deserialize)]
|
||
|
struct Config {
|
||
|
secret: String,
|
||
|
hooks: Vec<Hook>,
|
||
|
}
|
||
|
|
||
|
#[get("/")]
|
||
|
fn index() -> &'static str {
|
||
|
"Hello, world!"
|
||
|
}
|
||
|
|
||
|
#[post("/trigger", data = "<payload>")]
|
||
|
fn trigger(payload: SignatureDataGuard) -> &'static str {
|
||
|
"Sensitive data."
|
||
|
}
|
||
|
|
||
|
#[launch]
|
||
|
fn rocket() -> _ {
|
||
|
rocket::build()
|
||
|
.mount("/", routes![index])
|
||
|
.mount("/api", routes![trigger])
|
||
|
}
|