forked from kemitix/git-next
feat(server/webhook): only accept authorised messages
Closes kemitix/git-next#47
This commit is contained in:
parent
b398ac3fd3
commit
64a6b84ee4
4 changed files with 66 additions and 15 deletions
|
@ -1,13 +1,14 @@
|
|||
mod branch;
|
||||
mod config;
|
||||
pub mod status;
|
||||
mod webhook;
|
||||
pub mod webhook;
|
||||
|
||||
use actix::prelude::*;
|
||||
use kxio::network::Network;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::server::{
|
||||
actors::repo::webhook::WebhookAuth,
|
||||
config::{RepoConfig, RepoDetails, Webhook},
|
||||
forge,
|
||||
git::Git,
|
||||
|
@ -19,7 +20,7 @@ pub struct RepoActor {
|
|||
details: RepoDetails,
|
||||
webhook: Webhook,
|
||||
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured
|
||||
webhook_auth: Option<ulid::Ulid>,
|
||||
webhook_auth: Option<WebhookAuth>, // INFO: if [None] then no webhook is configured
|
||||
last_main_commit: Option<forge::Commit>,
|
||||
last_next_commit: Option<forge::Commit>,
|
||||
last_dev_commit: Option<forge::Commit>,
|
||||
|
@ -168,7 +169,7 @@ impl Handler<StartMonitoring> for RepoActor {
|
|||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct WebhookRegistered(pub WebhookId, pub ulid::Ulid);
|
||||
pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth);
|
||||
impl Handler<WebhookRegistered> for RepoActor {
|
||||
type Result = ();
|
||||
fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use actix::prelude::*;
|
||||
use kxio::network::{self, json};
|
||||
use tracing::{debug, info, warn};
|
||||
use ulid::DecodeError;
|
||||
|
||||
use std::{fmt::Display, ops::Deref};
|
||||
use std::{fmt::Display, ops::Deref, str::FromStr};
|
||||
|
||||
use crate::server::{
|
||||
actors::{
|
||||
|
@ -33,6 +34,29 @@ impl Display for WebhookId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct WebhookAuth(ulid::Ulid);
|
||||
impl WebhookAuth {
|
||||
pub fn from_str(authorisation: &str) -> Result<Self, DecodeError> {
|
||||
let id = ulid::Ulid::from_str(authorisation);
|
||||
Ok(Self(id?))
|
||||
}
|
||||
|
||||
fn generate() -> Self {
|
||||
Self(ulid::Ulid::new())
|
||||
}
|
||||
|
||||
fn header_value(&self) -> String {
|
||||
format!("Basic {}", self.0.to_string())
|
||||
}
|
||||
}
|
||||
impl Deref for WebhookAuth {
|
||||
type Target = ulid::Ulid;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn unregister(
|
||||
webhook_id: WebhookId,
|
||||
repo_details: crate::server::config::RepoDetails,
|
||||
|
@ -82,10 +106,10 @@ pub async fn register(
|
|||
let webhook_url = webhook.url();
|
||||
let repo_alias = repo_details.name;
|
||||
let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json");
|
||||
let authorisation = ulid::Ulid::new();
|
||||
let authorisation = WebhookAuth::generate();
|
||||
let body = json!({
|
||||
"active": true,
|
||||
"authorization_header": format!("Basic {}", authorisation),
|
||||
"authorization_header": authorisation.header_value(),
|
||||
"branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()),
|
||||
"config": {
|
||||
"content_type": "json",
|
||||
|
@ -130,6 +154,9 @@ impl Handler<WebhookMessage> for RepoActor {
|
|||
|
||||
#[allow(clippy::cognitive_complexity)] // TODO: (#49) reduce complexity
|
||||
fn handle(&mut self, msg: WebhookMessage, ctx: &mut Self::Context) -> Self::Result {
|
||||
if msg.authorisation() != self.webhook_auth {
|
||||
return; // invalid auth
|
||||
}
|
||||
let id = msg.id();
|
||||
let body = msg.body();
|
||||
debug!(?id, "RepoActor received message");
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
//
|
||||
use actix::prelude::*;
|
||||
|
||||
use crate::server::actors::repo::webhook::WebhookAuth;
|
||||
|
||||
#[derive(Message, Debug, Clone)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct WebhookMessage {
|
||||
id: String,
|
||||
path: String,
|
||||
authorisation: String,
|
||||
// query: String,
|
||||
// headers: warp::http::HeaderMap,
|
||||
body: String,
|
||||
|
@ -17,6 +20,7 @@ impl WebhookMessage {
|
|||
// query: String,
|
||||
// headers: warp::http::HeaderMap,
|
||||
body: String,
|
||||
authorisation: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
|
@ -24,6 +28,7 @@ impl WebhookMessage {
|
|||
// query,
|
||||
// headers,
|
||||
body,
|
||||
authorisation,
|
||||
}
|
||||
}
|
||||
pub const fn id(&self) -> &String {
|
||||
|
@ -41,4 +46,7 @@ impl WebhookMessage {
|
|||
pub const fn body(&self) -> &String {
|
||||
&self.body
|
||||
}
|
||||
pub fn authorisation(&self) -> Option<WebhookAuth> {
|
||||
WebhookAuth::from_str(&self.authorisation).ok()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,39 @@ pub async fn start(address: actix::prelude::Recipient<super::message::WebhookMes
|
|||
.map(move || address.clone())
|
||||
.and(warp::path::param())
|
||||
// .and(warp::query::raw())
|
||||
// .and(warp::header::headers_cloned())
|
||||
.and(warp::header::headers_cloned())
|
||||
.and(warp::body::bytes())
|
||||
.and_then(
|
||||
|recipient: Recipient<WebhookMessage>,
|
||||
path,
|
||||
// query: String,
|
||||
// headers: warp::http::HeaderMap,
|
||||
headers: warp::http::HeaderMap,
|
||||
body: bytes::Bytes| async move {
|
||||
let bytes = body.to_vec();
|
||||
let request_data = String::from_utf8_lossy(&bytes).to_string();
|
||||
let id = ulid::Ulid::new().to_string();
|
||||
match headers.get("Authorization") {
|
||||
Some(auhorisation) => {
|
||||
debug!(id, path, "Received webhook");
|
||||
let message =
|
||||
WebhookMessage::new(id, path, /* query, headers, */ request_data);
|
||||
let authorisation = auhorisation
|
||||
.to_str()
|
||||
.map_err(|_| warp::reject())? // valid characters
|
||||
.strip_prefix("Basic ")
|
||||
.ok_or_else(warp::reject)? // must start with "Basic "
|
||||
.to_string();
|
||||
let message = WebhookMessage::new(
|
||||
id,
|
||||
path,
|
||||
/* query, headers, */ request_data,
|
||||
authorisation,
|
||||
);
|
||||
recipient
|
||||
.try_send(message)
|
||||
.map(|_| warp::reply::with_status("OK", warp::http::StatusCode::OK))
|
||||
.map_err(|_| warp::reject::reject())
|
||||
.map_err(|_| warp::reject())
|
||||
}
|
||||
_ => Err(warp::reject()),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue