diff --git a/.gitignore b/.gitignore index d306d78..529058a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /result /.direnv -/target \ No newline at end of file +/target +/cert.pem +/key.pem +/.env +/tokens.txt \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3252bbf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.diagnostics.experimental.enable": true, + "rust-analyzer.typing.autoClosingAngleBrackets.enable": true, + "rust-analyzer.server.path": "rust-analyzer" +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d205b29..9ebc275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,18 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-trait" version = "0.1.68" @@ -19,6 +31,20 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "async-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2c69d237cf761215175f390021344f5530101cca8164d69878b8a1779e80c" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tungstenite 0.19.0", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -27,15 +53,17 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.15" +version = "0.6.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b32c5ea3aabaf4deb5f5ced2d688ec0844c881c9e6c696a8b769a05fc691e62" +checksum = "113713495a32dd0ab52baf5c10044725aa3aec00b31beda84218e469029b72a3" dependencies = [ "async-trait", "axum-core", + "base64 0.21.0", "bitflags", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -50,8 +78,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -94,6 +124,12 @@ dependencies = [ "tower-service", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.0" @@ -106,12 +142,38 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2b_simd" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.2.5", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.4.0" @@ -130,6 +192,62 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constant_time_eq" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -151,6 +269,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -158,6 +291,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -166,6 +300,34 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -184,10 +346,37 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", ] [[package]] @@ -215,6 +404,31 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -246,6 +460,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -282,6 +502,16 @@ dependencies = [ "want", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -307,6 +537,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.141" @@ -322,6 +558,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matchit" version = "0.7.0" @@ -356,10 +601,33 @@ dependencies = [ name = "musikquadrupled" version = "0.1.0" dependencies = [ + "async-tungstenite", "axum", "axum-server", + "base64 0.21.0", "dotenvy", + "futures", + "http", + "hyper", + "rand", + "rust-argon2", + "scc", + "serde", + "serde_json", "tokio", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", ] [[package]] @@ -378,6 +646,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -416,6 +690,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.56" @@ -434,6 +714,60 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "ring" version = "0.16.20" @@ -449,6 +783,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-argon2" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" +dependencies = [ + "base64 0.13.1", + "blake2b_simd", + "constant_time_eq 0.1.5", + "crossbeam-utils", +] + [[package]] name = "rustls" version = "0.20.8" @@ -467,7 +813,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] @@ -482,6 +828,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "scc" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9364e52847b408f401d7100885c74b4b26521cb1a5357f6b6ddbef7464aa5b" + [[package]] name = "sct" version = "0.7.0" @@ -497,6 +849,20 @@ name = "serde" version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] [[package]] name = "serde_json" @@ -530,6 +896,26 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "slab" version = "0.4.8" @@ -539,6 +925,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "socket2" version = "0.4.9" @@ -583,6 +975,51 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.27.0" @@ -622,6 +1059,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.18.0", +] + [[package]] name = "tokio-util" version = "0.7.7" @@ -652,6 +1101,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -673,9 +1141,21 @@ dependencies = [ "cfg-if", "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tracing-core" version = "0.1.30" @@ -683,6 +1163,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -691,18 +1201,106 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index a8de7e7..02d2e7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,20 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.6" +axum = {version = "0.6", features = ["ws", "headers"]} axum-server = {version = "0.4", features = ["tls-rustls"]} tokio = {version = "1", features = ["rt-multi-thread"]} -dotenvy = "0.15" \ No newline at end of file +dotenvy = "0.15" +tracing = "0.1" +tracing-subscriber = {version = "0.3", features = ["env-filter"]} +tower-http = {version = "0.4", features = ["trace", "cors"]} +hyper = {version = "0.14", features = ["client"]} +http = "0.2" +async-tungstenite = {version = "0.21", features = ["tokio-runtime"]} +futures = {version = "0.3"} +serde = {version = "1", features = ["derive"]} +serde_json = "1" +rust-argon2 = "1.0" +rand = "0.8" +scc = "1" +base64 = "0.21" \ No newline at end of file diff --git a/flake.nix b/flake.nix index a557bb7..26752ab 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,18 @@ }; devShells.default = crateOutputs.devShell.overrideAttrs (old: { RUST_SRC_PATH = "${config.nci.toolchains.shell}/lib/rustlib/src/rust/library"; + packages = (old.packages or []) ++ [ + pkgs.rust-analyzer + (pkgs.writeShellApplication { + name = "generate-cert"; + runtimeInputs = with pkgs; [mkcert coreutils]; + text = '' + mkcert localhost 127.0.0.1 ::1 + mv localhost+2.pem cert.pem + mv localhost+2-key.pem key.pem + ''; + }) + ]; }); packages.default = crateOutputs.packages.release; }; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a9bf143..21d6d23 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "stable" -components = ["rust-src", "rust-analyzer", "rustfmt"] +components = ["rust-src", "rustfmt"] diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..43ba6a2 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,38 @@ +use std::fmt::Display; + +use axum::response::IntoResponse; +use http::StatusCode; + +type BoxedError = Box; + +#[derive(Debug)] +pub(crate) struct AppError { + internal: BoxedError, +} + +impl From for AppError +where + E: Into, +{ + fn from(err: E) -> Self { + Self { + internal: err.into(), + } + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> axum::response::Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {}", self.internal), + ) + .into_response() + } +} + +impl Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.internal.fmt(f) + } +} diff --git a/src/handler.rs b/src/handler.rs new file mode 100644 index 0000000..2f26a3a --- /dev/null +++ b/src/handler.rs @@ -0,0 +1,414 @@ +use std::net::SocketAddr; + +use super::AppError; +use async_tungstenite::{ + tokio::TokioAdapter, + tungstenite::{protocol::CloseFrame as TungsteniteCloseFrame, Message as TungsteniteMessage}, + WebSocketStream, +}; +use axum::{ + extract::{ + ws::{CloseFrame as AxumCloseFrame, Message as AxumMessage, WebSocket, WebSocketUpgrade}, + ConnectInfo, Query, State, + }, + headers::UserAgent, + response::IntoResponse, + routing::get, + Router, TypedHeader, +}; +use base64::Engine; +use futures::{SinkExt, StreamExt}; +use http::{ + header::{AUTHORIZATION, CONTENT_TYPE}, + HeaderValue, Method, Request, Response, StatusCode, +}; +use hyper::Body; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tokio::net::TcpStream; +use tower_http::{ + cors::CorsLayer, + trace::{DefaultMakeSpan, TraceLayer}, +}; +use tracing::{Instrument, Span}; + +use crate::{get_conf, AppState}; + +const B64: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD; + +pub(super) async fn public(state: AppState) -> Result { + let trace_layer = + TraceLayer::new_for_http().make_span_with(DefaultMakeSpan::default().include_headers(true)); + + let router = Router::new() + .route("/thumbnails/:id", get(http)) + .route("/audio/id/:id", get(http)) + .route("/", get(metadata_ws)) + .layer(trace_layer) + .layer( + CorsLayer::new() + .allow_origin(get_conf("CORS_ALLOW_ORIGIN")?.parse::()?) + .allow_headers([CONTENT_TYPE]) + .allow_methods([Method::GET]) + .allow_credentials(true), + ) + .with_state(state.clone()); + + Ok(router) +} + +pub(super) async fn internal(state: AppState) -> Router { + Router::new() + .route("/generate_token", get(generate_token)) + .with_state(state) +} + +async fn generate_token(State(app): State) -> Result { + let token = app.tokens.generate().await?; + // start task to write tokens + tokio::spawn(async move { + if let Err(err) = app.tokens.write(&app.tokens_path).await { + tracing::error!("couldn't write tokens file: {err}"); + } + }); + return Ok(token); +} + +#[derive(Deserialize)] +struct Auth { + #[serde(default)] + token: Option, +} + +fn extract_password_from_basic_auth(auth: &str) -> Result { + let decoded = B64.decode(auth.trim_start_matches("Basic "))?; + let auth = String::from_utf8(decoded)?; + Ok(auth.trim_start_matches("default:").to_string()) +} + +async fn http( + State(app): State, + Query(query): Query, + mut req: Request, +) -> Result, AppError> { + let path = req.uri().path(); + let path_query = req + .uri() + .path_and_query() + .map(|v| v.as_str()) + .unwrap_or(path); + + *req.uri_mut() = format!( + "http://{}:{}{}", + app.musikcubed_address, app.musikcubed_http_port, path_query + ) + .parse()?; + + let maybe_token = query.token.or_else(|| { + req.headers() + .get(AUTHORIZATION) + .and_then(|h| h.to_str().ok()) + .and_then(|auth| extract_password_from_basic_auth(auth).ok()) + }); + + 'ok: { + tracing::debug!("verifying token: {maybe_token:?}"); + if let Some(token) = maybe_token { + if app.tokens.verify(token).await? { + break 'ok; + } + } + return Ok(Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body("Invalid token or token not present".to_string().into()) + .expect("cant fail")); + } + + let auth = B64.encode(format!("default:{}", app.musikcubed_password)); + req.headers_mut().insert( + AUTHORIZATION, + format!("Basic {auth}").parse().expect("valid header value"), + ); + + Ok(app.client.request(req).await?) +} + +async fn metadata_ws( + State(app): State, + TypedHeader(user_agent): TypedHeader, + ConnectInfo(addr): ConnectInfo, + ws: WebSocketUpgrade, +) -> Result { + use async_tungstenite::tokio::connect_async; + + let uri = format!( + "ws://{}:{}", + app.musikcubed_address, app.musikcubed_metadata_port + ); + let (ws_stream, _) = connect_async(uri).await?; + + let upgrade = ws + .on_failed_upgrade({ + let user_agent = user_agent.clone(); + move |error| { + let _entered = tracing::info_span!("metadata ws http", client_addr = %addr, client_user_agent = %user_agent).entered(); + tracing::error!("failed to upgrade to websocket for client: {error}"); + } + }) + .on_upgrade(move |socket| { + let span = tracing::info_span!( + "metadata ws", + client_addr = %addr, + client_user_agent = %user_agent + ); + handle_metadata_socket(ws_stream, socket, app).instrument(span) + }); + + Ok(upgrade) +} + +#[derive(Serialize, Deserialize)] +struct WsApiMessage { + name: String, + r#type: String, + id: String, + #[serde(default)] + device_id: Option, + options: serde_json::value::Map, +} + +async fn handle_metadata_socket( + mut server_socket: WebSocketStream>, + mut client_socket: WebSocket, + app: AppState, +) { + // get token + let (token, og_auth_msg) = 'ok: { + 'err: { + if let Some(Ok(AxumMessage::Text(raw))) = client_socket.recv().await { + tracing::debug!("got client auth message: {raw}"); + let Ok(parsed) = serde_json::from_str::(&raw) else { + tracing::error!("invalid auth message"); + break 'err; + }; + let Some(token) = parsed.options.get("password").and_then(|v| v.as_str()) else { + tracing::error!("token was not provided"); + break 'err; + }; + break 'ok (token.to_string(), parsed); + } else { + tracing::error!("did not receive auth message from client, closing socket"); + break 'err; + } + } + let _ = client_socket.close().await; + let _ = server_socket.close(None).await; + return; + }; + tracing::debug!("successfully extracted token from client request"); + + // validate token + 'ok: { + let verify_res = app + .tokens + .verify(&token) + .await + .map_err(|err| err.to_string()); + 'err: { + match verify_res { + Ok(verified) => { + if !verified { + tracing::error!("invalid token"); + break 'err; + } + break 'ok; + } + Err(err) => { + tracing::error!("internal server error while validating token: {err}"); + break 'err; + } + } + }; + let _ = client_socket.close().await; + let _ = server_socket.close(None).await; + return; + } + tracing::debug!("successfully validated token"); + + let og_auth_reply = 'ok: { + 'err: { + // send actual auth message to the musikcubed server + let auth_msg = WsApiMessage { + name: "authenticate".to_string(), + r#type: "request".to_string(), + id: og_auth_msg.id, + device_id: og_auth_msg.device_id, + options: { + let mut map = serde_json::Map::with_capacity(1); + map.insert( + "password".to_string(), + app.musikcubed_password.clone().into(), + ); + map + }, + }; + let auth_msg_ser = serde_json::to_string(&auth_msg).expect(""); + tracing::debug!("sending auth message to musikcubed: {auth_msg_ser}"); + if let Err(err) = server_socket + .send(TungsteniteMessage::Text(auth_msg_ser)) + .await + { + tracing::error!("failed to send auth message to musikcubed: {err}"); + break 'err; + } + // wait for auth reply + if let Some(Ok(TungsteniteMessage::Text(raw))) = server_socket.next().await { + tracing::debug!("got auth reply from musikcubed: {raw}"); + let Ok(parsed) = serde_json::from_str::(&raw) else { + tracing::error!("invalid auth response message: {raw}"); + break 'err; + }; + let is_authenticated = parsed + .options + .get("authenticated") + .and_then(Value::as_bool) + .unwrap_or(false); + match is_authenticated { + true => break 'ok parsed, + false => break 'err, + } + } + } + let _ = client_socket.close().await; + let _ = server_socket.close(None).await; + return; + }; + + 'ok: { + 'err: { + // send actual auth message to the musikcubed server + let auth_reply_msg = { + let mut auth_reply_msg = og_auth_reply; + let maybe_env_map = auth_reply_msg + .options + .get_mut("environment") + .and_then(Value::as_object_mut); + if let Some(map) = maybe_env_map { + map.insert("http_server_port".to_string(), Value::from(app.public_port)); + } + auth_reply_msg + }; + let auth_reply_msg_ser = serde_json::to_string(&auth_reply_msg).expect(""); + tracing::debug!("sending auth reply message to client: {auth_reply_msg_ser}"); + match client_socket + .send(AxumMessage::Text(auth_reply_msg_ser)) + .await + { + Ok(_) => { + tracing::debug!("successfully sent auth reply message to client"); + break 'ok; + } + Err(err) => { + tracing::error!("error while sending auth reply message to client: {err}"); + break 'err; + } + } + } + let _ = client_socket.close().await; + let _ = server_socket.close(None).await; + return; + } + tracing::info!("successfully authenticated"); + + let (mut in_write, mut in_read) = client_socket.split(); + let (mut out_write, mut out_read) = server_socket.split(); + + let in_read_fut = async move { + while let Some(res) = in_read.next().await { + match res { + Ok(msg) => { + tracing::trace!("got message from client: {msg:?}"); + let res = out_write.send(axum_msg_to_tungstenite(msg)).await; + if let Err(err) = res { + tracing::error!("could not write to server socket: {err}"); + break; + } + } + Err(err) => { + tracing::error!("could not read from client socket: {err}"); + break; + } + } + } + let _ = out_write.send(TungsteniteMessage::Close(None)).await; + }; + let in_read_task = tokio::spawn(in_read_fut.instrument(Span::current())); + + let in_write_fut = async move { + while let Some(res) = out_read.next().await { + match res { + Ok(msg) => { + tracing::trace!("got message from server: {msg:?}"); + let res = in_write.send(tungstenite_msg_to_axum(msg)).await; + if let Err(err) = res { + tracing::error!("could not write to client socket: {err}"); + break; + } + } + Err(err) => { + tracing::error!("could not read from server socket: {err}"); + break; + } + } + } + let _ = in_write.send(AxumMessage::Close(None)).await; + }; + let in_write_task = tokio::spawn(in_write_fut.instrument(Span::current())); + + let _ = tokio::join!(in_read_task, in_write_task); + + tracing::debug!("ending metadata ws task"); +} + +#[inline(always)] +fn tungstenite_msg_to_axum(msg: TungsteniteMessage) -> AxumMessage { + match msg { + TungsteniteMessage::Text(data) => AxumMessage::Text(data), + TungsteniteMessage::Binary(data) => AxumMessage::Binary(data), + TungsteniteMessage::Ping(data) => AxumMessage::Ping(data), + TungsteniteMessage::Pong(data) => AxumMessage::Pong(data), + TungsteniteMessage::Close(frame) => { + AxumMessage::Close(frame.map(tungstenite_close_frame_to_axum)) + } + TungsteniteMessage::Frame(_) => unreachable!("we don't use raw frames"), + } +} + +#[inline(always)] +fn axum_msg_to_tungstenite(msg: AxumMessage) -> TungsteniteMessage { + match msg { + AxumMessage::Text(data) => TungsteniteMessage::Text(data), + AxumMessage::Binary(data) => TungsteniteMessage::Binary(data), + AxumMessage::Ping(data) => TungsteniteMessage::Ping(data), + AxumMessage::Pong(data) => TungsteniteMessage::Pong(data), + AxumMessage::Close(frame) => { + TungsteniteMessage::Close(frame.map(axum_close_frame_to_tungstenite)) + } + } +} + +#[inline(always)] +fn tungstenite_close_frame_to_axum(frame: TungsteniteCloseFrame) -> AxumCloseFrame { + AxumCloseFrame { + code: frame.code.into(), + reason: frame.reason, + } +} + +#[inline(always)] +fn axum_close_frame_to_tungstenite(frame: AxumCloseFrame) -> TungsteniteCloseFrame { + TungsteniteCloseFrame { + code: frame.code.into(), + reason: frame.reason, + } +} diff --git a/src/main.rs b/src/main.rs index ec298ed..6c6ad32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,116 @@ -use axum::{routing::get, Router}; -use axum_server::tls_rustls::RustlsConfig; +use std::{net::SocketAddr, sync::Arc}; + use dotenvy::Error as DotenvError; +use error::AppError; +use hyper::{client::HttpConnector, Body}; +use token::Tokens; +use tracing::{error, info, warn}; +use tracing_subscriber::prelude::*; + +mod error; +mod handler; +mod token; #[tokio::main] async fn main() { - if let Err(DotenvError::Io(err)) = dotenvy::dotenv() {} - - let app = Router::new().route("/", get(|| async { "Hello world" })); - let config = RustlsConfig::from_pem_file(cert, key).await.unwrap(); + app().await.unwrap(); +} + +async fn app() -> Result<(), AppError> { + // init tracing + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "musikquadrupled=debug,tower_http=debug".into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + + // load config + match dotenvy::dotenv() { + Err(DotenvError::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { + warn!(".env file not found"); + } + Ok(_) => info!(".env file loaded"), + Err(err) => return Err(err.into()), + } + + // let cert_path = get_conf("TLS_CERT_PATH"); + // let key_path = get_conf("TLS_KEY_PATH"); + // info!("cert path is: {cert_path}"); + // info!("key path is: {key_path}"); + // let config = RustlsConfig::from_pem_file(cert_path, key_path) + // .await + // .unwrap(); + + let public_addr: SocketAddr = get_conf("ADDRESS")?.parse()?; + let local_addr: SocketAddr = get_conf("LOCAL_ADDRESS")?.parse()?; + + let state = AppState::new(AppStateInternal::new(public_addr.port()).await?); + let public = handler::public(state.clone()).await?; + let internal = handler::internal(state).await; + + info!("listening on {public_addr} for public APIs, on {local_addr} for internal API use"); + // axum_server::bind_rustls(addr, config) + let public = axum_server::bind(public_addr) + .serve(public.into_make_service_with_connect_info::()); + let internal = axum_server::bind(local_addr).serve(internal.into_make_service()); + + tokio::try_join!(public, internal) + .map(|_| ()) + .map_err(Into::into) +} + +fn get_conf(key: &str) -> Result { + const ENV_NAMESPACE: &str = "MUSIKQUAD"; + + let key = format!("{ENV_NAMESPACE}_{key}"); + match std::env::var(&key) { + Ok(val) => return Ok(val), + Err(err) => { + use std::env::VarError; + match err { + VarError::NotPresent => { + error!("Config option {key} was not set but is required"); + } + VarError::NotUnicode(_) => { + error!("Config option {key} was not unicode"); + } + } + return Err(err.into()); + } + } +} + +type Client = hyper::Client; + +type AppState = Arc; + +#[derive(Clone)] +struct AppStateInternal { + client: Client, + tokens: Tokens, + tokens_path: String, + public_port: u16, + musikcubed_address: String, + musikcubed_http_port: u16, + musikcubed_metadata_port: u16, + musikcubed_password: String, +} + +impl AppStateInternal { + async fn new(public_port: u16) -> Result { + let tokens_path = get_conf("TOKENS_FILE")?; + let this = Self { + public_port, + musikcubed_address: get_conf("MUSIKCUBED_ADDRESS")?, + musikcubed_http_port: get_conf("MUSIKCUBED_HTTP_PORT")?.parse()?, + musikcubed_metadata_port: get_conf("MUSIKCUBED_METADATA_PORT")?.parse()?, + musikcubed_password: get_conf("MUSIKCUBED_PASSWORD")?, + client: Client::new(), + tokens: Tokens::read(&tokens_path).await?, + tokens_path, + }; + Ok(this) + } } diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..7abb461 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,70 @@ +use rand::Rng; +use scc::HashSet; +use std::borrow::Cow; +use std::fmt::Write; +use std::path::Path; +use std::sync::Arc; + +use crate::error::AppError; + +fn hash_string(data: &[u8]) -> Result { + argon2::hash_encoded(data, "11111111".as_bytes(), &argon2::Config::default()) +} + +#[derive(Debug, Clone)] +pub(crate) struct Tokens { + hashed: Arc>>, + raw_contents: &'static str, +} + +impl Tokens { + pub async fn read(path: impl AsRef) -> Result { + let this = Self { + hashed: Arc::new(HashSet::new()), + raw_contents: Box::leak(tokio::fs::read_to_string(path).await?.into_boxed_str()), + }; + + for token in this.raw_contents.lines() { + let token = token.trim(); + if !token.is_empty() { + this.hashed + .insert_async(Cow::Borrowed(token)) + .await + .expect("the set will be empty"); + } + } + + Ok(this) + } + + pub async fn write(&self, path: impl AsRef) -> Result<(), AppError> { + let mut contents = String::new(); + self.hashed + .for_each_async(|hash| { + writeln!(&mut contents, "{hash}").expect("if this fails then too bad") + }) + .await; + + tokio::fs::write(path, contents).await.map_err(Into::into) + } + + pub async fn generate(&self) -> Result { + let token = rand::thread_rng() + .sample_iter(rand::distributions::Alphanumeric) + .take(30) + .map(|c| c as char) + .collect::(); + + let token_hash = hash_string(token.as_bytes())?; + + self.hashed.insert_async(token_hash.into()).await?; + Ok(token) + } + + pub async fn verify(&self, token: impl AsRef) -> Result { + let token = token.as_ref(); + let token_hash = hash_string(token.as_bytes())?; + tracing::debug!("verifying token {token}, hash {token_hash}"); + Ok(self.hashed.contains_async(&Cow::Owned(token_hash)).await) + } +}