diff --git a/dns/.gitignore b/dns/.gitignore new file mode 100644 index 0000000..536cf65 --- /dev/null +++ b/dns/.gitignore @@ -0,0 +1,2 @@ +config.toml +target \ No newline at end of file diff --git a/dns/Cargo.lock b/dns/Cargo.lock new file mode 100644 index 0000000..92660ec --- /dev/null +++ b/dns/Cargo.lock @@ -0,0 +1,3688 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-governor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e7b88f3804e01bd4191fdb08650430bbfcb43d3d9b2890064df3551ec7d25b" +dependencies = [ + "actix-http", + "actix-web", + "futures", + "governor", +] + +[[package]] +name = "actix-http" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.22.1", + "bitflags 2.5.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.104", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1cf67dadb19d7c95e5a299e2dda24193b89d5d4f33a3b9800888ede9e19aa32" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "actix-web-httpauth" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456348ed9dcd72a13a1f4a660449fafdecee9ac8205552e286809eb5b0b29bd3" +dependencies = [ + "actix-utils", + "actix-web", + "base64 0.22.1", + "futures-core", + "futures-util", + "log", + "pin-project-lite", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bcrypt" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" +dependencies = [ + "base64 0.22.1", + "blowfish", + "getrandom", + "subtle", + "zeroize", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + +[[package]] +name = "camino" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-verbosity-flag" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9b20c0dd58e4c2e991c8d203bbeb76c11304d1011659686b5b644bc29aa478" +dependencies = [ + "clap", + "log", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "command_attr" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fcc89439e1bb4e19050a9586a767781a3060000d2f3296fd2a40597ad9421c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[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.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[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 = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[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", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "governor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand", + "smallvec", + "spinning_top", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "macros-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb8136cf4bdbfd10cdf683dab195f53892cb3b6433397ef48b211556b9c49dcb" +dependencies = [ + "termcolor", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mini-moka" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "prettytable" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" +dependencies = [ + "csv", + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + +[[package]] +name = "proc-macro2" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.5.0", + "memchr", + "unicase", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +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 = "11.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.61", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_cow" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +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 = "serenity" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" +dependencies = [ + "arrayvec", + "async-trait", + "base64 0.22.1", + "bitflags 2.5.0", + "bytes", + "chrono", + "command_attr", + "dashmap", + "flate2", + "futures", + "fxhash", + "levenshtein", + "mime_guess", + "parking_lot", + "percent-encoding", + "reqwest", + "secrecy", + "serde", + "serde_cow", + "serde_json", + "static_assertions", + "time", + "tokio", + "tokio-tungstenite", + "tracing", + "typemap_rev", + "typesize", + "url", + "uwl", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.21.12", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror 1.0.61", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots 0.25.4", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.5.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.61", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.5.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.61", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl 1.0.61", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "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.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror 1.0.61", + "url", + "utf-8", +] + +[[package]] +name = "typemap_rev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typesize" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da66c62c5b7017a2787e77373c03e6a5aafde77a73bff1ff96e91cd2e128179" +dependencies = [ + "chrono", + "dashmap", + "hashbrown", + "mini-moka", + "parking_lot", + "secrecy", + "serde_json", + "time", + "typesize-derive", + "url", +] + +[[package]] +name = "typesize-derive" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + +[[package]] +name = "uwl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webx_dns" +version = "0.0.1" +dependencies = [ + "actix-governor", + "actix-web", + "actix-web-httpauth", + "anyhow", + "bcrypt", + "chrono", + "clap", + "clap-verbosity-flag", + "colored", + "futures", + "jsonwebtoken", + "log", + "macros-rs", + "pretty_env_logger", + "prettytable", + "rand", + "regex", + "serde", + "serde_json", + "serenity", + "sqlx", + "tokio", + "toml", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/dns/Cargo.toml b/dns/Cargo.toml new file mode 100644 index 0000000..1399166 --- /dev/null +++ b/dns/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "webx_dns" +version = "0.0.1" +edition = "2021" + +[dependencies] +log = "0.4.21" +toml = "0.8.13" +regex = "1.10.4" +jsonwebtoken = "9.2" +bcrypt = "0.15" +serenity = { version = "0.12", features = ["client", "gateway", "rustls_backend", "model"] } +actix-web-httpauth = "0.8" +chrono = { version = "0.4", features = ["serde"] } +colored = "2.1.0" +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid", "migrate", "json"] } +anyhow = "1.0.86" +futures = "0.3.30" +actix-web = "4.6.0" +macros-rs = "1.2.1" +prettytable = "0.10.0" +actix-governor = "0.5.0" +pretty_env_logger = "0.5.0" +clap-verbosity-flag = "2.2.0" + +tokio = { version = "1.38.0", features = ["full"] } +clap = { version = "4.5.4", features = ["derive"] } +rand = { version = "0.8.5", features = ["small_rng"] } +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0" diff --git a/dns/README.md b/dns/README.md new file mode 100644 index 0000000..b52773d --- /dev/null +++ b/dns/README.md @@ -0,0 +1,300 @@ +# Domain Management API + +This is a Domain Management API built with Rust (Actix Web) and PostgreSQL. It provides user authentication, domain registration with Discord approval workflow, and invite-based registration limits. + +## Features + +- 🔐 **JWT Authentication** - Secure user registration and login +- 📝 **Domain Registration** - Submit domains for approval with usage limits +- 🤖 **Discord Integration** - Automatic approval workflow via Discord bot +- 📧 **Invite System** - Users can share registration slots via invite codes +- 🛡️ **Rate Limiting** - Protection against abuse +- 📊 **PostgreSQL Database** - Reliable data storage with migrations + +## Table of Contents + +- [Authentication Endpoints](#authentication-endpoints) + - [POST /auth/register](#post-authregister) + - [POST /auth/login](#post-authlogin) + - [GET /auth/me](#get-authme) + - [POST /auth/invite](#post-authinvite) + - [POST /auth/redeem-invite](#post-authredeem-invite) +- [Domain Endpoints](#domain-endpoints) + - [GET /](#get-) + - [POST /domain](#post-domain) 🔒 + - [GET /domain/:name/:tld](#get-domainnametld) + - [PUT /domain/:name/:tld](#put-domainnametld) 🔒 + - [DELETE /domain/:name/:tld](#delete-domainnametld) 🔒 + - [GET /domains](#get-domains) + - [GET /tlds](#get-tlds) + - [POST /domain/check](#post-domaincheck) + +🔒 = Requires authentication + +## Authentication Endpoints + +### POST /auth/register + +Register a new user account. New users start with 3 domain registrations. + +**Request:** +```json +{ + "username": "myusername", + "password": "mypassword" +} +``` + +**Response:** +```json +{ + "token": "jwt-token-here", + "user": { + "id": 1, + "username": "myusername", + "registrations_remaining": 3, + "created_at": "2023-01-01T00:00:00Z" + } +} +``` + +### POST /auth/login + +Login with existing credentials. + +**Request:** +```json +{ + "username": "myusername", + "password": "mypassword" +} +``` + +**Response:** +```json +{ + "token": "jwt-token-here", + "user": { + "id": 1, + "username": "myusername", + "registrations_remaining": 2, + "created_at": "2023-01-01T00:00:00Z" + } +} +``` + +### GET /auth/me 🔒 + +Get current user information. Requires `Authorization: Bearer ` header. + +**Response:** +```json +{ + "id": 1, + "username": "myusername", + "registrations_remaining": 2, + "created_at": "2023-01-01T00:00:00Z" +} +``` + +### POST /auth/invite 🔒 + +Create an invite code that can be redeemed for 3 additional domain registrations. Requires authentication but does NOT consume any of your registrations. + +**Response:** +```json +{ + "invite_code": "abc123def456" +} +``` + +### POST /auth/redeem-invite 🔒 + +Redeem an invite code to get 3 additional domain registrations. Requires authentication. + +**Request:** +```json +{ + "invite_code": "abc123def456" +} +``` + +**Response:** +```json +{ + "message": "Invite code redeemed successfully", + "registrations_added": 3 +} +``` + +## Domain Endpoints + +### GET / + +Returns a simple message with the available endpoints and rate limits. + +**Response:** + +``` +Hello, world! The available endpoints are: +GET /domains, +GET /domain/{name}/{tld}, +POST /domain, +PUT /domain/{key}, +DELETE /domain/{key}, +GET /tlds. +Ratelimits are as follows: 10 requests per 60s. +``` + +### POST /domain 🔒 + +Submit a domain for approval. Requires authentication and consumes one registration slot. The domain will be sent to Discord for manual approval. + +**Request:** +```json +{ + "tld": "dev", + "ip": "192.168.1.100", + "name": "myawesome" +} +``` + +**Response:** +```json +{ + "message": "Domain registration submitted for approval", + "domain": "myawesome.dev", + "status": "pending" +} +``` + +**Error Responses:** +- `401 Unauthorized` - Missing or invalid JWT token +- `400 Bad Request` - No registrations remaining, invalid domain, or offensive name +- `409 Conflict` - Domain already exists + +### GET /domain/:name/:tld + +Fetch an approved domain by name and TLD. Only returns domains with 'approved' status. + +**Response:** +```json +{ + "tld": "dev", + "name": "myawesome", + "ip": "192.168.1.100" +} +``` + +### PUT /domain/:name/:tld 🔒 + +Update the IP address of your approved domain. You can only update domains you own. + +**Request:** +```json +{ + "ip": "10.0.0.50" +} +``` + +**Response:** +```json +{ + "ip": "10.0.0.50" +} +``` + +### DELETE /domain/:name/:tld 🔒 + +Delete your domain. You can only delete domains you own. + +**Response:** +- `200 OK` - Domain deleted successfully +- `404 Not Found` - Domain not found or not owned by you + +### GET /domains + +Fetch all approved domains with pagination support. Only shows domains with 'approved' status. + +**Query Parameters:** +- `page` (or `p`) - Page number (default: 1) +- `page_size` (or `s`, `size`, `l`, `limit`) - Items per page (default: 15, max: 100) + +**Response:** +```json +{ + "domains": [ + { + "tld": "dev", + "name": "myawesome", + "ip": "192.168.1.100" + } + ], + "page": 1, + "limit": 15 +} +``` + +### GET /tlds + +Get the list of allowed top-level domains. + +**Response:** +```json +["mf", "btw", "fr", "yap", "dev", "scam", "zip", "root", "web", "rizz", "habibi", "sigma", "now", "it", "soy", "lol", "uwu", "ohio", "cat"] +``` + +### POST /domain/check + +Check if domain name(s) are available. + +**Request:** +```json +{ + "name": "myawesome", + "tld": "dev" // Optional - if omitted, checks all TLDs +} +``` + +**Response:** +```json +[ + { + "domain": "myawesome.dev", + "taken": false + } +] +``` + +## Discord Integration + +When a user submits a domain registration, it's automatically sent to a configured Discord channel with: + +- 📝 Domain details (name, TLD, IP, user info) +- ✅ **Approve** button - Marks domain as approved +- ❌ **Deny** button - Opens modal asking for denial reason + +Discord admins can approve or deny registrations directly from Discord. + +## Configuration + +Copy `config.template.toml` to `config.toml` and configure your settings. + +## Rate Limits + +- **Domain Registration**: 5 requests per 10 minutes (per IP) +- **General API**: No specific limits (yet) + +## Domain Registration Limits + +- **User Limit**: Each user has a finite number of domain registrations +- **Usage**: Each domain submission consumes 1 registration from your account +- **Replenishment**: Use invite codes to get more registrations (3 per invite) + +## User Registration & Invites + +- **Registration**: Anyone can register - no invite required +- **New Users**: Start with 3 domain registrations automatically +- **Invite Creation**: Any authenticated user can create invite codes (no cost) +- **Invite Redemption**: Redeem invite codes for 3 additional domain registrations +- **Invite Usage**: Each invite code can only be redeemed once diff --git a/dns/config.template.toml b/dns/config.template.toml new file mode 100644 index 0000000..4811d93 --- /dev/null +++ b/dns/config.template.toml @@ -0,0 +1,35 @@ +# Copy this file to config.toml and update the values as needed + +[server] +address = "127.0.0.1" +port = 8080 + +[server.database] +url = "postgresql://username:password@localhost:5432/domains" + +# Maximum number of database connections +max_connections = 10 + +[settings] +# Available top-level domains +tld_list = [ + "mf", "btw", "fr", "yap", "dev", "scam", "zip", "root", + "web", "rizz", "habibi", "sigma", "now", "it", "soy", + "lol", "uwu", "ohio", "cat" +] + +# Words that are not allowed in domain names +offensive_words = [ + "nigg", "sex", "porn", "igg" +] + +[discord] +# Discord bot token for domain approval notifications +bot_token = "your-discord-bot-token-here" + +# Channel ID where domain approval messages will be sent +channel_id = 0 + +[auth] +# JWT secret key for authentication (change this!) +jwt_secret = "your-very-secure-secret-key-here" \ No newline at end of file diff --git a/dns/migrations/001_initial.sql b/dns/migrations/001_initial.sql new file mode 100644 index 0000000..08535b1 --- /dev/null +++ b/dns/migrations/001_initial.sql @@ -0,0 +1,40 @@ +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + registrations_remaining INTEGER DEFAULT 3, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create invite codes table +CREATE TABLE IF NOT EXISTS invite_codes ( + id SERIAL PRIMARY KEY, + code VARCHAR(32) UNIQUE NOT NULL, + created_by INTEGER REFERENCES users(id), + used_by INTEGER REFERENCES users(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + used_at TIMESTAMP +); + +-- Create domains table +CREATE TABLE IF NOT EXISTS domains ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + tld VARCHAR(20) NOT NULL, + ip VARCHAR(255) NOT NULL, + user_id INTEGER REFERENCES users(id), + status VARCHAR(20) DEFAULT 'pending', + denial_reason TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(name, tld) +); + +-- Create indexes for faster lookups +CREATE INDEX IF NOT EXISTS idx_domains_name_tld ON domains(name, tld); +CREATE INDEX IF NOT EXISTS idx_domains_user_id ON domains(user_id); +CREATE INDEX IF NOT EXISTS idx_domains_status ON domains(status); +CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); +CREATE INDEX IF NOT EXISTS idx_invite_codes_code ON invite_codes(code); \ No newline at end of file diff --git a/dns/migrations/002_remove_email.sql b/dns/migrations/002_remove_email.sql new file mode 100644 index 0000000..9df292c --- /dev/null +++ b/dns/migrations/002_remove_email.sql @@ -0,0 +1,5 @@ +-- Remove email field from users table +ALTER TABLE users DROP COLUMN IF EXISTS email; + +-- Drop email index if it exists +DROP INDEX IF EXISTS idx_users_email; \ No newline at end of file diff --git a/dns/migrations/003_fix_timestamp_types.sql b/dns/migrations/003_fix_timestamp_types.sql new file mode 100644 index 0000000..882a0fd --- /dev/null +++ b/dns/migrations/003_fix_timestamp_types.sql @@ -0,0 +1,5 @@ +-- Fix timestamp columns to use TIMESTAMPTZ instead of TIMESTAMP +ALTER TABLE users ALTER COLUMN created_at TYPE TIMESTAMPTZ; +ALTER TABLE invite_codes ALTER COLUMN created_at TYPE TIMESTAMPTZ; +ALTER TABLE invite_codes ALTER COLUMN used_at TYPE TIMESTAMPTZ; +ALTER TABLE domains ALTER COLUMN created_at TYPE TIMESTAMPTZ; \ No newline at end of file diff --git a/dns/migrations/004_add_domain_invite_codes.sql b/dns/migrations/004_add_domain_invite_codes.sql new file mode 100644 index 0000000..ee32ace --- /dev/null +++ b/dns/migrations/004_add_domain_invite_codes.sql @@ -0,0 +1,17 @@ +-- Add domain_invite_codes field to users table +ALTER TABLE users ADD COLUMN domain_invite_codes INTEGER DEFAULT 3; + +-- Create domain invite codes table for domain-specific invites +CREATE TABLE IF NOT EXISTS domain_invite_codes ( + id SERIAL PRIMARY KEY, + code VARCHAR(32) UNIQUE NOT NULL, + created_by INTEGER REFERENCES users(id), + used_by INTEGER REFERENCES users(id), + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + used_at TIMESTAMPTZ +); + +-- Create indexes for faster lookups +CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_code ON domain_invite_codes(code); +CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_created_by ON domain_invite_codes(created_by); +CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_used_by ON domain_invite_codes(used_by); \ No newline at end of file diff --git a/dns/src/auth.rs b/dns/src/auth.rs new file mode 100644 index 0000000..3f41cb4 --- /dev/null +++ b/dns/src/auth.rs @@ -0,0 +1,95 @@ +use actix_web::{dev::ServiceRequest, web, Error, HttpMessage}; +use actix_web_httpauth::extractors::bearer::BearerAuth; +use actix_web_httpauth::extractors::AuthenticationError; +use actix_web_httpauth::headers::www_authenticate::bearer::Bearer; +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation, Algorithm}; +use serde::{Deserialize, Serialize}; +use bcrypt::{hash, verify, DEFAULT_COST}; +use std::time::{SystemTime, UNIX_EPOCH}; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub user_id: i32, + pub username: String, + pub exp: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RegisterRequest { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginResponse { + pub token: String, + pub user: UserInfo, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserInfo { + pub id: i32, + pub username: String, + pub registrations_remaining: i32, + pub domain_invite_codes: i32, + pub created_at: DateTime, +} + +pub fn generate_jwt(user_id: i32, username: &str, secret: &str) -> Result { + let expiration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + 86400 * 7; // 7 days + + let claims = Claims { + user_id, + username: username.to_string(), + exp: expiration as usize, + }; + + encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref())) +} + +pub fn validate_jwt(token: &str, secret: &str) -> Result { + let mut validation = Validation::new(Algorithm::HS256); + validation.validate_exp = true; + + decode::(token, &DecodingKey::from_secret(secret.as_ref()), &validation) + .map(|token_data| token_data.claims) +} + +pub fn hash_password(password: &str) -> Result { + hash(password, DEFAULT_COST) +} + +pub fn verify_password(password: &str, hash: &str) -> Result { + verify(password, hash) +} + +pub async fn jwt_middleware( + req: ServiceRequest, + credentials: BearerAuth, +) -> Result { + let jwt_secret = req + .app_data::>() + .unwrap() + .as_ref(); + + match validate_jwt(credentials.token(), jwt_secret) { + Ok(claims) => { + req.extensions_mut().insert(claims); + Ok(req) + } + Err(_) => { + let config = AuthenticationError::new(Bearer::default()); + Err((Error::from(config), req)) + } + } +} \ No newline at end of file diff --git a/dns/src/config/file.rs b/dns/src/config/file.rs new file mode 100644 index 0000000..f56839b --- /dev/null +++ b/dns/src/config/file.rs @@ -0,0 +1,15 @@ +use colored::Colorize; +use macros_rs::fmt::{crashln, string}; +use std::fs; + +pub fn read(path: &String) -> T { + let contents = match fs::read_to_string(path) { + Ok(contents) => contents, + Err(err) => crashln!("Cannot find config.\n{}", string!(err).white()), + }; + + match toml::from_str(&contents).map_err(|err| string!(err)) { + Ok(parsed) => parsed, + Err(err) => crashln!("Cannot parse config.\n{}", err.white()), + } +} diff --git a/dns/src/config/mod.rs b/dns/src/config/mod.rs new file mode 100644 index 0000000..6fc9e11 --- /dev/null +++ b/dns/src/config/mod.rs @@ -0,0 +1,77 @@ +mod file; +mod structs; + +use colored::Colorize; +use macros_rs::fmt::{crashln, string}; +use sqlx::{PgPool, Error}; +use std::fs::write; +use structs::{Auth, Database, Discord, Server, Settings}; + +pub use structs::Config; + +impl Config { + pub fn new() -> Self { + let default_offensive_words = vec!["nigg", "sex", "porn", "igg"]; + let default_tld_list = vec![ + "mf", "btw", "fr", "yap", "dev", "scam", "zip", "root", "web", "rizz", "habibi", "sigma", "now", "it", "soy", "lol", "uwu", "ohio", "cat", + ]; + + Config { + config_path: "config.toml".into(), + server: Server { + address: "127.0.0.1".into(), + port: 8080, + database: Database { + url: "postgresql://username:password@localhost/domains".into(), + max_connections: 10, + }, + }, + discord: Discord { + bot_token: "".into(), + channel_id: 0, + }, + auth: Auth { + jwt_secret: "your-secret-key-here".into(), + }, + settings: Settings { + tld_list: default_tld_list.iter().map(|s| s.to_string()).collect(), + offensive_words: default_offensive_words.iter().map(|s| s.to_string()).collect(), + }, + } + } + + pub fn read(&self) -> Self { file::read(&self.config_path) } + pub fn get_address(&self) -> String { format!("{}:{}", self.server.address.clone(), self.server.port) } + pub fn tld_list(&self) -> Vec<&str> { self.settings.tld_list.iter().map(AsRef::as_ref).collect::>() } + pub fn offen_words(&self) -> Vec<&str> { self.settings.offensive_words.iter().map(AsRef::as_ref).collect::>() } + + pub fn set_path(&mut self, config_path: &String) -> &mut Self { + self.config_path = config_path.clone(); + return self; + } + + pub fn write(&self) -> &Self { + let contents = match toml::to_string(self) { + Ok(contents) => contents, + Err(err) => crashln!("Cannot parse config.\n{}", string!(err).white()), + }; + + if let Err(err) = write(&self.config_path, contents) { + crashln!("Error writing config to {}.\n{}", self.config_path, string!(err).white()) + } + + log::info!("Created config: {}", &self.config_path,); + + return self; + } + + pub async fn connect_to_db(&self) -> Result { + let pool = PgPool::connect(&self.server.database.url).await?; + + // Run migrations + sqlx::migrate!("./migrations").run(&pool).await?; + + log::info!("PostgreSQL database connected"); + Ok(pool) + } +} diff --git a/dns/src/config/structs.rs b/dns/src/config/structs.rs new file mode 100644 index 0000000..c53e31c --- /dev/null +++ b/dns/src/config/structs.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Config { + #[serde(skip)] + pub config_path: String, + pub(crate) server: Server, + pub(crate) settings: Settings, + pub(crate) discord: Discord, + pub(crate) auth: Auth, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Server { + pub(crate) address: String, + pub(crate) port: u64, + pub(crate) database: Database, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Database { + pub(crate) url: String, + pub(crate) max_connections: u32, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Settings { + pub(crate) tld_list: Vec, + pub(crate) offensive_words: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Discord { + pub(crate) bot_token: String, + pub(crate) channel_id: u64, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Auth { + pub(crate) jwt_secret: String, +} diff --git a/dns/src/discord_bot.rs b/dns/src/discord_bot.rs new file mode 100644 index 0000000..ee8f32d --- /dev/null +++ b/dns/src/discord_bot.rs @@ -0,0 +1,207 @@ +use serenity::async_trait; +use serenity::all::*; +use serenity::prelude::*; +use sqlx::PgPool; + +pub struct DiscordBot { + pub pool: PgPool, +} + +#[derive(Debug)] +pub struct DomainRegistration { + pub id: i32, + pub domain_name: String, + pub tld: String, + pub ip: String, + pub user_id: i32, + pub username: String, +} + +pub struct BotHandler { + pub pool: PgPool, +} + +#[async_trait] +impl EventHandler for BotHandler { + async fn ready(&self, _: Context, ready: Ready) { + log::info!("Discord bot {} is connected!", ready.user.name); + } + + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + match interaction { + Interaction::Component(component) => { + let custom_id = &component.data.custom_id; + + if custom_id.starts_with("approve_") { + let domain_id: i32 = match custom_id.strip_prefix("approve_").unwrap().parse() { + Ok(id) => id, + Err(_) => { + log::error!("Invalid domain ID in approve button"); + return; + } + }; + + // Update domain status to approved + match sqlx::query("UPDATE domains SET status = 'approved' WHERE id = $1") + .bind(domain_id) + .execute(&self.pool) + .await + { + Ok(_) => { + let response = CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("✅ Domain approved!") + .ephemeral(true) + ); + + if let Err(e) = component.create_response(&ctx.http, response).await { + log::error!("Error responding to interaction: {}", e); + } + } + Err(e) => { + log::error!("Error approving domain: {}", e); + let response = CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("❌ Error approving domain") + .ephemeral(true) + ); + let _ = component.create_response(&ctx.http, response).await; + } + } + } else if custom_id.starts_with("deny_") { + let domain_id = custom_id.strip_prefix("deny_").unwrap(); + + // Create modal for denial reason + let modal = CreateModal::new( + format!("deny_modal_{}", domain_id), + "Deny Domain Registration" + ) + .components(vec![ + CreateActionRow::InputText( + CreateInputText::new( + InputTextStyle::Paragraph, + "Reason", + "reason" + ) + .placeholder("Please provide a reason for denying this domain registration") + .required(true) + ) + ]); + + let response = CreateInteractionResponse::Modal(modal); + + if let Err(e) = component.create_response(&ctx.http, response).await { + log::error!("Error showing modal: {}", e); + } + } + } + Interaction::Modal(modal_submit) => { + if modal_submit.data.custom_id.starts_with("deny_modal_") { + let domain_id: i32 = match modal_submit.data.custom_id.strip_prefix("deny_modal_").unwrap().parse() { + Ok(id) => id, + Err(_) => { + log::error!("Invalid domain ID in deny modal"); + return; + } + }; + + // Get the reason from modal input + let reason = modal_submit.data.components.get(0) + .and_then(|row| row.components.get(0)) + .and_then(|component| { + if let ActionRowComponent::InputText(input) = component { + input.value.as_ref().map(|v| v.as_str()) + } else { + None + } + }) + .unwrap_or("No reason provided"); + + // Update domain status to denied with reason + match sqlx::query("UPDATE domains SET status = 'denied', denial_reason = $1 WHERE id = $2") + .bind(reason) + .bind(domain_id) + .execute(&self.pool) + .await + { + Ok(_) => { + let response = CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("❌ Domain denied!") + .ephemeral(true) + ); + + if let Err(e) = modal_submit.create_response(&ctx.http, response).await { + log::error!("Error responding to modal: {}", e); + } + } + Err(e) => { + log::error!("Error denying domain: {}", e); + let response = CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("❌ Error denying domain") + .ephemeral(true) + ); + let _ = modal_submit.create_response(&ctx.http, response).await; + } + } + } + } + _ => { + // Handle other interaction types if needed + log::debug!("Unhandled interaction type: {:?}", interaction.kind()); + } + } + } +} + +pub async fn send_domain_approval_request( + channel_id: u64, + registration: DomainRegistration, + bot_token: &str, +) -> Result<(), Box> { + let http = serenity::http::Http::new(bot_token); + + let embed = CreateEmbed::new() + .title("New Domain Registration") + .field("Domain", format!("{}.{}", registration.domain_name, registration.tld), true) + .field("IP", ®istration.ip, true) + .field("User", ®istration.username, true) + .field("User ID", registration.user_id.to_string(), true) + .color(0x00ff00); + + let approve_button = CreateButton::new(format!("approve_{}", registration.id)) + .style(ButtonStyle::Success) + .label("✅ Approve"); + + let deny_button = CreateButton::new(format!("deny_{}", registration.id)) + .style(ButtonStyle::Danger) + .label("❌ Deny"); + + let action_row = CreateActionRow::Buttons(vec![approve_button, deny_button]); + + let message = CreateMessage::new() + .embed(embed) + .components(vec![action_row]); + + let channel_id = ChannelId::new(channel_id); + channel_id.send_message(&http, message).await?; + + Ok(()) +} + +pub async fn start_discord_bot(token: String, pool: PgPool) -> Result<(), Box> { + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; + + let mut client = Client::builder(&token, intents) + .event_handler(BotHandler { pool }) + .await?; + + tokio::spawn(async move { + if let Err(e) = client.start().await { + log::error!("Discord bot error: {}", e); + } + }); + + Ok(()) +} \ No newline at end of file diff --git a/dns/src/http.rs b/dns/src/http.rs new file mode 100644 index 0000000..7e14bd7 --- /dev/null +++ b/dns/src/http.rs @@ -0,0 +1,95 @@ +mod auth_routes; +mod helpers; +mod models; +mod ratelimit; +mod routes; + +use crate::{auth::jwt_middleware, config::Config, discord_bot}; +use actix_governor::{Governor, GovernorConfigBuilder}; +use actix_web::{http::Method, web, web::Data, App, HttpRequest, HttpServer}; +use actix_web_httpauth::middleware::HttpAuthentication; +use anyhow::{anyhow, Error}; +use colored::Colorize; +use macros_rs::fmt::{crashln, string}; +use ratelimit::RealIpKeyExtractor; +use std::{net::IpAddr, str::FromStr, time::Duration}; + +// Domain struct is now defined in models.rs + +#[derive(Clone)] +pub(crate) struct AppState { + trusted: IpAddr, + config: Config, + db: sqlx::PgPool, +} + + +#[actix_web::main] +pub async fn start(cli: crate::Cli) -> std::io::Result<()> { + let config = Config::new().set_path(&cli.config).read(); + + let trusted_ip = match IpAddr::from_str(&config.server.address) { + Ok(addr) => addr, + Err(err) => crashln!("Cannot parse address.\n{}", string!(err).white()), + }; + + let governor_builder = GovernorConfigBuilder::default() + .methods(vec![Method::POST]) + .period(Duration::from_secs(600)) + .burst_size(5) + .key_extractor(RealIpKeyExtractor) + .finish() + .unwrap(); + + let db = match config.connect_to_db().await { + Ok(pool) => pool, + Err(err) => crashln!("Failed to connect to PostgreSQL database.\n{}", string!(err).white()), + }; + + // Start Discord bot + if !config.discord.bot_token.is_empty() { + if let Err(e) = discord_bot::start_discord_bot(config.discord.bot_token.clone(), db.clone()).await { + log::error!("Failed to start Discord bot: {}", e); + } + } + + let auth_middleware = HttpAuthentication::bearer(jwt_middleware); + let jwt_secret = config.auth.jwt_secret.clone(); + + let app = move || { + let data = AppState { + db: db.clone(), + trusted: trusted_ip, + config: Config::new().set_path(&cli.config).read(), + }; + + App::new() + .app_data(Data::new(data)) + .app_data(Data::new(jwt_secret.clone())) + // Public routes + .service(routes::index) + .service(routes::get_domain) + .service(routes::get_domains) + .service(routes::get_tlds) + .service(routes::check_domain) + // Auth routes + .service(auth_routes::register) + .service(auth_routes::login) + // Protected routes + .service( + web::scope("") + .wrap(auth_middleware.clone()) + .service(auth_routes::get_user_info) + .service(auth_routes::create_invite) + .service(auth_routes::redeem_invite) + .service(auth_routes::create_domain_invite) + .service(auth_routes::redeem_domain_invite) + .service(routes::update_domain) + .service(routes::delete_domain) + .route("/domain", web::post().to(routes::create_domain).wrap(Governor::new(&governor_builder))) + ) + }; + + log::info!("Listening on {}", config.get_address()); + HttpServer::new(app).bind(config.get_address())?.run().await +} diff --git a/dns/src/http/auth_routes.rs b/dns/src/http/auth_routes.rs new file mode 100644 index 0000000..19f6962 --- /dev/null +++ b/dns/src/http/auth_routes.rs @@ -0,0 +1,547 @@ +use super::{models::*, AppState}; +use crate::auth::*; +use actix_web::{web, HttpResponse, Responder, HttpRequest, HttpMessage}; +use sqlx::Row; +use rand::Rng; +use chrono::Utc; + +#[actix_web::post("/auth/register")] +pub(crate) async fn register( + user: web::Json, + app: web::Data +) -> impl Responder { + let registrations = 3; // New users get 3 registrations by default + + // Hash password + let password_hash = match hash_password(&user.password) { + Ok(hash) => hash, + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to hash password", + error: "HASH_ERROR".into(), + }); + } + }; + + // Create user + let user_result = sqlx::query( + "INSERT INTO users (username, password_hash, registrations_remaining, domain_invite_codes) VALUES ($1, $2, $3, $4) RETURNING id" + ) + .bind(&user.username) + .bind(&password_hash) + .bind(registrations) + .bind(3) // Default 3 domain invite codes + .fetch_one(&app.db) + .await; + + match user_result { + Ok(row) => { + let user_id: i32 = row.get("id"); + + + // Generate JWT + match generate_jwt(user_id, &user.username, &app.config.auth.jwt_secret) { + Ok(token) => { + HttpResponse::Ok().json(LoginResponse { + token, + user: UserInfo { + id: user_id, + username: user.username.clone(), + registrations_remaining: registrations, + domain_invite_codes: 3, + created_at: Utc::now(), + }, + }) + } + Err(_) => HttpResponse::InternalServerError().json(Error { + msg: "Failed to generate token", + error: "TOKEN_ERROR".into(), + }), + } + } + Err(sqlx::Error::Database(db_err)) => { + if db_err.is_unique_violation() { + HttpResponse::Conflict().json(Error { + msg: "Username already exists", + error: "USER_EXISTS".into(), + }) + } else { + HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }) + } + } + Err(_) => HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }), + } +} + +#[actix_web::post("/auth/login")] +pub(crate) async fn login( + credentials: web::Json, + app: web::Data +) -> impl Responder { + match sqlx::query_as::<_, User>( + "SELECT id, username, password_hash, registrations_remaining, domain_invite_codes, created_at FROM users WHERE username = $1" + ) + .bind(&credentials.username) + .fetch_optional(&app.db) + .await + { + Ok(Some(user)) => { + match verify_password(&credentials.password, &user.password_hash) { + Ok(true) => { + match generate_jwt(user.id, &user.username, &app.config.auth.jwt_secret) { + Ok(token) => { + HttpResponse::Ok().json(LoginResponse { + token, + user: UserInfo { + id: user.id, + username: user.username, + registrations_remaining: user.registrations_remaining, + domain_invite_codes: user.domain_invite_codes, + created_at: user.created_at, + }, + }) + } + Err(e) => { + eprintln!("JWT generation error: {:?}", e); + HttpResponse::InternalServerError().json(Error { + msg: "Failed to generate token", + error: "TOKEN_ERROR".into(), + }) + }, + } + } + Ok(false) | Err(_) => { + HttpResponse::Unauthorized().json(Error { + msg: "Invalid credentials", + error: "INVALID_CREDENTIALS".into(), + }) + } + } + } + Ok(None) => { + HttpResponse::Unauthorized().json(Error { + msg: "Invalid credentials", + error: "INVALID_CREDENTIALS".into(), + }) + } + Err(e) => { + eprintln!("Database error: {:?}", e); + HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }) + }, + } +} + +#[actix_web::get("/auth/me")] +pub(crate) async fn get_user_info( + req: HttpRequest, + app: web::Data +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + match sqlx::query_as::<_, User>( + "SELECT id, username, password_hash, registrations_remaining, domain_invite_codes, created_at FROM users WHERE id = $1" + ) + .bind(claims.user_id) + .fetch_optional(&app.db) + .await + { + Ok(Some(user)) => { + HttpResponse::Ok().json(UserInfo { + id: user.id, + username: user.username, + registrations_remaining: user.registrations_remaining, + domain_invite_codes: user.domain_invite_codes, + created_at: user.created_at, + }) + } + Ok(None) => HttpResponse::NotFound().json(Error { + msg: "User not found", + error: "USER_NOT_FOUND".into(), + }), + Err(_) => HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }), + } +} + +#[actix_web::post("/auth/invite")] +pub(crate) async fn create_invite( + req: HttpRequest, + app: web::Data +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + // Generate random invite code + let invite_code: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(16) + .map(char::from) + .collect(); + + // Create invite code (no registration cost) + match sqlx::query( + "INSERT INTO invite_codes (code, created_by) VALUES ($1, $2)" + ) + .bind(&invite_code) + .bind(claims.user_id) + .execute(&app.db) + .await + { + Ok(_) => {}, + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to create invite code", + error: "DB_ERROR".into(), + }); + } + } + + HttpResponse::Ok().json(serde_json::json!({ + "invite_code": invite_code + })) +} + +#[actix_web::post("/auth/redeem-invite")] +pub(crate) async fn redeem_invite( + invite_request: web::Json, + req: HttpRequest, + app: web::Data +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + let invite_code = match invite_request.get("invite_code").and_then(|v| v.as_str()) { + Some(code) => code, + None => { + return HttpResponse::BadRequest().json(Error { + msg: "Invite code is required", + error: "INVITE_CODE_REQUIRED".into(), + }); + } + }; + + // Find and validate invite code + let invite = match sqlx::query_as::<_, InviteCode>( + "SELECT id, code, created_by, used_by, created_at, used_at FROM invite_codes WHERE code = $1 AND used_by IS NULL" + ) + .bind(invite_code) + .fetch_optional(&app.db) + .await + { + Ok(Some(invite)) => invite, + Ok(None) => { + return HttpResponse::BadRequest().json(Error { + msg: "Invalid or already used invite code", + error: "INVALID_INVITE".into(), + }); + } + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + // Start transaction to redeem invite + let mut tx = match app.db.begin().await { + Ok(tx) => tx, + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + // Mark invite as used + if let Err(_) = sqlx::query( + "UPDATE invite_codes SET used_by = $1, used_at = CURRENT_TIMESTAMP WHERE id = $2" + ) + .bind(claims.user_id) + .bind(invite.id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to redeem invite code", + error: "DB_ERROR".into(), + }); + } + + // Add registrations to user (3 registrations per invite) + if let Err(_) = sqlx::query( + "UPDATE users SET registrations_remaining = registrations_remaining + 3 WHERE id = $1" + ) + .bind(claims.user_id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to add registrations", + error: "DB_ERROR".into(), + }); + } + + if let Err(_) = tx.commit().await { + return HttpResponse::InternalServerError().json(Error { + msg: "Transaction failed", + error: "DB_ERROR".into(), + }); + } + + HttpResponse::Ok().json(serde_json::json!({ + "message": "Invite code redeemed successfully", + "registrations_added": 3 + })) +} + +#[actix_web::post("/auth/domain-invite")] +pub(crate) async fn create_domain_invite( + req: HttpRequest, + app: web::Data +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + // Check if user has domain invite codes remaining + let user = match sqlx::query_as::<_, User>( + "SELECT id, username, password_hash, registrations_remaining, domain_invite_codes, created_at FROM users WHERE id = $1" + ) + .bind(claims.user_id) + .fetch_optional(&app.db) + .await + { + Ok(Some(user)) => user, + Ok(None) => { + return HttpResponse::NotFound().json(Error { + msg: "User not found", + error: "USER_NOT_FOUND".into(), + }); + } + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + if user.domain_invite_codes <= 0 { + return HttpResponse::BadRequest().json(Error { + msg: "No domain invite codes remaining", + error: "NO_DOMAIN_INVITES".into(), + }); + } + + // Generate random domain invite code + let invite_code: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(16) + .map(char::from) + .collect(); + + // Start transaction + let mut tx = match app.db.begin().await { + Ok(tx) => tx, + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + // Create domain invite code + if let Err(_) = sqlx::query( + "INSERT INTO domain_invite_codes (code, created_by) VALUES ($1, $2)" + ) + .bind(&invite_code) + .bind(claims.user_id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to create domain invite code", + error: "DB_ERROR".into(), + }); + } + + // Decrease user's domain invite codes + if let Err(_) = sqlx::query( + "UPDATE users SET domain_invite_codes = domain_invite_codes - 1 WHERE id = $1" + ) + .bind(claims.user_id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to update domain invite codes", + error: "DB_ERROR".into(), + }); + } + + if let Err(_) = tx.commit().await { + return HttpResponse::InternalServerError().json(Error { + msg: "Transaction failed", + error: "DB_ERROR".into(), + }); + } + + HttpResponse::Ok().json(serde_json::json!({ + "domain_invite_code": invite_code + })) +} + +#[actix_web::post("/auth/redeem-domain-invite")] +pub(crate) async fn redeem_domain_invite( + invite_request: web::Json, + req: HttpRequest, + app: web::Data +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + let invite_code = match invite_request.get("domain_invite_code").and_then(|v| v.as_str()) { + Some(code) => code, + None => { + return HttpResponse::BadRequest().json(Error { + msg: "Domain invite code is required", + error: "DOMAIN_INVITE_CODE_REQUIRED".into(), + }); + } + }; + + // Find and validate domain invite code + let invite = match sqlx::query_as::<_, DomainInviteCode>( + "SELECT id, code, created_by, used_by, created_at, used_at FROM domain_invite_codes WHERE code = $1 AND used_by IS NULL" + ) + .bind(invite_code) + .fetch_optional(&app.db) + .await + { + Ok(Some(invite)) => invite, + Ok(None) => { + return HttpResponse::BadRequest().json(Error { + msg: "Invalid or already used domain invite code", + error: "INVALID_DOMAIN_INVITE".into(), + }); + } + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + // Start transaction to redeem invite + let mut tx = match app.db.begin().await { + Ok(tx) => tx, + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + // Mark domain invite as used + if let Err(_) = sqlx::query( + "UPDATE domain_invite_codes SET used_by = $1, used_at = CURRENT_TIMESTAMP WHERE id = $2" + ) + .bind(claims.user_id) + .bind(invite.id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to redeem domain invite code", + error: "DB_ERROR".into(), + }); + } + + // Add domain invite codes to user (1 per domain invite) + if let Err(_) = sqlx::query( + "UPDATE users SET domain_invite_codes = domain_invite_codes + 1 WHERE id = $1" + ) + .bind(claims.user_id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to add domain invite codes", + error: "DB_ERROR".into(), + }); + } + + if let Err(_) = tx.commit().await { + return HttpResponse::InternalServerError().json(Error { + msg: "Transaction failed", + error: "DB_ERROR".into(), + }); + } + + HttpResponse::Ok().json(serde_json::json!({ + "message": "Domain invite code redeemed successfully", + "domain_invite_codes_added": 1 + })) +} \ No newline at end of file diff --git a/dns/src/http/helpers.rs b/dns/src/http/helpers.rs new file mode 100644 index 0000000..0a697df --- /dev/null +++ b/dns/src/http/helpers.rs @@ -0,0 +1,72 @@ +use super::{models::*, AppState}; +use actix_web::{web::Data, HttpResponse}; +use regex::Regex; +use serde::Deserialize; +use std::net::{Ipv4Addr, Ipv6Addr}; + +pub fn validate_ip(domain: &Domain) -> Result<(), HttpResponse> { + let valid_url = Regex::new(r"(?i)\bhttps?://[-a-z0-9+&@#/%?=~_|!:,.;]*[-a-z0-9+&@#/%=~_|]").unwrap(); + + let is_valid_ip = domain.ip.parse::().is_ok() || domain.ip.parse::().is_ok(); + let is_valid_url = valid_url.is_match(&domain.ip); + + if is_valid_ip || is_valid_url { + if domain.name.len() <= 100 { + Ok(()) + } else { + Err(HttpResponse::BadRequest().json(Error { + msg: "Failed to create domain", + error: "Invalid name, non-existent TLD, or name too long (100 chars).".into(), + })) + } + } else { + Err(HttpResponse::BadRequest().json(Error { + msg: "Failed to create domain", + error: "Invalid name, non-existent TLD, or name too long (100 chars).".into(), + })) + } +} + +pub fn deserialize_lowercase<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + Ok(s.to_lowercase()) +} + +pub async fn is_domain_taken(name: &str, tld: Option<&str>, app: Data) -> Vec { + if let Some(tld) = tld { + let count: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM domains WHERE name = ? AND tld = ?" + ) + .bind(name) + .bind(tld) + .fetch_one(&app.db) + .await + .unwrap_or(0); + + vec![DomainList { + taken: count > 0, + domain: format!("{}.{}", name, tld), + }] + } else { + let mut result = Vec::new(); + for tld in &*app.config.tld_list() { + let count: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM domains WHERE name = ? AND tld = ?" + ) + .bind(name) + .bind(tld) + .fetch_one(&app.db) + .await + .unwrap_or(0); + + result.push(DomainList { + taken: count > 0, + domain: format!("{}.{}", name, tld), + }); + } + result + } +} diff --git a/dns/src/http/models.rs b/dns/src/http/models.rs new file mode 100644 index 0000000..3837bde --- /dev/null +++ b/dns/src/http/models.rs @@ -0,0 +1,105 @@ +use super::helpers::deserialize_lowercase; +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, types::chrono::{DateTime, Utc}}; +use chrono; + +#[derive(Clone, Debug, Deserialize, Serialize, FromRow)] +pub struct Domain { + #[serde(skip_deserializing)] + pub(crate) id: Option, + pub(crate) ip: String, + #[serde(deserialize_with = "deserialize_lowercase")] + pub(crate) tld: String, + #[serde(deserialize_with = "deserialize_lowercase")] + pub(crate) name: String, + #[serde(skip_deserializing)] + pub(crate) user_id: Option, + #[serde(skip_deserializing)] + pub(crate) status: Option, + #[serde(skip_deserializing)] + pub(crate) denial_reason: Option, + #[serde(skip_deserializing)] + pub(crate) created_at: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize, FromRow)] +pub struct User { + pub(crate) id: i32, + pub(crate) username: String, + pub(crate) password_hash: String, + pub(crate) registrations_remaining: i32, + pub(crate) domain_invite_codes: i32, + pub(crate) created_at: DateTime, +} + +#[derive(Clone, Debug, Deserialize, Serialize, FromRow)] +pub struct InviteCode { + pub(crate) id: i32, + pub(crate) code: String, + pub(crate) created_by: Option, + pub(crate) used_by: Option, + pub(crate) created_at: DateTime, + pub(crate) used_at: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize, FromRow)] +pub struct DomainInviteCode { + pub(crate) id: i32, + pub(crate) code: String, + pub(crate) created_by: Option, + pub(crate) used_by: Option, + pub(crate) created_at: DateTime, + pub(crate) used_at: Option>, +} + +#[derive(Debug, Serialize)] +pub(crate) struct ResponseDomain { + pub(crate) tld: String, + pub(crate) ip: String, + pub(crate) name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct UpdateDomain { + pub(crate) ip: String, +} + +#[derive(Serialize)] +pub(crate) struct Error { + pub(crate) msg: &'static str, + pub(crate) error: String, +} + +#[derive(Serialize)] +pub(crate) struct Ratelimit { + pub(crate) msg: String, + pub(crate) error: &'static str, + pub(crate) after: u64, +} + +#[derive(Deserialize)] +pub(crate) struct PaginationParams { + #[serde(alias = "p", alias = "doc")] + pub(crate) page: Option, + #[serde(alias = "s", alias = "size", alias = "l", alias = "limit")] + pub(crate) page_size: Option, +} + +#[derive(Serialize)] +pub(crate) struct PaginationResponse { + pub(crate) domains: Vec, + pub(crate) page: u32, + pub(crate) limit: u32, +} + +#[derive(Deserialize)] +pub(crate) struct DomainQuery { + pub(crate) name: String, + pub(crate) tld: Option, +} + +#[derive(Serialize)] +pub(crate) struct DomainList { + pub(crate) domain: String, + pub(crate) taken: bool, +} diff --git a/dns/src/http/ratelimit.rs b/dns/src/http/ratelimit.rs new file mode 100644 index 0000000..fb809df --- /dev/null +++ b/dns/src/http/ratelimit.rs @@ -0,0 +1,61 @@ +use super::models::Ratelimit; +use actix_web::{dev::ServiceRequest, web, HttpResponse, HttpResponseBuilder}; + +use std::{ + net::{IpAddr, SocketAddr}, + str::FromStr, + time::{SystemTime, UNIX_EPOCH}, +}; + +use actix_governor::{ + governor::clock::{Clock, DefaultClock, QuantaInstant}, + governor::NotUntil, + KeyExtractor, SimpleKeyExtractionError, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct RealIpKeyExtractor; + +impl KeyExtractor for RealIpKeyExtractor { + type Key = IpAddr; + type KeyExtractionError = SimpleKeyExtractionError<&'static str>; + + fn extract(&self, req: &ServiceRequest) -> Result { + let reverse_proxy_ip = req + .app_data::>() + .map(|ip| ip.get_ref().trusted.to_owned()) + .unwrap_or_else(|| IpAddr::from_str("0.0.0.0").unwrap()); + + let peer_ip = req.peer_addr().map(|socket| socket.ip()); + let connection_info = req.connection_info(); + + match peer_ip { + Some(peer) if peer == reverse_proxy_ip => connection_info + .realip_remote_addr() + .ok_or_else(|| SimpleKeyExtractionError::new("Could not extract real IP address from request")) + .and_then(|str| { + SocketAddr::from_str(str) + .map(|socket| socket.ip()) + .or_else(|_| IpAddr::from_str(str)) + .map_err(|_| SimpleKeyExtractionError::new("Could not extract real IP address from request")) + }), + _ => connection_info + .peer_addr() + .ok_or_else(|| SimpleKeyExtractionError::new("Could not extract peer IP address from request")) + .and_then(|str| SocketAddr::from_str(str).map_err(|_| SimpleKeyExtractionError::new("Could not extract peer IP address from request"))) + .map(|socket| socket.ip()), + } + } + + fn exceed_rate_limit_response(&self, negative: &NotUntil, mut response: HttpResponseBuilder) -> HttpResponse { + let current_unix_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs(); + let wait_time = negative.wait_time_from(DefaultClock::default().now()).as_secs(); + let wait_time_unix = current_unix_timestamp + negative.wait_time_from(DefaultClock::default().now()).as_secs(); + + response.json(Ratelimit { + after: wait_time_unix, + error: "ratelimited_endpoint", + msg: format!("Too many requests, try again in {wait_time}s"), + }) + } +} diff --git a/dns/src/http/routes.rs b/dns/src/http/routes.rs new file mode 100644 index 0000000..80219d2 --- /dev/null +++ b/dns/src/http/routes.rs @@ -0,0 +1,398 @@ +use super::{models::*, AppState}; +use crate::{auth::Claims, discord_bot::*, http::helpers}; +use std::env; + +use actix_web::{ + web::{self, Data}, + HttpRequest, HttpResponse, Responder, HttpMessage, +}; + +#[actix_web::get("/")] +pub(crate) async fn index() -> impl Responder { + HttpResponse::Ok().body(format!( + "GurtDNS v{}!\n\nThe available endpoints are:\n\n - [GET] /domains\n - [GET] /domain/{{name}}/{{tld}}\n - [POST] /domain\n - [PUT] /domain/{{key}}\n - [DELETE] /domain/{{key}}\n - [GET] /tlds\n\nRatelimits are as follows: 5 requests per 10 minutes on `[POST] /domain`.\n\nCode link: https://github.com/outpoot/gurted",env!("CARGO_PKG_VERSION")), + ) +} + +pub(crate) async fn create_logic(domain: Domain, user_id: i32, app: &AppState) -> Result { + helpers::validate_ip(&domain)?; + + if !app.config.tld_list().contains(&domain.tld.as_str()) || !domain.name.chars().all(|c| c.is_alphabetic() || c == '-') || domain.name.len() > 24 { + return Err(HttpResponse::BadRequest().json(Error { + msg: "Failed to create domain", + error: "Invalid name, non-existent TLD, or name too long (24 chars).".into(), + })); + } + + if app.config.offen_words().iter().any(|word| domain.name.contains(word)) { + return Err(HttpResponse::BadRequest().json(Error { + msg: "Failed to create domain", + error: "The given domain name is offensive.".into(), + })); + } + + let existing_count: i64 = sqlx::query_scalar( + "SELECT COUNT(*) FROM domains WHERE name = ? AND tld = ?" + ) + .bind(&domain.name) + .bind(&domain.tld) + .fetch_one(&app.db) + .await + .map_err(|_| HttpResponse::InternalServerError().finish())?; + + if existing_count > 0 { + return Err(HttpResponse::Conflict().finish()); + } + + sqlx::query( + "INSERT INTO domains (name, tld, ip, user_id, status) VALUES ($1, $2, $3, $4, 'pending')" + ) + .bind(&domain.name) + .bind(&domain.tld) + .bind(&domain.ip) + .bind(user_id) + .execute(&app.db) + .await + .map_err(|_| HttpResponse::Conflict().finish())?; + + Ok(domain) +} + +pub(crate) async fn create_domain( + domain: web::Json, + app: Data, + req: HttpRequest +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + // Check if user has registrations or domain invite codes remaining + let (user_registrations, user_domain_invites): (i32, i32) = match sqlx::query_as::<_, (i32, i32)>( + "SELECT registrations_remaining, domain_invite_codes FROM users WHERE id = $1" + ) + .bind(claims.user_id) + .fetch_one(&app.db) + .await + { + Ok((registrations, domain_invites)) => (registrations, domain_invites), + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + if user_registrations <= 0 && user_domain_invites <= 0 { + return HttpResponse::BadRequest().json(Error { + msg: "No domain registrations or domain invite codes remaining", + error: "NO_REGISTRATIONS_OR_INVITES".into(), + }); + } + + let domain = domain.into_inner(); + + match create_logic(domain.clone(), claims.user_id, app.as_ref()).await { + Ok(_) => { + // Start transaction for domain registration + let mut tx = match app.db.begin().await { + Ok(tx) => tx, + Err(_) => { + return HttpResponse::InternalServerError().json(Error { + msg: "Database error", + error: "DB_ERROR".into(), + }); + } + }; + + // Get the created domain ID + let domain_id: i32 = match sqlx::query_scalar( + "SELECT id FROM domains WHERE name = $1 AND tld = $2 AND user_id = $3 ORDER BY created_at DESC LIMIT 1" + ) + .bind(&domain.name) + .bind(&domain.tld) + .bind(claims.user_id) + .fetch_one(&mut *tx) + .await + { + Ok(id) => id, + Err(_) => { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to get domain ID", + error: "DB_ERROR".into(), + }); + } + }; + + // Get user's current domain invite codes + let user_domain_invites: i32 = match sqlx::query_scalar( + "SELECT domain_invite_codes FROM users WHERE id = $1" + ) + .bind(claims.user_id) + .fetch_one(&mut *tx) + .await + { + Ok(invites) => invites, + Err(_) => { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Database error getting user domain invites", + error: "DB_ERROR".into(), + }); + } + }; + + // Auto-consume domain invite code if available, otherwise use registration + if user_domain_invites > 0 { + // Use domain invite code + if let Err(_) = sqlx::query( + "UPDATE users SET domain_invite_codes = domain_invite_codes - 1 WHERE id = $1" + ) + .bind(claims.user_id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to consume domain invite code", + error: "DB_ERROR".into(), + }); + } + } else { + // Use regular registration + if let Err(_) = sqlx::query( + "UPDATE users SET registrations_remaining = registrations_remaining - 1 WHERE id = $1" + ) + .bind(claims.user_id) + .execute(&mut *tx) + .await + { + let _ = tx.rollback().await; + return HttpResponse::InternalServerError().json(Error { + msg: "Failed to consume registration", + error: "DB_ERROR".into(), + }); + } + } + + // Commit the transaction + if let Err(_) = tx.commit().await { + return HttpResponse::InternalServerError().json(Error { + msg: "Transaction failed", + error: "DB_ERROR".into(), + }); + } + + // Send to Discord for approval + let registration = DomainRegistration { + id: domain_id, + domain_name: domain.name.clone(), + tld: domain.tld.clone(), + ip: domain.ip.clone(), + user_id: claims.user_id, + username: claims.username.clone(), + }; + + let bot_token = app.config.discord.bot_token.clone(); + let channel_id = app.config.discord.channel_id; + + tokio::spawn(async move { + if let Err(e) = send_domain_approval_request( + channel_id, + registration, + &bot_token, + ).await { + log::error!("Failed to send Discord message: {}", e); + } + }); + + HttpResponse::Ok().json(serde_json::json!({ + "message": "Domain registration submitted for approval", + "domain": format!("{}.{}", domain.name, domain.tld), + "status": "pending" + })) + } + Err(error) => error, + } +} + + +#[actix_web::get("/domain/{name}/{tld}")] +pub(crate) async fn get_domain(path: web::Path<(String, String)>, app: Data) -> impl Responder { + let (name, tld) = path.into_inner(); + + match sqlx::query_as::<_, Domain>( + "SELECT id, name, tld, ip, user_id, status, denial_reason, created_at FROM domains WHERE name = $1 AND tld = $2 AND status = 'approved'" + ) + .bind(&name) + .bind(&tld) + .fetch_optional(&app.db) + .await + { + Ok(Some(domain)) => HttpResponse::Ok().json(ResponseDomain { + tld: domain.tld, + name: domain.name, + ip: domain.ip, + }), + Ok(None) => HttpResponse::NotFound().finish(), + Err(_) => HttpResponse::InternalServerError().finish(), + } +} + +#[actix_web::put("/domain/{name}/{tld}")] +pub(crate) async fn update_domain( + path: web::Path<(String, String)>, + domain_update: web::Json, + app: Data, + req: HttpRequest +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + let (name, tld) = path.into_inner(); + + match sqlx::query( + "UPDATE domains SET ip = $1 WHERE name = $2 AND tld = $3 AND user_id = $4 AND status = 'approved'" + ) + .bind(&domain_update.ip) + .bind(&name) + .bind(&tld) + .bind(claims.user_id) + .execute(&app.db) + .await + { + Ok(result) => { + if result.rows_affected() == 1 { + HttpResponse::Ok().json(domain_update.into_inner()) + } else { + HttpResponse::NotFound().json(Error { + msg: "Domain not found or not owned by user", + error: "DOMAIN_NOT_FOUND".into(), + }) + } + } + Err(_) => HttpResponse::InternalServerError().finish(), + } +} + +#[actix_web::delete("/domain/{name}/{tld}")] +pub(crate) async fn delete_domain( + path: web::Path<(String, String)>, + app: Data, + req: HttpRequest +) -> impl Responder { + let extensions = req.extensions(); + let claims = match extensions.get::() { + Some(claims) => claims, + None => { + return HttpResponse::Unauthorized().json(Error { + msg: "Authentication required", + error: "AUTH_REQUIRED".into(), + }); + } + }; + + let (name, tld) = path.into_inner(); + + match sqlx::query( + "DELETE FROM domains WHERE name = $1 AND tld = $2 AND user_id = $3" + ) + .bind(&name) + .bind(&tld) + .bind(claims.user_id) + .execute(&app.db) + .await + { + Ok(result) => { + if result.rows_affected() == 1 { + HttpResponse::Ok().finish() + } else { + HttpResponse::NotFound().json(Error { + msg: "Domain not found or not owned by user", + error: "DOMAIN_NOT_FOUND".into(), + }) + } + } + Err(_) => HttpResponse::InternalServerError().finish(), + } +} + +#[actix_web::post("/domain/check")] +pub(crate) async fn check_domain(query: web::Json, app: Data) -> impl Responder { + let DomainQuery { name, tld } = query.into_inner(); + + let result = helpers::is_domain_taken(&name, tld.as_deref(), app).await; + HttpResponse::Ok().json(result) +} + +#[actix_web::get("/domains")] +pub(crate) async fn get_domains(query: web::Query, app: Data) -> impl Responder { + let page = query.page.unwrap_or(1); + let limit = query.page_size.unwrap_or(15); + + if page == 0 || limit == 0 { + return HttpResponse::BadRequest().json(Error { + msg: "page_size or page must be greater than 0", + error: "Invalid pagination parameters".into(), + }); + } + + if limit > 100 { + return HttpResponse::BadRequest().json(Error { + msg: "page_size must be greater than 0 and less than or equal to 100", + error: "Invalid pagination parameters".into(), + }); + } + + let offset = (page - 1) * limit; + + match sqlx::query_as::<_, Domain>( + "SELECT id, name, tld, ip, user_id, status, denial_reason, created_at FROM domains WHERE status = 'approved' ORDER BY created_at DESC LIMIT $1 OFFSET $2" + ) + .bind(limit as i64) + .bind(offset as i64) + .fetch_all(&app.db) + .await + { + Ok(domains) => { + let response_domains: Vec = domains + .into_iter() + .map(|domain| ResponseDomain { + tld: domain.tld, + name: domain.name, + ip: domain.ip, + }) + .collect(); + + HttpResponse::Ok().json(PaginationResponse { + domains: response_domains, + page, + limit, + }) + } + Err(err) => HttpResponse::InternalServerError().json(Error { + msg: "Failed to fetch domains", + error: err.to_string(), + }), + } +} + +#[actix_web::get("/tlds")] +pub(crate) async fn get_tlds(app: Data) -> impl Responder { HttpResponse::Ok().json(&*app.config.tld_list()) } diff --git a/dns/src/main.rs b/dns/src/main.rs new file mode 100644 index 0000000..ad1a9d5 --- /dev/null +++ b/dns/src/main.rs @@ -0,0 +1,55 @@ +mod config; +mod http; +mod secret; +mod auth; +mod discord_bot; + +use clap::{Parser, Subcommand}; +use clap_verbosity_flag::{LogLevel, Verbosity}; +use config::Config; +use macros_rs::fs::file_exists; + +#[derive(Copy, Clone, Debug, Default)] +struct Info; +impl LogLevel for Info { + fn default() -> Option { Some(log::Level::Info) } +} + +#[derive(Parser)] +struct Cli { + #[command(subcommand)] + command: Commands, + #[clap(flatten)] + verbose: Verbosity, + #[arg(global = true, short, long, default_value_t = String::from("config.toml"), help = "config path")] + config: String, +} + +#[derive(Subcommand)] +enum Commands { + /// Start the daemon + Start, +} + + +fn main() { + let cli = Cli::parse(); + let mut env = pretty_env_logger::formatted_builder(); + let level = cli.verbose.log_level_filter(); + + env.filter_level(level).init(); + + if !file_exists!(&cli.config) { + Config::new().set_path(&cli.config).write(); + log::warn!("Written initial config, please configure database URL"); + std::process::exit(1); + } + + match &cli.command { + Commands::Start => { + if let Err(err) = http::start(cli) { + log::error!("Failed to start server: {err}") + } + } + }; +} diff --git a/dns/src/secret.rs b/dns/src/secret.rs new file mode 100644 index 0000000..ce3e4b7 --- /dev/null +++ b/dns/src/secret.rs @@ -0,0 +1,26 @@ +use rand::{rngs::StdRng, Rng, SeedableRng}; + +pub fn generate(size: usize) -> String { + const ALPHABET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let alphabet_len = ALPHABET.len(); + let mask = alphabet_len.next_power_of_two() - 1; + let step = 8 * size / 5; + + let mut id = String::with_capacity(size); + let mut rng = StdRng::from_entropy(); + + while id.len() < size { + let bytes: Vec = (0..step).map(|_| rng.gen::()).collect::>(); + + id.extend( + bytes + .iter() + .map(|&byte| (byte as usize) & mask) + .filter_map(|index| ALPHABET.get(index).copied()) + .take(size - id.len()) + .map(char::from), + ); + } + + id +}