feat: middleware (WIP)

This commit is contained in:
2025-11-28 18:05:19 +08:00
parent e46690cb21
commit be35040e26
7 changed files with 334 additions and 122 deletions

147
Cargo.lock generated
View File

@@ -43,6 +43,12 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@@ -60,18 +66,15 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.23" version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473"
dependencies = [ dependencies = [
"brotli", "compression-codecs",
"flate2", "compression-core",
"futures-core", "futures-core",
"memchr",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"zstd",
"zstd-safe",
] ]
[[package]] [[package]]
@@ -161,9 +164,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "8.0.1" version = "8.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@@ -203,9 +206,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.47" version = "1.2.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -248,6 +251,26 @@ dependencies = [
"inout", "inout",
] ]
[[package]]
name = "compression-codecs"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad"
dependencies = [
"brotli",
"compression-core",
"flate2",
"memchr",
"zstd",
"zstd-safe",
]
[[package]]
name = "compression-core"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.7" version = "0.8.7"
@@ -265,9 +288,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -313,9 +336,9 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.1" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@@ -327,6 +350,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.2" version = "1.2.2"
@@ -436,15 +465,21 @@ name = "hashbrown"
version = "0.16.1" version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
"serde",
"serde_core",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.3.1" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv",
"itoa", "itoa",
] ]
@@ -717,9 +752,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.33" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
"libc", "libc",
@@ -727,9 +762,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.82" version = "0.3.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@@ -808,6 +843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [ dependencies = [
"adler2", "adler2",
"simd-adler32",
] ]
[[package]] [[package]]
@@ -832,6 +868,7 @@ dependencies = [
"cbc", "cbc",
"chrono", "chrono",
"dotenvy", "dotenvy",
"hashbrown",
"http-body-util", "http-body-util",
"hyper", "hyper",
"reqwest", "reqwest",
@@ -1144,9 +1181,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
dependencies = [ dependencies = [
"web-time", "web-time",
"zeroize", "zeroize",
@@ -1271,6 +1308,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.11" version = "0.4.11"
@@ -1459,9 +1502,9 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.6.6" version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"bitflags", "bitflags",
@@ -1493,9 +1536,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.41" version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
dependencies = [ dependencies = [
"log", "log",
"pin-project-lite", "pin-project-lite",
@@ -1505,9 +1548,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.30" version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1516,9 +1559,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.34" version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable", "valuable",
@@ -1537,9 +1580,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.20" version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [ dependencies = [
"matchers", "matchers",
"nu-ansi-term", "nu-ansi-term",
@@ -1633,9 +1676,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.105" version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -1646,9 +1689,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.55" version = "0.4.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@@ -1659,9 +1702,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.105" version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -1669,9 +1712,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.105" version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@@ -1682,18 +1725,18 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.105" version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.82" version = "0.3.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -1970,18 +2013,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.28" version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.28" version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2068,9 +2111,9 @@ dependencies = [
[[package]] [[package]]
name = "zstd-sys" name = "zstd-sys"
version = "2.0.15+zstd.1.5.7" version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",

View File

@@ -21,3 +21,4 @@ thiserror = "2"
http-body-util = "0.1.1" http-body-util = "0.1.1"
chrono = { version = "0.4", features = ["clock"] } chrono = { version = "0.4", features = ["clock"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "gzip"], default-features = false } reqwest = { version = "0.12", features = ["json", "rustls-tls", "gzip"], default-features = false }
hashbrown = { version = "0.16", features = ["serde"] }

View File

@@ -1,12 +1,13 @@
use std::fmt; use std::fmt;
use aes::Aes128; use aes::Aes128;
use base64::{Engine as _, engine::general_purpose::STANDARD}; use base64::{Engine, engine::general_purpose::STANDARD};
use cbc::{ use cbc::{
Decryptor, Decryptor, Encryptor,
cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7}, cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7},
}; };
type Aes128CbcEnc = Encryptor<Aes128>;
type Aes128CbcDec = Decryptor<Aes128>; type Aes128CbcDec = Decryptor<Aes128>;
#[derive(Debug)] #[derive(Debug)]
@@ -14,6 +15,7 @@ pub enum CryptoError {
Base64(base64::DecodeError), Base64(base64::DecodeError),
Aes(cbc::cipher::InvalidLength), Aes(cbc::cipher::InvalidLength),
Unpad(cbc::cipher::block_padding::UnpadError), Unpad(cbc::cipher::block_padding::UnpadError),
Pad(aes::cipher::inout::PadError),
} }
impl fmt::Display for CryptoError { impl fmt::Display for CryptoError {
@@ -22,6 +24,7 @@ impl fmt::Display for CryptoError {
CryptoError::Base64(e) => write!(f, "Base64 decode failed: {}", e), CryptoError::Base64(e) => write!(f, "Base64 decode failed: {}", e),
CryptoError::Aes(e) => write!(f, "AES decryption failed: {}", e), CryptoError::Aes(e) => write!(f, "AES decryption failed: {}", e),
CryptoError::Unpad(e) => write!(f, "PKCS7 unpadding failed: {}", e), CryptoError::Unpad(e) => write!(f, "PKCS7 unpadding failed: {}", e),
CryptoError::Pad(e) => write!(f, "PKCS7 padding failed: {}", e),
} }
} }
} }
@@ -52,3 +55,19 @@ pub fn decrypt(ciphertext_b64: &str, key: &str, iv: &str) -> Result<String, Cryp
// 4. Convert plaintext to a UTF-8 string // 4. Convert plaintext to a UTF-8 string
Ok(String::from_utf8_lossy(plaintext_slice).to_string()) Ok(String::from_utf8_lossy(plaintext_slice).to_string())
} }
pub fn encrypt(plaintext: &str, key: &str, iv: &str) -> Result<String, CryptoError> {
// 1. Initialize AES-128 in CBC mode
let key_bytes = key.as_bytes();
let iv_bytes = iv.as_bytes();
let encryptor = Aes128CbcEnc::new_from_slices(key_bytes, iv_bytes).map_err(CryptoError::Aes)?;
// 2. Encrypt the plaintext with PKCS7 padding
let mut buffer = plaintext.as_bytes().to_vec();
let ciphertext = encryptor
.encrypt_padded_mut::<Pkcs7>(&mut buffer, plaintext.len())
.map_err(CryptoError::Pad)?;
// 3. Base64 encode the ciphertext
Ok(STANDARD.encode(ciphertext))
}

View File

@@ -1 +1,19 @@
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Serialize, Deserialize)]
pub struct Request {
pub method: String,
pub params: Option<serde_json::Map<String, Value>>,
pub id: Value,
pub jsonrpc: Option<String>
}
#[derive(Serialize, Deserialize)]
pub struct Response {
pub result: Option<Value>,
pub params: Option<HashMap<String, Value>>,
pub id: Value,
pub jsonrpc: Option<String>
}

View File

@@ -1,13 +1,17 @@
use std::{net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use anyhow::Context; use anyhow::Context;
use axum::{Router, routing::any}; use axum::handler::Handler;
use dotenvy::dotenv; use dotenvy::dotenv;
use serde_json::Value;
use tokio::sync::RwLock;
use tower_http::compression::CompressionLayer; use tower_http::compression::CompressionLayer;
use tracing::{error, info, level_filters::LevelFilter}; use tracing::{error, info, level_filters::LevelFilter};
use tracing_subscriber::{EnvFilter, fmt, prelude::*}; use tracing_subscriber::{EnvFilter, fmt, prelude::*};
mod crypto;
mod jsonrpc; mod jsonrpc;
mod middleware;
mod proxy; mod proxy;
#[derive(Clone)] #[derive(Clone)]
@@ -16,6 +20,8 @@ pub struct AppState {
pub target_url: reqwest::Url, pub target_url: reqwest::Url,
pub key: String, pub key: String,
pub iv: String, pub iv: String,
pub command_queue: Arc<RwLock<Vec<Value>>>,
pub saved_tactics: Arc<RwLock<Option<Value>>>,
} }
const DEFAULT_TARGET_URL: &str = "https://cloud.linspirer.com:883"; const DEFAULT_TARGET_URL: &str = "https://cloud.linspirer.com:883";
@@ -65,11 +71,16 @@ async fn main() -> anyhow::Result<()> {
target_url, target_url,
key, key,
iv, iv,
command_queue: Arc::new(RwLock::new(Vec::new())),
saved_tactics: Arc::new(RwLock::new(None)),
}); });
// Build our application with a single route // Build our application
let app = Router::new() let app = proxy::proxy_handler
.route("/{*path}", any(proxy::proxy_handler)) .layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::log_middleware,
))
.layer(CompressionLayer::new().gzip(true)) .layer(CompressionLayer::new().gzip(true))
.with_state(state); .with_state(state);

178
src/middleware.rs Normal file
View File

@@ -0,0 +1,178 @@
use std::str;
use std::sync::Arc;
use axum::{
extract::{OriginalUri, State},
http::{Request, StatusCode},
middleware::Next,
response::{IntoResponse, Response},
};
use http_body_util::BodyExt;
use serde_json::Value;
use tracing::{info, warn};
use crate::{AppState, crypto};
enum ResponseBody {
Original(String),
Modified(Value),
}
pub async fn log_middleware(
State(state): State<Arc<AppState>>,
OriginalUri(uri): OriginalUri,
req: Request<axum::body::Body>,
next: Next,
) -> impl IntoResponse {
let (parts, body) = req.into_parts();
let body_bytes = match body.collect().await {
Ok(body) => body.to_bytes(),
Err(e) => {
warn!("Failed to read request body: {}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to read request body".to_string(),
)
.into_response();
}
};
let path = uri.path();
let (decrypted_request_log, method) = match str::from_utf8(&body_bytes)
.map_err(anyhow::Error::from)
.and_then(|body| process_and_log_request(body, &state.key, &state.iv))
{
Ok(request_data) => {
let method = request_data
.get("method")
.and_then(Value::as_str)
.map(str::to_string);
(request_data, method)
}
Err(e) => {
warn!("Failed to process request for logging: {}", e);
let val = Value::String("Could not decrypt request".to_string());
(val, None)
}
};
let req = Request::from_parts(parts, axum::body::Body::from(body_bytes));
let res = next.run(req).await;
let (resp_parts, body_bytes) = {
let (parts, body) = res.into_parts();
let bytes = match body.collect().await {
Ok(b) => b.to_bytes(),
Err(e) => {
warn!("Failed to read response body: {}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to read response body".to_string(),
)
.into_response();
}
};
(parts, bytes)
};
let resp_body_text = String::from_utf8(body_bytes.clone().to_vec()).unwrap_or_default();
let response_body_to_log = if Some("com.linspirer.device.getcommand") == method.as_deref() {
match handle_getcommand_response(&resp_body_text, &state).await {
Ok(new_body) => ResponseBody::Modified(new_body),
Err(e) => {
warn!(
"Failed to handle getcommand response: {}. Responding with empty command list.",
e
);
let mut empty_response =
serde_json::from_str::<Value>(&resp_body_text).unwrap_or(Value::Null);
if let Some(obj) = empty_response.as_object_mut() {
obj.insert("result".to_string(), Value::Array(vec![]));
}
ResponseBody::Modified(empty_response)
}
}
} else {
ResponseBody::Original(resp_body_text)
};
let (decrypted_response_for_log, final_response_body) = match response_body_to_log {
ResponseBody::Original(body_text) => {
let decrypted = decrypt_and_format(&body_text, &state.key, &state.iv)
.unwrap_or_else(|_| "Could not decrypt or format response".to_string());
(decrypted, body_text)
}
ResponseBody::Modified(body_value) => {
let pretty_printed = serde_json::to_string_pretty(&body_value).unwrap_or_default();
let encrypted = crypto::encrypt(&pretty_printed, &state.key, &state.iv)
.unwrap_or_else(|_| "Failed to encrypt modified response".to_string());
(pretty_printed, encrypted)
}
};
info!(
"{}\nRequest:\n{}\nResponse:\n{}\n{}",
path,
serde_json::to_string_pretty(&decrypted_request_log).unwrap_or_default(),
decrypted_response_for_log,
"-".repeat(80),
);
let mut response_builder = Response::builder().status(resp_parts.status);
if !resp_parts.headers.is_empty() {
*response_builder.headers_mut().unwrap() = resp_parts.headers;
}
response_builder
.body(axum::body::Body::from(final_response_body))
.unwrap()
}
fn process_and_log_request(body: &str, key: &str, iv: &str) -> anyhow::Result<Value> {
let mut request_data: Value = serde_json::from_str(body)?;
if let Some(params_value) = request_data.get_mut("params")
&& let Some(params_str) = params_value.as_str()
{
let params_str_owned = params_str.to_string();
match crypto::decrypt(&params_str_owned, key, iv) {
Ok(decrypted_str) => {
let decrypted_params: Value =
serde_json::from_str(&decrypted_str).unwrap_or(Value::String(decrypted_str));
*params_value = decrypted_params;
}
Err(e) => {
*params_value = Value::String(format!("decrypt failed: {}", e));
}
}
}
Ok(request_data)
}
async fn handle_getcommand_response(body_text: &str, state: &Arc<AppState>) -> anyhow::Result<Value> {
let decrypted = crypto::decrypt(body_text, &state.key, &state.iv)?;
let mut response_json: Value = serde_json::from_str(&decrypted)?;
if let Some(result) = response_json.get("result")
&& let Some(commands) = result.as_array()
&& !commands.is_empty()
{
let mut queue = state.command_queue.write().await;
for cmd in commands {
queue.push(cmd.clone());
info!("Added command to the queue: {:?}", cmd);
}
}
if let Some(obj) = response_json.as_object_mut() {
obj.insert("result".to_string(), Value::Array(vec![]));
}
Ok(response_json)
}
fn decrypt_and_format(body_text: &str, key: &str, iv: &str) -> anyhow::Result<String> {
let decrypted = crypto::decrypt(body_text, key, iv)?;
let formatted: Value = serde_json::from_str(&decrypted)?;
Ok(serde_json::to_string_pretty(&formatted)?)
}

View File

@@ -1,23 +1,21 @@
use std::sync::Arc; use std::sync::Arc;
use axum::{ use axum::{
extract::{Path, State}, extract::{OriginalUri, State},
http::{Request, StatusCode}, http::{Request, StatusCode},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use http_body_util::BodyExt; use http_body_util::BodyExt;
use serde_json::Value; use tracing::error;
use tracing::{error, info, warn};
use crate::AppState; use crate::AppState;
mod crypto;
pub async fn proxy_handler( pub async fn proxy_handler(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
Path(path): Path<String>, OriginalUri(uri): OriginalUri,
req: Request<axum::body::Body>, req: Request<axum::body::Body>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let path = uri.path();
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
let body = match body.collect().await { let body = match body.collect().await {
Ok(body) => body.to_bytes(), Ok(body) => body.to_bytes(),
@@ -31,22 +29,8 @@ pub async fn proxy_handler(
} }
}; };
let decrypted_request_log = match str::from_utf8(&body)
.map(|body| process_and_log_request(body, &state.key, &state.iv))
{
Ok(Ok(log)) => log,
Ok(Err(e)) => {
warn!("Failed to process request for logging: {}", e);
Value::String("Could not decrypt request".to_string())
}
Err(e) => {
warn!("Failed to decode request for logging: {}", e);
Value::String("Could not decrypt request".to_string())
}
};
let mut target_url = state.target_url.clone(); let mut target_url = state.target_url.clone();
target_url.set_path(&path); target_url.set_path(path);
target_url.set_query(parts.uri.query()); target_url.set_query(parts.uri.query());
let mut forwarded_req = state.client.request(parts.method, target_url); let mut forwarded_req = state.client.request(parts.method, target_url);
if !parts.headers.is_empty() { if !parts.headers.is_empty() {
@@ -77,21 +61,6 @@ pub async fn proxy_handler(
} }
}; };
let decrypted_response_log = match decrypt_response(&resp_body, &state.key, &state.iv).await {
Ok(log) => log,
Err(e) => {
warn!("Failed to process response for logging: {}", e);
"Could not decrypt response".to_string()
}
};
info!(
"\nRequest:\n{}\nResponse:\n{}\n{}",
serde_json::to_string_pretty(&decrypted_request_log).unwrap_or_default(),
decrypted_response_log,
"-".repeat(80),
);
let mut response_builder = Response::builder().status(resp_parts.status); let mut response_builder = Response::builder().status(resp_parts.status);
if !resp_parts.headers.is_empty() { if !resp_parts.headers.is_empty() {
*response_builder.headers_mut().unwrap() = resp_parts.headers; *response_builder.headers_mut().unwrap() = resp_parts.headers;
@@ -101,33 +70,6 @@ pub async fn proxy_handler(
.unwrap() .unwrap()
} }
fn process_and_log_request(body: &str, key: &str, iv: &str) -> anyhow::Result<Value> {
let mut request_data: Value = serde_json::from_str(body)?;
if let Some(params_value) = request_data.get_mut("params")
&& let Some(params_str) = params_value.as_str()
{
let params_str_owned = params_str.to_string();
match crypto::decrypt(&params_str_owned, key, iv) {
Ok(decrypted_str) => {
let decrypted_params: Value =
serde_json::from_str(&decrypted_str).unwrap_or(Value::String(decrypted_str));
*params_value = decrypted_params;
}
Err(e) => {
*params_value = Value::String(format!("decrypt failed: {}", e));
}
}
}
Ok(request_data)
}
async fn decrypt_response(body_text: &str, key: &str, iv: &str) -> anyhow::Result<String> {
let decrypted = crypto::decrypt(body_text, key, iv)?;
let formatted: Value = serde_json::from_str(&decrypted)?;
Ok(serde_json::to_string_pretty(&formatted)?)
}
async fn clone_response( async fn clone_response(
resp: reqwest::Response, resp: reqwest::Response,
) -> anyhow::Result<(axum::http::response::Parts, String)> { ) -> anyhow::Result<(axum::http::response::Parts, String)> {