initial impl

This commit is contained in:
dusk 2023-04-21 18:48:39 +03:00
parent 7bece578c0
commit f6dd056503
Signed by: dusk
GPG Key ID: 1D8F8FAF2294D6EA
10 changed files with 1272 additions and 13 deletions

4
.gitignore vendored
View File

@ -1,3 +1,7 @@
/result
/.direnv
/target
/cert.pem
/key.pem
/.env
/tokens.txt

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"rust-analyzer.diagnostics.experimental.enable": true,
"rust-analyzer.typing.autoClosingAngleBrackets.enable": true,
"rust-analyzer.server.path": "rust-analyzer"
}

604
Cargo.lock generated
View File

@ -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"

View File

@ -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"
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"

View File

@ -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;
};

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "stable"
components = ["rust-src", "rust-analyzer", "rustfmt"]
components = ["rust-src", "rustfmt"]

38
src/error.rs Normal file
View File

@ -0,0 +1,38 @@
use std::fmt::Display;
use axum::response::IntoResponse;
use http::StatusCode;
type BoxedError = Box<dyn std::error::Error>;
#[derive(Debug)]
pub(crate) struct AppError {
internal: BoxedError,
}
impl<E> From<E> for AppError
where
E: Into<BoxedError>,
{
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)
}
}

414
src/handler.rs Normal file
View File

@ -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<Router, AppError> {
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::<HeaderValue>()?)
.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<AppState>) -> Result<impl IntoResponse, AppError> {
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<String>,
}
fn extract_password_from_basic_auth(auth: &str) -> Result<String, AppError> {
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<AppState>,
Query(query): Query<Auth>,
mut req: Request<Body>,
) -> Result<Response<Body>, 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<AppState>,
TypedHeader(user_agent): TypedHeader<UserAgent>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
ws: WebSocketUpgrade,
) -> Result<impl IntoResponse, AppError> {
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<String>,
options: serde_json::value::Map<String, Value>,
}
async fn handle_metadata_socket(
mut server_socket: WebSocketStream<TokioAdapter<TcpStream>>,
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::<WsApiMessage>(&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::<WsApiMessage>(&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,
}
}

View File

@ -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::<SocketAddr>());
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<String, AppError> {
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<HttpConnector, Body>;
type AppState = Arc<AppStateInternal>;
#[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<Self, AppError> {
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)
}
}

70
src/token.rs Normal file
View File

@ -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<String, argon2::Error> {
argon2::hash_encoded(data, "11111111".as_bytes(), &argon2::Config::default())
}
#[derive(Debug, Clone)]
pub(crate) struct Tokens {
hashed: Arc<HashSet<Cow<'static, str>>>,
raw_contents: &'static str,
}
impl Tokens {
pub async fn read(path: impl AsRef<Path>) -> Result<Self, AppError> {
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<Path>) -> 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<String, AppError> {
let token = rand::thread_rng()
.sample_iter(rand::distributions::Alphanumeric)
.take(30)
.map(|c| c as char)
.collect::<String>();
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<str>) -> Result<bool, AppError> {
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)
}
}