diff --git a/.gitignore b/.gitignore
index b60de5b..4e5103b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
**/target
+.env
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 9879e30..7a41547 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,26 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "async-io"
version = "1.12.0"
@@ -42,18 +62,119 @@ dependencies = [
"syn",
]
+[[package]]
+name = "async-trait"
+version = "0.1.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+[[package]]
+name = "axum"
+version = "0.6.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8582122b8edba2af43eaf6b80dbfd33f421b5a0eb3a3113d21bc096ac5b44faf"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-http",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "block-buffer"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bson"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8746d07211bb12a7c34d995539b4a2acd4e0b0e757de98ce2ab99bcf17443fad"
+dependencies = [
+ "ahash",
+ "base64 0.13.1",
+ "hex",
+ "indexmap",
+ "lazy_static",
+ "rand",
+ "serde",
+ "serde_bytes",
+ "serde_json",
+ "time",
+ "uuid",
+]
+
[[package]]
name = "bumpalo"
version = "3.12.0"
@@ -98,7 +219,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
dependencies = [
"camino",
"cargo-platform",
- "semver",
+ "semver 1.0.16",
"serde",
"serde_json",
]
@@ -115,6 +236,28 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "iana-time-zone",
+ "num-integer",
+ "num-traits",
+ "winapi",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
[[package]]
name = "concurrent-queue"
version = "2.1.0"
@@ -124,6 +267,38 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cookie"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crossbeam-channel"
version = "0.5.7"
@@ -156,6 +331,154 @@ 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 = "cxx"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "darling"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version 0.4.0",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dotenv"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
+
+[[package]]
+name = "enum-as-inner"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "errno"
version = "0.2.8"
@@ -201,12 +524,63 @@ dependencies = [
"instant",
]
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
[[package]]
name = "futures-core"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
+[[package]]
+name = "futures-executor"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
[[package]]
name = "futures-io"
version = "0.3.26"
@@ -239,6 +613,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "futures-sink"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
+
[[package]]
name = "futures-task"
version = "0.3.26"
@@ -251,14 +631,28 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
[[package]]
name = "getrandom"
version = "0.2.8"
@@ -276,6 +670,18 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
[[package]]
name = "hermit-abi"
version = "0.2.6"
@@ -285,6 +691,156 @@ dependencies = [
"libc",
]
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi",
+]
+
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -304,6 +860,24 @@ dependencies = [
"windows-sys 0.45.0",
]
+[[package]]
+name = "ipconfig"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be"
+dependencies = [
+ "socket2",
+ "widestring",
+ "winapi",
+ "winreg",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
+
[[package]]
name = "itoa"
version = "1.0.5"
@@ -331,6 +905,21 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
@@ -356,6 +945,15 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
[[package]]
name = "mach"
version = "0.3.2"
@@ -365,6 +963,33 @@ dependencies = [
"libc",
]
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
+[[package]]
+name = "matchit"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
+
+[[package]]
+name = "md-5"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
+dependencies = [
+ "digest",
+]
+
[[package]]
name = "memchr"
version = "2.5.0"
@@ -380,6 +1005,22 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
[[package]]
name = "mio"
version = "0.8.6"
@@ -408,7 +1049,7 @@ dependencies = [
"once_cell",
"parking_lot",
"quanta",
- "rustc_version",
+ "rustc_version 0.4.0",
"scheduled-thread-pool",
"skeptic",
"smallvec",
@@ -418,6 +1059,53 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "mongodb"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a37fe10c1485a0cd603468e284a1a8535b4ecf46808f5f7de3639a1e1252dbf8"
+dependencies = [
+ "async-trait",
+ "base64 0.13.1",
+ "bitflags",
+ "bson",
+ "chrono",
+ "derivative",
+ "derive_more",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-util",
+ "hex",
+ "hmac",
+ "lazy_static",
+ "md-5",
+ "pbkdf2",
+ "percent-encoding",
+ "rand",
+ "rustc_version_runtime",
+ "rustls",
+ "rustls-pemfile",
+ "serde",
+ "serde_bytes",
+ "serde_with",
+ "sha-1",
+ "sha2",
+ "socket2",
+ "stringprep",
+ "strsim",
+ "take_mut",
+ "thiserror",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "trust-dns-proto",
+ "trust-dns-resolver",
+ "typed-builder",
+ "uuid",
+ "webpki-roots",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -428,6 +1116,25 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "num_cpus"
version = "1.15.0"
@@ -479,6 +1186,41 @@ dependencies = [
"windows-sys 0.45.0",
]
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "pin-project"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@@ -505,6 +1247,12 @@ dependencies = [
"windows-sys 0.42.0",
]
+[[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.51"
@@ -541,6 +1289,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
[[package]]
name = "quote"
version = "1.0.23"
@@ -550,6 +1304,36 @@ 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 = "raw-cpuid"
version = "10.7.0"
@@ -568,13 +1352,57 @@ dependencies = [
"bitflags",
]
+[[package]]
+name = "resolv-conf"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
+dependencies = [
+ "hostname",
+ "quick-error",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver 0.9.0",
+]
+
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
- "semver",
+ "semver 1.0.16",
+]
+
+[[package]]
+name = "rustc_version_runtime"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f"
+dependencies = [
+ "rustc_version 0.2.3",
+ "semver 0.9.0",
]
[[package]]
@@ -591,6 +1419,33 @@ dependencies = [
"windows-sys 0.45.0",
]
+[[package]]
+name = "rustls"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+dependencies = [
+ "base64 0.21.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
+
[[package]]
name = "ryu"
version = "1.0.12"
@@ -621,6 +1476,31 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+[[package]]
+name = "scratch"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5e082f6ea090deaf0e6dd04b68360fd5cddb152af6ce8927c9d25db299f98c"
+
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
[[package]]
name = "semver"
version = "1.0.16"
@@ -630,6 +1510,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
[[package]]
name = "serde"
version = "1.0.152"
@@ -639,6 +1525,15 @@ dependencies = [
"serde_derive",
]
+[[package]]
+name = "serde_bytes"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_derive"
version = "1.0.152"
@@ -652,15 +1547,81 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.93"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
+checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
+ "indexmap",
"itoa",
"ryu",
"serde",
]
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db0969fff533976baadd92e08b1d102c5a3d8a8049eadfd69d4d1e3c5b2ed189"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
+dependencies = [
+ "serde",
+ "serde_with_macros",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "sharded-slab"
version = "0.1.4"
@@ -719,6 +1680,34 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "stringprep"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
[[package]]
name = "syn"
version = "1.0.109"
@@ -730,12 +1719,24 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
[[package]]
name = "tagptr"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+[[package]]
+name = "take_mut"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
+
[[package]]
name = "tempfile"
version = "3.4.0"
@@ -749,6 +1750,15 @@ dependencies = [
"windows-sys 0.42.0",
]
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "thiserror"
version = "1.0.38"
@@ -779,6 +1789,48 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "time"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
+dependencies = [
+ "itoa",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
+dependencies = [
+ "time-core",
+]
+
+[[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.25.0"
@@ -810,6 +1862,102 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-cookies"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40f38d941a2ffd8402b36e02ae407637a9caceb693aaf2edc910437db0f36984"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "cookie",
+ "futures-util",
+ "http",
+ "parking_lot",
+ "pin-project-lite",
+ "tower-layer",
+ "tower-service",
+]
+
+[[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",
+ "httpdate",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
[[package]]
name = "tracing"
version = "0.1.37"
@@ -817,6 +1965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
+ "log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -874,6 +2023,74 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db"
+[[package]]
+name = "trust-dns-proto"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "enum-as-inner",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "idna 0.2.3",
+ "ipnet",
+ "lazy_static",
+ "log",
+ "rand",
+ "smallvec",
+ "thiserror",
+ "tinyvec",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "trust-dns-resolver"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "ipconfig",
+ "lazy_static",
+ "log",
+ "lru-cache",
+ "parking_lot",
+ "resolv-conf",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "trust-dns-proto",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "typed-builder"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
[[package]]
name = "unicase"
version = "2.6.0"
@@ -883,12 +2100,50 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "unicode-bidi"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
+
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[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 0.3.0",
+ "percent-encoding",
+]
+
[[package]]
name = "uuid"
version = "1.3.0"
@@ -896,6 +2151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [
"getrandom",
+ "serde",
]
[[package]]
@@ -927,6 +2183,16 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
@@ -1003,6 +2269,25 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.22.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
+dependencies = [
+ "webpki",
+]
+
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
@@ -1012,6 +2297,12 @@ dependencies = [
"cc",
]
+[[package]]
+name = "widestring"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -1124,13 +2415,33 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "wrapper"
version = "0.1.0"
dependencies = [
"async-recursion",
+ "axum",
+ "bytes",
+ "dotenv",
+ "futures",
"moka",
+ "mongodb",
+ "rand",
+ "serde",
+ "serde_json",
"tokio",
+ "tower",
+ "tower-cookies",
+ "tower-http",
"tracing",
"tracing-subscriber",
]
diff --git a/Cargo.toml b/Cargo.toml
index a8c6cd9..c114ff5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,8 +4,34 @@ version = "0.1.0"
edition = "2021"
[dependencies]
+# Blazingly fast runtime
tokio = { version = "1", features = ["full"] }
-async-recursion = "1"
-tracing = "0.1.37"
tracing-subscriber = "0.3.16"
-moka = { version = "0.10.0", features = ["future"] }
\ No newline at end of file
+tracing = "0.1.37"
+
+# Allow recursion inside tokio async
+async-recursion = "1"
+
+# DNS Caching Layer
+moka = { version = "0.10.0", features = ["future"] }
+
+# Mongodb
+mongodb = { version = "2.4", features = ["tokio-sync"] }
+futures = "0.3.26"
+
+# Convert values to json for Mongodb
+serde = { version = "1.0", features = ["derive"] }
+
+# Reading env vars from .env
+dotenv = "0.15.0"
+
+# For the meme records
+rand = "0.8.5"
+
+# For the http web frontend
+axum = "0.6.4"
+tower-http = { version = "0.4.0", features = ["fs"] }
+tower-cookies = "0.9.0"
+tower = "0.4.13"
+bytes = "1.4.0"
+serde_json = "1"
\ No newline at end of file
diff --git a/public/css/home.css b/public/css/home.css
new file mode 100644
index 0000000..2548a28
--- /dev/null
+++ b/public/css/home.css
@@ -0,0 +1,40 @@
+span {
+ margin-top: 5rem;
+ margin-bottom: 1rem;
+ width: 45rem;
+ font-size: 2em;
+}
+
+#new {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ padding-top: 2rem;
+ padding-bottom: 1rem;
+ border-bottom: solid 1px var(--gray);
+}
+
+#new input, .block {
+ border-radius: 1rem 0 0 1rem;
+ width: 40rem;
+}
+
+.block {
+ width: 33em;
+}
+
+#new button {
+ border-radius: 0 1rem 1rem 0;
+}
+
+.domain {
+ margin-top: 2rem;
+}
+
+.domain .delete {
+ border-radius: 0 1rem 1rem 0;
+}
+
+.domain .edit {
+ border-radius: 0;
+}
\ No newline at end of file
diff --git a/public/css/login.css b/public/css/login.css
new file mode 100644
index 0000000..2be7c13
--- /dev/null
+++ b/public/css/login.css
@@ -0,0 +1,18 @@
+#login {
+ margin-top: 20em;
+}
+
+#logo {
+ font-size: 6em;
+ font-weight: 750;
+ font-family: bold;
+ margin-bottom: 2rem;
+}
+
+form {
+ width: 30rem;
+}
+
+form input {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/public/css/main.css b/public/css/main.css
new file mode 100644
index 0000000..971e2bb
--- /dev/null
+++ b/public/css/main.css
@@ -0,0 +1,119 @@
+:root {
+ --dark: #222428;
+ --dark-alternate: #2b2e36;
+ --header: #1e1e22;
+
+ --accent: #8849f5;
+ --accent-alternate: #6829d5;
+ --gray: #2f2f3f;
+ --main: #ffffff;
+ --main-alternate: #cccccc;
+}
+
+* {
+ padding: 0;
+ margin: 0;
+}
+
+@font-face {
+ font-family: main;
+ src: url("../fonts/helvetica.ttf") format("truetype");
+ font-display: swap;
+}
+
+@font-face {
+ font-family: bold;
+ src: url("../fonts/overpass-bold.otf") format("opentype");
+ font-display: swap;
+}
+
+@font-face {
+ font-family: bold-italic;
+ src: url("../fonts/overpass-bold-italic.otf") format("opentype");
+ font-display: swap;
+}
+
+html {
+ background-color: var(--dark);
+ font-family: main;
+ color: var(--main);
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.accent {
+ color: var(--accent);
+}
+
+.fill {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+input, button, .block {
+ all: unset;
+ display: inline-block;
+ font: main;
+ background-color: var(--dark-alternate);
+ font-size: 1rem;
+ padding: 1rem;
+ border-radius: 1rem;
+ margin-bottom: 20px;
+}
+
+button {
+ background-color: var(--accent);
+ width: 5em;
+ text-align: center;
+}
+
+button:hover {
+ cursor: pointer;
+ background-color: var(--accent-alternate);
+}
+
+.delete {
+ background-color: #f54842;
+}
+
+.delete:hover {
+ cursor: pointer;
+ background-color: #d52822;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+}
+
+#header {
+ width: calc(100% - 4rem);
+ background-color: var(--header);
+ border-bottom: solid 1px var(--gray);
+ padding: 1rem;
+ padding-left: 3rem;
+}
+
+#logo {
+ font-size: 2em;
+ font-weight: 500;
+ font-family: bold;
+}
+
+#title {
+ font-size: 2em;
+ font-weight: 300;
+ font-family: sans-serif;
+ padding-left: 1em;
+}
\ No newline at end of file
diff --git a/public/css/record.css b/public/css/record.css
new file mode 100644
index 0000000..3dc257a
--- /dev/null
+++ b/public/css/record.css
@@ -0,0 +1,67 @@
+#buttons {
+ margin-top: 2rem;
+ width: 50rem;
+}
+
+#buttons button {
+ margin: 0;
+ margin-right: 2rem;
+ border-radius: 10px;
+ width: auto;
+ padding: .75rem 1rem;
+}
+
+.record {
+ width: 50rem;
+ background-color: var(--header);
+ padding: 1rem;
+ margin-top: 2rem;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.header span {
+ font-family: bold;
+}
+
+.header button {
+ margin: 0;
+ margin-left: 2rem;
+ padding: .5rem 1rem;
+ width: auto;
+ border-radius: 5px;
+}
+
+.type {
+ margin-right: 1rem;
+ background-color: var(--accent);
+ padding: .25rem .5rem;
+ border-radius: 5px;
+}
+
+.domain {
+ color: var(--main-alternate);
+ flex-grow: 1;
+}
+
+.properties {
+ display: flex;
+ flex-direction: column;
+}
+
+.poperty {
+ display: flex;
+ flex-direction: row;
+ border-bottom: solid 1px var(--gray);
+ margin-top: 1rem;
+}
+
+.key {
+ font-family: bold;
+ width: 5rem;
+}
+
diff --git a/public/domain.html b/public/domain.html
new file mode 100644
index 0000000..ba22eaf
--- /dev/null
+++ b/public/domain.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Wrapper - Records
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/fonts/helvetica-bold.ttf b/public/fonts/helvetica-bold.ttf
new file mode 100644
index 0000000..332b66c
Binary files /dev/null and b/public/fonts/helvetica-bold.ttf differ
diff --git a/public/fonts/helvetica.ttf b/public/fonts/helvetica.ttf
new file mode 100644
index 0000000..718f22d
Binary files /dev/null and b/public/fonts/helvetica.ttf differ
diff --git a/public/fonts/overpass-bold-italic.otf b/public/fonts/overpass-bold-italic.otf
new file mode 100644
index 0000000..f4929ae
Binary files /dev/null and b/public/fonts/overpass-bold-italic.otf differ
diff --git a/public/fonts/overpass-bold.otf b/public/fonts/overpass-bold.otf
new file mode 100644
index 0000000..962a5d7
Binary files /dev/null and b/public/fonts/overpass-bold.otf differ
diff --git a/public/home.html b/public/home.html
new file mode 100644
index 0000000..9efb21e
--- /dev/null
+++ b/public/home.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Wrapper - Domains
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/js/api.js b/public/js/api.js
new file mode 100644
index 0000000..7a29d65
--- /dev/null
+++ b/public/js/api.js
@@ -0,0 +1,51 @@
+const endpoint = '/api'
+
+const request = async (url, method, body) => {
+
+ let response;
+
+ if (method == 'GET') {
+ response = await fetch(endpoint + url, {
+ method,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ } else {
+ response = await fetch(endpoint + url, {
+ method,
+ body: JSON.stringify(body),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ }
+
+ if (response.status == 401) {
+ location.href = '/login'
+ }
+ const contentType = response.headers.get("content-type");
+ if (contentType && contentType.indexOf("application/json") !== -1) {
+ const json = await response.json()
+ return { status: response.status, msg: json.msg, json }
+ } else {
+ const msg = await response.text();
+ return { status: response.status, msg }
+ }
+}
+
+export const login = async (user, pass) => {
+ return await request('/login', 'POST', {user, pass})
+}
+
+export const domains = async () => {
+ return await request('/domains', 'GET')
+}
+
+export const del_domain = async (domain) => {
+ return await request('/domains', 'DELETE', {domain})
+}
+
+export const records = async (domain) => {
+ return await request(`/records?domain=${domain}`, 'GET')
+}
\ No newline at end of file
diff --git a/public/js/components.js b/public/js/components.js
new file mode 100644
index 0000000..00def5f
--- /dev/null
+++ b/public/js/components.js
@@ -0,0 +1,12 @@
+import { div, parse, span } from './main.js';
+
+export function header(title) {
+ return div({id: 'header'},
+ span({id: 'logo', class: 'accent'},
+ parse("Wrapper")
+ ),
+ span({id: 'title'},
+ parse(title)
+ ),
+ )
+}
\ No newline at end of file
diff --git a/public/js/domain.js b/public/js/domain.js
new file mode 100644
index 0000000..36af422
--- /dev/null
+++ b/public/js/domain.js
@@ -0,0 +1,95 @@
+import { del_domain, domains, records } from './api.js'
+import { header } from './components.js'
+import { body, parse, div, input, button, span, is_domain } from './main.js';
+
+function render(domain, records) {
+
+ let divs = []
+ for (const record of records) {
+ divs.push(gen_record(record))
+ }
+
+ document.body.replaceWith(
+ body({},
+ header(domain),
+ div({id: 'buttons'},
+ button({onclick: (event) => {
+ location.href = '/home'
+ }}, parse("Home")),
+ button({}, parse("New Record")),
+ ),
+ ...divs
+ )
+ )
+}
+
+function gen_record(record) {
+ let domain = record.domain
+ let prefix = record.prefix
+
+ if (prefix.length > 0) {
+ prefix = prefix + '.'
+ }
+
+ let type = Object.keys(record.record)[0]
+ let data = record.record[type]
+
+ let divs = []
+ for (const key in data) {
+ let disp_key;
+ if (key == 'ttl') {
+ disp_key = 'TTL'
+ } else {
+ disp_key = upper(key)
+ }
+ divs.push(
+ div({class: 'poperty'},
+ div({class: 'key'}, parse(disp_key)),
+ div({class: 'value'}, parse(data[key])),
+ )
+ )
+ }
+
+ return div({class: 'record'},
+ div({class: 'header'},
+ span({class: 'type'}, parse(type)),
+ span({class: 'prefix'}, parse(prefix)),
+ span({class: 'domain'}, parse(domain)),
+ button({}, parse("Edit")),
+ button({class: 'delete'}, parse("Delete"))
+ ),
+ div({class: 'properties'},
+ ...divs
+ )
+ )
+}
+
+function upper(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+async function init() {
+
+ const params = new Proxy(new URLSearchParams(window.location.search), {
+ get: (searchParams, prop) => searchParams.get(prop),
+ });
+
+ let domain = params.domain;
+
+ if (!is_domain(domain)) {
+ location.href = '/home'
+ return
+ }
+
+ let res = await records(domain);
+
+ if (res.status !== 200) {
+ alert(res.msg)
+ return
+ }
+
+ render(domain, res.json)
+
+}
+
+init()
\ No newline at end of file
diff --git a/public/js/home.js b/public/js/home.js
new file mode 100644
index 0000000..f615632
--- /dev/null
+++ b/public/js/home.js
@@ -0,0 +1,91 @@
+import { del_domain, domains } from './api.js'
+import { header } from './components.js'
+import { body, parse, div, input, button, span, is_domain } from './main.js';
+
+function render(domains) {
+ document.body.replaceWith(
+ body({},
+ header('domains'),
+ div({id: 'new'},
+ input({
+ type: 'text',
+ name: 'domain',
+ id: 'domain',
+ placeholder: 'Type domain name to create new records',
+ autocomplete: "off",
+ }),
+ button({onclick: () => {
+ let domain = document.getElementById('domain').value
+
+ if (!is_domain(domain)) {
+ alert("Invalid domain")
+ return
+ }
+
+ location.href = '/domain?domain='+domain
+ }},
+ parse("Create")
+ )
+ ),
+ ...domain(domains)
+ )
+ )
+}
+
+function domain(domains) {
+ let divs = []
+ for (const domain of domains) {
+ divs.push(
+ div({class: 'domain'},
+ div({class: 'block'},
+ parse(domain)
+ ),
+ button({class: 'edit', onclick: (event) => {
+ console.log(event.target.parentElement)
+ let domain = event
+ .target
+ .parentElement
+ .getElementsByClassName('block')[0]
+ .innerText
+
+ if (!is_domain(domain)) {
+ alert("Invalid domain")
+ return
+ }
+
+ location.href = '/domain?domain='+domain
+ }},
+ parse("Edit")
+ ),
+ button({class: 'delete', onclick: async () => {
+ let res = await del_domain(domain)
+
+ if (res.status != 204) {
+ alert(res.msg)
+ return
+ }
+
+ location.reload()
+ }},
+ parse("Delete")
+ )
+ )
+ )
+ }
+ return divs
+}
+
+async function init() {
+
+ let res = await domains();
+
+ if (res.status !== 200) {
+ alert(res.msg)
+ return
+ }
+
+ render(res.json)
+
+}
+
+init()
\ No newline at end of file
diff --git a/public/js/login.js b/public/js/login.js
new file mode 100644
index 0000000..3bd64ad
--- /dev/null
+++ b/public/js/login.js
@@ -0,0 +1,44 @@
+import { body, div, form, input, p, parse, span} from './main.js'
+import { login } from './api.js'
+
+function render() {
+ document.body.replaceWith(
+ body({},
+ div({id: 'login', class: 'fill'},
+ span({id: 'logo'},
+ span({class: 'accent'}, parse('Wrapper'))
+ ),
+ form({autocomplete: "off"},
+ input({
+ type: 'text',
+ name: 'user',
+ id: 'user',
+ placeholder: 'Username',
+ autofocus: 1
+ }),
+ input({
+ type: 'password',
+ name: 'pass',
+ id: 'pass',
+ placeholder: 'Password',
+ onkeydown: async (event) => {
+ if (event.key == 'Enter') {
+ event.preventDefault()
+ let user = document.getElementById('user').value
+ let pass = document.getElementById('pass').value
+
+ let res = await login(user, pass)
+
+ if (res.status === 200) {
+ location.href = '/home'
+ }
+ }
+ }
+ })
+ )
+ )
+ )
+ )
+}
+
+render()
diff --git a/public/js/main.js b/public/js/main.js
new file mode 100644
index 0000000..615b3d6
--- /dev/null
+++ b/public/js/main.js
@@ -0,0 +1,136 @@
+function createElement(name, attrs, ...children) {
+ const el = document.createElement(name);
+
+ for (const attr in attrs) {
+ if(attr.startsWith("on")) {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr])
+ }
+ }
+
+ for (const child of children) {
+ if (child == null) {
+ continue
+ }
+ el.appendChild(child)
+ }
+
+ return el
+}
+
+export function createElementNS(name, attrs, ...children) {
+ var svgns = "http://www.w3.org/2000/svg";
+ var el = document.createElementNS(svgns, name);
+
+ for (const attr in attrs) {
+ if(attr.startsWith("on")) {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr])
+ }
+ }
+
+ for (const child of children) {
+ if (child == null) {
+ continue
+ }
+ el.appendChild(child)
+ }
+
+ return el
+}
+
+export function p(attrs, ...children) {
+ return createElement("p", attrs, ...children)
+}
+
+export function span(attrs, ...children) {
+ return createElement("span", attrs, ...children)
+}
+
+export function div(attrs, ...children) {
+ return createElement("div", attrs, ...children)
+}
+
+export function a(attrs, ...children) {
+ return createElement("a", attrs, ...children)
+}
+
+export function i(attrs, ...children) {
+ return createElement("i", attrs, ...children)
+}
+
+export function form(attrs, ...children) {
+ return createElement("form", attrs, ...children)
+}
+
+export function img(alt, attrs, ...children) {
+ attrs['onerror'] = (event) => event.target.remove()
+ attrs['alt'] = alt
+ return createElement("img", attrs, ...children)
+}
+
+export function input(attrs, ...children) {
+ return createElement("input", attrs, ...children)
+}
+
+export function button(attrs, ...children) {
+ return createElement("button", attrs, ...children)
+}
+
+export function path(attrs, ...children) {
+ return createElementNS("path", attrs, ...children)
+}
+
+export function svg(attrs, ...children) {
+ return createElementNS("svg", attrs, ...children)
+}
+
+export function body(attrs, ...children) {
+ return createElement("body", attrs, ...children)
+}
+
+export function textarea(attrs, ...children) {
+ return createElement("textarea", attrs, ...children)
+}
+
+export function parse(input) {
+ const pattern = /^[ a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*$/;
+
+ input = input + '';
+
+ if (!pattern.test(input)) {
+ return null;
+ }
+
+ const sanitized = input.replace(//g, '>');
+ return document.createRange().createContextualFragment(sanitized);
+}
+
+export function is_domain(domain) {
+ domain = domain.toLowerCase()
+
+ const pattern = /^[a-z0-9_\-.]*$/;
+ if (!pattern.test(domain)) {
+ return false
+ }
+
+ let parts = domain.split('.').reverse()
+ for (const part of parts) {
+ if (part.length < 1) {
+ return false
+ }
+ }
+
+ if (parts.length < 2 || parts[0].length < 2) {
+ return false
+ }
+
+ const tld_pattern = /^[a-z]*$/;
+ if (!tld_pattern.test(parts[0])) {
+ return false
+ }
+
+ return true
+}
\ No newline at end of file
diff --git a/public/login.html b/public/login.html
new file mode 100644
index 0000000..b03324b
--- /dev/null
+++ b/public/login.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Wrapper - Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..b1b4ec3
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,9 @@
+User-agent: Googlebot
+Disallow: /api
+
+User-agent: Googlebot
+User-agent: AdsBot-Google
+Disallow: /api
+
+User-agent: *
+Disallow: /api
\ No newline at end of file
diff --git a/src/config.rs b/src/config.rs
index 9350adf..547e853 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,35 +1,57 @@
-use std::net::IpAddr;
+use std::{env, net::IpAddr, str::FromStr, fmt::Display};
#[derive(Clone)]
pub struct Config {
- fallback: IpAddr,
- port: u16,
+ pub dns_fallback: IpAddr,
+ pub dns_port: u16,
+ pub dns_cache_size: u64,
+
+ pub db_host: String,
+ pub db_port: u16,
+ pub db_user: String,
+ pub db_pass: String,
+
+ pub web_user: String,
+ pub web_pass: String,
+ pub web_port: u16,
}
impl Config {
pub fn new() -> Self {
- let fallback = "9.9.9.9"
- .parse::()
- .expect("Failed to create default ns fallback");
+ let dns_port = Self::get_var::("WRAPPER_DNS_PORT", 53);
+ let dns_fallback = Self::get_var::("WRAPPER_FALLBACK_DNS", [9, 9, 9, 9].into());
+ let dns_cache_size = Self::get_var::("WRAPPER_CACHE_SIZE", 1000);
+
+ let db_host = Self::get_var::("WRAPPER_DB_HOST", String::from("localhost"));
+ let db_port = Self::get_var::("WRAPPER_DB_PORT", 27017);
+ let db_user = Self::get_var::("WRAPPER_DB_USER", String::from("root"));
+ let db_pass = Self::get_var::("WRAPPER_DB_PASS", String::from(""));
+
+ let web_user = Self::get_var::("WRAPPER_WEB_USER", String::from("admin"));
+ let web_pass = Self::get_var::("WRAPPER_WEB_PASS", String::from("wrapper"));
+ let web_port = Self::get_var::("WRAPPER_WEB_PORT", 80);
+
Self {
- fallback,
- port: 2000,
+ dns_fallback,
+ dns_port,
+ dns_cache_size,
+
+ db_host,
+ db_port,
+ db_user,
+ db_pass,
+
+ web_user,
+ web_pass,
+ web_port,
}
}
- pub fn get_fallback_ns(&self) -> &IpAddr {
- &self.fallback
- }
-
- pub fn get_port(&self) -> u16 {
- self.port
- }
-
- pub fn set_fallback_ns(&mut self, addr: &IpAddr) {
- self.fallback = *addr;
- }
-
- pub fn set_port(&mut self, port: u16) {
- self.port = port;
+ fn get_var(name: &str, default: T) -> T
+ where
+ T: FromStr + Display,
+ {
+ let env = env::var(name).unwrap_or(format!("{default}"));
+ env.parse::().unwrap_or(default)
}
}
diff --git a/src/database/mod.rs b/src/database/mod.rs
new file mode 100644
index 0000000..0d81dc3
--- /dev/null
+++ b/src/database/mod.rs
@@ -0,0 +1,146 @@
+use futures::TryStreamExt;
+use mongodb::{
+ bson::doc,
+ options::{ClientOptions, Credential, ServerAddress},
+ Client,
+};
+use serde::{Deserialize, Serialize};
+use tracing::info;
+
+use crate::{
+ config::Config,
+ dns::packet::{query::QueryType, record::DnsRecord},
+};
+
+use crate::Result;
+
+#[derive(Clone)]
+pub struct Database {
+ client: Client,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StoredRecord {
+ record: DnsRecord,
+ domain: String,
+ prefix: String,
+}
+
+impl StoredRecord {
+ fn get_domain_parts(domain: &str) -> (String, String) {
+ let parts: Vec<&str> = domain.split(".").collect();
+ let len = parts.len();
+ if len == 1 {
+ (String::new(), String::from(parts[0]))
+ } else if len == 2 {
+ (String::new(), String::from(parts.join(".")))
+ } else {
+ (
+ String::from(parts[0..len - 2].join(".")),
+ String::from(parts[len - 2..len].join(".")),
+ )
+ }
+ }
+}
+
+impl From for StoredRecord {
+ fn from(record: DnsRecord) -> Self {
+ let (prefix, domain) = Self::get_domain_parts(&record.get_domain());
+ Self {
+ record,
+ domain,
+ prefix,
+ }
+ }
+}
+
+impl Into for StoredRecord {
+ fn into(self) -> DnsRecord {
+ self.record
+ }
+}
+
+impl Database {
+ pub async fn new(config: Config) -> Result {
+ let options = ClientOptions::builder()
+ .hosts(vec![ServerAddress::Tcp {
+ host: config.db_host,
+ port: Some(config.db_port),
+ }])
+ .credential(
+ Credential::builder()
+ .username(config.db_user)
+ .password(config.db_pass)
+ .build(),
+ )
+ .max_pool_size(100)
+ .app_name(String::from("wrapper"))
+ .build();
+
+ let client = Client::with_options(options)?;
+
+ client
+ .database("wrapper")
+ .run_command(doc! {"ping": 1}, None)
+ .await?;
+
+ info!("Connection to mongodb successfully");
+
+ Ok(Database { client })
+ }
+
+ pub async fn get_records(&self, domain: &str, qtype: QueryType) -> Result> {
+ let (prefix, domain) = StoredRecord::get_domain_parts(domain);
+ Ok(self
+ .get_domain(&domain)
+ .await?
+ .into_iter()
+ .filter(|r| r.prefix == prefix)
+ .filter(|r| {
+ let rqtype = r.record.get_qtype();
+ if qtype == QueryType::A {
+ return rqtype == QueryType::A || rqtype == QueryType::AR;
+ } else if qtype == QueryType::AAAA {
+ return rqtype == QueryType::AAAA || rqtype == QueryType::AAAAR;
+ } else {
+ r.record.get_qtype() == qtype
+ }
+ })
+ .map(|r| r.into())
+ .collect())
+ }
+
+ pub async fn get_domain(&self, domain: &str) -> Result> {
+ let db = self.client.database("wrapper");
+ let col = db.collection::(domain);
+
+ let filter = doc! { "domain": domain };
+ let mut cursor = col.find(filter, None).await?;
+
+ let mut records = Vec::new();
+ while let Some(record) = cursor.try_next().await? {
+ records.push(record);
+ }
+
+ Ok(records)
+ }
+
+ pub async fn add_record(&self, record: DnsRecord) -> Result<()> {
+ let record = StoredRecord::from(record);
+ let db = self.client.database("wrapper");
+ let col = db.collection::(&record.domain);
+ col.insert_one(record, None).await?;
+ Ok(())
+ }
+
+ pub async fn get_domains(&self) -> Result> {
+ let db = self.client.database("wrapper");
+ Ok(db.list_collection_names(None).await?)
+ }
+
+ pub async fn delete_domain(&self, domain: String) -> Result<()> {
+ let db = self.client.database("wrapper");
+ let col = db.collection::(&domain);
+ Ok(col.drop(None).await?)
+ }
+}
diff --git a/src/server/binding.rs b/src/dns/binding.rs
similarity index 95%
rename from src/server/binding.rs
rename to src/dns/binding.rs
index 1c69651..4c7e15f 100644
--- a/src/server/binding.rs
+++ b/src/dns/binding.rs
@@ -3,7 +3,8 @@ use std::{
sync::Arc,
};
-use crate::packet::{buffer::PacketBuffer, Packet, Result};
+use super::packet::{buffer::PacketBuffer, Packet};
+use crate::Result;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream, UdpSocket},
@@ -140,11 +141,4 @@ impl Connection {
}
}
}
-
- // fn pb(buf: &[u8]) {
- // for i in 0..buf.len() {
- // print!("{:02X?} ", buf[i]);
- // }
- // println!("");
- // }
}
diff --git a/src/server/mod.rs b/src/dns/mod.rs
similarity index 72%
rename from src/server/mod.rs
rename to src/dns/mod.rs
index 25076ef..6f1e59e 100644
--- a/src/server/mod.rs
+++ b/src/dns/mod.rs
@@ -1,3 +1,4 @@
mod binding;
+pub mod packet;
mod resolver;
pub mod server;
diff --git a/src/packet/buffer.rs b/src/dns/packet/buffer.rs
similarity index 86%
rename from src/packet/buffer.rs
rename to src/dns/packet/buffer.rs
index 4ecc605..058156e 100644
--- a/src/packet/buffer.rs
+++ b/src/dns/packet/buffer.rs
@@ -1,4 +1,4 @@
-use super::Result;
+use crate::Result;
pub struct PacketBuffer {
pub buf: Vec,
@@ -9,19 +9,9 @@ pub struct PacketBuffer {
impl PacketBuffer {
pub fn new(buf: Vec) -> Self {
Self {
+ size: buf.len(),
buf,
pos: 0,
- size: 0,
- }
- }
-
- fn check(&mut self, pos: usize) {
- if self.size < pos {
- self.size = pos;
- }
-
- if self.buf.len() <= self.size {
- self.buf.resize(self.size + 1, 0x00);
}
}
@@ -42,32 +32,25 @@ impl PacketBuffer {
}
pub fn read(&mut self) -> Result {
- // if self.pos >= 512 {
- // error!("Tried to read past end of buffer");
- // return Err("End of buffer".into());
- // }
- self.check(self.pos);
+ if self.pos >= self.size {
+ return Err("Tried to read past end of buffer".into());
+ }
let res = self.buf[self.pos];
self.pos += 1;
-
Ok(res)
}
pub fn get(&mut self, pos: usize) -> Result {
- // if pos >= 512 {
- // error!("Tried to read past end of buffer");
- // return Err("End of buffer".into());
- // }
- self.check(pos);
+ if pos >= self.size {
+ return Err("Tried to read past end of buffer".into());
+ }
Ok(self.buf[pos])
}
pub fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> {
- // if start + len >= 512 {
- // error!("Tried to read past end of buffer");
- // return Err("End of buffer".into());
- // }
- self.check(start + len);
+ if start + len >= self.size {
+ return Err("Tried to read past end of buffer".into());
+ }
Ok(&self.buf[start..start + len])
}
@@ -169,7 +152,13 @@ impl PacketBuffer {
}
pub fn write(&mut self, val: u8) -> Result<()> {
- self.check(self.pos);
+ if self.size < self.pos {
+ self.size = self.pos;
+ }
+
+ if self.buf.len() <= self.size {
+ self.buf.resize(self.size + 1, 0x00);
+ }
self.buf[self.pos] = val;
self.pos += 1;
@@ -208,7 +197,9 @@ impl PacketBuffer {
}
}
- self.write_u8(0)?;
+ if !qname.is_empty() {
+ self.write_u8(0)?;
+ }
Ok(())
}
diff --git a/src/packet/header.rs b/src/dns/packet/header.rs
similarity index 97%
rename from src/packet/header.rs
rename to src/dns/packet/header.rs
index a75f6ba..2355ecb 100644
--- a/src/packet/header.rs
+++ b/src/dns/packet/header.rs
@@ -1,4 +1,5 @@
-use super::{buffer::PacketBuffer, result::ResultCode, Result};
+use super::{buffer::PacketBuffer, result::ResultCode};
+use crate::Result;
#[derive(Clone, Debug)]
pub struct DnsHeader {
diff --git a/src/packet/mod.rs b/src/dns/packet/mod.rs
similarity index 97%
rename from src/packet/mod.rs
rename to src/dns/packet/mod.rs
index 0b7cb7b..9873b94 100644
--- a/src/packet/mod.rs
+++ b/src/dns/packet/mod.rs
@@ -4,9 +4,7 @@ use self::{
buffer::PacketBuffer, header::DnsHeader, query::QueryType, question::DnsQuestion,
record::DnsRecord,
};
-
-type Error = Box;
-pub type Result = std::result::Result;
+use crate::Result;
pub mod buffer;
pub mod header;
diff --git a/src/packet/query.rs b/src/dns/packet/query.rs
similarity index 54%
rename from src/packet/query.rs
rename to src/dns/packet/query.rs
index cae6f09..732b9b2 100644
--- a/src/packet/query.rs
+++ b/src/dns/packet/query.rs
@@ -12,6 +12,8 @@ pub enum QueryType {
SRV, // 33
OPT, // 41
CAA, // 257
+ AR, // 1000
+ AAAAR, // 1001
}
impl QueryType {
@@ -29,6 +31,8 @@ impl QueryType {
Self::SRV => 33,
Self::OPT => 41,
Self::CAA => 257,
+ Self::AR => 1000,
+ Self::AAAAR => 1001,
}
}
@@ -45,7 +49,30 @@ impl QueryType {
33 => Self::SRV,
41 => Self::OPT,
257 => Self::CAA,
+ 1000 => Self::AR,
+ 1001 => Self::AAAAR,
_ => Self::UNKNOWN(num),
}
}
+
+ pub fn allowed_actions(&self) -> (bool, bool) {
+ // 0. duplicates allowed
+ // 1. allowed to be created by database
+ match self {
+ QueryType::UNKNOWN(_) => (false, false),
+ QueryType::A => (true, true),
+ QueryType::NS => (false, true),
+ QueryType::CNAME => (false, true),
+ QueryType::SOA => (false, false),
+ QueryType::PTR => (false, true),
+ QueryType::MX => (false, true),
+ QueryType::TXT => (true, true),
+ QueryType::AAAA => (true, true),
+ QueryType::SRV => (false, true),
+ QueryType::OPT => (false, false),
+ QueryType::CAA => (false, true),
+ QueryType::AR => (false, true),
+ QueryType::AAAAR => (false, true),
+ }
+ }
}
diff --git a/src/packet/question.rs b/src/dns/packet/question.rs
similarity index 100%
rename from src/packet/question.rs
rename to src/dns/packet/question.rs
diff --git a/src/packet/record.rs b/src/dns/packet/record.rs
similarity index 83%
rename from src/packet/record.rs
rename to src/dns/packet/record.rs
index c29dd8f..88008f0 100644
--- a/src/packet/record.rs
+++ b/src/dns/packet/record.rs
@@ -1,11 +1,12 @@
use std::net::{Ipv4Addr, Ipv6Addr};
+use rand::RngCore;
+use serde::{Deserialize, Serialize};
use tracing::{trace, warn};
use super::{buffer::PacketBuffer, query::QueryType, Result};
-#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
-#[allow(dead_code)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum DnsRecord {
UNKNOWN {
domain: String,
@@ -76,10 +77,17 @@ pub enum DnsRecord {
value: String,
ttl: u32,
}, // 257
+ AR {
+ domain: String,
+ ttl: u32,
+ },
+ AAAAR {
+ domain: String,
+ ttl: u32,
+ },
}
impl DnsRecord {
-
pub fn read(buffer: &mut PacketBuffer) -> Result {
let mut domain = String::new();
buffer.read_qname(&mut domain)?;
@@ -90,10 +98,10 @@ impl DnsRecord {
let ttl = buffer.read_u32()?;
let data_len = buffer.read_u16()?;
- let header_pos = buffer.pos();
-
trace!("Reading DNS Record TYPE: {:?}", qtype);
+ let header_pos = buffer.pos();
+
match qtype {
QueryType::A => {
let raw_addr = buffer.read_u32()?;
@@ -471,6 +479,29 @@ impl DnsRecord {
let size = buffer.pos() - (pos + 2);
buffer.set_u16(pos, size as u16)?;
}
+ Self::AR { ref domain, ttl } => {
+ buffer.write_qname(domain)?;
+ buffer.write_u16(QueryType::A.to_num())?;
+ buffer.write_u16(1)?;
+ buffer.write_u32(ttl)?;
+ buffer.write_u16(4)?;
+
+ let mut rand = rand::thread_rng();
+ buffer.write_u32(rand.next_u32())?;
+ }
+ Self::AAAAR { ref domain, ttl } => {
+ buffer.write_qname(domain)?;
+ buffer.write_u16(QueryType::A.to_num())?;
+ buffer.write_u16(1)?;
+ buffer.write_u32(ttl)?;
+ buffer.write_u16(4)?;
+
+ let mut rand = rand::thread_rng();
+ buffer.write_u32(rand.next_u32())?;
+ buffer.write_u32(rand.next_u32())?;
+ buffer.write_u32(rand.next_u32())?;
+ buffer.write_u32(rand.next_u32())?;
+ }
Self::UNKNOWN { .. } => {
warn!("Skipping record: {self:?}");
}
@@ -479,20 +510,35 @@ impl DnsRecord {
Ok(buffer.pos() - start_pos)
}
+ pub fn get_domain(&self) -> String {
+ self.get_shared_domain().0
+ }
+
+ pub fn get_qtype(&self) -> QueryType {
+ self.get_shared_domain().1
+ }
+
pub fn get_ttl(&self) -> u32 {
- match *self {
- DnsRecord::UNKNOWN { .. } => 0,
- DnsRecord::AAAA { ttl, .. } => ttl,
- DnsRecord::A { ttl, .. } => ttl,
- DnsRecord::NS { ttl, .. } => ttl,
- DnsRecord::CNAME { ttl, .. } => ttl,
- DnsRecord::SOA { ttl, .. } => ttl,
- DnsRecord::PTR { ttl, .. } => ttl,
- DnsRecord::MX { ttl, .. } => ttl,
- DnsRecord::TXT { ttl, .. } => ttl,
- DnsRecord::SRV { ttl, .. } => ttl,
- DnsRecord::CAA { ttl, .. } => ttl,
+ self.get_shared_domain().2
+ }
+
+ fn get_shared_domain(&self) -> (String, QueryType, u32) {
+ match self {
+ DnsRecord::UNKNOWN {
+ domain, ttl, qtype, ..
+ } => (domain.clone(), QueryType::UNKNOWN(*qtype), *ttl),
+ DnsRecord::AAAA { domain, ttl, .. } => (domain.clone(), QueryType::AAAA, *ttl),
+ DnsRecord::A { domain, ttl, .. } => (domain.clone(), QueryType::A, *ttl),
+ DnsRecord::NS { domain, ttl, .. } => (domain.clone(), QueryType::NS, *ttl),
+ DnsRecord::CNAME { domain, ttl, .. } => (domain.clone(), QueryType::CNAME, *ttl),
+ DnsRecord::SOA { domain, ttl, .. } => (domain.clone(), QueryType::SOA, *ttl),
+ DnsRecord::PTR { domain, ttl, .. } => (domain.clone(), QueryType::PTR, *ttl),
+ DnsRecord::MX { domain, ttl, .. } => (domain.clone(), QueryType::MX, *ttl),
+ DnsRecord::TXT { domain, ttl, .. } => (domain.clone(), QueryType::TXT, *ttl),
+ DnsRecord::SRV { domain, ttl, .. } => (domain.clone(), QueryType::SRV, *ttl),
+ DnsRecord::CAA { domain, ttl, .. } => (domain.clone(), QueryType::CAA, *ttl),
+ DnsRecord::AR { domain, ttl, .. } => (domain.clone(), QueryType::AR, *ttl),
+ DnsRecord::AAAAR { domain, ttl, .. } => (domain.clone(), QueryType::AAAAR, *ttl),
}
}
-
}
diff --git a/src/packet/result.rs b/src/dns/packet/result.rs
similarity index 100%
rename from src/packet/result.rs
rename to src/dns/packet/result.rs
diff --git a/src/server/resolver.rs b/src/dns/resolver.rs
similarity index 57%
rename from src/server/resolver.rs
rename to src/dns/resolver.rs
index 464620c..18b5bba 100644
--- a/src/server/resolver.rs
+++ b/src/dns/resolver.rs
@@ -1,11 +1,7 @@
use super::binding::Connection;
-use crate::{
- config::Config,
- packet::{
- query::QueryType, question::DnsQuestion, result::ResultCode, Packet,
- Result,
- }, get_time,
-};
+use super::packet::{query::QueryType, question::DnsQuestion, result::ResultCode, Packet};
+use crate::Result;
+use crate::{config::Config, database::Database, get_time};
use async_recursion::async_recursion;
use moka::future::Cache;
use std::{net::IpAddr, sync::Arc, time::Duration};
@@ -15,6 +11,7 @@ pub struct Resolver {
request_id: u16,
connection: Connection,
config: Arc,
+ database: Arc,
cache: Cache,
}
@@ -23,18 +20,59 @@ impl Resolver {
request_id: u16,
connection: Connection,
config: Arc,
+ database: Arc,
cache: Cache,
) -> Self {
Self {
request_id,
connection,
config,
+ database,
cache,
}
}
- async fn lookup_cache(&mut self, qname: &str, qtype: QueryType) -> Option {
- let question = DnsQuestion::new(qname.to_string(), qtype);
+ async fn lookup_database(&self, question: &DnsQuestion) -> Option {
+ let records = match self
+ .database
+ .get_records(&question.name, question.qtype)
+ .await
+ {
+ Ok(record) => record,
+ Err(err) => {
+ error!("{err}");
+ return None;
+ }
+ };
+
+ if records.is_empty() {
+ return None;
+ }
+
+ let mut packet = Packet::new();
+
+ packet.header.id = self.request_id;
+ packet.header.questions = 1;
+ packet.header.answers = records.len() as u16;
+ packet.header.recursion_desired = true;
+ packet
+ .questions
+ .push(DnsQuestion::new(question.name.to_string(), question.qtype));
+
+ for record in records {
+ packet.answers.push(record);
+ }
+
+ trace!(
+ "Found stored value for {:?} {}",
+ question.qtype,
+ question.name
+ );
+
+ Some(packet)
+ }
+
+ async fn lookup_cache(&self, question: &DnsQuestion) -> Option {
let Some((packet, date)) = self.cache.get(&question) else {
return None
};
@@ -46,16 +84,20 @@ impl Resolver {
let ttl = answer.get_ttl();
if diff > ttl {
self.cache.invalidate(&question).await;
- return None
+ return None;
}
}
- trace!("Found cached value for {qtype:?} {qname}");
+ trace!(
+ "Found cached value for {:?} {}",
+ question.qtype,
+ question.name
+ );
Some(packet)
}
- async fn lookup(&mut self, qname: &str, qtype: QueryType, server: (IpAddr, u16)) -> Packet {
+ async fn lookup_fallback(&self, question: &DnsQuestion, server: (IpAddr, u16)) -> Packet {
let mut packet = Packet::new();
packet.header.id = self.request_id;
@@ -63,7 +105,7 @@ impl Resolver {
packet.header.recursion_desired = true;
packet
.questions
- .push(DnsQuestion::new(qname.to_string(), qtype));
+ .push(DnsQuestion::new(question.name.to_string(), question.qtype));
let packet = match self.connection.request_packet(packet, server).await {
Ok(packet) => packet,
@@ -78,28 +120,47 @@ impl Resolver {
packet
}
+ async fn lookup(&self, question: &DnsQuestion, server: (IpAddr, u16)) -> Packet {
+ if let Some(packet) = self.lookup_cache(question).await {
+ return packet;
+ };
+
+ if let Some(packet) = self.lookup_database(question).await {
+ return packet;
+ };
+
+ trace!(
+ "Attempting lookup of {:?} {} with ns {}",
+ question.qtype,
+ question.name,
+ server.0
+ );
+
+ self.lookup_fallback(question, server).await
+ }
+
#[async_recursion]
async fn recursive_lookup(&mut self, qname: &str, qtype: QueryType) -> Packet {
let question = DnsQuestion::new(qname.to_string(), qtype);
- let mut ns = self.config.get_fallback_ns().clone();
-
- if let Some(packet) = self.lookup_cache(qname, qtype).await { return packet }
+ let mut ns = self.config.dns_fallback.clone();
loop {
- trace!("Attempting lookup of {qtype:?} {qname} with ns {ns}");
-
let ns_copy = ns;
let server = (ns_copy, 53);
- let response = self.lookup(qname, qtype, server).await;
+ let response = self.lookup(&question, server).await;
if !response.answers.is_empty() && response.header.rescode == ResultCode::NOERROR {
- self.cache.insert(question, (response.clone(), get_time())).await;
+ self.cache
+ .insert(question, (response.clone(), get_time()))
+ .await;
return response;
}
if response.header.rescode == ResultCode::NXDOMAIN {
- self.cache.insert(question, (response.clone(), get_time())).await;
+ self.cache
+ .insert(question, (response.clone(), get_time()))
+ .await;
return response;
}
@@ -111,9 +172,11 @@ impl Resolver {
let new_ns_name = match response.get_unresolved_ns(qname) {
Some(x) => x,
None => {
- self.cache.insert(question, (response.clone(), get_time())).await;
- return response
- },
+ self.cache
+ .insert(question, (response.clone(), get_time()))
+ .await;
+ return response;
+ }
};
let recursive_response = self.recursive_lookup(new_ns_name, QueryType::A).await;
@@ -121,7 +184,9 @@ impl Resolver {
if let Some(new_ns) = recursive_response.get_random_a() {
ns = new_ns;
} else {
- self.cache.insert(question, (response.clone(), get_time())).await;
+ self.cache
+ .insert(question, (response.clone(), get_time()))
+ .await;
return response;
}
}
diff --git a/src/dns/server.rs b/src/dns/server.rs
new file mode 100644
index 0000000..65d15df
--- /dev/null
+++ b/src/dns/server.rs
@@ -0,0 +1,85 @@
+use super::{
+ binding::Binding,
+ packet::{question::DnsQuestion, Packet},
+ resolver::Resolver,
+};
+use crate::{config::Config, database::Database, Result};
+use moka::future::Cache;
+use std::{net::SocketAddr, sync::Arc, time::Duration};
+use tokio::task::JoinHandle;
+use tracing::{error, info};
+
+pub struct DnsServer {
+ addr: SocketAddr,
+ config: Arc,
+ database: Arc,
+ cache: Cache,
+}
+
+impl DnsServer {
+ pub async fn new(config: Config, database: Database) -> Result {
+ let addr = format!("[::]:{}", config.dns_port).parse::()?;
+ let cache = Cache::builder()
+ .time_to_live(Duration::from_secs(60 * 60))
+ .max_capacity(config.dns_cache_size)
+ .build();
+
+ info!("Created DNS cache with size of {}", config.dns_cache_size);
+
+ Ok(Self {
+ addr,
+ config: Arc::new(config),
+ database: Arc::new(database),
+ cache,
+ })
+ }
+
+ pub async fn run(&self) -> Result<(JoinHandle<()>, JoinHandle<()>)> {
+ let tcp = Binding::tcp(self.addr).await?;
+ let tcp_handle = self.listen(tcp);
+
+ let udp = Binding::udp(self.addr).await?;
+ let udp_handle = self.listen(udp);
+
+ info!(
+ "Fallback DNS Server is set to: {:?}",
+ self.config.dns_fallback
+ );
+ info!(
+ "Listening for TCP and UDP traffic on [::]:{}",
+ self.config.dns_port
+ );
+
+ Ok((udp_handle, tcp_handle))
+ }
+
+ fn listen(&self, mut binding: Binding) -> JoinHandle<()> {
+ let config = self.config.clone();
+ let database = self.database.clone();
+ let cache = self.cache.clone();
+ tokio::spawn(async move {
+ let mut id = 0;
+ loop {
+ let Ok(connection) = binding.connect().await else { continue };
+ info!("Received request on {}", binding.name());
+
+ let resolver = Resolver::new(
+ id,
+ connection,
+ config.clone(),
+ database.clone(),
+ cache.clone(),
+ );
+
+ let name = binding.name().to_string();
+ tokio::spawn(async move {
+ if let Err(err) = resolver.handle_query().await {
+ error!("{} request {} failed: {:?}", name, id, err);
+ };
+ });
+
+ id += 1;
+ }
+ })
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index c891d50..679e87b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,19 +1,34 @@
-use std::{time::{UNIX_EPOCH, SystemTime}, env, net::IpAddr};
+use std::time::{SystemTime, UNIX_EPOCH};
use config::Config;
-use server::server::Server;
-use tracing::metadata::LevelFilter;
+use database::Database;
+use dotenv::dotenv;
+use dns::server::DnsServer;
+use tracing::{error, metadata::LevelFilter};
use tracing_subscriber::{
filter::filter_fn, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
};
+use web::WebServer;
mod config;
-mod packet;
-mod server;
+mod database;
+mod dns;
+mod web;
+
+type Error = Box;
+pub type Result = std::result::Result;
#[tokio::main]
async fn main() {
+ if let Err(err) = run().await {
+ error!("{err}")
+ };
+}
+
+async fn run() -> Result<()> {
+ dotenv().ok();
+
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
@@ -24,19 +39,20 @@ async fn main() {
)
.init();
- let mut config = Config::new();
+ let config = Config::new();
+ let database = Database::new(config.clone()).await?;
- if let Ok(port) = env::var("PORT").unwrap_or(String::new()).parse::() {
- config.set_port(port);
- }
+ let dns_server = DnsServer::new(config.clone(), database.clone()).await?;
+ let (udp, tcp) = dns_server.run().await?;
- if let Ok(fallback) = env::var("FALLBACK_DNS").unwrap_or(String::new()).parse::() {
- config.set_fallback_ns(&fallback);
- }
+ let web_server = WebServer::new(config, database).await?;
+ let web = web_server.run().await?;
- let server = Server::new(config).await.expect("Failed to bind server");
+ tokio::join!(udp).0?;
+ tokio::join!(tcp).0?;
+ tokio::join!(web).0?;
- server.run().await.unwrap();
+ Ok(())
}
pub fn get_time() -> u64 {
diff --git a/src/server/server.rs b/src/server/server.rs
deleted file mode 100644
index e006bb1..0000000
--- a/src/server/server.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use moka::future::Cache;
-use std::net::SocketAddr;
-use std::sync::Arc;
-use std::time::Duration;
-use tokio::task::JoinHandle;
-use tracing::{error, info};
-
-use crate::config::Config;
-use crate::packet::question::DnsQuestion;
-use crate::packet::{Result, Packet};
-
-use super::binding::Binding;
-use super::resolver::Resolver;
-
-pub struct Server {
- addr: SocketAddr,
- config: Arc,
- cache: Cache,
-}
-
-impl Server {
- pub async fn new(config: Config) -> Result {
- let addr = format!("[::]:{}", config.get_port()).parse::()?;
- let cache = Cache::builder()
- .time_to_live(Duration::from_secs(60 * 60))
- .max_capacity(1_000)
- .build();
- Ok(Self {
- addr,
- config: Arc::new(config),
- cache,
- })
- }
-
- pub async fn run(&self) -> Result<()> {
- let tcp = Binding::tcp(self.addr).await?;
- let tcp_handle = self.listen(tcp);
-
- let udp = Binding::udp(self.addr).await?;
- let udp_handle = self.listen(udp);
-
- info!("Fallback DNS Server is set to: {:?}", self.config.get_fallback_ns());
- info!("Listening for TCP and UDP traffic on [::]:{}", self.config.get_port());
-
- tokio::join!(tcp_handle)
- .0
- .expect("Failed to join tcp thread");
- tokio::join!(udp_handle)
- .0
- .expect("Failed to join udp thread");
- Ok(())
- }
-
- fn listen(&self, mut binding: Binding) -> JoinHandle<()> {
- let config = self.config.clone();
- let cache = self.cache.clone();
- tokio::spawn(async move {
- let mut id = 0;
- loop {
- let Ok(connection) = binding.connect().await else { continue };
- info!("Received request on {}", binding.name());
-
- let resolver = Resolver::new(id, connection, config.clone(), cache.clone());
-
- if let Err(err) = resolver.handle_query().await {
- error!("{} request {} failed: {:?}", binding.name(), id, err);
- };
-
- id += 1;
- }
- })
- }
-}
diff --git a/src/web/api.rs b/src/web/api.rs
new file mode 100644
index 0000000..1fddb5f
--- /dev/null
+++ b/src/web/api.rs
@@ -0,0 +1,156 @@
+use std::net::IpAddr;
+
+use axum::{
+ extract::Query,
+ response::Response,
+ routing::{get, post, put, delete},
+ Extension, Router,
+};
+use moka::future::Cache;
+use rand::distributions::{Alphanumeric, DistString};
+use serde::Deserialize;
+use tower_cookies::{Cookie, Cookies};
+
+use crate::{config::Config, database::Database, dns::packet::record::DnsRecord};
+
+use super::{
+ extract::{Authorized, Body, RequestIp},
+ http::{json, text},
+};
+
+pub fn router() -> Router {
+ Router::new()
+ .route("/login", post(login))
+ .route("/domains", get(list_domains))
+ .route("/domains", delete(delete_domain))
+ .route("/records", get(get_domain))
+ .route("/records", put(add_record))
+}
+
+async fn list_domains(_: Authorized, Extension(database): Extension) -> Response {
+ let domains = match database.get_domains().await {
+ Ok(domains) => domains,
+ Err(err) => return text(500, &format!("{err}")),
+ };
+
+ let Ok(domains) = serde_json::to_string(&domains) else {
+ return text(500, "Failed to fetch domains")
+ };
+
+ json(200, &domains)
+}
+
+#[derive(Deserialize)]
+struct DomainRequest {
+ domain: String,
+}
+
+async fn get_domain(
+ _: Authorized,
+ Extension(database): Extension,
+ Query(query): Query,
+) -> Response {
+ let records = match database.get_domain(&query.domain).await {
+ Ok(records) => records,
+ Err(err) => return text(500, &format!("{err}")),
+ };
+
+ let Ok(records) = serde_json::to_string(&records) else {
+ return text(500, "Failed to fetch records")
+ };
+
+ json(200, &records)
+}
+
+async fn delete_domain(
+ _: Authorized,
+ Extension(database): Extension,
+ Body(body): Body,
+) -> Response {
+
+ let Ok(request) = serde_json::from_str::(&body) else {
+ return text(400, "Missing request parameters")
+ };
+
+ let Ok(domains) = database.get_domains().await else {
+ return text(500, "Failed to delete domain")
+ };
+
+ if !domains.contains(&request.domain) {
+ return text(400, "Domain does not exist")
+ }
+
+ if database.delete_domain(request.domain).await.is_err() {
+ return text(500, "Failed to delete domain")
+ };
+
+ return text(204, "Successfully deleted domain")
+}
+
+async fn add_record(
+ _: Authorized,
+ Extension(database): Extension,
+ Body(body): Body,
+) -> Response {
+ let Ok(record) = serde_json::from_str::(&body) else {
+ return text(400, "Invalid DNS record")
+ };
+
+ let allowed = record.get_qtype().allowed_actions();
+ if !allowed.1 {
+ return text(400, "Not allowed to create record")
+ }
+
+ let Ok(records) = database.get_records(&record.get_domain(), record.get_qtype()).await else {
+ return text(500, "Failed to complete record check");
+ };
+
+ if !records.is_empty() && !allowed.0 {
+ return text(400, "Not allowed to create duplicate record")
+ };
+
+ if records.contains(&record) {
+ return text(400, "Not allowed to create duplicate record")
+ }
+
+ if let Err(err) = database.add_record(record).await {
+ return text(500, &format!("{err}"));
+ }
+
+ return text(201, "Added record to database successfully");
+}
+
+#[derive(Deserialize)]
+struct LoginRequest {
+ user: String,
+ pass: String,
+}
+
+async fn login(
+ Extension(config): Extension,
+ Extension(cache): Extension>,
+ RequestIp(ip): RequestIp,
+ cookies: Cookies,
+ Body(body): Body,
+) -> Response {
+ let Ok(request) = serde_json::from_str::(&body) else {
+ return text(400, "Missing request parameters")
+ };
+
+ if request.user != config.web_user || request.pass != config.web_pass {
+ return text(400, "Invalid credentials");
+ };
+
+ let token = Alphanumeric.sample_string(&mut rand::thread_rng(), 128);
+
+ cache.insert(token.clone(), ip).await;
+
+ let mut cookie = Cookie::new("auth", token);
+ cookie.set_secure(true);
+ cookie.set_http_only(true);
+ cookie.set_path("/");
+
+ cookies.add(cookie);
+
+ text(200, "Successfully logged in")
+}
diff --git a/src/web/extract.rs b/src/web/extract.rs
new file mode 100644
index 0000000..4b6cd7c
--- /dev/null
+++ b/src/web/extract.rs
@@ -0,0 +1,139 @@
+use std::{
+ io::Read,
+ net::{IpAddr, SocketAddr},
+};
+
+use axum::{
+ async_trait,
+ body::HttpBody,
+ extract::{ConnectInfo, FromRequest, FromRequestParts},
+ http::{request::Parts, Request},
+ response::Response,
+ BoxError,
+};
+use bytes::Bytes;
+use moka::future::Cache;
+use tower_cookies::Cookies;
+
+use super::http::text;
+
+pub struct Authorized;
+
+#[async_trait]
+impl FromRequestParts for Authorized
+where
+ S: Send + Sync,
+{
+ type Rejection = Response;
+
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result {
+ let Ok(Some(cookies)) = Option::::from_request_parts(parts, state).await else {
+ return Err(text(403, "No cookies provided"))
+ };
+
+ let Some(token) = cookies.get("auth") else {
+ return Err(text(403, "No auth token provided"))
+ };
+
+ let auth_ip: IpAddr;
+ {
+ let Some(cache) = parts.extensions.get::>() else {
+ return Err(text(500, "Failed to load auth store"))
+ };
+
+ let Some(ip) = cache.get(token.value()) else {
+ return Err(text(401, "Unauthorized"))
+ };
+
+ auth_ip = ip
+ }
+
+ let Ok(Some(RequestIp(ip))) = Option::::from_request_parts(parts, state).await else {
+ return Err(text(403, "You have no ip"))
+ };
+
+ if auth_ip != ip {
+ return Err(text(403, "Auth token does not match current ip"));
+ }
+
+ Ok(Self)
+ }
+}
+
+pub struct RequestIp(pub IpAddr);
+
+#[async_trait]
+impl FromRequestParts for RequestIp
+where
+ S: Send + Sync,
+{
+ type Rejection = Response;
+
+ async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result {
+ let headers = &parts.headers;
+
+ let forwardedfor = headers
+ .get("x-forwarded-for")
+ .and_then(|h| h.to_str().ok())
+ .and_then(|h| {
+ h.split(',')
+ .rev()
+ .find_map(|s| s.trim().parse::().ok())
+ });
+
+ if let Some(forwardedfor) = forwardedfor {
+ return Ok(Self(forwardedfor));
+ }
+
+ let realip = headers
+ .get("x-real-ip")
+ .and_then(|hv| hv.to_str().ok())
+ .and_then(|s| s.parse::().ok());
+
+ if let Some(realip) = realip {
+ return Ok(Self(realip));
+ }
+
+ let realip = headers
+ .get("x-real-ip")
+ .and_then(|hv| hv.to_str().ok())
+ .and_then(|s| s.parse::().ok());
+
+ if let Some(realip) = realip {
+ return Ok(Self(realip));
+ }
+
+ let info = parts.extensions.get::>();
+
+ if let Some(info) = info {
+ return Ok(Self(info.0.ip()));
+ }
+
+ Err(text(403, "You have no ip"))
+ }
+}
+
+pub struct Body(pub String);
+
+#[async_trait]
+impl FromRequest for Body
+where
+ B: HttpBody + Sync + Send + 'static,
+ B::Data: Send,
+ B::Error: Into,
+ S: Send + Sync,
+{
+ type Rejection = Response;
+
+ async fn from_request(req: Request, state: &S) -> Result {
+ let Ok(bytes) = Bytes::from_request(req, state).await else {
+ return Err(text(413, "Payload too large"));
+ };
+
+ let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else {
+ return Err(text(400, "Invalid utf8 body"))
+ };
+
+ Ok(Self(body))
+ }
+}
diff --git a/src/web/file.rs b/src/web/file.rs
new file mode 100644
index 0000000..73ecdc9
--- /dev/null
+++ b/src/web/file.rs
@@ -0,0 +1,31 @@
+use axum::{extract::Path, response::Response};
+
+use super::http::serve;
+
+pub async fn js(Path(path): Path) -> Response {
+ let path = format!("/js/{path}");
+ serve(&path).await
+}
+
+pub async fn css(Path(path): Path) -> Response {
+ let path = format!("/css/{path}");
+ serve(&path).await
+}
+
+pub async fn fonts(Path(path): Path) -> Response {
+ let path = format!("/fonts/{path}");
+ serve(&path).await
+}
+
+pub async fn image(Path(path): Path) -> Response {
+ let path = format!("/image/{path}");
+ serve(&path).await
+}
+
+pub async fn favicon() -> Response {
+ serve("/favicon.ico").await
+}
+
+pub async fn robots() -> Response {
+ serve("/robots.txt").await
+}
diff --git a/src/web/http.rs b/src/web/http.rs
new file mode 100644
index 0000000..7ab1b11
--- /dev/null
+++ b/src/web/http.rs
@@ -0,0 +1,50 @@
+use axum::{
+ body::Body,
+ http::{header::HeaderName, HeaderValue, Request, StatusCode},
+ response::{IntoResponse, Response},
+};
+use std::str;
+use tower::ServiceExt;
+use tower_http::services::ServeFile;
+
+pub fn text(code: u16, msg: &str) -> Response {
+ (status_code(code), msg.to_owned()).into_response()
+}
+
+pub fn json(code: u16, json: &str) -> Response {
+ let mut res = (status_code(code), json.to_owned()).into_response();
+ res.headers_mut().insert(
+ HeaderName::from_static("content-type"),
+ HeaderValue::from_static("application/json"),
+ );
+ res
+}
+
+pub async fn serve(path: &str) -> Response {
+ if !path.chars().any(|c| c == '.') {
+ return text(403, "Invalid file path");
+ }
+
+ let path = format!("public{path}");
+ let file = ServeFile::new(path);
+
+ let Ok(mut res) = file.oneshot(Request::new(Body::empty())).await else {
+ tracing::error!("Error while fetching file");
+ return text(500, "Error when fetching file")
+ };
+
+ if res.status() != StatusCode::OK {
+ return text(404, "File not found");
+ }
+
+ res.headers_mut().insert(
+ HeaderName::from_static("cache-control"),
+ HeaderValue::from_static("max-age=300"),
+ );
+
+ res.into_response()
+}
+
+fn status_code(code: u16) -> StatusCode {
+ StatusCode::from_u16(code).map_or(StatusCode::OK, |code| code)
+}
diff --git a/src/web/mod.rs b/src/web/mod.rs
new file mode 100644
index 0000000..530a3f9
--- /dev/null
+++ b/src/web/mod.rs
@@ -0,0 +1,82 @@
+use std::net::{IpAddr, SocketAddr, TcpListener};
+use std::time::Duration;
+
+use axum::routing::get;
+use axum::{Extension, Router};
+use moka::future::Cache;
+use tokio::task::JoinHandle;
+use tower_cookies::CookieManagerLayer;
+use tracing::{error, info};
+
+use crate::config::Config;
+use crate::database::Database;
+use crate::Result;
+
+mod api;
+mod extract;
+mod file;
+mod http;
+mod pages;
+
+pub struct WebServer {
+ config: Config,
+ database: Database,
+ addr: SocketAddr,
+}
+
+impl WebServer {
+ pub async fn new(config: Config, database: Database) -> Result {
+ let addr = format!("[::]:{}", config.web_port).parse::()?;
+ Ok(Self {
+ config,
+ database,
+ addr,
+ })
+ }
+
+ pub async fn run(&self) -> Result> {
+ let config = self.config.clone();
+ let database = self.database.clone();
+ let listener = TcpListener::bind(self.addr)?;
+
+ info!(
+ "Listening for HTTP traffic on [::]:{}",
+ self.config.web_port
+ );
+
+ let app = Self::router(config, database);
+ let server = axum::Server::from_tcp(listener)?;
+
+ let web_handle = tokio::spawn(async move {
+ if let Err(err) = server
+ .serve(app.into_make_service_with_connect_info::())
+ .await
+ {
+ error!("{err}");
+ }
+ });
+
+ Ok(web_handle)
+ }
+
+ fn router(config: Config, database: Database) -> Router {
+ let cache: Cache = Cache::builder()
+ .time_to_live(Duration::from_secs(60 * 15))
+ .max_capacity(config.dns_cache_size)
+ .build();
+
+ Router::new()
+ .nest("/", pages::router())
+ .nest("/api", api::router())
+ .layer(Extension(config))
+ .layer(Extension(cache))
+ .layer(Extension(database))
+ .layer(CookieManagerLayer::new())
+ .route("/js/*path", get(file::js))
+ .route("/css/*path", get(file::css))
+ .route("/fonts/*path", get(file::fonts))
+ .route("/image/*path", get(file::image))
+ .route("/favicon.ico", get(file::favicon))
+ .route("/robots.txt", get(file::robots))
+ }
+}
diff --git a/src/web/pages.rs b/src/web/pages.rs
new file mode 100644
index 0000000..a8605ef
--- /dev/null
+++ b/src/web/pages.rs
@@ -0,0 +1,31 @@
+use axum::{response::Response, routing::get, Router};
+
+use super::{extract::Authorized, http::serve};
+
+pub fn router() -> Router {
+ Router::new()
+ .route("/", get(root))
+ .route("/login", get(login))
+ .route("/home", get(home))
+ .route("/domain", get(domain))
+}
+
+async fn root(user: Option) -> Response {
+ if user.is_some() {
+ home().await
+ } else {
+ login().await
+ }
+}
+
+async fn login() -> Response {
+ serve("/login.html").await
+}
+
+async fn home() -> Response {
+ serve("/home.html").await
+}
+
+async fn domain() -> Response {
+ serve("/domain.html").await
+}