mirror of
https://codeberg.org/Mo8it/git-webhook-client
synced 2024-12-03 12:30:31 +00:00
FromData
This commit is contained in:
parent
9ddf7e6799
commit
5c35ca6a0e
5 changed files with 111 additions and 3 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
/target
|
||||
|
||||
/config.json
|
||||
|
|
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -395,10 +395,14 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitea-webhook"
|
||||
name = "git-webhook-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"rocket",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "gitea-webhook"
|
||||
name = "git-webhook-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Mo Bitar <mo8it@proton.me>"]
|
||||
edition = "2021"
|
||||
|
@ -7,4 +7,8 @@ readme = "README.adoc"
|
|||
license-file = "LICENSE"
|
||||
|
||||
[dependencies]
|
||||
hmac = "0.12.1"
|
||||
rocket = "0.5.0-rc.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.85"
|
||||
sha2 = "0.10.6"
|
||||
|
|
|
@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively
|
|||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
gitea-webhook
|
||||
Git webhook client
|
||||
Copyright (C) 2022 Mo Bitar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
|
98
src/main.rs
Normal file
98
src/main.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
#[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])
|
||||
}
|
Loading…
Reference in a new issue