diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +/target diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb10b84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +# Ignore the server configration file (Used for local development only) +config.toml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7983c05 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4461 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[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 = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +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 = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[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.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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 = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.68", + "syn_derive", +] + +[[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 = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" +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 = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[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.5", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "aes-gcm", + "base64 0.22.1", + "hmac", + "percent-encoding", + "rand", + "sha2", + "subtle", + "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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +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-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +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 = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-new" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[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 = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[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 = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etag" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3d0661a2ccddc26cba0b834e9b717959ed6fdd76c7129ee159c170a875bf44" +dependencies = [ + "str-buf", + "xxhash-rust", +] + +[[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 = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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 = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[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-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.68", +] + +[[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-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 = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[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 = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[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 = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "log", + "rustls 0.23.10", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[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 0.14.5", + "serde", +] + +[[package]] +name = "inherent" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "elliptic-curve", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[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 = "logcall" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb377687ad730d661a29ee17ca44644d388c72f0d8a83d69a75744a6041b1c3" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime-infer" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91caed19dd472bc88bcd063571df18153529d49301a1918f4cf37f42332bee2e" +dependencies = [ + "mime", + "unicase", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "moka" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0d88686dc561d743b40de8269b26eaf0dc58781bde087b0984646602021d08" +dependencies = [ + "async-lock", + "async-trait", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "event-listener 5.3.1", + "futures-util", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +dependencies = [ + "serde", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[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 = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +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.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ouroboros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "oxidetalis" +version = "0.1.0" +dependencies = [ + "chrono", + "derive-new", + "log", + "logcall", + "oxidetalis_config", + "oxidetalis_core", + "oxidetalis_entities", + "oxidetalis_migrations", + "pretty_env_logger", + "salvo", + "sea-orm", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "oxidetalis_config" +version = "0.1.0" +dependencies = [ + "base58", + "clap", + "derivative", + "log", + "oxidetalis_core", + "salvo-oapi", + "salvo_core", + "serde", + "thiserror", + "toml", +] + +[[package]] +name = "oxidetalis_core" +version = "0.1.0" +dependencies = [ + "aes", + "base58", + "cbc", + "hex", + "hmac", + "k256", + "log", + "logcall", + "rand", + "salvo-oapi", + "salvo_core", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "oxidetalis_entities" +version = "0.1.0" +dependencies = [ + "sea-orm", +] + +[[package]] +name = "oxidetalis_migrations" +version = "0.1.0" +dependencies = [ + "sea-orm", + "sea-orm-migration", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[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 0.5.2", + "smallvec", + "windows-targets 0.52.5", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +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" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[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 = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[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 = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", + "version_check", + "yansi", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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.6.0", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.1.2", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "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", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-embed" +version = "8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.68", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[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.6.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 = [ + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[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-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "salvo" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591a41ac90e952ca622b6d012129336a5aca71dd1ba03d05fd1ab4b3ee476125" +dependencies = [ + "salvo-jwt-auth", + "salvo-oapi", + "salvo-proxy", + "salvo-rate-limiter", + "salvo_core", + "salvo_extra", +] + +[[package]] +name = "salvo-jwt-auth" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2140ece82476b670589f1ca90639277e5be9d3a7780b1b15cb801b6851a2de6" +dependencies = [ + "base64 0.22.1", + "bytes", + "http-body-util", + "hyper-rustls", + "hyper-util", + "jsonwebtoken", + "salvo_core", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "salvo-oapi" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db73bd4f5cb788d7ae1cf40e0123cc1b16db70f53568f151cd89e8f797e23a4a" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "futures-util", + "http", + "indexmap", + "inventory", + "mime-infer", + "once_cell", + "parking_lot", + "regex", + "rust-embed", + "rust_decimal", + "salvo-oapi-macros", + "salvo_core", + "serde", + "serde_json", + "serde_yaml", + "smallvec", + "thiserror", + "time", + "tokio", + "tracing", + "ulid", + "url", + "uuid", +] + +[[package]] +name = "salvo-oapi-macros" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5949e8880aed053da8f120ca6bc1a771c58d03bfaa147c49113943c12592227e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "regex", + "salvo-serde-util", + "syn 2.0.68", +] + +[[package]] +name = "salvo-proxy" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc8a85fff7503a3fc0673ea5e32f656c76147d345ea2b7ab5e9dff5e1b8cd0b" +dependencies = [ + "fastrand", + "futures-util", + "hyper", + "hyper-rustls", + "hyper-util", + "percent-encoding", + "reqwest", + "salvo_core", + "tokio", + "tracing", +] + +[[package]] +name = "salvo-rate-limiter" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdea460c6b4b09a1f01ea74fa43dbf2da0f52944463cea651f6612769de964eb" +dependencies = [ + "moka", + "salvo_core", + "serde", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "salvo-serde-util" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170e19c27303e855beb58a28226712b3ede149a4a019098747b123bbbb222d48" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "salvo_core" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0a64876b439b2e5176459f5159be6131321a11554db1cfd34204580b9f5b12" +dependencies = [ + "async-trait", + "base64 0.22.1", + "brotli", + "bytes", + "cookie", + "encoding_rs", + "enumflags2", + "flate2", + "form_urlencoded", + "futures-channel", + "futures-util", + "headers", + "http", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap", + "mime", + "mime-infer", + "multer", + "multimap", + "native-tls", + "nix", + "once_cell", + "parking_lot", + "percent-encoding", + "pin-project", + "rand", + "regex", + "salvo_macros", + "serde", + "serde-xml-rs", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tempfile", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tracing", + "url", + "zstd", +] + +[[package]] +name = "salvo_extra" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca626969c04dca7acbfe2fc10a1d44d597c6b032eeb2ee21204c2aad240aff19" +dependencies = [ + "base64 0.22.1", + "etag", + "futures-util", + "hyper", + "pin-project", + "salvo_core", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "ulid", +] + +[[package]] +name = "salvo_macros" +version = "0.68.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5a8f4a082a91529f085bc53bdad4e7e0fbe419f7188f5dd6265acdaf990876" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "salvo-serde-util", + "syn 2.0.68", +] + +[[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 = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[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 = "sea-bae" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "sea-orm" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8814e37dc25de54398ee62228323657520b7f29713b8e238649385dbe473ee0" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "futures", + "log", + "ouroboros", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde", + "serde_json", + "sqlx", + "strum", + "thiserror", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-macros" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e115c6b078e013aa963cc2d38c196c2c40b05f03d0ac872fe06b6e0d5265603" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "sea-bae", + "syn 2.0.68", + "unicode-ident", +] + +[[package]] +name = "sea-orm-migration" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8269bc6ff71afd6b78aa4333ac237a69eebd2cdb439036291e64fb4b8db23c" +dependencies = [ + "async-trait", + "futures", + "sea-orm", + "sea-schema", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sea-query" +version = "0.30.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4166a1e072292d46dc91f31617c2a1cdaf55a8be4b5c9f4bf2ba248e3ac4999b" +dependencies = [ + "bigdecimal", + "chrono", + "derivative", + "inherent", + "ordered-float", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "sea-query-binder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a82fcb49253abcb45cdcb2adf92956060ec0928635eb21b4f7a6d8f25ab0bc" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.68", + "thiserror", +] + +[[package]] +name = "sea-schema" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d148608012d25222442d1ebbfafd1228dbc5221baf4ec35596494e27a2394e" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f686050f76bffc4f635cda8aea6df5548666b830b52387e8bc7de11056d11e" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-xml-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "serde_json" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +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 = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[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 = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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 = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[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.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +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 0.8.11", + "atoi", + "bigdecimal", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener 2.5.3", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rust_decimal", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots", +] + +[[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", + "bigdecimal", + "bitflags 2.6.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", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "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", + "bigdecimal", + "bitflags 2.6.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "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", + "time", + "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 = "str-buf" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceb97b7225c713c2fd4db0153cb6b3cab244eb37900c3f634ed4d43310d8c34" + +[[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 = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[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.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[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 = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[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", +] + +[[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.68", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +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", + "pin-project-lite", + "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.68", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.10", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.14", +] + +[[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.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.13", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[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 = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "triomphe" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369" + +[[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.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "log", + "rand", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ulid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34778c17965aa2a08913b57e1f34db9b4a63f5de31768b55bf20d2795f921259" +dependencies = [ + "getrandom", + "rand", + "web-time", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +dependencies = [ + "getrandom", + "serde", +] + +[[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.68", + "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.68", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +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 = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +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 = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "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.5", +] + +[[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.5", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + +[[package]] +name = "xxhash-rust" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[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.68", +] + +[[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.11+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..98d8fc7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.package] +authors = ["OxideTalis Developers "] +readme = "README.md" +repository = "https://git.4rs.nl/oxidetalis/oxidetalis" +version = "0.1.0" +rust-version = "1.76.0" + +[workspace.dependencies] +# Local crates +oxidetalis_core = { path = "crates/oxidetalis_core" } +oxidetalis_config = { path = "crates/oxidetalis_config" } +oxidetalis_migrations = { path = "crates/oxidetalis_migrations" } +oxidetalis_entities = { path = "crates/oxidetalis_entities" } +# Shered dependencies +base58 = "0.2.0" +serde = "1.0.203" +thiserror = "1.0.61" +log = "0.4.21" +logcall = "0.1.9" +chrono = "0.4.38" +sea-orm = { version = "0.12.15", features = ["with-chrono", "macros"] } +salvo_core = { version = "0.68.3", default-features = false } +salvo-oapi = { version = "0.68.3", features = ["rapidoc","redoc","scalar","swagger-ui"] } + +[profile.release] +strip = true # Automatically strip symbols from the binary. diff --git a/crates/oxidetalis/Cargo.toml b/crates/oxidetalis/Cargo.toml new file mode 100644 index 0000000..035cf8b --- /dev/null +++ b/crates/oxidetalis/Cargo.toml @@ -0,0 +1,75 @@ +[package] +name = "oxidetalis" +description = "OxideTalis Messaging Protocol homeserver" +edition = "2021" +license = "AGPL-3.0-or-later" +authors.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +rust-version.workspace = true + + +[dependencies] +oxidetalis_core = { workspace = true } +oxidetalis_config = { workspace = true } +oxidetalis_entities = { workspace = true } +oxidetalis_migrations = { workspace = true } +log = { workspace = true } +logcall = { workspace = true } +sea-orm = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +chrono = { workspace = true } +salvo = { version = "0.68.2", features = ["affix", "logging", "native-tls", "oapi", "rate-limiter", "websocket"] } +tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] } +derive-new = "0.6.0" +pretty_env_logger = "0.5.0" +serde_json = "1.0.117" + +[lints.rust] +unsafe_code = "deny" +missing_docs = "warn" + + +[lints.clippy] +wildcard_imports = "deny" +manual_let_else = "deny" +match_bool = "deny" +match_on_vec_items = "deny" +or_fun_call = "deny" +panic = "deny" +unwrap_used = "deny" + +missing_assert_message = "warn" +missing_const_for_fn = "warn" +missing_errors_doc = "warn" +absolute_paths = "warn" +cast_lossless = "warn" +clone_on_ref_ptr = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +default_trait_access = "warn" +empty_enum_variants_with_brackets = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +empty_structs_with_brackets = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_iter_loop = "warn" +filetype_is_file = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "warn" +format_push_string = "warn" +future_not_send = "warn" +if_not_else = "warn" +if_then_some_else_none = "warn" +implicit_clone = "warn" +inconsistent_struct_constructor = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +manual_is_variant_and = "warn" +option_if_let_else = "warn" +option_option = "warn" diff --git a/crates/oxidetalis/Dockerfile b/crates/oxidetalis/Dockerfile new file mode 100644 index 0000000..de75af1 --- /dev/null +++ b/crates/oxidetalis/Dockerfile @@ -0,0 +1,15 @@ +FROM rust:1.70.0-slim-bullseye as builder + +WORKDIR /builder + +COPY ./ ./ + +RUN cargo build --release + +FROM debian:bullseye-slim + +WORKDIR /app + +COPY --from=builder /builder/target/release/oxidetalis . + +CMD ["./oxidetalis"] diff --git a/crates/oxidetalis/src/database/mod.rs b/crates/oxidetalis/src/database/mod.rs new file mode 100644 index 0000000..a9578ae --- /dev/null +++ b/crates/oxidetalis/src/database/mod.rs @@ -0,0 +1,21 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Database utilities for the OxideTalis homeserver. + +mod user; + +pub use user::*; diff --git a/crates/oxidetalis/src/database/user.rs b/crates/oxidetalis/src/database/user.rs new file mode 100644 index 0000000..7f52764 --- /dev/null +++ b/crates/oxidetalis/src/database/user.rs @@ -0,0 +1,60 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Functions for interacting with the user table in the database. + +use logcall::logcall; +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::errors::{ApiError, ApiResult}; + +pub trait UserTableExt { + /// Returns true if there is users in the database + async fn users_exists_in_database(&self) -> ApiResult; + /// Register new user + async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()>; +} + +impl UserTableExt for DatabaseConnection { + #[logcall] + async fn users_exists_in_database(&self) -> ApiResult { + UserEntity::find() + .one(self) + .await + .map_err(Into::into) + .map(|u| u.is_some()) + } + + #[logcall] + async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()> { + if let Err(err) = (UserActiveModel { + public_key: Set(public_key.to_string()), + is_admin: Set(is_admin), + ..Default::default() + }) + .save(self) + .await + { + if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() { + return Err(ApiError::DuplicatedUser); + } + } + + Ok(()) + } +} diff --git a/crates/oxidetalis/src/errors.rs b/crates/oxidetalis/src/errors.rs new file mode 100644 index 0000000..ed419ad --- /dev/null +++ b/crates/oxidetalis/src/errors.rs @@ -0,0 +1,81 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use salvo::{ + http::StatusCode, + oapi::{Components as OapiComponents, EndpointOutRegister, Operation as OapiOperation}, + Response, + Scribe, +}; + +use crate::{routes::write_json_body, schemas::MessageSchema}; + +/// Result type of the homeserver +#[allow(clippy::absolute_paths)] +pub(crate) type Result = std::result::Result; +#[allow(clippy::absolute_paths)] +pub type ApiResult = std::result::Result; + +/// The homeserver errors +#[derive(Debug, thiserror::Error)] +pub(crate) enum Error { + #[error("Database Error: {0}")] + Database(#[from] sea_orm::DbErr), + #[error("{0}")] + Configuration(#[from] oxidetalis_config::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum ApiError { + /// Error from the database (500 Internal Server Error) + #[error("Internal server error")] + SeaOrm(#[from] sea_orm::DbErr), + /// The server registration is closed (403 Forbidden) + #[error("Server registration is closed")] + RegistrationClosed, + /// The entered public key is already registered (400 Bad Request) + #[error("The entered public key is already registered")] + DuplicatedUser, + /// The user enterd tow different public keys + /// one in the header and other in the request body + /// (400 Bad Request) + #[error("TODO")] + TwoDifferentKeys, +} + +impl ApiError { + /// Status code of the error + pub const fn status_code(&self) -> StatusCode { + match self { + Self::SeaOrm(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::RegistrationClosed => StatusCode::FORBIDDEN, + Self::DuplicatedUser | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST, + } + } +} + +impl EndpointOutRegister for ApiError { + fn register(_: &mut OapiComponents, _: &mut OapiOperation) {} +} + +impl Scribe for ApiError { + fn render(self, res: &mut Response) { + log::error!("Error: {self}"); + + res.status_code(self.status_code()); + write_json_body(res, MessageSchema::new(self.to_string())); + } +} diff --git a/crates/oxidetalis/src/extensions.rs b/crates/oxidetalis/src/extensions.rs new file mode 100644 index 0000000..ad91c2d --- /dev/null +++ b/crates/oxidetalis/src/extensions.rs @@ -0,0 +1,91 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::sync::Arc; + +use chrono::Utc; +use oxidetalis_config::Config; +use salvo::Depot; +use sea_orm::DatabaseConnection; + +use crate::{routes::DEPOT_NONCE_CACHE_SIZE, NonceCache}; + +/// Extension trait for the Depot. +pub trait DepotExt { + /// Returns the database connection + fn db_conn(&self) -> &DatabaseConnection; + /// Returns the server configuration + fn config(&self) -> &Config; + /// Retutns the nonce cache + fn nonce_cache(&self) -> &NonceCache; + /// Returns the size of the nonce cache + fn nonce_cache_size(&self) -> &usize; +} + +/// Extension trait for the nonce cache. +pub trait NonceCacheExt { + /// Add a nonce to the cache, returns `true` if the nonce is added, `false` + /// if the nonce is already exist in the cache. + fn add_nonce(&self, nonce: &[u8; 16], limit: &usize) -> bool; +} + +impl DepotExt for Depot { + fn db_conn(&self) -> &DatabaseConnection { + self.obtain::>() + .expect("Database connection not found") + } + + fn config(&self) -> &Config { + self.obtain::>().expect("Config not found") + } + + fn nonce_cache(&self) -> &NonceCache { + self.obtain::>() + .expect("Nonce cache not found") + } + + fn nonce_cache_size(&self) -> &usize { + let s: &Arc = self + .get(DEPOT_NONCE_CACHE_SIZE) + .expect("Nonce cache size not found"); + s.as_ref() + } +} + +impl NonceCacheExt for &NonceCache { + fn add_nonce(&self, nonce: &[u8; 16], limit: &usize) -> bool { + let mut cache = self.lock().expect("Nonce cache lock poisoned, aborting..."); + let now = Utc::now().timestamp(); + cache.retain(|_, time| (now - *time) < 30); + + if &cache.len() >= limit { + log::warn!("Nonce cache limit reached, clearing 10% of the cache"); + let num_to_remove = limit / 10; + let keys: Vec<[u8; 16]> = cache.keys().copied().collect(); + for key in keys.iter().take(num_to_remove) { + cache.remove(key); + } + } + + // We can use insert directly, but it's will update the value if the key is + // already exist so we need to check if the key is already exist or not + if cache.contains_key(nonce) { + return false; + } + cache.insert(*nonce, now); + true + } +} diff --git a/crates/oxidetalis/src/main.rs b/crates/oxidetalis/src/main.rs new file mode 100644 index 0000000..b770e18 --- /dev/null +++ b/crates/oxidetalis/src/main.rs @@ -0,0 +1,77 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#![doc = include_str!("../../../README.md")] +#![warn(missing_docs, unsafe_code)] + +use std::{collections::HashMap, process::ExitCode, sync::Mutex}; + +use oxidetalis_config::{CliArgs, Parser}; +use oxidetalis_migrations::MigratorTrait; +use salvo::{conn::TcpListener, Listener, Server}; + +mod database; +mod errors; +mod extensions; +mod middlewares; +mod routes; +mod schemas; +mod utils; + +/// Nonce cache type, used to store nonces for a certain amount of time +pub type NonceCache = Mutex>; + +async fn try_main() -> errors::Result<()> { + pretty_env_logger::init_timed(); + + log::info!("Parsing configuration"); + let config = oxidetalis_config::Config::load(CliArgs::parse())?; + log::info!("Configuration parsed successfully"); + log::info!("Connecting to the database"); + let connection = sea_orm::Database::connect(utils::postgres_url(&config.postgresdb)).await?; + log::info!("Connected to the database successfully"); + oxidetalis_migrations::Migrator::up(&connection, None).await?; + log::info!("Migrations applied successfully"); + + let local_addr = format!("{}:{}", config.server.host, config.server.port); + let acceptor = TcpListener::new(&local_addr).bind().await; + log::info!("Server listening on http://{local_addr}"); + if config.openapi.enable { + log::info!( + "The openapi schema is available at http://{local_addr}{}", + config.openapi.path + ); + log::info!( + "The openapi viewer is available at http://{local_addr}{}", + config.openapi.viewer_path + ); + } + log::info!("Server version: {}", env!("CARGO_PKG_VERSION")); + Server::new(acceptor) + .serve(routes::service(connection, &config)) + .await; + Ok(()) +} + +#[tokio::main] +async fn main() -> ExitCode { + if let Err(err) = try_main().await { + eprintln!("{err}"); + log::error!("{err}"); + return ExitCode::FAILURE; + } + ExitCode::SUCCESS +} diff --git a/crates/oxidetalis/src/middlewares/mod.rs b/crates/oxidetalis/src/middlewares/mod.rs new file mode 100644 index 0000000..959b892 --- /dev/null +++ b/crates/oxidetalis/src/middlewares/mod.rs @@ -0,0 +1,58 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Middlewares for the OxideTalis homeserver. + +use salvo::{ + handler, + http::{header, HeaderValue, StatusCode}, + FlowCtrl, + Request, + Response, +}; + +mod public_key; +mod signature; + +pub use public_key::*; +pub use signature::*; + +use crate::{routes::write_json_body, schemas::MessageSchema}; + +/// Add server headers to the response and request. +#[handler] +pub async fn add_server_headers(req: &mut Request, res: &mut Response) { + let res_headers = res.headers_mut(); + let req_headers = req.headers_mut(); + res_headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ); + // Insert the accept header for salvo, so it returns JSON if there is error + req_headers.insert(header::ACCEPT, HeaderValue::from_static("application/json")); +} + +/// Write an errror message in the response +pub fn write_error( + res: &mut Response, + ctrl: &mut FlowCtrl, + message: String, + status_code: StatusCode, +) { + res.status_code(status_code); + write_json_body(res, MessageSchema::new(message)); + ctrl.skip_rest(); +} diff --git a/crates/oxidetalis/src/middlewares/public_key.rs b/crates/oxidetalis/src/middlewares/public_key.rs new file mode 100644 index 0000000..939dc63 --- /dev/null +++ b/crates/oxidetalis/src/middlewares/public_key.rs @@ -0,0 +1,32 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Request sender public key middleware. + +use salvo::{handler, http::StatusCode, FlowCtrl, Request, Response}; + +use crate::utils; + +/// Middleware to check the public key of the request sender. +/// +/// If the public key is valid, the request will be passed to the next handler. +/// Otherwise, a 401 Unauthorized response will be returned. +#[handler] +pub async fn public_key_check(req: &mut Request, res: &mut Response, ctrl: &mut FlowCtrl) { + if let Err(err) = utils::extract_public_key(req) { + super::write_error(res, ctrl, err.to_string(), StatusCode::UNAUTHORIZED) + } +} diff --git a/crates/oxidetalis/src/middlewares/signature.rs b/crates/oxidetalis/src/middlewares/signature.rs new file mode 100644 index 0000000..b75dfbe --- /dev/null +++ b/crates/oxidetalis/src/middlewares/signature.rs @@ -0,0 +1,86 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Request signature middleware. + +use salvo::{ + handler, + http::{Body, StatusCode}, + Depot, + FlowCtrl, + Request, + Response, +}; + +use crate::{extensions::DepotExt, utils}; + +/// Middleware to check the signature of the request. +/// +/// If the signature is valid, the request will be passed to the next handler. +/// Otherwise, a 401 Unauthorized response will be returned. +#[handler] +pub async fn signature_check( + req: &mut Request, + res: &mut Response, + depot: &mut Depot, + ctrl: &mut FlowCtrl, +) { + const UNAUTHORIZED: StatusCode = StatusCode::UNAUTHORIZED; + let mut write_err = + |message: &str, status_code| super::write_error(res, ctrl, message.to_owned(), status_code); + + if req.body().is_end_stream() { + write_err( + "Request body is empty, the signature need a signed body", + UNAUTHORIZED, + ); + return; + } + let json_body = match req.parse_json::().await { + Ok(j) => j.to_string(), + Err(err) => { + write_err(&err.to_string(), UNAUTHORIZED); + return; + } + }; + let signature = match utils::extract_signature(req) { + Ok(s) => s, + Err(err) => { + write_err(&err.to_string(), UNAUTHORIZED); + return; + } + }; + + let sender_public_key = match utils::extract_public_key(req) { + Ok(k) => k, + Err(err) => { + write_err(&err.to_string(), UNAUTHORIZED); + return; + } + }; + + if !utils::is_valid_nonce(&signature, depot.nonce_cache(), depot.nonce_cache_size()) + || !utils::is_valid_signature( + &sender_public_key, + &depot.config().server.private_key, + &signature, + json_body.as_bytes(), + ) + { + write_err("Invalid signature", UNAUTHORIZED); + return; + } +} diff --git a/crates/oxidetalis/src/routes/mod.rs b/crates/oxidetalis/src/routes/mod.rs new file mode 100644 index 0000000..47fddb8 --- /dev/null +++ b/crates/oxidetalis/src/routes/mod.rs @@ -0,0 +1,162 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::{env, mem}; + +use oxidetalis_config::Config; +use salvo::http::ResBody; +use salvo::oapi::{Info, License}; +use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer}; +use salvo::{catcher::Catcher, logging::Logger, prelude::*}; + +use crate::schemas::MessageSchema; +use crate::{middlewares, NonceCache}; + +mod user; + +/// Size of each entry in the nonce cache +pub(crate) const NONCE_ENTRY_SIZE: usize = mem::size_of::<[u8; 16]>() + mem::size_of::(); +/// Size of the hashmap itself without the entrys (48 bytes) +pub(crate) const HASH_MAP_SIZE: usize = mem::size_of::>(); +/// Name of the nonce cache size in the depot +pub(crate) const DEPOT_NONCE_CACHE_SIZE: &str = "NONCE_CACHE_SIZE"; + +pub fn write_json_body(res: &mut Response, json_body: impl serde::Serialize) { + res.write_body(serde_json::to_string(&json_body).expect("Json serialization can't be fail")) + .ok(); +} + +#[handler] +async fn handle404(res: &mut Response, ctrl: &mut FlowCtrl) { + if res.status_code == Some(StatusCode::NOT_FOUND) { + write_json_body(res, MessageSchema::new("Not Found".to_owned())); + ctrl.skip_rest(); + } +} + +#[handler] +async fn handle_server_errors(res: &mut Response, ctrl: &mut FlowCtrl) { + log::info!("New response catched: {res:#?}"); + if matches!(res.status_code, Some(status) if !status.is_success()) { + if res.status_code == Some(StatusCode::TOO_MANY_REQUESTS) { + write_json_body( + res, + MessageSchema::new("Too many requests, please try again later".to_owned()), + ); + ctrl.skip_rest(); + } else if let ResBody::Error(err) = &res.body { + log::error!("Error: {err}"); + write_json_body( + res, + MessageSchema::new(format!( + "{}, {}: {}", + err.name, + err.brief.trim_end_matches('.'), + err.cause + .as_deref() + .map_or_else(|| "".to_owned(), ToString::to_string) + .trim_end_matches('.') + .split(':') + .last() + .unwrap_or_default() + .trim() + )), + ); + ctrl.skip_rest(); + } else { + log::warn!("Unknown error uncatched: {res:#?}"); + } + } else { + log::warn!("Unknown response uncatched: {res:#?}"); + } +} + +/// Hoop a middleware if the condation is true +fn hoop_if(router: Router, middleware: impl Handler, condation: bool) -> Router { + if condation { + router.hoop(middleware) + } else { + router + } +} + +/// Create the ratelimit middleware +fn ratelimiter( + config: &Config, +) -> RateLimiter, RemoteIpIssuer, BasicQuota> { + RateLimiter::new( + FixedGuard::new(), + MokaStore::::new(), + RemoteIpIssuer, + BasicQuota::set_seconds(config.ratelimit.limit, config.ratelimit.period_secs as i64), + ) + .add_headers(true) +} + +/// Create openapi and its viewer, and unshift them +fn route_openapi(config: &Config, router: Router) -> Router { + if config.openapi.enable { + let openapi = OpenApi::new(&config.openapi.title, env!("CARGO_PKG_VERSION")) + .info( + Info::new(&config.openapi.title, env!("CARGO_PKG_VERSION")) + .license( + License::new("AGPL-3.0-or-later").url("https://gnu.org/licenses/agpl-3.0"), + ) + .description(&config.openapi.description), + ) + .merge_router(&router); + let router = router + .unshift(openapi.into_router(&config.openapi.path)) + .unshift(config.openapi.viewer.into_router(config)); + return router; + } + router +} + +pub fn service(conn: sea_orm::DatabaseConnection, config: &Config) -> Service { + let nonce_cache_size = config.server.nonce_cache_size.as_bytes(); + let nonce_cache: NonceCache = Mutex::new(HashMap::with_capacity( + (nonce_cache_size - HASH_MAP_SIZE) / NONCE_ENTRY_SIZE, + )); + log::info!( + "Nonce cache created with a capacity of {} ({})", + (nonce_cache_size - HASH_MAP_SIZE) / NONCE_ENTRY_SIZE, + config.server.nonce_cache_size + ); + + let router = Router::new() + .push(Router::with_path("user").push(user::route())) + .hoop(middlewares::add_server_headers) + .hoop(Logger::new()) + .hoop( + affix::inject(Arc::new(conn)) + .insert(DEPOT_NONCE_CACHE_SIZE, Arc::new(nonce_cache_size)) + .inject(Arc::new(config.clone())) + .inject(Arc::new(nonce_cache)), + ); + + let router = hoop_if(router, ratelimiter(config), config.ratelimit.enable); + let router = route_openapi(config, router); + + Service::new(router).catcher( + Catcher::default() + .hoop(middlewares::add_server_headers) + .hoop(handle404) + .hoop(handle_server_errors), + ) +} diff --git a/crates/oxidetalis/src/routes/user.rs b/crates/oxidetalis/src/routes/user.rs new file mode 100644 index 0000000..ccefe73 --- /dev/null +++ b/crates/oxidetalis/src/routes/user.rs @@ -0,0 +1,88 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! REST API endpoints for user management + +use oxidetalis_core::types::{PublicKey, Signature}; +use salvo::{ + http::StatusCode, + oapi::{endpoint, extract::JsonBody}, + Depot, + Request, + Router, + Writer, +}; + +use crate::{ + database::UserTableExt, + errors::{ApiError, ApiResult}, + extensions::DepotExt, + middlewares, + schemas::{EmptySchema, MessageSchema, RegisterUserBody}, + utils, +}; + +#[endpoint( + operation_id = "register", + tags("User"), + responses( + (status_code = 201, description = "User registered"), + (status_code = 403, description = "Server registration is closed", content_type = "application/json", body = MessageSchema), + (status_code = 400, description = "The public key in the header is not the same as the key in the body", content_type = "application/json", body = MessageSchema), + (status_code = 400, description = "The entered public key is already registered", content_type = "application/json", body = MessageSchema), + (status_code = 401, description = "The entered signature is invalid", content_type = "application/json", body = MessageSchema), + (status_code = 401, description = "The entered public key is invalid", content_type = "application/json", body = MessageSchema), + (status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema), + (status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema), + ), + parameters( + ("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"), + ("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"), + ), +)] +pub async fn register( + body: JsonBody, + req: &Request, + depot: &mut Depot, +) -> ApiResult { + let body = body.into_inner(); + let db = depot.db_conn(); + let config = depot.config(); + + if utils::extract_public_key(req).expect("Public key should be checked in the middleware") + != body.public_key + { + return Err(ApiError::TwoDifferentKeys); + } + + if !db.users_exists_in_database().await? { + db.register_user(&body.public_key, true).await?; + } else if config.register.enable { + db.register_user(&body.public_key, false).await?; + } else { + return Err(ApiError::RegistrationClosed); + } + + Ok(EmptySchema::new(StatusCode::CREATED)) +} + +/// The route of the endpoints of this module +pub fn route() -> Router { + Router::new() + .push(Router::with_path("register").post(register)) + .hoop(middlewares::public_key_check) + .hoop(middlewares::signature_check) +} diff --git a/crates/oxidetalis/src/schemas/mod.rs b/crates/oxidetalis/src/schemas/mod.rs new file mode 100644 index 0000000..a068073 --- /dev/null +++ b/crates/oxidetalis/src/schemas/mod.rs @@ -0,0 +1,71 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use salvo::{ + http::{header, StatusCode}, + oapi::{ + Components as OapiComponents, + EndpointOutRegister, + Operation as OapiOperation, + ToSchema, + }, + Response, + Scribe, +}; +use serde::{Deserialize, Serialize}; + +mod user; + +pub use user::*; + +/// Json message schema, used for returning messages to the client, the message +/// must be human readable. +/// +/// # Example +/// ```json +/// { +/// "message": "Message" +/// } +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, derive_new::new)] +#[salvo(schema(name = MessageSchema, example = json!(MessageSchema::new("Message".to_owned()))))] +pub struct MessageSchema { + #[salvo(schema(example = "Message"))] + message: String, +} + +/// Empty schema, used for returning empty responses. +#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)] +#[salvo(schema(name = EmptySchema))] +pub struct EmptySchema(u16); + +impl EmptySchema { + /// Returns empty schema with the given status code + pub fn new(code: StatusCode) -> Self { + Self(code.as_u16()) + } +} + +impl EndpointOutRegister for EmptySchema { + fn register(_components: &mut OapiComponents, _operation: &mut OapiOperation) {} +} + +impl Scribe for EmptySchema { + fn render(self, res: &mut Response) { + res.status_code(StatusCode::from_u16(self.0).expect("Is correct, from new function")); + res.headers_mut().remove(header::CONTENT_TYPE); + } +} diff --git a/crates/oxidetalis/src/schemas/user.rs b/crates/oxidetalis/src/schemas/user.rs new file mode 100644 index 0000000..321ce28 --- /dev/null +++ b/crates/oxidetalis/src/schemas/user.rs @@ -0,0 +1,27 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use oxidetalis_core::{cipher::K256Secret, types::PublicKey}; +use salvo::oapi::ToSchema; +use serde::{Deserialize, Serialize}; + +/// The schema for the user registration request +#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, derive_new::new)] +#[salvo(schema(name = RegisterUserBody, example = json!(RegisterUserBody::new(K256Secret::new().pubkey()))))] +pub struct RegisterUserBody { + /// The public key of the user + pub public_key: PublicKey, +} diff --git a/crates/oxidetalis/src/utils.rs b/crates/oxidetalis/src/utils.rs new file mode 100644 index 0000000..76f10c1 --- /dev/null +++ b/crates/oxidetalis/src/utils.rs @@ -0,0 +1,95 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::str::FromStr; + +use chrono::Utc; +use logcall::logcall; +use oxidetalis_config::Postgres; +use oxidetalis_core::{ + cipher::K256Secret, + types::{PrivateKey, PublicKey, Signature}, + PUBLIC_KEY_HEADER, + SIGNATURE_HEADER, +}; +use salvo::Request; + +use crate::{extensions::NonceCacheExt, NonceCache}; + +/// Returns the postgres database url +#[logcall] +pub(crate) fn postgres_url(db_config: &Postgres) -> String { + format!( + "postgres://{}:{}@{}:{}/{}", + db_config.user, + db_config.password, + db_config.host.as_str(), + db_config.port, + db_config.name + ) +} + +/// Returns true if the given nonce a valid nonce. +pub(crate) fn is_valid_nonce( + signature: &Signature, + nonce_cache: &NonceCache, + nonce_cache_limit: &usize, +) -> bool { + let new_timestamp = Utc::now() + .timestamp() + .checked_sub(u64::from_be_bytes(*signature.timestamp()) as i64) + .is_some_and(|n| n <= 20); + let unused_nonce = new_timestamp && nonce_cache.add_nonce(signature.nonce(), nonce_cache_limit); + new_timestamp && unused_nonce +} + +/// Returns true if the given signature is valid. +pub(crate) fn is_valid_signature( + signer: &PublicKey, + private_key: &PrivateKey, + signature: &Signature, + data: &[u8], +) -> bool { + K256Secret::from_privkey(private_key).verify(data, signature, signer) +} + +/// Extract the sender public key from the request +/// +/// Returns the public key of the sender extracted from the request, or the +/// reason why it failed. +pub(crate) fn extract_public_key(req: &Request) -> Result { + req.headers() + .get(PUBLIC_KEY_HEADER) + .map(|v| { + PublicKey::from_str(v.to_str().map_err(|err| err.to_string())?) + .map_err(|err| err.to_string()) + }) + .ok_or_else(|| "The public key is missing".to_owned())? +} + +/// Extract the signature from the request +/// +/// Returns the signature extracted from the request, or the reason why it +/// failed. +pub(crate) fn extract_signature(req: &Request) -> Result { + req.headers() + .get(SIGNATURE_HEADER) + .map(|v| { + Signature::from_str(v.to_str().map_err(|err| err.to_string())?) + .map_err(|err| err.to_string()) + }) + .ok_or_else(|| "The signature is missing".to_owned())? +} diff --git a/crates/oxidetalis_config/Cargo.toml b/crates/oxidetalis_config/Cargo.toml new file mode 100644 index 0000000..3b6b05f --- /dev/null +++ b/crates/oxidetalis_config/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "oxidetalis_config" +description = "A library for managing configurations of Oxidetalis homeserver" +edition = "2021" +license = "MIT" +authors.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +rust-version.workspace = true + + +[dependencies] +oxidetalis_core = { workspace = true } +thiserror = { workspace = true } +serde = { workspace = true } +log = { workspace = true } +salvo_core = { workspace = true } +salvo-oapi = { workspace = true } +clap = { version = "4.5.7", features = ["derive", "env"] } +base58 = "0.2.0" +toml = "0.8.14" +derivative = "2.2.0" + + +[lints.rust] +unsafe_code = "deny" +missing_docs = "warn" + +[lints.clippy] +wildcard_imports = "deny" +manual_let_else = "deny" +match_bool = "deny" +match_on_vec_items = "deny" +or_fun_call = "deny" +panic = "deny" +unwrap_used = "deny" + +missing_assert_message = "warn" +missing_const_for_fn = "warn" +missing_errors_doc = "warn" +absolute_paths = "warn" +cast_lossless = "warn" +clone_on_ref_ptr = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +default_trait_access = "warn" +empty_enum_variants_with_brackets = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +empty_structs_with_brackets = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_iter_loop = "warn" +filetype_is_file = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "warn" +format_push_string = "warn" +future_not_send = "warn" +if_not_else = "warn" +if_then_some_else_none = "warn" +implicit_clone = "warn" +inconsistent_struct_constructor = "warn" +indexing_slicing = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +manual_is_variant_and = "warn" +option_if_let_else = "warn" +option_option = "warn" diff --git a/crates/oxidetalis_config/README.md b/crates/oxidetalis_config/README.md new file mode 100644 index 0000000..c72965a --- /dev/null +++ b/crates/oxidetalis_config/README.md @@ -0,0 +1,25 @@ +# Oxidetalis configurations +A library for managing configurations of Oxidetalis homeserver. + +## Key Features +- **Load and write configurations**: Load configurations from a file and write + configurations to a file. +- **Multiple configuration entries**: The configurations are collected from CLI + arguments, environment variables, and configuration files. +- **Configuration validation**: Validate the configurations before using them. +- **Configuration defaults**: Set default values for configurations. + +## Must to know +- The configurations are loaded in the following order (from highest priority to + lowest priority) + 1. Command-line options + 2. Environment variables + 3. Configuration file + 4. Default values (or ask you to provide the value) +- The configurations are written to the configuration file every time you run + the server, even if you don't change any configuration. This is to ensure that + the configuration file is always up-to-date. + + +## License +This crate is licensed under the MIT license. diff --git a/crates/oxidetalis_config/src/commandline.rs b/crates/oxidetalis_config/src/commandline.rs new file mode 100644 index 0000000..021172e --- /dev/null +++ b/crates/oxidetalis_config/src/commandline.rs @@ -0,0 +1,98 @@ +// OxideTalis homeserver configurations +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Command-line arguments parser + +use std::{net::IpAddr, path::PathBuf}; + +use clap::Parser; +use oxidetalis_core::types::Size; + +use crate::{types::OpenApiViewer, IpOrUrl}; + +#[derive(Parser)] +#[clap(version)] +/// Command-line arguments for the Oxidetalis server. +pub struct CliArgs { + /// Path to the configuration file, toml format. + #[clap(long, env = "OXIDETALIS_CONFIG")] + pub config: PathBuf, + /// Server name, for example, `example.com`. + #[clap(long, env = "OXIDETALIS_SERVER_NAME")] + pub server_name: Option, + /// Local IP address to bind the server to. + #[clap(long, env = "OXIDETALIS_SERVER_HOST")] + pub server_host: Option, + /// Port to bind the server to. + #[clap(long, env = "OXIDETALIS_SERVER_PORT")] + pub server_port: Option, + /// Nonce cache size + /// + /// e.g. "50B", "300KB", "1MB", "1GB" + #[clap(long, env = "OXIDETALIS_SERVER_NONCE_CACHE_SIZE")] + pub server_nonce_cache_size: Option, + /// Enable or disable user registration. + #[clap(long, env = "OXIDETALIS_REGISTER_ENABLE")] + pub register_enable: Option, + /// Hostname or IP address of the PostgreSQL database. + #[clap(long, env = "OXIDETALIS_DB_HOST")] + pub postgres_host: Option, + /// Port number of the PostgreSQL database. + #[clap(long, env = "OXIDETALIS_DB_PORT")] + pub postgres_port: Option, + /// Username for the PostgreSQL database. + #[clap(long, env = "OXIDETALIS_DB_USER")] + pub postgres_user: Option, + /// Password for the PostgreSQL database. + #[clap(long, env = "OXIDETALIS_DB_PASSWORD")] + pub postgres_password: Option, + /// Name of the PostgreSQL database. + #[clap(long, env = "OXIDETALIS_DB_NAME")] + pub postgres_name: Option, + /// Enable or disable rate limiting. + #[clap(long, env = "OXIDETALIS_RATELIMIT_ENABLE")] + pub ratelimit_enable: Option, + /// Maximum number of requests allowed within a given time period for rate + /// limiting. + #[clap(long, env = "OXIDETALIS_RATELIMIT_LIMIT")] + pub ratelimit_limit: Option, + /// Time period in seconds for rate limiting. + #[clap(long, env = "OXIDETALIS_RATELIMIT_PREIOD")] + pub ratelimit_preiod: Option, + /// Enable or disable OpenAPI documentation generation. + #[clap(long, env = "OXIDETALIS_OPENAPI_ENABLE")] + pub openapi_enable: Option, + /// Title for the OpenAPI documentation. + #[clap(long, env = "OXIDETALIS_OPENAPI_TITLE")] + pub openapi_title: Option, + /// Description for the OpenAPI documentation. + #[clap(long, env = "OXIDETALIS_OPENAPI_DESCRIPTION")] + pub openapi_description: Option, + /// Path to serve the OpenAPI documentation. + #[clap(long, env = "OXIDETALIS_OPENAPI_PATH")] + pub openapi_path: Option, + /// OpenAPI viewer to use for rendering the documentation. + #[clap(long, env = "OXIDETALIS_OPENAPI_VIEWER")] + pub openapi_viewer: Option, + /// Path to the OpenAPI viewer HTML file. + #[clap(long, env = "OXIDETALIS_OPENAPI_VIEWER_PATH")] + pub openapi_viewer_path: Option, +} diff --git a/crates/oxidetalis_config/src/defaults.rs b/crates/oxidetalis_config/src/defaults.rs new file mode 100644 index 0000000..57047c4 --- /dev/null +++ b/crates/oxidetalis_config/src/defaults.rs @@ -0,0 +1,109 @@ +// OxideTalis homeserver configurations +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! The config defaults value + +/// Server default configs +pub(crate) mod server { + use std::net::{IpAddr, Ipv4Addr}; + + use oxidetalis_core::{ + cipher::K256Secret, + types::{PrivateKey, Size}, + }; + + pub fn name() -> String { + "example.com".to_owned() + } + pub const fn host() -> IpAddr { + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)) + } + pub const fn port() -> u16 { + 3873 + } + pub fn private_key() -> PrivateKey { + K256Secret::new().privkey() + } + pub const fn nonce_cache_size() -> Size { + Size::MB(1) + } +} + +/// Ratelimit default configs +pub(crate) mod ratelimit { + + pub const fn limit() -> usize { + 1500 + } + pub const fn period_secs() -> usize { + 60 + } +} + +/// OpenApi default configs +pub(crate) mod openapi { + use crate::types; + + pub fn title() -> String { + "Oxidetalis homeserver".to_owned() + } + pub fn description() -> String { + "OxideTalis Messaging Protocol homeserver".to_owned() + } + pub fn path() -> String { + "/openapi.json".to_owned() + } + pub const fn viewer() -> types::OpenApiViewer { + types::OpenApiViewer::SwaggerUi + } + pub fn viewer_path() -> String { + "/swagger-ui".to_owned() + } +} + +/// Postgres default configs +pub(crate) mod postgres { + use std::str::FromStr; + + pub fn user() -> String { + "oxidetalis".to_owned() + } + pub fn password() -> String { + "oxidetalis".to_owned() + } + pub fn host() -> crate::IpOrUrl { + crate::IpOrUrl::from_str("localhost").expect("Is a valid localhost") + } + pub fn name() -> String { + "oxidetalis_db".to_owned() + } + pub const fn port() -> u16 { + 5432 + } +} + +pub(crate) const fn bool_true() -> bool { + true +} + +pub(crate) const fn bool_false() -> bool { + false +} diff --git a/crates/oxidetalis_config/src/lib.rs b/crates/oxidetalis_config/src/lib.rs new file mode 100644 index 0000000..700b8bb --- /dev/null +++ b/crates/oxidetalis_config/src/lib.rs @@ -0,0 +1,247 @@ +// OxideTalis homeserver configurations +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#![doc = include_str!("../README.md")] + +use std::{fs, io::Error as IoError, net::IpAddr, path::Path}; + +use derivative::Derivative; +use oxidetalis_core::types::{PrivateKey, Size}; +use serde::{Deserialize, Serialize}; +use toml::{de::Error as TomlDeError, ser::Error as TomlSerError}; + +mod commandline; +mod defaults; +mod serde_with; +mod types; + +pub use clap::Parser; +pub use commandline::CliArgs; +pub use types::*; + +/// Configuration errors +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("IO: {0}")] + IO(#[from] IoError), + #[error("Toml error: {0}")] + DeToml(#[from] TomlDeError), + #[error("Toml error: {0}")] + SeToml(#[from] TomlSerError), + #[error("Missing required option `--{0}`")] + RequiredConfiguration(String), +} + +/// Server startup configuration +#[derive(Deserialize, Serialize, Derivative, Clone)] +#[derivative(Default)] +#[serde(default)] +pub struct Server { + /// Name of the server, for example, `example.com` + #[derivative(Default(value = "defaults::server::name()"))] + pub server_name: String, + /// Host that the server will listen in + #[derivative(Default(value = "defaults::server::host()"))] + pub host: IpAddr, + /// Port that the server will listen in + #[derivative(Default(value = "defaults::server::port()"))] + pub port: u16, + /// Server keypair + #[derivative(Default(value = "defaults::server::private_key()"))] + pub private_key: PrivateKey, + /// Nonce cache limit + #[derivative(Default(value = "defaults::server::nonce_cache_size()"))] + pub nonce_cache_size: Size, +} + +/// Registration config +#[derive(Debug, Deserialize, Serialize, Derivative, Clone)] +#[derivative(Default)] +#[serde(default)] +pub struct Register { + /// Whether to enable the registration or not + #[derivative(Default(value = "defaults::bool_false()"))] + pub enable: bool, +} + +/// Database configuration +#[derive(Debug, Deserialize, Serialize, Derivative, Clone)] +#[derivative(Default)] +#[serde(default)] +pub struct Postgres { + /// Username + #[derivative(Default(value = "defaults::postgres::user()"))] + pub user: String, + /// User password + #[derivative(Default(value = "defaults::postgres::password()"))] + pub password: String, + /// Database host + #[derivative(Default(value = "defaults::postgres::host()"))] + pub host: IpOrUrl, + /// Database port + #[derivative(Default(value = "defaults::postgres::port()"))] + pub port: u16, + /// Database name + #[derivative(Default(value = "defaults::postgres::name()"))] + pub name: String, +} + +/// Ratelimit configuration +#[derive(Debug, Deserialize, Serialize, Derivative, Clone)] +#[derivative(Default)] +#[serde(default)] +pub struct Ratelimit { + /// Whether to enable the ratelimit or not + #[derivative(Default(value = "defaults::bool_true()"))] + pub enable: bool, + /// The limit of requests. + #[derivative(Default(value = "defaults::ratelimit::limit()"))] + pub limit: usize, + /// The period of requests. + #[derivative(Default(value = "defaults::ratelimit::period_secs()"))] + pub period_secs: usize, +} + +/// OpenApi configuration +#[derive(Debug, Deserialize, Serialize, Derivative, Clone)] +#[derivative(Default)] +#[serde(default)] +pub struct OpenApi { + /// Whether to enable the openapi or not + #[derivative(Default(value = "defaults::bool_false()"))] + pub enable: bool, + /// Title of the openapi + #[derivative(Default(value = "defaults::openapi::title()"))] + pub title: String, + /// Description of the openapi + #[derivative(Default(value = "defaults::openapi::description()"))] + pub description: String, + /// Location to serve openapi json in + #[derivative(Default(value = "defaults::openapi::path()"))] + #[serde(deserialize_with = "serde_with::deserialize_url_path")] + pub path: String, + /// The openapi viewer + #[derivative(Default(value = "defaults::openapi::viewer()"))] + pub viewer: types::OpenApiViewer, + /// Location to server the viewer in + #[derivative(Default(value = "defaults::openapi::viewer_path()"))] + #[serde(deserialize_with = "serde_with::deserialize_url_path")] + pub viewer_path: String, +} + +#[derive(Deserialize, Serialize, Default, Clone)] +/// Oxidetalis homeserver configurations +pub struct Config { + /// Server configuration (server startup configuration) + #[serde(default)] + pub server: Server, + /// Server registration configuration + #[serde(default)] + pub register: Register, + /// Database configuration + pub postgresdb: Postgres, + /// Ratelimit configuration + #[serde(default)] + pub ratelimit: Ratelimit, + /// OpenApi configuration + #[serde(default)] + pub openapi: OpenApi, +} + +/// Check if required new configuration options are provided +fn check_required_new_config(args: &CliArgs) -> Result<(), Error> { + log::info!("Checking the required options for the new configuration"); + if args.server_name.is_none() { + return Err(Error::RequiredConfiguration("server-name".to_owned())); + } + Ok(()) +} + +impl Config { + /// Load the config from toml file and command-line options + /// + /// The priority is: + /// 1. Command-line options + /// 2. Environment variables + /// 3. Configuration file + /// 4. Default values (or ask you to provide the value) + /// + /// ## Errors + /// - Failed to read the config file + /// - Invalid toml file + pub fn load(args: CliArgs) -> Result { + let mut config = if args.config.exists() { + log::info!("Loading configuration from {}", args.config.display()); + toml::from_str(&fs::read_to_string(&args.config)?)? + } else { + log::info!("Configuration file not found, creating a new one"); + check_required_new_config(&args)?; + if let Some(parent) = args.config.parent() { + if !parent.exists() { + fs::create_dir_all(parent)?; + } + } + Config::default() + }; + + assign_option(&mut config.server.server_name, args.server_name); + assign_option(&mut config.server.host, args.server_host); + assign_option(&mut config.server.port, args.server_port); + assign_option( + &mut config.server.nonce_cache_size, + args.server_nonce_cache_size, + ); + assign_option(&mut config.register.enable, args.register_enable); + assign_option(&mut config.postgresdb.host, args.postgres_host); + assign_option(&mut config.postgresdb.port, args.postgres_port); + assign_option(&mut config.postgresdb.user, args.postgres_user); + assign_option(&mut config.postgresdb.password, args.postgres_password); + assign_option(&mut config.postgresdb.name, args.postgres_name); + assign_option(&mut config.ratelimit.enable, args.ratelimit_enable); + assign_option(&mut config.ratelimit.limit, args.ratelimit_limit); + assign_option(&mut config.ratelimit.period_secs, args.ratelimit_preiod); + assign_option(&mut config.openapi.enable, args.openapi_enable); + assign_option(&mut config.openapi.title, args.openapi_title); + assign_option(&mut config.openapi.description, args.openapi_description); + assign_option(&mut config.openapi.path, args.openapi_path); + assign_option(&mut config.openapi.viewer, args.openapi_viewer); + assign_option(&mut config.openapi.viewer_path, args.openapi_viewer_path); + + config.write(&args.config)?; + Ok(config) + } + + /// Write the configs to the config file + /// + /// ## Errors + /// - Failed to write to the config file + pub fn write(&self, config_file: impl AsRef) -> Result<(), Error> { + fs::write(config_file, toml::to_string_pretty(self)?)?; + Ok(()) + } +} + +/// Assign the command-line option to the config if it is not None +fn assign_option(config: &mut T, arg: Option) { + if let Some(value) = arg { + *config = value + } +} diff --git a/crates/oxidetalis_config/src/serde_with.rs b/crates/oxidetalis_config/src/serde_with.rs new file mode 100644 index 0000000..725d7c8 --- /dev/null +++ b/crates/oxidetalis_config/src/serde_with.rs @@ -0,0 +1,63 @@ +// OxideTalis homeserver configurations +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Serialize and deserialize some oxidetalis configurations + +use serde::{de::Error as DeError, Deserialize, Deserializer}; + +/// Serialize and deserialze the string of IpOrUrl struct +pub(crate) mod ip_or_url { + use std::str::FromStr; + + use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer}; + + use crate::IpOrUrl; + + pub fn serialize(value: &str, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(value) + } + + pub fn deserialize<'de, D>(de: D) -> Result + where + D: Deserializer<'de>, + { + Ok(IpOrUrl::from_str(&String::deserialize(de)?) + .map_err(DeError::custom)? + .as_str() + .to_owned()) + } +} + +pub fn deserialize_url_path<'de, D>(de: D) -> Result +where + D: Deserializer<'de>, +{ + let url_path = String::deserialize(de)?; + if !url_path.starts_with('/') || url_path.ends_with('/') { + return Err(DeError::custom( + "Invalid url path, must start with `/` and not ends with `/`", + )); + } + Ok(url_path) +} diff --git a/crates/oxidetalis_config/src/types.rs b/crates/oxidetalis_config/src/types.rs new file mode 100644 index 0000000..fe82322 --- /dev/null +++ b/crates/oxidetalis_config/src/types.rs @@ -0,0 +1,126 @@ +// OxideTalis homeserver configurations +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Oxidetalis config types + +use std::{net::IpAddr, str::FromStr}; + +use salvo_oapi::{rapidoc::RapiDoc, redoc::ReDoc, scalar::Scalar, swagger_ui::SwaggerUi}; +use serde::{Deserialize, Serialize}; + +/// OpenApi viewers, the viewers that can be used to view the OpenApi +/// documentation +#[derive(Debug, Clone, Deserialize, Serialize, clap::ValueEnum)] +#[serde(rename_all = "PascalCase")] +pub enum OpenApiViewer { + /// Redoc viewer + RapiDoc, + /// Redoc viewer + ReDoc, + /// Scalar viewer + Scalar, + /// Swagger-UI viewer + SwaggerUi, +} + +impl OpenApiViewer { + /// Create a router for the viewer + pub fn into_router(&self, config: &crate::Config) -> salvo_core::Router { + let spec_url = config.openapi.path.clone(); + let title = config.openapi.title.clone(); + let description = config.openapi.description.clone(); + + match self { + OpenApiViewer::RapiDoc => { + RapiDoc::new(spec_url) + .title(title) + .description(description) + .into_router(&config.openapi.viewer_path) + } + OpenApiViewer::ReDoc => { + ReDoc::new(spec_url) + .title(title) + .description(description) + .into_router(&config.openapi.viewer_path) + } + OpenApiViewer::Scalar => { + Scalar::new(spec_url) + .title(title) + .description(description) + .into_router(&config.openapi.viewer_path) + } + OpenApiViewer::SwaggerUi => { + SwaggerUi::new(spec_url) + .title(title) + .description(description) + .into_router(&config.openapi.viewer_path) + } + } + } +} + +/// Type hold url or ip (used for database host) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IpOrUrl(#[serde(with = "crate::serde_with::ip_or_url")] String); + +impl Default for IpOrUrl { + fn default() -> Self { + IpOrUrl("localhost".to_owned()) + } +} + +impl IpOrUrl { + /// Returns &str ip or url + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl FromStr for IpOrUrl { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(IpOrUrl( + if let Ok(res) = IpAddr::from_str(s).map(|i| i.to_string()) { + res + } else { + validate_domain(s)? + }, + )) + } +} + +fn validate_domain(domain: &str) -> Result { + if domain != "localhost" { + let subs = domain.split('.'); + for sub in subs { + let length = sub.chars().count(); + if !sub.chars().all(|c| c.is_alphanumeric() || c == '-') + || sub.starts_with('-') + || sub.ends_with('-') + || (length > 0 && length <= 64) + { + return Err("Invalid domain name".to_owned()); + } + } + } + Ok(domain.to_owned()) +} diff --git a/crates/oxidetalis_core/Cargo.toml b/crates/oxidetalis_core/Cargo.toml new file mode 100644 index 0000000..00c8746 --- /dev/null +++ b/crates/oxidetalis_core/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "oxidetalis_core" +description = "OxideTalis server core" +edition = "2021" +license = "MIT" +authors.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +rust-version.workspace = true + + +[dependencies] +base58 = { workspace = true } +thiserror = { workspace = true } +salvo_core = { workspace = true } +salvo-oapi = { workspace = true } +serde = { workspace = true } +log = { workspace = true } +logcall = { workspace = true } +cbc = { version = "0.1.2", features = ["alloc", "std"] } +k256 = { version = "0.13.3", default-features = false, features = ["ecdh"] } +rand = { version = "0.8.5", default-features = false, features = ["std_rng", "std"] } +aes = "0.8.4" +hex = "0.4.3" +hmac = "0.12.1" +sha2 = "0.10.8" + + +[lints.rust] +unsafe_code = "deny" +missing_docs = "warn" + +[lints.clippy] +wildcard_imports = "deny" +manual_let_else = "deny" +match_bool = "deny" +match_on_vec_items = "deny" +or_fun_call = "deny" +panic = "deny" +unwrap_used = "deny" + +missing_assert_message = "warn" +missing_const_for_fn = "warn" +missing_errors_doc = "warn" +absolute_paths = "warn" +cast_lossless = "warn" +clone_on_ref_ptr = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +default_trait_access = "warn" +empty_enum_variants_with_brackets = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +empty_structs_with_brackets = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_iter_loop = "warn" +filetype_is_file = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "warn" +format_push_string = "warn" +future_not_send = "warn" +if_not_else = "warn" +if_then_some_else_none = "warn" +implicit_clone = "warn" +inconsistent_struct_constructor = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +manual_is_variant_and = "warn" +option_if_let_else = "warn" +option_option = "warn" diff --git a/crates/oxidetalis_core/src/cipher.rs b/crates/oxidetalis_core/src/cipher.rs new file mode 100644 index 0000000..00c2f9e --- /dev/null +++ b/crates/oxidetalis_core/src/cipher.rs @@ -0,0 +1,224 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! The `cipher` module contains the encryption and decryption functions for the +//! OxideTalis protocol. + +use std::time::{SystemTime, UNIX_EPOCH}; + +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use hmac::Mac; +use k256::{ + ecdh::diffie_hellman, + elliptic_curve::sec1::ToEncodedPoint, + FieldBytes, + NonZeroScalar, + PublicKey, +}; +use logcall::logcall; +use rand::{thread_rng, RngCore}; + +use crate::types::{ + PrivateKey as CorePrivateKey, + PublicKey as CorePublicKey, + Signature as CoreSignature, +}; + +/// The errors that can occur during in the cipher module. +#[derive(Debug, thiserror::Error)] +pub enum CipherError { + /// The public key is invalid. + #[error("Invalid Public Key")] + InvalidPublicKey, + /// The private key is invalid. + #[error("Invalid Private Key")] + InvalidPrivateKey, + /// The signature is invalid + #[error("Invalid signature")] + InvalidSignature, + + /// A decryption error + #[error("Decryption Error")] + Decryption, + /// Invalid base58 string + #[error("Invalid base58 string `{0}`")] + InvalidBase58(String), + /// Invalid hex string + #[error("Invalid hex string `{0}`")] + InvalidHex(String), +} +#[allow(clippy::absolute_paths)] +type Result = std::result::Result; +type Aes256CbcEnc = cbc::Encryptor; +type Aes256CbcDec = cbc::Decryptor; +type HmacSha256 = hmac::Hmac; + +/// An wrapper around the k256 crate to provide a simple API for ecdh key +/// exchange and keypair generation. +pub struct K256Secret { + /// The private key scalar + scalar: NonZeroScalar, + /// The public key + public_key: PublicKey, +} + +impl From for K256Secret { + fn from(scalar: NonZeroScalar) -> Self { + Self { + public_key: PublicKey::from_secret_scalar(&scalar), + scalar, + } + } +} + +impl K256Secret { + /// Generate a new random keypair, using the system random number generator. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self::from(NonZeroScalar::random(&mut rand::thread_rng())) + } + + /// Restore a keypair from a private key. + pub fn from_privkey(private_key: &CorePrivateKey) -> Self { + Self::from( + Option::::from(NonZeroScalar::from_repr(*FieldBytes::from_slice( + private_key.as_bytes(), + ))) + .expect("The private key is correct"), + ) + } + + /// Returns the public key. + pub fn pubkey(&self) -> CorePublicKey { + CorePublicKey::try_from( + <[u8; 33]>::try_from(self.public_key.to_encoded_point(true).as_bytes()) + .expect("The length is correct"), + ) + .expect("Is correct public key") + } + + /// Returns the private key. + pub fn privkey(&self) -> CorePrivateKey { + CorePrivateKey::try_from(<[u8; 32]>::from(FieldBytes::from(self.scalar))) + .expect("Correct private key") + } + + /// Compute the shared secret with the given public key. + pub fn shared_secret(&self, with: &CorePublicKey) -> [u8; 32] { + let mut secret_buf = [0u8; 32]; + diffie_hellman( + self.scalar, + PublicKey::from_sec1_bytes(with.as_bytes()) + .expect("Correct public key") + .as_affine(), + ) + .extract::(None) + .expand(&[], &mut secret_buf) + .expect("The buffer size is correct"); + + secret_buf + } + + /// Encrypt a data with the shared secret. + /// + /// The data is encrypted using AES-256-CBC with a random IV (last 16 bytes + /// of the ciphertext). + pub fn encrypt_data(&self, encrypt_to: &CorePublicKey, data: &[u8]) -> Vec { + let mut iv = [0u8; 16]; + thread_rng().fill_bytes(&mut iv); + + let mut ciphertext = + Aes256CbcEnc::new(self.shared_secret(encrypt_to).as_slice().into(), &iv.into()) + .encrypt_padded_vec_mut::(data); + ciphertext.extend(&iv); + ciphertext + } + + /// Decrypt a data with the shared secret. + /// + /// The data is decrypted using AES-256-CBC with the IV being the last 16 + /// bytes of the ciphertext. + /// + /// ## Errors + /// - If the data less then 16 bytes. + /// - If the iv less then 16 bytes. + /// - Falid to decrypt the data (invalid encrypted data) + pub fn decrypt_data(&self, decrypt_from: &CorePublicKey, data: &[u8]) -> Result> { + let (ciphertext, iv) = + data.split_at(data.len().checked_sub(16).ok_or(CipherError::Decryption)?); + + if iv.len() != 16 { + return Err(CipherError::Decryption); + } + + Aes256CbcDec::new( + self.shared_secret(decrypt_from).as_slice().into(), + iv.into(), + ) + .decrypt_padded_vec_mut::(ciphertext) + .map_err(|_| CipherError::Decryption) + } + + /// Sign a data with the shared secret. + /// + /// The signature is exiplained in the OTMP specification. + #[logcall] + pub fn sign(&self, data: &[u8], sign_to: &CorePublicKey) -> CoreSignature { + let mut time_and_nonce = [0u8; 24]; + time_and_nonce[0..=7].copy_from_slice( + &SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("SystemTime before UNIX EPOCH!") + .as_secs() + .to_be_bytes(), + ); + thread_rng().fill_bytes(&mut time_and_nonce[8..=23]); + + let mut hmac_secret = [0u8; 56]; + hmac_secret[0..=31].copy_from_slice(&self.shared_secret(sign_to)); + hmac_secret[32..=55].copy_from_slice(&time_and_nonce); + let mut signature = [0u8; 56]; + signature[0..=31].copy_from_slice(&hmac_sha256(data, &hmac_secret)); + signature[32..=55].copy_from_slice(&time_and_nonce); + + CoreSignature::from(signature) + } + + /// Verify a signature with the shared secret. + /// + /// Note: + /// The time and the nonce will not be checked here + #[logcall] + pub fn verify(&self, data: &[u8], signature: &CoreSignature, signer: &CorePublicKey) -> bool { + let mut hmac_secret = [0u8; 56]; + hmac_secret[0..=31].copy_from_slice(&self.shared_secret(signer)); + hmac_secret[32..=39].copy_from_slice(signature.timestamp()); + hmac_secret[40..=55].copy_from_slice(signature.nonce()); + + &hmac_sha256(data, &hmac_secret) == signature.hmac_output() + } +} + +fn hmac_sha256(data: &[u8], secret: &[u8]) -> [u8; 32] { + let mut mac = HmacSha256::new_from_slice(secret).expect("HMAC can take key of any size"); + mac.update(data); + mac.finalize().into_bytes().into() +} diff --git a/crates/oxidetalis_core/src/lib.rs b/crates/oxidetalis_core/src/lib.rs new file mode 100644 index 0000000..af4e532 --- /dev/null +++ b/crates/oxidetalis_core/src/lib.rs @@ -0,0 +1,33 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! The core library for the OxideTalis homeserver implementation. + +pub mod cipher; +pub mod types; + +/// The header name for the signature. The signature is a hex encoded string. +pub const SIGNATURE_HEADER: &str = "X-OTMP-SIGNATURE"; +/// The header name of the request sender public key. The public key is a base58 +/// encoded string. +pub const PUBLIC_KEY_HEADER: &str = "X-OTMP-PUBLIC"; +/// Server name header name +pub const SERVER_NAME_HEADER: &str = "X-OTMP-SERVER"; diff --git a/crates/oxidetalis_core/src/types/cipher.rs b/crates/oxidetalis_core/src/types/cipher.rs new file mode 100644 index 0000000..e7285df --- /dev/null +++ b/crates/oxidetalis_core/src/types/cipher.rs @@ -0,0 +1,213 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use std::{fmt, str::FromStr}; + +use base58::{FromBase58, ToBase58}; +use salvo_oapi::{ + schema::{ + Schema as OapiSchema, + SchemaFormat as OapiSchemaFormat, + SchemaType as OapiSchemaType, + }, + ToSchema, +}; + +use crate::cipher::CipherError; + +/// Correct length except message +const CORRECT_LENGTH: &str = "The length is correct"; + +/// K256 public key +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PublicKey([u8; 33]); + +/// K256 private key +#[derive(Clone, Copy)] +pub struct PrivateKey([u8; 32]); + +/// OTMP signature +#[derive(Clone, Copy, Debug)] +pub struct Signature { + hmac_output: [u8; 32], + timestamp: [u8; 8], + nonce: [u8; 16], +} + +impl PublicKey { + /// Returns the public key as bytes + pub const fn as_bytes(&self) -> &[u8; 33] { + &self.0 + } +} + +impl PrivateKey { + /// Returns the private key as bytes + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +impl Signature { + /// Returns the hmac output from the signature + pub const fn hmac_output(&self) -> &[u8; 32] { + &self.hmac_output + } + + /// Returns the timestamp from the signature + pub const fn timestamp(&self) -> &[u8; 8] { + &self.timestamp + } + + /// Returns the nonce from the signature + pub const fn nonce(&self) -> &[u8; 16] { + &self.nonce + } + + /// Returns the signature as bytes + pub fn as_bytes(&self) -> [u8; 56] { + let mut sig = [0u8; 56]; + sig[0..=31].copy_from_slice(&self.hmac_output); + sig[32..=39].copy_from_slice(&self.timestamp); + sig[40..=55].copy_from_slice(&self.nonce); + sig + } +} + +/// Public key to base58 string +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_base58()) + } +} + +/// Public key to base58 string +impl fmt::Display for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_base58()) + } +} + +/// Signature to hex string +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.as_bytes())) + } +} + +/// Public key from base58 string +impl FromStr for PublicKey { + type Err = CipherError; + + fn from_str(s: &str) -> Result { + let public_key = s + .from_base58() + .map_err(|_| CipherError::InvalidBase58(s.to_owned()))?; + if public_key.len() != 33 { + return Err(CipherError::InvalidPublicKey); + } + Self::try_from(<[u8; 33]>::try_from(public_key).expect(CORRECT_LENGTH)) + } +} + +/// Private key from base58 string +impl FromStr for PrivateKey { + type Err = CipherError; + + fn from_str(s: &str) -> Result { + let private_key = s + .from_base58() + .map_err(|_| CipherError::InvalidBase58(s.to_owned()))?; + if private_key.len() != 32 { + return Err(CipherError::InvalidPrivateKey); + } + + Self::try_from(<[u8; 32]>::try_from(private_key).expect(CORRECT_LENGTH)) + } +} + +/// Signature from hex string +impl FromStr for Signature { + type Err = CipherError; + + fn from_str(s: &str) -> Result { + let signature = hex::decode(s).map_err(|_| CipherError::InvalidHex(s.to_owned()))?; + if signature.len() != 56 { + return Err(CipherError::InvalidSignature); + } + Ok(Signature::from( + <[u8; 56]>::try_from(signature).expect(CORRECT_LENGTH), + )) + } +} + +impl TryFrom<[u8; 33]> for PublicKey { + type Error = CipherError; + + fn try_from(public_key: [u8; 33]) -> Result { + if k256::PublicKey::from_sec1_bytes(&public_key).is_err() { + return Err(CipherError::InvalidPublicKey); + } + Ok(Self(public_key)) + } +} + +impl TryFrom<[u8; 32]> for PrivateKey { + type Error = CipherError; + + fn try_from(private_key: [u8; 32]) -> Result { + if k256::NonZeroScalar::from_repr(*k256::FieldBytes::from_slice(&private_key)) + .is_none() + .into() + { + return Err(CipherError::InvalidPrivateKey); + } + Ok(Self(private_key)) + } +} + +impl From<[u8; 56]> for Signature { + fn from(signature: [u8; 56]) -> Self { + Self { + hmac_output: signature[0..=31].try_into().expect(CORRECT_LENGTH), + timestamp: signature[32..=39].try_into().expect(CORRECT_LENGTH), + nonce: signature[40..=55].try_into().expect(CORRECT_LENGTH), + } + } +} + +impl ToSchema for PublicKey { + fn to_schema(_components: &mut salvo_oapi::Components) -> salvo_oapi::RefOr { + salvo_oapi::Object::new() + .schema_type(OapiSchemaType::String) + .format(OapiSchemaFormat::Custom("base58".to_owned())) + .into() + } +} + +impl ToSchema for Signature { + fn to_schema(_components: &mut salvo_oapi::Components) -> salvo_oapi::RefOr { + salvo_oapi::Object::new() + .schema_type(OapiSchemaType::String) + .format(OapiSchemaFormat::Custom("hex".to_owned())) + .into() + } +} diff --git a/crates/oxidetalis_core/src/types/impl_serde.rs b/crates/oxidetalis_core/src/types/impl_serde.rs new file mode 100644 index 0000000..2f787dc --- /dev/null +++ b/crates/oxidetalis_core/src/types/impl_serde.rs @@ -0,0 +1,99 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use base58::FromBase58; +use serde::{de::Error as DeError, Deserialize, Serialize}; + +use super::{PrivateKey, PublicKey, Signature}; + +impl<'de> Deserialize<'de> for PrivateKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let private_key = String::deserialize(deserializer)? + .from_base58() + .map_err(|_| DeError::custom("Invalid base58"))?; + + Self::try_from( + <[u8; 32]>::try_from(private_key) + .map_err(|_| DeError::custom("Invalid private key length, must be 32 bytes"))?, + ) + .map_err(|_| DeError::custom("Invalid private key")) + } +} + +impl Serialize for PrivateKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let public_key = String::deserialize(deserializer)? + .from_base58() + .map_err(|_| DeError::custom("Invalid base58"))?; + + Self::try_from( + <[u8; 33]>::try_from(public_key) + .map_err(|_| DeError::custom("Invalid public key length, must be 33 bytes"))?, + ) + .map_err(|_| DeError::custom("Invalid public key")) + } +} + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let signature = hex::decode(String::deserialize(deserializer)?) + .map_err(|_| DeError::custom("Invalid hex string"))?; + Ok(Self::from(<[u8; 56]>::try_from(signature).map_err( + |_| DeError::custom("Invalid signature length, must be 56 bytes"), + )?)) + } +} + +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} diff --git a/crates/oxidetalis_core/src/types/mod.rs b/crates/oxidetalis_core/src/types/mod.rs new file mode 100644 index 0000000..a5a28ec --- /dev/null +++ b/crates/oxidetalis_core/src/types/mod.rs @@ -0,0 +1,29 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Oxidetalis server types + +mod cipher; +mod impl_serde; +mod size; + +pub use cipher::*; +pub use size::*; diff --git a/crates/oxidetalis_core/src/types/size.rs b/crates/oxidetalis_core/src/types/size.rs new file mode 100644 index 0000000..f292142 --- /dev/null +++ b/crates/oxidetalis_core/src/types/size.rs @@ -0,0 +1,125 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Size type. Used to represent sizes in bytes, kilobytes, megabytes, and +//! gigabytes. + +use std::{fmt, str::FromStr}; + +use logcall::logcall; +use serde::{de::Error as DeError, Deserialize, Serialize}; + +/// Size type. Used to represent sizes in bytes, kilobytes, megabytes, and +/// gigabytes. +#[derive(Copy, Clone, Debug)] +pub enum Size { + /// Byte + B(usize), + /// Kilobyte + KB(usize), + /// Megabyte + MB(usize), + /// Gigabyte + GB(usize), +} + +impl Size { + /// Returns the size in bytes, regardless of the unit + pub const fn as_bytes(&self) -> usize { + match self { + Size::B(n) => *n, + Size::KB(n) => *n * 1e+3 as usize, + Size::MB(n) => *n * 1e+6 as usize, + Size::GB(n) => *n * 1e+9 as usize, + } + } + + /// Returns the unit name of the size (e.g. `B`, `KB`, `MB`, `GB`) + pub const fn unit_name(&self) -> &'static str { + match self { + Size::B(_) => "B", + Size::KB(_) => "KB", + Size::MB(_) => "MB", + Size::GB(_) => "GB", + } + } + + /// Returns the size in the unit (e.g. `2MB` -> `2`, `2GB` -> `2`) + pub const fn size(&self) -> usize { + match self { + Size::B(n) | Size::KB(n) | Size::MB(n) | Size::GB(n) => *n, + } + } +} + +impl fmt::Display for Size { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.size(), self.unit_name()) + } +} + +impl FromStr for Size { + type Err = String; + + #[logcall] + fn from_str(s: &str) -> Result { + let Some(first_alpha) = s.find(|c: char| c.is_alphabetic()) else { + return Err("Missing unit, e.g. `2MB`".to_owned()); + }; + + let (size, unit) = s.split_at(first_alpha); + let Ok(size) = size.parse() else { + return Err(format!("Invalid size `{size}`")); + }; + Ok(match unit { + "B" => Self::B(size), + "KB" => Self::KB(size), + "MB" => Self::MB(size), + "GB" => Self::GB(size), + unknown_unit => { + return Err(format!( + "Unsupported unit `{unknown_unit}`, supported units are `B`, `KB`, `MB`, `GB`" + )); + } + }) + } +} + +impl<'de> Deserialize<'de> for Size { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)? + .as_str() + .parse() + .map_err(DeError::custom) + } +} + +impl Serialize for Size { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } +} diff --git a/crates/oxidetalis_entities/Cargo.toml b/crates/oxidetalis_entities/Cargo.toml new file mode 100644 index 0000000..b8f6e33 --- /dev/null +++ b/crates/oxidetalis_entities/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "oxidetalis_entities" +description = "Database entities for the Oxidetalis homeserver" +edition = "2021" +license = "MIT" +authors.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +rust-version.workspace = true + + +[dependencies] +sea-orm = {workspace = true } + +[lints.rust] +unsafe_code = "deny" + +[lints.clippy] +wildcard_imports = "deny" +manual_let_else = "deny" +match_bool = "deny" +match_on_vec_items = "deny" +or_fun_call = "deny" +panic = "deny" +unwrap_used = "deny" + +missing_assert_message = "warn" +missing_const_for_fn = "warn" +missing_errors_doc = "warn" +absolute_paths = "warn" +cast_lossless = "warn" +clone_on_ref_ptr = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +default_trait_access = "warn" +empty_enum_variants_with_brackets = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +empty_structs_with_brackets = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_iter_loop = "warn" +filetype_is_file = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "warn" +format_push_string = "warn" +future_not_send = "warn" +if_not_else = "warn" +if_then_some_else_none = "warn" +implicit_clone = "warn" +inconsistent_struct_constructor = "warn" +indexing_slicing = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +manual_is_variant_and = "warn" +option_if_let_else = "warn" +option_option = "warn" diff --git a/crates/oxidetalis_entities/README.md b/crates/oxidetalis_entities/README.md new file mode 100644 index 0000000..47cd9d2 --- /dev/null +++ b/crates/oxidetalis_entities/README.md @@ -0,0 +1,16 @@ +# Oxidetalis database entities + +This crate contains the database entities for the Oxidetalis homeserver, using +SeaORM. + +## Must to know +- Don't import sea_orm things in another crates, import the entities and sea_orm + things from this crate, from `prelude` module. + +## How to write a new entity +Check the [SeaORM +documentation](https://www.sea-ql.org/SeaORM/docs/generate-entity/entity-structure/) +for more information about how to write entities. + +## License +This crate is licensed under the MIT license. diff --git a/crates/oxidetalis_entities/src/lib.rs b/crates/oxidetalis_entities/src/lib.rs new file mode 100644 index 0000000..21ae991 --- /dev/null +++ b/crates/oxidetalis_entities/src/lib.rs @@ -0,0 +1,23 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +pub mod prelude; +pub mod users; diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs new file mode 100644 index 0000000..037a7e4 --- /dev/null +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -0,0 +1,41 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +pub use sea_orm::{ + ActiveModelTrait, + ColumnTrait, + EntityTrait, + IntoActiveModel, + Order, + PaginatorTrait, + QueryFilter, + QueryOrder, + QuerySelect, + Set, + SqlErr, +}; + +pub use super::users::{ + ActiveModel as UserActiveModel, + Column as UserColumn, + Entity as UserEntity, + Model as UserModel, +}; diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs new file mode 100644 index 0000000..1bae627 --- /dev/null +++ b/crates/oxidetalis_entities/src/users.rs @@ -0,0 +1,36 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "users")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub public_key: String, + pub is_admin: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_migrations/Cargo.toml b/crates/oxidetalis_migrations/Cargo.toml new file mode 100644 index 0000000..c0725e1 --- /dev/null +++ b/crates/oxidetalis_migrations/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "oxidetalis_migrations" +description = "Database migrations for the Oxidetalis homeserver" +edition = "2021" +license = "MIT" +authors.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +rust-version.workspace = true + + +[dependencies] +sea-orm = { workspace = true } +sea-orm-migration = { version = "0.12.15", default-features = false, features = ["runtime-tokio-rustls", "sqlx-postgres"] } + +[lints.rust] +unsafe_code = "deny" + +[lints.clippy] +wildcard_imports = "deny" +manual_let_else = "deny" +match_bool = "deny" +match_on_vec_items = "deny" +or_fun_call = "deny" +panic = "deny" +unwrap_used = "deny" + +missing_assert_message = "warn" +missing_const_for_fn = "warn" +missing_errors_doc = "warn" +absolute_paths = "warn" +cast_lossless = "warn" +clone_on_ref_ptr = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +default_trait_access = "warn" +empty_enum_variants_with_brackets = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +empty_structs_with_brackets = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +explicit_iter_loop = "warn" +filetype_is_file = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "warn" +format_push_string = "warn" +future_not_send = "warn" +if_not_else = "warn" +if_then_some_else_none = "warn" +implicit_clone = "warn" +inconsistent_struct_constructor = "warn" +indexing_slicing = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +manual_is_variant_and = "warn" +option_if_let_else = "warn" +option_option = "warn" diff --git a/crates/oxidetalis_migrations/README.md b/crates/oxidetalis_migrations/README.md new file mode 100644 index 0000000..c2f2764 --- /dev/null +++ b/crates/oxidetalis_migrations/README.md @@ -0,0 +1,52 @@ +# Oxidetalis database migrations + +This crate contains the database migrations for the Oxidetalis homeserver, using +SeaORM. + +## How to run the migrations +The migrations are run when the server starts. The server will check if the +database is up-to-date and run the migrations if needed. So, you don't need to +run the migrations manually. + +## How to create a new migration +The migrations will saved in the database, so SeaORM will track the migrations, +and you don't need to worry about the migration files, just write the migration +and SeaORM will take care of the rest. + +To create a new migration, you need to create a new migration file in the `src` +directory. You can name the file anything you want, for example, +`create_users_table.rs`. The file should contain the migration code, you can +take this as a template: +```rust +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Here you can write the migration code, the `manager` can do anything you want. + + // When the homeserver starts, it will run the `up` function for each migration that is not run yet. + } +} + +#[derive(DeriveIden)] +enum TableName { + Table, // Required for the table name + Id, // Required for the primary key + // Add more columns here +d} +``` + +> [!NOTE] Don't write the `down` function, I prefer to do each migration in a +> separate migration file, so you don't need to write the `down` function. If you +> want to delete a table later, you can create a new migration file that deletes +> the table. + +After you write the migration code, you need to add the migration to the +`src/lib.rs` file. + +## License +This crate is licensed under the MIT license. diff --git a/crates/oxidetalis_migrations/src/create_users_table.rs b/crates/oxidetalis_migrations/src/create_users_table.rs new file mode 100644 index 0000000..71e771e --- /dev/null +++ b/crates/oxidetalis_migrations/src/create_users_table.rs @@ -0,0 +1,66 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Users::Table) + .if_not_exists() + .col( + ColumnDef::new(Users::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(Users::PublicKey) + .string() + .not_null() + .unique_key(), + ) + .col( + ColumnDef::new(Users::IsAdmin) + .boolean() + .not_null() + .default(false), + ) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, + PublicKey, + IsAdmin, +} diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs new file mode 100644 index 0000000..83f94c0 --- /dev/null +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -0,0 +1,33 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +pub use sea_orm_migration::prelude::*; + +mod create_users_table; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(create_users_table::Migration)] + } +}