diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..019c8a4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,90 @@ +name: Release + +on: + push: + branches: [ "main" ] + tags: 'v*' + paths-ignore: + - 'doc/**' + - '.github/**' + workflow_dispatch: + + +jobs: + build_test_and_bundle: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-20.04, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + + - name: setup rust stable + run: curl https://sh.rustup.rs -sSf | sh -s -- -y + + - name: unit tests + run: cargo test --all --release + + - name: bundle + shell: bash + run: | + mkdir Pulse + cp target/release/pulse Pulse/pulse-${{ matrix.os }} + cp LICENSE Pulse/ + + - name: upload bundle + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.os }} + path: Pulse + + release: + needs: [build_test_and_bundle] + if: github.ref_type == 'tag' + runs-on: ubuntu-22.04 + + steps: + + - name: get linux build + uses: actions/download-artifact@v3 + with: + name: ubuntu-latest + + - name: get windows build + uses: actions/download-artifact@v3 + with: + name: windows-latest + + - name: get macos build + uses: actions/download-artifact@v3 + with: + name: macos-latest + + - name: pack + run: | + mkdir linux windows macos + ls + mv pulse-ubuntu-latest pulse + chmod +x pulse + tar cvpfz linux-x86_64.tar.gz pulse LICENSE + mv pulse-windows-latest.exe pulse.exe + chmod +x pulse.exe + zip windows.zip pulse.exe LICENSE + mv pulse-macos-latest pulse + chmod +x pulse + tar cvpfz macOS.tar.gz pulse LICENSE + + + # https://github.com/softprops/action-gh-release + - name: release + uses: softprops/action-gh-release@v1 + with: + draft: true + prerelease: true + name: "release-${{ github.ref_name }}" + tag_name: ${{ github.ref }} + files: | + linux-x86_64.tar.gz + windows.zip + macOS.tar.gz \ No newline at end of file diff --git a/src/web/response/github_release.rs b/src/web/response/github_release.rs new file mode 100644 index 0000000..437d7d5 --- /dev/null +++ b/src/web/response/github_release.rs @@ -0,0 +1,103 @@ +//! Github release response + +use std::collections::HashMap; + +use axum::extract::State; +use axum::http::StatusCode; +use axum::response::IntoResponse; + +use axum::response::Response; + +use std::convert::{From, Into}; + +use chrono::Local; +use regex::Regex; + +fn strip_control_characters(s: String) -> String +{ + let re = Regex::new(r"[\u0000-\u001F]").unwrap().replace_all(&s, ""); + return re.to_string() +} + +#[derive(Debug)] +enum GITHUB_RELEASE_ACTION_TYPE +{ + CREATED, + DELETED, + EDITED, + PRERELEASED, + PUBLISHED, + RELEASED, + UNPUBLISHED, + UNKOWN +} + +impl From<&str> for GITHUB_RELEASE_ACTION_TYPE +{ + fn from(s: &str) -> GITHUB_RELEASE_ACTION_TYPE + { + match s + { + "created" => Self::CREATED, + "deleted" => Self::DELETED, + "edited" => Self::EDITED, + "prereleased" => Self::PRERELEASED, + "published" => Self::PUBLISHED, + "released" => Self::RELEASED, + "unpublished" => Self::UNPUBLISHED, + _ => Self::UNKOWN + } + } +} + +pub async fn github_release +( + State(body): State +) -> Result +{ + + let parsed_data: HashMap = match serde_json::from_str(&strip_control_characters(body)) + { + Ok(d) => d, + Err(e) => + { + crate::debug(format!("error parsing body: {}", e), None); + return Ok((StatusCode::INTERNAL_SERVER_ERROR).into_response()); + } + }; + + if parsed_data.contains_key("action") + { + let action: GITHUB_RELEASE_ACTION_TYPE = match parsed_data["action"].to_owned().as_str() + { + Some(s) => {s.into()}, + None => + { + crate::debug(format!("action could not be parsed \n\nGot:\n {:?}", parsed_data["action"]), None); + return Ok((StatusCode::BAD_REQUEST).into_response()); + } + }; + + return respond(action, parsed_data).await; + } + else + { + crate::debug(format!("no action entry in JSON payload \n\nGot:\n {:?}", parsed_data), None); + return Ok((StatusCode::BAD_REQUEST).into_response()); + } + +} + +async fn respond(action: GITHUB_RELEASE_ACTION_TYPE, data: HashMap) -> Result +{ + crate::debug(format!("Processing github release payload: {:?}", action), None); + + match action + { + GITHUB_RELEASE_ACTION_TYPE::CREATED => {} + GITHUB_RELEASE_ACTION_TYPE::PUBLISHED => {}, + _ => {} + } + + Ok((StatusCode::OK).into_response()) +} \ No newline at end of file diff --git a/src/web/response/github_verify.rs b/src/web/response/github_verify.rs index e768a3f..e629d96 100644 --- a/src/web/response/github_verify.rs +++ b/src/web/response/github_verify.rs @@ -1,4 +1,4 @@ -//! Utility responses for the axum server +//! Github POST verification use axum::extract::State; use axum::http::{StatusCode, HeaderMap}; @@ -41,6 +41,43 @@ pub fn read_bytes(v: String) -> Vec .collect() } +/// Middleware to detect and verify a github POST request form a +/// Github webhook +/// +/// The github user agent header must be of the form GitHub-Hookshot/xxx +/// +/// The hmac provided by the hmac x-hub-signature-256, is checked against +/// the State(app_state) value and the bodies bytes +/// +/// The body is only read after the user agent matches +/// +/// # Example +/// +/// ```rust +/// use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +/// use std::sync::{Arc, Mutex}; +/// +/// use axum:: +/// { +/// routing::post, +/// Router, +/// middleware +/// }; +/// +/// let authenticated_state = "this_is_a_secret".to_string(); +/// +/// let app = Router::new() +/// .route("/", post(|| async move { })) +/// .layer(middleware::from_fn_with_state(authenticated_state.clone(), github_verify)) +/// +/// let ip = Ipv4Addr::new(127,0,0,1); +/// let addr = SocketAddr::new(IpAddr::V4(ip), port); +/// +/// axum::Server::bind(&addr) +/// .serve(app.into_make_service_with_connect_info::()) +/// .await +/// .unwrap(); +/// ```` pub async fn github_verify ( State(app_state): State, diff --git a/src/web/response/mod.rs b/src/web/response/mod.rs index dd54942..39f698a 100644 --- a/src/web/response/mod.rs +++ b/src/web/response/mod.rs @@ -1,2 +1,3 @@ pub mod util; -pub mod github_verify; \ No newline at end of file +pub mod github_verify; +pub mod github_release; \ No newline at end of file