feat: middleware (WIP)
This commit is contained in:
147
Cargo.lock
generated
147
Cargo.lock
generated
@@ -43,6 +43,12 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -60,18 +66,15 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.23"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07"
|
||||
checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"flate2",
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"zstd",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -161,9 +164,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.1"
|
||||
version = "8.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d"
|
||||
checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -203,9 +206,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.47"
|
||||
version = "1.2.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
|
||||
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -248,6 +251,26 @@ dependencies = [
|
||||
"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]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -265,9 +288,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -313,9 +336,9 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.1"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -327,6 +350,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@@ -436,15 +465,21 @@ name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
@@ -717,9 +752,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
@@ -727,9 +762,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.82"
|
||||
version = "0.3.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
|
||||
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -808,6 +843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -832,6 +868,7 @@ dependencies = [
|
||||
"cbc",
|
||||
"chrono",
|
||||
"dotenvy",
|
||||
"hashbrown",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"reqwest",
|
||||
@@ -1144,9 +1181,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
|
||||
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
@@ -1271,6 +1308,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
@@ -1459,9 +1502,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags",
|
||||
@@ -1493,9 +1536,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -1505,9 +1548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1516,9 +1559,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -1537,9 +1580,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.20"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
@@ -1633,9 +1676,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.105"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
|
||||
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1646,9 +1689,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.55"
|
||||
version = "0.4.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
|
||||
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -1659,9 +1702,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.105"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
|
||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1669,9 +1712,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.105"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
|
||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1682,18 +1725,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.105"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
|
||||
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.82"
|
||||
version = "0.3.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
|
||||
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -1970,18 +2013,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.28"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
|
||||
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.28"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
|
||||
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2068,9 +2111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
||||
@@ -21,3 +21,4 @@ thiserror = "2"
|
||||
http-body-util = "0.1.1"
|
||||
chrono = { version = "0.4", features = ["clock"] }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls", "gzip"], default-features = false }
|
||||
hashbrown = { version = "0.16", features = ["serde"] }
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt;
|
||||
|
||||
use aes::Aes128;
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use cbc::{
|
||||
Decryptor,
|
||||
cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7},
|
||||
Decryptor, Encryptor,
|
||||
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7},
|
||||
};
|
||||
|
||||
type Aes128CbcEnc = Encryptor<Aes128>;
|
||||
type Aes128CbcDec = Decryptor<Aes128>;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -14,6 +15,7 @@ pub enum CryptoError {
|
||||
Base64(base64::DecodeError),
|
||||
Aes(cbc::cipher::InvalidLength),
|
||||
Unpad(cbc::cipher::block_padding::UnpadError),
|
||||
Pad(aes::cipher::inout::PadError),
|
||||
}
|
||||
|
||||
impl fmt::Display for CryptoError {
|
||||
@@ -22,6 +24,7 @@ impl fmt::Display for CryptoError {
|
||||
CryptoError::Base64(e) => write!(f, "Base64 decode failed: {}", e),
|
||||
CryptoError::Aes(e) => write!(f, "AES decryption 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
|
||||
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))
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
19
src/main.rs
19
src/main.rs
@@ -1,13 +1,17 @@
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{Router, routing::any};
|
||||
use axum::handler::Handler;
|
||||
use dotenvy::dotenv;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::RwLock;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
use tracing::{error, info, level_filters::LevelFilter};
|
||||
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
||||
|
||||
mod crypto;
|
||||
mod jsonrpc;
|
||||
mod middleware;
|
||||
mod proxy;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -16,6 +20,8 @@ pub struct AppState {
|
||||
pub target_url: reqwest::Url,
|
||||
pub key: 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";
|
||||
@@ -65,11 +71,16 @@ async fn main() -> anyhow::Result<()> {
|
||||
target_url,
|
||||
key,
|
||||
iv,
|
||||
command_queue: Arc::new(RwLock::new(Vec::new())),
|
||||
saved_tactics: Arc::new(RwLock::new(None)),
|
||||
});
|
||||
|
||||
// Build our application with a single route
|
||||
let app = Router::new()
|
||||
.route("/{*path}", any(proxy::proxy_handler))
|
||||
// Build our application
|
||||
let app = proxy::proxy_handler
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
middleware::log_middleware,
|
||||
))
|
||||
.layer(CompressionLayer::new().gzip(true))
|
||||
.with_state(state);
|
||||
|
||||
|
||||
178
src/middleware.rs
Normal file
178
src/middleware.rs
Normal 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(¶ms_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)?)
|
||||
}
|
||||
68
src/proxy.rs
68
src/proxy.rs
@@ -1,23 +1,21 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
extract::{OriginalUri, State},
|
||||
http::{Request, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use serde_json::Value;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::error;
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
mod crypto;
|
||||
|
||||
pub async fn proxy_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(path): Path<String>,
|
||||
OriginalUri(uri): OriginalUri,
|
||||
req: Request<axum::body::Body>,
|
||||
) -> impl IntoResponse {
|
||||
let path = uri.path();
|
||||
let (parts, body) = req.into_parts();
|
||||
let body = match body.collect().await {
|
||||
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();
|
||||
target_url.set_path(&path);
|
||||
target_url.set_path(path);
|
||||
target_url.set_query(parts.uri.query());
|
||||
let mut forwarded_req = state.client.request(parts.method, target_url);
|
||||
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);
|
||||
if !resp_parts.headers.is_empty() {
|
||||
*response_builder.headers_mut().unwrap() = resp_parts.headers;
|
||||
@@ -101,33 +70,6 @@ pub async fn proxy_handler(
|
||||
.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(¶ms_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(
|
||||
resp: reqwest::Response,
|
||||
) -> anyhow::Result<(axum::http::response::Parts, String)> {
|
||||
|
||||
Reference in New Issue
Block a user