diff --git a/Cargo.lock b/Cargo.lock index 953ccd3..8f144b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,18 +29,12 @@ dependencies = [ ] [[package]] -name = "alloc-no-stdlib" -version = "2.0.3" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "alloc-no-stdlib", + "libc", ] [[package]] @@ -54,23 +48,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" - -[[package]] -name = "async-compression" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345fd392ab01f746c717b1357165b76f0b67a60192007b234058c9045fdcf695" -dependencies = [ - "brotli", - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", -] +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "async-trait" @@ -100,9 +80,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.15" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de18bc5f2e9df8f52da03856bf40e29b747de5a84e43aefff90e3dc4a21529b" +checksum = "d2628a243073c55aef15a1c1fe45c87f21b84f9e89ca9e7b262a180d3d03543d" dependencies = [ "async-trait", "axum-core", @@ -130,23 +110,11 @@ dependencies = [ "tower-service", ] -[[package]] -name = "axum-auth" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9770f9a9147b2324066609acb5495538cb25f973129663fba2658ba7ed69407" -dependencies = [ - "async-trait", - "axum-core", - "base64", - "http", -] - [[package]] name = "axum-core" -version = "0.2.7" +version = "0.3.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +checksum = "473bd0762170028bb6b5068be9e97de2a9f0af3bf2084498d840498f47194d3d" dependencies = [ "async-trait", "bytes", @@ -154,13 +122,15 @@ dependencies = [ "http", "http-body", "mime", + "tower-layer", + "tower-service", ] [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", @@ -177,16 +147,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "better-panic" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" -dependencies = [ - "backtrace", - "console", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -195,39 +155,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] -[[package]] -name = "brotli" -version = "3.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byte-unit" @@ -246,9 +185,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -264,35 +203,30 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] [[package]] -name = "console" -version = "0.15.0" +name = "core-foundation-sys" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "terminal_size", - "winapi", -] +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -312,20 +246,11 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-queue" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" dependencies = [ "cfg-if", "crossbeam-utils", @@ -333,9 +258,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if", "once_cell", @@ -343,9 +268,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -399,24 +324,18 @@ dependencies = [ [[package]] name = "dotenvy" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e851a83c30366fd01d75b913588e95e74a1705c1ecc5d58b1f8e1a6d556525f" +checksum = "da3db6fcad7c1fc4abdd99bf5276a4db30d6a819127903a709ed41e5ff016e84" dependencies = [ "dirs", ] [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "encoding_rs" @@ -429,30 +348,20 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide", -] +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "flume" -version = "0.10.13" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.3", + "spin 0.9.4", ] [[package]] @@ -463,34 +372,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -498,15 +391,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -524,17 +417,11 @@ dependencies = [ "parking_lot", ] -[[package]] -name = "futures-io" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" - [[package]] name = "futures-macro" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -543,29 +430,26 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -573,9 +457,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -594,15 +478,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -619,9 +503,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -670,62 +554,22 @@ dependencies = [ [[package]] name = "homedisk" -version = "0.0.0" +version = "0.0.0-dev" dependencies = [ "anyhow", - "better-panic", - "homedisk-database", - "homedisk-server", - "homedisk-types", - "tokio", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "homedisk-database" -version = "0.0.0" -dependencies = [ - "futures-util", - "homedisk-types", - "sqlx", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "homedisk-server" -version = "0.0.0" -dependencies = [ "axum", - "axum-auth", - "base64", + "backtrace", "byte-unit", "crypto-utils", - "futures", - "homedisk-database", - "homedisk-types", - "hyper", - "log", - "serde", - "thiserror", - "tower-http", -] - -[[package]] -name = "homedisk-types" -version = "0.0.0" -dependencies = [ - "anyhow", - "axum", - "crypto-utils", - "dirs", + "futures-util", "hex", "serde", "sqlx", "thiserror", + "tokio", "toml", + "tracing", + "tracing-subscriber", "uuid", ] @@ -759,9 +603,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -794,12 +638,25 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.2.3" +name = "iana-time-zone" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -823,35 +680,26 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "iri-string" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0f7638c1e223529f1bfdc48c8b133b9e0b434094d1d28473161ee48b235f78" -dependencies = [ - "nom", -] - [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -878,9 +726,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libsqlite3-sys" @@ -895,9 +743,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg", "scopeguard", @@ -912,17 +760,11 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "matchit" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "3dfc802da7b1cf80aefffa0c7b2f77247c8b32206cc83c270b61264f5b360a80" [[package]] name = "memchr" @@ -936,16 +778,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -954,9 +786,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -975,9 +807,9 @@ dependencies = [ [[package]] name = "multer" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" +checksum = "a30ba6d97eb198c5e8a35d67d5779d6680cca35652a60ee90fc23dc431d4fde8" dependencies = [ "bytes", "encoding_rs", @@ -987,7 +819,7 @@ dependencies = [ "log", "memchr", "mime", - "spin 0.9.3", + "spin 0.9.4", "version_check", ] @@ -1052,18 +884,18 @@ dependencies = [ [[package]] name = "object" -version = "0.28.4" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" [[package]] name = "parking_lot" @@ -1092,39 +924,39 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pem" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" dependencies = [ "base64", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -1151,27 +983,27 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1222,18 +1054,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "scopeguard" @@ -1273,9 +1105,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -1296,26 +1128,20 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" dependencies = [ "cfg-if", "cpufeatures", "digest", ] -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" dependencies = [ "cfg-if", "cpufeatures", @@ -1340,26 +1166,29 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.11", + "time 0.3.14", ] [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -1373,9 +1202,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" dependencies = [ "lock_api", ] @@ -1495,9 +1324,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -1510,30 +1339,20 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" dependencies = [ "proc-macro2", "quote", @@ -1562,9 +1381,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "itoa", "libc", @@ -1595,9 +1414,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.1" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" dependencies = [ "autocfg", "bytes", @@ -1647,9 +1466,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -1690,8 +1509,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" dependencies = [ - "async-compression", - "base64", "bitflags", "bytes", "futures-core", @@ -1699,19 +1516,10 @@ dependencies = [ "http", "http-body", "http-range-header", - "httpdate", - "iri-string", - "mime", - "mime_guess", - "percent-encoding", "pin-project-lite", - "tokio", - "tokio-util", "tower", "tower-layer", "tower-service", - "tracing", - "uuid", ] [[package]] @@ -1797,15 +1605,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.8" @@ -1814,24 +1613,24 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode_categories" @@ -1847,13 +1646,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -1870,7 +1668,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" dependencies = [ "getrandom", - "sha1_smol", ] [[package]] @@ -1915,9 +1712,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1925,13 +1722,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1940,9 +1737,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1950,9 +1747,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -1963,15 +1760,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1989,9 +1786,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" dependencies = [ "webpki", ] diff --git a/Cargo.toml b/Cargo.toml index df23107..e5c9e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,40 @@ -[workspace] -members = [ - "core", - "database", - "server", - "types", -] -resolver = "2" +[package] +name = "homedisk" +authors = ["MedzikUser "] +homepage = "https://github.com/HomeDisk/cloud" +repository = "https://github.com/HomeDisk/cloud" +license = "GPL-3.0-or-later" +version = "0.0.0-dev" +edition = "2021" + +[dependencies] +# Types +serde = { version = "1.0", features = ["derive"] } +# Errors +anyhow = "1.0" +thiserror = "1.0" + +# Logger +tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info", "log"] } +tracing-subscriber = "0.3" +# Backtrace in panic hook +backtrace = "0.3" + +# Tokio runtime +tokio = { version = "1.20", features = ["rt-multi-thread", "macros"] } + +# Database +futures-util = "0.3" +sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite"] } +uuid = { version = "1.1", features = ["v4"] } + +# Cryptographic +crypto-utils = "0.4" +hex = "0.4" + +# Config +toml = "0.5" + +# HTTP server +axum = { version = "0.6.0-rc.2", features = ["http2", "multipart"] } +byte-unit = "4.0.14" diff --git a/core/Cargo.toml b/core/Cargo.toml deleted file mode 100644 index 08a56ba..0000000 --- a/core/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "homedisk" -authors = ["MedzikUser "] -homepage = "https://github.com/HomeDisk/cloud" -repository = "https://github.com/HomeDisk/cloud" -license = "GPL-3.0-or-later" -version = "0.0.0" -edition = "2021" - -[[bin]] -name = "homedisk" -path = "./src/main.rs" - -[dependencies] -anyhow = "1.0.62" -better-panic = "0.3.0" -tracing-subscriber = "0.3.15" -tracing = { version = "0.1.36", features = ["max_level_debug", "release_max_level_info"] } -tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] } -homedisk-database = { path = "../database" } -homedisk-server = { path = "../server" } -homedisk-types = { path = "../types", features = ["config"] } diff --git a/core/src/lib.rs b/core/src/lib.rs deleted file mode 100644 index 975a4b2..0000000 --- a/core/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! # HomeDisk cloud server -//! -//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github -//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs -//! [total-lines]: https://img.shields.io/tokei/lines/github/MedzikUser/HomeDisk?style=for-the-badge&logo=github&color=fede00 -//! [code-size]: https://img.shields.io/github/languages/code-size/MedzikUser/HomeDisk?style=for-the-badge&color=c8df52&logo=github -//! [ci]: https://img.shields.io/github/workflow/status/MedzikUser/HomeDisk/Rust/main?style=for-the-badge -//! -//! [home-screenshot]: https://i.imgur.com/x4Glw7w.png -//! [login-screenshot]: https://i.imgur.com/KpwY4nb.png -//! -//! [![github]](https://github.com/MedzikUser/HomeDisk) -//! [![docs-rs]](https://homedisk-doc.vercel.app) -//! [![total-lines]](https://github.com/MedzikUser/HomeDisk) -//! [![code-size]](https://github.com/MedzikUser/HomeDisk) -//! [![ci]](https://github.com/MedzikUser/HomeDisk/actions/workflows/rust.yml) -//! -//! ![home-screenshot] -//! ![login-screenshot] -//! -//! ## 👨‍💻 Building -//! -//! First clone the repository: `git clone https://github.com/MedzikUser/HomeDisk.git` -//! -//! ### Requirements -//! - [Rust](https://rust-lang.org) -//! -//! To build run the command: `cargo build --release` -//! -//! The compiled binary can be found in `./target/release/homedisk` -//! -//! ## Configure -//! -//! Go to [config](homedisk_types::config) module - -#![doc(html_root_url = "https://homedisk-doc.medzik.xyz")] diff --git a/core/src/logger.rs b/core/src/logger.rs deleted file mode 100644 index f96695d..0000000 --- a/core/src/logger.rs +++ /dev/null @@ -1,17 +0,0 @@ -use tracing::level_filters::LevelFilter; - -// Max Logger Level on debug build -#[cfg(debug_assertions)] -const MAX_LEVEL: LevelFilter = LevelFilter::DEBUG; - -// Max Logger Level on release build -#[cfg(not(debug_assertions))] -const MAX_LEVEL: LevelFilter = LevelFilter::INFO; - -pub fn init() { - // initialize better_panic - better_panic::install(); - - // initialize tracing - tracing_subscriber::fmt().with_max_level(MAX_LEVEL).init(); -} diff --git a/core/src/main.rs b/core/src/main.rs deleted file mode 100644 index 5d18824..0000000 --- a/core/src/main.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{fs::File, path::Path}; - -use homedisk_database::Database; -use homedisk_server::serve_http; -use homedisk_types::config::Config; -use tracing::{info, warn}; - -mod logger; - -pub const DATABASE_FILE: &str = "homedisk.db"; - -#[tokio::main] -async fn main() { - logger::init(); - - let config = Config::parse().expect("Failed to parse configuration file"); - - // open database connection - let db = - // if database file doesn't exists create it - if !Path::new(DATABASE_FILE).exists() { - warn!("Database file doesn't exists."); - info!("Creating database file..."); - - // create an empty database file - File::create(DATABASE_FILE).expect("Failed to create a database file"); - - // open database file - let db = Database::open(DATABASE_FILE) - .await - .expect("Failed to open database file"); - - // create tables in the database - db.create_tables() - .await - .expect("Failed to create tables in the database"); - - db - } - // if database file exists - else { - Database::open(DATABASE_FILE) - .await - .expect("Failed to open database file") - }; - - // change the type from Vec to Vec so that the http server can correctly detect CORS hosts - let origins = config - .http - .cors - .iter() - .map(|e| e.parse().expect("parse CORS hosts")) - .collect(); - - // format host ip and port - let host = format!( - "{host}:{port}", - host = config.http.host, - port = config.http.port - ); - - serve_http(host, origins, db, config) - .await - .expect("start http server"); -} diff --git a/database/Cargo.toml b/database/Cargo.toml deleted file mode 100644 index 591b4c5..0000000 --- a/database/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "homedisk-database" -version = "0.0.0" -edition = "2021" - -[dependencies] -tracing = "0.1.36" -futures-util = "0.3.23" -sqlx = { version = "0.6.1", features = ["runtime-tokio-rustls", "sqlite"] } -uuid = { version = "1.1.2", features = ["v5"] } -homedisk-types = { path = "../types", features = ["database"] } - -[dev-dependencies] -tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] } diff --git a/database/src/lib.rs b/database/src/lib.rs deleted file mode 100644 index 66cada5..0000000 --- a/database/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![doc(html_root_url = "https://homedisk-doc.medzik.xyz")] - -mod sqlite; - -pub use homedisk_types::{ - database::User, - errors::{DatabaseError as Error, DatabaseResult as Result}, -}; -pub use sqlite::*; diff --git a/database/src/sqlite.rs b/database/src/sqlite.rs deleted file mode 100644 index 975c840..0000000 --- a/database/src/sqlite.rs +++ /dev/null @@ -1,286 +0,0 @@ -use std::str::FromStr; - -use futures_util::TryStreamExt; -use sqlx::{ - sqlite::{SqliteConnectOptions, SqliteQueryResult}, - ConnectOptions, Executor, Row, SqlitePool, -}; -use tracing::{debug, log::LevelFilter}; - -use super::{Error, Result, User}; - -/// SQL Database -#[derive(Debug, Clone)] -pub struct Database { - /// SQLite Connection Pool - pub conn: SqlitePool, -} - -impl Database { - /// Open a SQLite database - /// ```no_run - /// # async fn foo() -> homedisk_database::Result<()> { - /// use homedisk_database::Database; - /// - /// // open database in memory - /// Database::open("sqlite::memory:").await?; - /// - /// // open database from file - /// Database::open("path/to/database.db").await?; - /// - /// # Ok(()) } - /// ``` - pub async fn open(path: &str) -> Result { - debug!("Opening SQLite database"); - - // sqlite connection options - let mut options = SqliteConnectOptions::from_str(path).map_err(Error::OpenDatabase)?; - - // set log level to Debug - options.log_statements(LevelFilter::Debug); - - // create a database pool - let conn = SqlitePool::connect_with(options) - .await - .map_err(Error::ConnectDatabase)?; - - // return `Database` - Ok(Self { conn }) - } - - /// Create all required tabled for HomeDisk - /// ``` - /// # async fn foo() -> homedisk_database::Result<()> { - /// # let db = homedisk_database::Database::open("sqlite::memory:").await?; - /// db.create_tables().await?; - /// - /// # Ok(()) } - /// ``` - pub async fn create_tables(&self) -> Result { - let query = sqlx::query(include_str!("../../tables.sql")); - - self.conn.execute(query).await.map_err(Error::Execute) - } - - /// Create a new User - /// ``` - /// # async fn foo() -> homedisk_database::Result<()> { - /// # let db = homedisk_database::Database::open("sqlite::memory:").await?; - /// # db.create_tables().await?; - /// use homedisk_database::User; - /// - /// // create `User` type - /// let user = User::new("username", "password"); - /// - /// // create a user in database - /// db.create_user(&user).await?; - /// - /// # Ok(()) } - /// ``` - pub async fn create_user(&self, user: &User) -> Result { - debug!("Creating user - {}", user.username); - - // insert user to a database - let query = sqlx::query("INSERT INTO user (id, username, password) VALUES (?, ?, ?)") - .bind(&user.id) - .bind(&user.username) - .bind(&user.password); - - // execute query and return output - self.conn.execute(query).await.map_err(Error::Execute) - } - - /// Search for a user - /// ``` - /// # async fn foo() -> homedisk_database::Result<()> { - /// # let db = homedisk_database::Database::open("sqlite::memory:").await?; - /// # db.create_tables().await?; - /// use homedisk_database::User; - /// - /// // create `User` type - /// let user = User::new("username", "password"); - /// - /// # db.create_user(&user).await?; - /// db.find_user(&user).await?; - /// - /// # Ok(()) } - /// ``` - pub async fn find_user(&self, user: &User) -> Result { - debug!("Searching for a user - {}", user.username); - - // create query request to database - let query = - sqlx::query_as::<_, User>("SELECT * FROM user WHERE username = ? AND password = ?") - .bind(&user.username) - .bind(&user.password); - - // fetch query - let mut stream = self.conn.fetch(query); - - // get rows from query - let row = stream - .try_next() - .await - .map_err(Error::Execute)? - .ok_or(Error::UserNotFound)?; - - // get `id` row - let id = row.try_get("id").map_err(Error::GetRow)?; - // get `username` row - let username = row.try_get("username").map_err(Error::GetRow)?; - // get `password` row - let password = row.try_get("password").map_err(Error::GetRow)?; - - // return `User` - Ok(User { - id, - username, - password, - }) - } - - /// Search for a user by UUID - /// ``` - /// # async fn foo() -> homedisk_database::Result<()> { - /// # let db = homedisk_database::Database::open("sqlite::memory:").await?; - /// # db.create_tables().await?; - /// use homedisk_database::User; - /// - /// // create `User` type - /// let user = User::new("username", "password"); - /// - /// # db.create_user(&user).await?; - /// db.find_user_by_id(&user.id).await?; - /// - /// # Ok(()) } - /// ``` - pub async fn find_user_by_id(&self, id: &str) -> Result { - debug!("Searching for a user by UUID - {}", id); - - // create query request to database - let query = sqlx::query_as::<_, User>("SELECT * FROM user WHERE id = ?").bind(id); - - // fetch query - let mut stream = self.conn.fetch(query); - - // get rows from query - let row = stream - .try_next() - .await - .map_err(Error::Execute)? - .ok_or(Error::UserNotFound)?; - - // get `id` row - let id = row.try_get("id").map_err(Error::GetRow)?; - // get `username` row - let username = row.try_get("username").map_err(Error::GetRow)?; - // get `password` row - let password = row.try_get("password").map_err(Error::GetRow)?; - - // return `User` - Ok(User { - id, - username, - password, - }) - } -} - -#[cfg(test)] -mod tests { - use crate::{Database, User}; - - const USERNAME: &str = "medzik"; - const PASSWORD: &str = "SuperSecretPassword123!"; - - /// Utils to open database in tests - async fn open_db() -> Database { - Database::open("sqlite::memory:").await.expect("open db") - } - - /// Utils to create a new user in tests - async fn new_user(db: &Database) { - // create tables - db.create_tables().await.expect("create tables"); - - // create new user - let user = User::new(USERNAME, PASSWORD); - db.create_user(&user).await.expect("create user"); - } - - /// Test a create user - #[tokio::test] - async fn create_user() { - let db = open_db().await; - new_user(&db).await; - } - - // Test a search for a user - #[tokio::test] - async fn find_user() { - let db = open_db().await; - - new_user(&db).await; - - let user = User::new(USERNAME, PASSWORD); - - let user = db.find_user(&user).await.unwrap(); - - assert_eq!(user.username, USERNAME) - } - - // Test a search for a user by id - #[tokio::test] - async fn find_user_by_id() { - let db = open_db().await; - new_user(&db).await; - - let user = User::new(USERNAME, PASSWORD); - - let user = db.find_user_by_id(&user.id).await.unwrap(); - - assert_eq!(user.username, USERNAME) - } - - /// Test a search for a user with an invalid password to see if the user is returned (it shouldn't be) - #[tokio::test] - async fn find_user_wrong_password() { - let db = open_db().await; - - new_user(&db).await; - - let user = User::new(USERNAME, "wrong password 123!"); - - let err = db.find_user(&user).await.unwrap_err(); - - assert_eq!(err.to_string(), "user not found") - } - - /// Test a search for a user who doesn't exist - #[tokio::test] - async fn find_user_wrong_username() { - let db = open_db().await; - - new_user(&db).await; - - let user = User::new("not_exists_user", PASSWORD); - - let err = db.find_user(&user).await.unwrap_err(); - - assert_eq!(err.to_string(), "user not found") - } - - /// Test a search for a user by UUID who doesn't exist - #[tokio::test] - async fn find_user_wrong_id() { - let db = open_db().await; - - new_user(&db).await; - - let other_user = User::new("not_exists_user", "my secret passphrase"); - - let err = db.find_user_by_id(&other_user.id).await.unwrap_err(); - - assert_eq!(err.to_string(), "user not found") - } -} diff --git a/renovate.json b/renovate.json index 995d4f7..d5d1e0b 100644 --- a/renovate.json +++ b/renovate.json @@ -22,4 +22,4 @@ "automerge": true } ] -} \ No newline at end of file +} diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index b33cd56..0000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "homedisk-server" -version = "0.0.0" -edition = "2021" - -[dependencies] -axum = { version = "0.5.15", features = ["multipart"] } -axum-auth = "0.3.0" -base64 = "0.13.0" -byte-unit = "4.0.14" -futures = "0.3.23" -hyper = { version = "0.14.20", features = ["full"] } -log = "0.4.17" -crypto-utils = { version = "0.4.0", features = ["jwt"] } -serde = { version = "1.0.144", features = ["derive"] } -thiserror = "1.0.32" -tower-http = { version = "0.3.4", features = ["full"] } -homedisk-database = { path = "../database" } -homedisk-types = { path = "../types", features = ["axum"] } diff --git a/server/src/auth/login.rs b/server/src/auth/login.rs deleted file mode 100644 index c57cd77..0000000 --- a/server/src/auth/login.rs +++ /dev/null @@ -1,46 +0,0 @@ -use axum::{extract::rejection::JsonRejection, Extension, Json}; -use homedisk_database::{Database, Error, User}; -use homedisk_types::{ - auth::login::{Request, Response}, - config::Config, - errors::{AuthError, ServerError}, -}; - -use crate::middleware::{create_token, validate_json}; - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - request: Result, JsonRejection>, -) -> Result, ServerError> { - // validate json request - let request = validate_json(request)?; - - // create `User` type - let user = User::new(&request.username, &request.password); - - // search for a user in database - let response = match db.find_user(&user).await { - Ok(user) => { - // create user token - let token = create_token(&user, config.jwt.secret.as_bytes(), config.jwt.expires)?; - - Response { - access_token: token, - } - }, - - // error while searching for a user - Err(err) => { - return match err { - // user not found - Error::UserNotFound => Err(ServerError::AuthError(AuthError::UserNotFound)), - // other error - _ => Err(ServerError::AuthError(AuthError::Other(err.to_string()))), - }; - }, - }; - - // send response - Ok(Json(response)) -} diff --git a/server/src/auth/mod.rs b/server/src/auth/mod.rs deleted file mode 100644 index 7bd765e..0000000 --- a/server/src/auth/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod login; -mod register; -mod whoami; - -pub fn app() -> axum::Router { - use axum::routing::{get, post}; - - axum::Router::new() - .route("/login", post(login::handle)) - .route("/register", post(register::handle)) - .route("/whoami", get(whoami::handle)) -} diff --git a/server/src/auth/register.rs b/server/src/auth/register.rs deleted file mode 100644 index ebec0ff..0000000 --- a/server/src/auth/register.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::fs; - -use axum::{extract::rejection::JsonRejection, Extension, Json}; -use homedisk_database::{Database, User}; -use homedisk_types::{ - auth::login::{Request, Response}, - config::Config, - errors::{AuthError, FsError, ServerError}, -}; - -use crate::middleware::{create_token, validate_json}; - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - request: Result, JsonRejection>, -) -> Result, ServerError> { - // validate json request - let request = validate_json(request)?; - - // username must contain at least 4 characters - if request.username.len() < 4 { - return Err(ServerError::AuthError(AuthError::UsernameTooShort)); - } - - // username must be less than 25 characters - if request.username.len() > 25 { - return Err(ServerError::AuthError(AuthError::UsernameTooLong)); - } - - // password must contain at least 8 characters - if request.password.len() < 8 { - return Err(ServerError::AuthError(AuthError::PasswordTooShort)); - } - - // create `User` type and hash password - let user = User::new(&request.username, &request.password); - - // create user in the database - let response = match db.create_user(&user).await { - Ok(_result) => { - let token = create_token(&user, config.jwt.secret.as_bytes(), config.jwt.expires)?; - - Response { - access_token: token, - } - }, - - // error while searching for a user - Err(err) => { - // user already exists - if err.to_string().contains("UNIQUE constraint failed") { - return Err(ServerError::AuthError(AuthError::UserAlreadyExists)); - } - - // other error - return Err(ServerError::AuthError(AuthError::Other(err.to_string()))); - }, - }; - - // create directory for the user files - fs::create_dir_all(&format!("{}/{}", config.storage.path, user.username,)) - .map_err(|e| ServerError::FsError(FsError::CreateDirectory(e.to_string())))?; - - // send response - Ok(Json(response)) -} diff --git a/server/src/auth/whoami.rs b/server/src/auth/whoami.rs deleted file mode 100644 index c6a6f34..0000000 --- a/server/src/auth/whoami.rs +++ /dev/null @@ -1,37 +0,0 @@ -use axum::{Extension, Json}; -use axum_auth::AuthBearer; -use homedisk_database::{Database, Error}; -use homedisk_types::{ - auth::whoami::Response, - config::Config, - errors::{AuthError, ServerError}, -}; - -use crate::middleware::validate_jwt; - -pub async fn handle( - db: Extension, - config: Extension, - AuthBearer(token): AuthBearer, -) -> Result, ServerError> { - // validate user token - let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; - - // search for a user in database - let response = match db.find_user_by_id(&token.claims.sub).await { - Ok(user) => Response { - username: user.username, - }, - - // error while searching for a user - Err(err) => match err { - // user not found - Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), - // other error - _ => return Err(ServerError::AuthError(AuthError::Other(err.to_string()))), - }, - }; - - // send response - Ok(Json(response)) -} diff --git a/server/src/error.rs b/server/src/error.rs deleted file mode 100644 index 9a3a0a3..0000000 --- a/server/src/error.rs +++ /dev/null @@ -1,34 +0,0 @@ -// HTTP Error -#[derive(Debug, thiserror::Error)] -pub enum Error { - // axum::Error - #[error("axum error - {0}")] - Axum(axum::Error), - // hyper::Error - #[error("hyper error - {0}")] - Hyper(hyper::Error), - // std::net::AddrParseError - #[error("std::net::AddrParseError - {0}")] - AddrParseError(std::net::AddrParseError), -} - -/// Custom Result -pub type Result = std::result::Result; - -impl From for Error { - fn from(err: axum::Error) -> Self { - Error::Axum(err) - } -} - -impl From for Error { - fn from(err: hyper::Error) -> Self { - Error::Hyper(err) - } -} - -impl From for Error { - fn from(err: std::net::AddrParseError) -> Self { - Error::AddrParseError(err) - } -} diff --git a/server/src/fs/create_dir.rs b/server/src/fs/create_dir.rs deleted file mode 100644 index 2ddc2e8..0000000 --- a/server/src/fs/create_dir.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::fs; - -use axum::{extract::rejection::JsonRejection, Extension, Json}; -use axum_auth::AuthBearer; -use homedisk_database::Database; -use homedisk_types::{ - config::Config, - errors::{FsError, ServerError}, - fs::create_dir::Request, -}; - -use crate::middleware::{find_user, validate_json, validate_jwt, validate_path}; - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - AuthBearer(token): AuthBearer, - request: Result, JsonRejection>, -) -> Result<(), ServerError> { - // validate json request - let Json(request) = validate_json(request)?; - - // validate user token - let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; - - // validate the `path` can be used - validate_path(&request.path)?; - - // search for a user by UUID from a token - let user = find_user(&db, &token.claims.sub).await?; - - // directory where the file will be placed - let path = format!( - "{user_dir}/{req_dir}", - user_dir = user.user_dir(&config.storage.path), - req_dir = request.path - ); - - // create directories - fs::create_dir_all(path) - .map_err(|err| ServerError::FsError(FsError::CreateDirectory(err.to_string())))?; - - // send an empty response - Ok(()) -} diff --git a/server/src/fs/delete.rs b/server/src/fs/delete.rs deleted file mode 100644 index d1d8365..0000000 --- a/server/src/fs/delete.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::{fs, path::Path}; - -use axum::{extract::Query, Extension}; -use axum_auth::AuthBearer; -use homedisk_database::Database; -use homedisk_types::{ - config::Config, - errors::{FsError, ServerError}, - fs::delete::Request, -}; - -use crate::middleware::{find_user, validate_jwt, validate_path}; - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - AuthBearer(token): AuthBearer, - query: Query, -) -> Result<(), ServerError> { - // validate user token - let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; - - // validate the `path` can be used - validate_path(&query.path)?; - - // search for a user by UUID from a token - let user = find_user(&db, &token.claims.sub).await?; - - // path to the file - let path = format!( - "{user_dir}/{request_path}", - user_dir = user.user_dir(&config.storage.path), - request_path = query.path - ); - let path = Path::new(&path); - - // if file does not exist return error - if !path.exists() { - return Err(ServerError::FsError(FsError::FileDoesNotExist)); - } - - // delete file - if path.is_file() { - fs::remove_file(&path) - .map_err(|err| ServerError::FsError(FsError::DeleteFile(err.to_string())))?; - } - // delete directory - else if path.is_dir() { - fs::remove_dir(&path) - .map_err(|err| ServerError::FsError(FsError::DeleteDirectory(err.to_string())))?; - } - - // send an empty response - Ok(()) -} diff --git a/server/src/fs/download.rs b/server/src/fs/download.rs deleted file mode 100644 index e808cec..0000000 --- a/server/src/fs/download.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::fs; - -use axum::{extract::Query, Extension}; -use axum_auth::AuthBearer; -use homedisk_database::Database; -use homedisk_types::{ - config::Config, - errors::{FsError, ServerError}, - fs::upload::Pagination, -}; - -use crate::middleware::{find_user, validate_jwt, validate_path}; - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - AuthBearer(token): AuthBearer, - query: Query, -) -> Result, ServerError> { - // validate user token - let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; - - // validate the `path` can be used - validate_path(&query.path)?; - - // search for a user by UUID from a token - let user = find_user(&db, &token.claims.sub).await?; - - // directory where the file will be placed - let path = format!( - "{user_dir}/{req_dir}", - user_dir = user.user_dir(&config.storage.path), - req_dir = query.path - ); - - // read file content - let content = - fs::read(path).map_err(|err| ServerError::FsError(FsError::ReadFile(err.to_string())))?; - - // send file content in response - Ok(content) -} diff --git a/server/src/fs/list.rs b/server/src/fs/list.rs deleted file mode 100644 index 38c2e76..0000000 --- a/server/src/fs/list.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::{fs, io, path::PathBuf, time::SystemTime}; - -use axum::{extract::rejection::JsonRejection, Extension, Json}; -use axum_auth::AuthBearer; -use byte_unit::Byte; -use homedisk_database::Database; -use homedisk_types::{ - config::Config, - errors::{FsError, ServerError}, - fs::list::{DirInfo, FileInfo, Request, Response}, -}; - -use crate::middleware::{find_user, validate_json, validate_jwt, validate_path}; - -/// Get directory size on disk (size of all files in directory). -fn dir_size(path: impl Into) -> io::Result { - fn dir_size(mut dir: fs::ReadDir) -> io::Result { - dir.try_fold(0, |acc, file| { - let file = file?; - let size = match file.metadata()? { - data if data.is_dir() => dir_size(fs::read_dir(file.path())?)?, - data => data.len(), - }; - Ok(acc + size) - }) - } - - dir_size(fs::read_dir(path.into())?) -} - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - AuthBearer(token): AuthBearer, - request: Result, JsonRejection>, -) -> Result, ServerError> { - // validate json request - let Json(request) = validate_json::(request)?; - - // validate user token - let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; - - // validate the `path` can be used - validate_path(&request.path)?; - - // search for a user by UUID from a token - let user = find_user(&db, &token.claims.sub).await?; - - // directory where the file will be placed - let path = format!( - "{user_dir}/{req_dir}", - user_dir = user.user_dir(&config.storage.path), - req_dir = request.path - ); - - // get paths from dir - let paths = fs::read_dir(&path) - .map_err(|err| ServerError::FsError(FsError::ReadDirectory(err.to_string())))?; - - let mut files = vec![]; - let mut dirs = vec![]; - - for f in paths { - // handle Error - let f = f.map_err(|err| ServerError::FsError(FsError::Other(err.to_string())))?; - - // get path metadata - let metadata = f - .metadata() - .map_err(|err| ServerError::FsError(FsError::Other(err.to_string())))?; - - // get name of the path - let name = f.path().display().to_string().replace(&path, ""); - - // if path is directory - if metadata.is_dir() { - let size = Byte::from_bytes( - dir_size(f.path().display().to_string()) - .map_err(|err| ServerError::FsError(FsError::Other(err.to_string())))? - .into(), - ) - .get_appropriate_unit(true) - .to_string(); - - dirs.push(DirInfo { name, size }) - } - // if path is file - else { - // get file size in bytes - let size = Byte::from_bytes(metadata.len().into()) - .get_appropriate_unit(true) - .to_string(); - - // get modification time in unix time format - let modified = metadata - .modified() - .unwrap() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - - files.push(FileInfo { - name, - size, - modified, - }) - } - } - - // send response - Ok(Json(Response { files, dirs })) -} diff --git a/server/src/fs/mod.rs b/server/src/fs/mod.rs deleted file mode 100644 index 14b0395..0000000 --- a/server/src/fs/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod create_dir; -mod delete; -mod download; -mod list; -mod upload; - -pub fn app() -> axum::Router { - use axum::routing::{delete, get, post}; - - axum::Router::new() - .route("/list", post(list::handle)) - .route("/upload", post(upload::handle)) - .route("/delete", delete(delete::handle)) - .route("/download", get(download::handle)) - .route("/createdir", post(create_dir::handle)) -} diff --git a/server/src/fs/upload.rs b/server/src/fs/upload.rs deleted file mode 100644 index 16842e5..0000000 --- a/server/src/fs/upload.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::{fs, io::Write, path::Path}; - -use axum::{ - extract::{Multipart, Query}, - Extension, -}; -use axum_auth::AuthBearer; -use futures::TryStreamExt; -use homedisk_database::Database; -use homedisk_types::{ - config::Config, - errors::{FsError, ServerError}, - fs::upload::Pagination, -}; - -use crate::middleware::{find_user, validate_jwt, validate_path}; - -pub async fn handle( - Extension(db): Extension, - Extension(config): Extension, - AuthBearer(token): AuthBearer, - mut multipart: Multipart, - query: Query, -) -> Result<(), ServerError> { - // validate user token - let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?; - - // validate the `path` can be used - validate_path(&query.path)?; - - // search for a user by UUID from a token - let user = find_user(&db, &token.claims.sub).await?; - - // path to the file - let file_path = format!( - "{user_dir}/{request_path}", - user_dir = user.user_dir(&config.storage.path), - request_path = query.path - ); - let file_path = Path::new(&file_path); - - // check if the file currently exists to avoid overwriting it - if file_path.exists() { - return Err(ServerError::FsError(FsError::FileAlreadyExists)); - } - - // create a directory where the file will be placed - // e.g. path ==> `/secret/files/images/screenshot.png` - // directories up to `{storage dir}/{username}/secret/files/images/` will be created - if let Some(prefix) = file_path.parent() { - fs::create_dir_all(&prefix) - .map_err(|err| ServerError::FsError(FsError::CreateFile(err.to_string())))? - } - - // get multipart field - let field = multipart - .next_field() - .await - .map_err(|_| ServerError::FsError(FsError::MultipartError))? - .ok_or(ServerError::FsError(FsError::MultipartError))?; - - // create file - let file = std::fs::File::create(&file_path) - .map_err(|err| ServerError::FsError(FsError::CreateFile(err.to_string())))?; - - // write file (chunk by chunk) - field - .try_fold((file, 0u64), |(mut file, written_len), bytes| async move { - file.write_all(bytes.as_ref()) - .expect("failed to write chunk to file"); - - Ok((file, written_len + bytes.len() as u64)) - }) - .await - .map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?; - - // send an empty response - Ok(()) -} diff --git a/server/src/lib.rs b/server/src/lib.rs deleted file mode 100644 index 2d19cb7..0000000 --- a/server/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![doc(html_root_url = "https://homedisk-doc.medzik.xyz")] - -mod auth; -mod error; -mod fs; -mod middleware; - -use axum::{http::HeaderValue, routing::get, Extension, Router, Server}; -use homedisk_database::Database; -use homedisk_types::config::Config; -use log::{debug, info}; -use tower_http::cors::{AllowOrigin, CorsLayer}; - -/// Handle `/health-check` requests -async fn health_check() -> &'static str { - "I'm alive!" -} - -/// Start HTTP server -pub async fn serve_http( - host: String, - origins: Vec, - db: Database, - config: Config, -) -> error::Result<()> { - debug!("Starting http server"); - info!("Website available at: http://{host}"); - - // create http Router - let app = Router::new() - .route("/health-check", get(health_check)) - .nest("/api/auth", auth::app()) - .nest("/api/fs", fs::app()) - .layer(CorsLayer::new().allow_origin(AllowOrigin::list(origins))) - .layer(Extension(db)) - .layer(Extension(config)); - - // bind the provided address and serve Router - Server::bind(&host.parse()?) - .serve(app.into_make_service()) - .await?; - - Ok(()) -} diff --git a/server/src/middleware/auth.rs b/server/src/middleware/auth.rs deleted file mode 100644 index f5310ae..0000000 --- a/server/src/middleware/auth.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crypto_utils::jsonwebtoken::{Token, TokenData}; -use homedisk_types::errors::{AuthError, ServerError}; - -/// Validate user token -pub fn validate_jwt(secret: &[u8], token: &str) -> Result { - match Token::decode(secret, token.to_string()) { - // if success return claims - Ok(claims) => Ok(claims), - // invalid token - Err(_) => Err(ServerError::AuthError(AuthError::InvalidToken)), - } -} - -#[cfg(test)] -mod tests { - use homedisk_database::User; - - use super::validate_jwt; - use crate::middleware::create_token; - - const USERNAME: &str = "username"; - const PASSWORD: &str = "password"; - - const SECRET: &[u8] = b"secret"; - const INVALID_SECRET: &[u8] = b"invalid secret"; - - /// Test a token validation - #[test] - fn validate_token() { - let user = User::new(USERNAME, PASSWORD); - - let token = create_token(&user, SECRET, 1).unwrap(); - - validate_jwt(SECRET, &token).unwrap(); - } - - /// Test a token validation (invalid secret) - #[test] - fn validate_token_invalid_secret() { - let user = User::new(USERNAME, PASSWORD); - - let token = create_token(&user, SECRET, 1).unwrap(); - - validate_jwt(INVALID_SECRET, &token).unwrap_err(); - } -} diff --git a/server/src/middleware/jwt.rs b/server/src/middleware/jwt.rs deleted file mode 100644 index 1bac704..0000000 --- a/server/src/middleware/jwt.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crypto_utils::jsonwebtoken::{Claims, Token}; -use homedisk_database::{Database, User}; -use homedisk_types::errors::{AuthError, ServerError}; - -/// Create user token -pub fn create_token(user: &User, secret: &[u8], expires: i64) -> Result { - let token = Token::new(secret, Claims::new(&user.id, expires)) - .map_err(|_| ServerError::AuthError(AuthError::TokenGenerate))?; - - Ok(token.encoded) -} - -/// Search for a user -pub async fn find_user(db: &Database, user_id: &str) -> Result { - match db.find_user_by_id(user_id).await { - // if success return user - Ok(user) => Ok(user), - // errors - Err(err) => match err { - // user not found - homedisk_database::Error::UserNotFound => { - Err(ServerError::AuthError(AuthError::UserNotFound)) - }, - // other error - _ => Err(ServerError::AuthError(AuthError::Other(err.to_string()))), - }, - } -} - -#[cfg(test)] -mod tests { - use homedisk_database::User; - - use super::create_token; - - const SECRET: &[u8] = b"secret"; - - const USERNAME: &str = "username"; - const PASSWORD: &str = "password"; - - /// Test a token creation - #[test] - fn test_create_token() { - let secret = SECRET; - let user = User::new(USERNAME, PASSWORD); - - create_token(&user, secret, 1).unwrap(); - } -} diff --git a/server/src/middleware/mod.rs b/server/src/middleware/mod.rs deleted file mode 100644 index 34c28d6..0000000 --- a/server/src/middleware/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod auth; -mod jwt; -mod validate_json; -mod validate_path; - -pub use auth::*; -pub use jwt::*; -pub use validate_json::*; -pub use validate_path::*; diff --git a/server/src/middleware/validate_json.rs b/server/src/middleware/validate_json.rs deleted file mode 100644 index 05530d4..0000000 --- a/server/src/middleware/validate_json.rs +++ /dev/null @@ -1,20 +0,0 @@ -use axum::{extract::rejection::JsonRejection, Json}; -use homedisk_types::errors::ServerError; - -/// Validate json request -pub fn validate_json(payload: Result, JsonRejection>) -> Result, ServerError> { - match payload { - // if success return payload - Ok(payload) => Ok(payload), - // mission json in Content-Type Header - Err(JsonRejection::MissingJsonContentType(_)) => Err(ServerError::InvalidContentType), - // failed to deserialize json - Err(JsonRejection::JsonDataError(_)) => Err(ServerError::JsonDataError), - // syntax error in json - Err(JsonRejection::JsonSyntaxError(_)) => Err(ServerError::JsonSyntaxError), - // failed to extract the request body - Err(JsonRejection::BytesRejection(_)) => Err(ServerError::BytesRejection), - // other error - Err(err) => Err(ServerError::Other(err.to_string())), - } -} diff --git a/server/src/middleware/validate_path.rs b/server/src/middleware/validate_path.rs deleted file mode 100644 index 38007d5..0000000 --- a/server/src/middleware/validate_path.rs +++ /dev/null @@ -1,40 +0,0 @@ -use homedisk_types::errors::{FsError, ServerError}; - -/// Validate path param provided in the request -pub fn validate_path(path: &str) -> Result<(), ServerError> { - // `path` can't contain `..` - // to prevent attack attempts because by using a `..` you can access the previous folder - if path.contains("..") { - return Err(ServerError::FsError(FsError::ReadDirectory( - "the `path` can't contain `..`".to_string(), - ))); - } - - // `path` can't contain `~` - // to prevent attack attempts because `~` can get up a directory on `$HOME` - if path.contains('~') { - return Err(ServerError::FsError(FsError::ReadDirectory( - "the `path` can't not contain `~`".to_string(), - ))); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_validate_path() { - // Successfully - assert!(validate_path("Directory/path/to/test.png").is_ok()); - assert!(validate_path("/test.png").is_ok()); // `/` doesn't point to the system root - assert!(validate_path("./test.png").is_ok()); - - // Errors - assert!(validate_path("../../test.png").is_err()); - assert!(validate_path("../test.png").is_err()); - assert!(validate_path("~/test.png").is_err()); - } -} diff --git a/types/src/errors/database.rs b/src/database/error.rs similarity index 85% rename from types/src/errors/database.rs rename to src/database/error.rs index ff7f3fb..cc99587 100644 --- a/types/src/errors/database.rs +++ b/src/database/error.rs @@ -11,6 +11,8 @@ pub enum Error { ConnectDatabase(sqlx::Error), #[error("failed to get row: {0}")] GetRow(sqlx::Error), + #[error("failed to create all required tables: {0}")] + CreateTables(sqlx::Error), #[error("failed to execute the query: {0}")] Execute(sqlx::Error), } diff --git a/src/database/mod.rs b/src/database/mod.rs new file mode 100644 index 0000000..4eca388 --- /dev/null +++ b/src/database/mod.rs @@ -0,0 +1,8 @@ +//! [SQLite database functions](sqlite::Database). + +pub mod error; +mod sqlite; +mod user; + +pub use sqlite::*; +pub use user::*; diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs new file mode 100644 index 0000000..f51f977 --- /dev/null +++ b/src/database/sqlite.rs @@ -0,0 +1,174 @@ +use std::str::FromStr; + +use futures_util::TryStreamExt; +use sqlx::{ + sqlite::{SqliteConnectOptions, SqliteQueryResult, SqliteRow}, + ConnectOptions, Executor, Row, SqlitePool, +}; +use tracing::{debug, info, log::LevelFilter}; + +use super::{ + error::{Error, Result}, + User, +}; + +/// SQLite database +#[derive(Clone)] +pub struct Database { + /// Sqlite connection pool + pub pool: SqlitePool, +} + +// TODO: Check UUID +impl Database { + /// Open a SQLite database + pub async fn open(path: &str) -> Result { + debug!("Opening SQLite database: {}", path); + + // sqlite connection options + let mut options = SqliteConnectOptions::from_str(path).map_err(Error::OpenDatabase)?; + + // set log level to Debug + options.log_statements(LevelFilter::Debug); + + // create a database pool + let pool = SqlitePool::connect_with(options) + .await + .map_err(Error::ConnectDatabase)?; + + info!("Connected to database!"); + + Ok(Self { pool }) + } + + /// Create all required tables for HomeDisk. + pub async fn create_tables(&self) -> Result { + self.pool + .execute(include_str!("../../tables.sql")) + .await + .map_err(Error::CreateTables) + } + + /// Create new user in the database. + pub async fn create_user(&self, user: &User) -> Result { + debug!("Creating user - {}", user.username); + + // build sql query + let query = sqlx::query("INSERT INTO user (id, username, password) VALUES (?, ?, ?)") + .bind(&user.id) + .bind(&user.username) + .bind(&user.password); + + self.pool.execute(query).await.map_err(Error::Execute) + } + + /// Search for a user. + pub async fn find_user(&self, user: &User) -> Result { + debug!("Searching for a user - {}", user.username); + + // create query request to database + let query = sqlx::query("SELECT * FROM user WHERE username = ? AND password = ?") + .bind(&user.username) + .bind(&user.password); + + // fetch query + let mut stream = self.pool.fetch(query); + + // get rows from query + let row = stream + .try_next() + .await + .map_err(Error::Execute)? + .ok_or(Error::UserNotFound)?; + + Self::find(row) + } + + /// Search for a user by UUID. + pub async fn find_user_by_id(&self, id: &str) -> Result { + debug!("Searching for a user by UUID - {}", id); + + // create query request to database + let query = sqlx::query("SELECT * FROM user WHERE id = ?").bind(id); + + // fetch query + let mut stream = self.pool.fetch(query); + + // get rows from query + let row = stream + .try_next() + .await + .map_err(Error::Execute)? + .ok_or(Error::UserNotFound)?; + + Self::find(row) + } + + fn find(row: SqliteRow) -> Result { + // get `id` row + let id = row.try_get("id").map_err(Error::GetRow)?; + // get `username` row + let username = row.try_get("username").map_err(Error::GetRow)?; + // get `password` row + let password = row.try_get("password").map_err(Error::GetRow)?; + + Ok(User { + id, + username, + password, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const USERNAME: &str = "medzik"; + const PASSWORD: &str = "SuperSecretPassword123!"; + + async fn open_db() -> Database { + Database::open("sqlite::memory:") + .await + .expect("Failed to open database in memory") + } + + async fn new_user() -> Database { + let db = open_db().await; + + // create tables + db.create_tables().await.expect("create tables"); + + // create new user + let user = User::new(USERNAME, PASSWORD, true); + db.create_user(&user).await.expect("create user"); + + db + } + + #[tokio::test] + async fn test_create_user() { + new_user().await; + } + + #[tokio::test] + async fn test_find_user() { + let db = new_user().await; + + let user = db.find_user(&User::new(USERNAME, PASSWORD, false)).await.unwrap(); + + assert_eq!(user.username, USERNAME) + } + + #[tokio::test] + async fn test_find_user_wrong_password() { + let db = new_user().await; + + let err = db + .find_user(&User::new(USERNAME, "wrong password 123!", false)) + .await + .unwrap_err(); + + assert_eq!(err.to_string(), "user not found") + } +} diff --git a/src/database/user.rs b/src/database/user.rs new file mode 100644 index 0000000..ad514fc --- /dev/null +++ b/src/database/user.rs @@ -0,0 +1,93 @@ +use crypto_utils::sha::{Algorithm, CryptographicHash}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// SQL user entry +#[derive(Debug, Serialize, Deserialize)] +pub struct User { + pub id: String, + pub username: String, + pub password: String, +} + +impl User { + /// The function create a unique UUID and compute SHA-512 hash from salted user password + /// and returns the [User] type. + /// + /// **Note**: This function **does not** create a user in database! + pub fn new(username: &str, password: &str, gen_uuid: bool) -> Self { + // change username to lowercase + let username = username.to_lowercase(); + + // salting the password + let password = format!("{username}${password}"); + + // hash the password using SHA-512 algorithm and encode it into String. + let password = hex::encode(CryptographicHash::hash( + Algorithm::SHA512, + password.as_bytes(), + )); + + // generate UUID + let id = if gen_uuid { + Uuid::new_v4().to_string() + } else { + "none".to_string() + }; + + Self { + id, + username, + password, + } + } + + /// The function returns the directory where the user files is located. + pub fn user_dir(&self, storage: &str) -> String { + format!("{storage}/{username}", username = self.username) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Check if the username is in lowercase. + #[test] + fn test_username_is_lowercase() { + // example user data + let username = "MeDZiK"; + let password = "password"; + + // expected username + let username_expected = "medzik"; + + let user = User::new(username, password, false); + + // username validation + assert_eq!(user.username, username_expected) + } + + /// Check if the password is hashed. + #[test] + fn test_password_hashed() { + // example user data + let username = "username"; + let password = "password"; + + let user = User::new(username, password, false); + + assert_ne!(user.password, password) + } + + #[test] + fn test_user_dir() { + let storage_dir: &str = "/storage"; + + let user = User::new("medzik", "whatever", false); + + let user_dir = user.user_dir(storage_dir); + + assert_eq!(user_dir, "/storage/medzik"); + } +} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..084affe --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,77 @@ +//! Initialize logger. + +use std::{panic, thread}; + +use tracing::{error, level_filters::LevelFilter}; + +#[cfg(debug_assertions)] +const MAX_LEVEL: LevelFilter = LevelFilter::DEBUG; + +#[cfg(not(debug_assertions))] +const MAX_LEVEL: LevelFilter = LevelFilter::INFO; + +/// Initialize logger (tracing and panic hook). +pub fn init() { + tracing_subscriber::fmt().with_max_level(MAX_LEVEL).init(); + + // catch panic and log them using tracing instead of default output to StdErr + panic::set_hook(Box::new(|info| { + let thread = thread::current(); + let thread = thread.name().unwrap_or("unknown"); + + let msg = match info.payload().downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload().downcast_ref::() { + Some(s) => &**s, + None => "Box", + }, + }; + + let backtrace = backtrace::Backtrace::new(); + + match info.location() { + Some(location) => { + // without backtrace + if msg.starts_with("notrace - ") { + error!( + target: "panic", "thread '{}' panicked at '{}': {}:{}", + thread, + msg.replace("notrace - ", ""), + location.file(), + location.line() + ); + } + // with backtrace + else { + error!( + target: "panic", "thread '{}' panicked at '{}': {}:{}\n{:?}", + thread, + msg, + location.file(), + location.line(), + backtrace + ); + } + }, + None => { + // without backtrace + if msg.starts_with("notrace - ") { + error!( + target: "panic", "thread '{}' panicked at '{}'", + thread, + msg.replace("notrace - ", ""), + ); + } + // with backtrace + else { + error!( + target: "panic", "thread '{}' panicked at '{}'\n{:?}", + thread, + msg, + backtrace + ); + } + }, + } + })); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eed5d22 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,90 @@ +//! HomeDisk cloud server +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! [total-lines]: https://img.shields.io/tokei/lines/github/MedzikUser/HomeDisk?style=for-the-badge&logo=github&color=fede00 +//! [code-size]: https://img.shields.io/github/languages/code-size/MedzikUser/HomeDisk?style=for-the-badge&color=c8df52&logo=github +//! [ci]: https://img.shields.io/github/workflow/status/MedzikUser/HomeDisk/Rust/main?style=for-the-badge +//! +//! [home-screenshot]: https://raw.githubusercontent.com/HomeDisk/.github/main/screenshots/home.png +//! +//! [![github]](https://github.com/MedzikUser/HomeDisk) +//! [![docs-rs]](https://homedisk-doc.vercel.app) +//! [![total-lines]](https://github.com/MedzikUser/HomeDisk) +//! [![code-size]](https://github.com/MedzikUser/HomeDisk) +//! [![ci]](https://github.com/MedzikUser/HomeDisk/actions/workflows/rust.yml) +//! +//! ![home-screenshot] +//! +//! ## 👨‍💻 Building +//! +//! First clone the repository: `git clone https://github.com/MedzikUser/HomeDisk.git` +//! +//! ### Requirements +//! - [Rust](https://rust-lang.org) +//! +//! To build run the command: `cargo build --release` +//! +//! The compiled binary can be found in `./target/release/homedisk` +//! +//! ## Configure +//! +//! Go to [config] module + +use std::{fs::File, path::Path}; + +use config::Config; +use tracing::{info, warn}; + +use crate::database::Database; + +#[path = "./types/config.rs"] +mod config; +mod database; +mod logger; +mod server; + +/// Default SQLite file path for the database. +pub const DATABASE_FILE: &str = "homedisk.db"; +/// Default configuration file. +pub const CONFIG_FILE: &str = "config.toml"; + +#[tokio::main] +async fn main() { + logger::init(); + + let config = Config::parse(CONFIG_FILE).expect("notrace - Failed to parse configuration file"); + + // open database connection + let db = + // if database file doesn't exists create it + if !Path::new(DATABASE_FILE).exists() { + warn!("Database file doesn't exists."); + info!("Creating database file..."); + + // create an empty database file + File::create(DATABASE_FILE).expect("notrace - Failed to create a database file"); + + // open database file + let db = Database::open(DATABASE_FILE) + .await + .expect("notrace - Failed to open database file"); + + // create tables in the database + db.create_tables() + .await + .expect("notrace - Failed to create tables in the database"); + + db + } + // if database file exists + else { + Database::open(DATABASE_FILE) + .await + .expect("notrace - Failed to open database file") + }; + + server::start_server(config, db) + .await + .expect("notrace - HTTP Server error"); +} diff --git a/src/server/api/auth/login.rs b/src/server/api/auth/login.rs new file mode 100644 index 0000000..b57efd6 --- /dev/null +++ b/src/server/api/auth/login.rs @@ -0,0 +1,50 @@ +use axum::{Extension, Json}; +use crypto_utils::jsonwebtoken::{Claims, Token}; +use serde::{Deserialize, Serialize}; + +use crate::{ + config::Config, + database::{error::Error as DatabaseError, Database, User}, + server::error::*, +}; + +pub async fn login( + Extension(db): Extension, + Extension(config): Extension, + request: Json, +) -> Result> { + let user = User::new(&request.username, &request.password, false); + + let response = match db.find_user(&user).await { + Ok(user) => { + let token = Token::new( + config.jwt.secret.as_bytes(), + Claims::new(&user.id, config.jwt.expires), + ) + .map_err(|_| Error::GenerateToken)?; + + Response { + access_token: token.encoded, + } + }, + Err(err) => { + return match err { + DatabaseError::UserNotFound => Err(Error::UserNotFound), + _ => Err(Error::Database), + }; + }, + }; + + Ok(Json(response)) +} + +#[derive(Debug, Deserialize)] +pub struct Request { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub access_token: String, +} diff --git a/src/server/api/auth/mod.rs b/src/server/api/auth/mod.rs new file mode 100644 index 0000000..f566e92 --- /dev/null +++ b/src/server/api/auth/mod.rs @@ -0,0 +1,12 @@ +mod login; +mod register; +mod whoami; + +use axum::routing::*; + +pub fn app() -> Router { + Router::new() + .route("/login", post(login::login)) + .route("/register", post(register::register)) + .route("/whoami", get(whoami::whoami)) +} diff --git a/src/server/api/auth/register.rs b/src/server/api/auth/register.rs new file mode 100644 index 0000000..5b5c655 --- /dev/null +++ b/src/server/api/auth/register.rs @@ -0,0 +1,50 @@ +use axum::{Extension, Json}; +use serde::{Deserialize, Serialize}; + +use crate::{ + database::{Database, User}, + server::error::*, +}; + +pub async fn register( + Extension(db): Extension, + request: Json, +) -> Result> { + // username must contain at least 4 characters + if request.username.len() < 4 { + return Err(Error::UsernameTooShort); + } + + // username must be less than 25 characters + if request.username.len() > 25 { + return Err(Error::UsernameTooLong); + } + + // password must contain at least 8 characters + if request.password.len() < 8 { + return Err(Error::PasswordTooShort); + } + + let user = User::new(&request.username, &request.password, true); + + if let Err(err) = db.create_user(&user).await { + if err.to_string().contains("UNIQUE constraint failed") { + return Err(Error::UserAlreadyExists); + } + + return Err(Error::Database); + } + + Ok(Json(Response { success: true })) +} + +#[derive(Debug, Deserialize)] +pub struct Request { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub success: bool, +} diff --git a/src/server/api/auth/whoami.rs b/src/server/api/auth/whoami.rs new file mode 100644 index 0000000..31aeb50 --- /dev/null +++ b/src/server/api/auth/whoami.rs @@ -0,0 +1,15 @@ +use axum::Json; +use serde::Serialize; + +use crate::server::{error::*, utils::token::Token}; + +pub async fn whoami(Token(user): Token) -> Result> { + Ok(Json(Response { + user: user.username, + })) +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub user: String, +} diff --git a/src/server/api/fs/create_dir.rs b/src/server/api/fs/create_dir.rs new file mode 100644 index 0000000..81334ae --- /dev/null +++ b/src/server/api/fs/create_dir.rs @@ -0,0 +1,38 @@ +use std::{fs, path::Path}; + +use axum::{Extension, Json}; +use serde::Serialize; + +use crate::{ + config::Config, + server::{ + error::*, + utils::{ + path::{validate_path, PathQuery}, + token::Token, + }, + }, +}; + +pub async fn create_dir( + Extension(config): Extension, + path: PathQuery, + Token(user): Token, +) -> Result> { + let path = validate_path(path)?; + + let path = format!("{}/{}", user.user_dir(&config.storage.path), path); + + if Path::new(&path).exists() { + return Err(Error::AlreadyExists); + } + + fs::create_dir_all(path).map_err(|_| Error::CreateDirectory)?; + + Ok(Json(Response { success: true })) +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub success: bool, +} diff --git a/src/server/api/fs/delete.rs b/src/server/api/fs/delete.rs new file mode 100644 index 0000000..8e5c768 --- /dev/null +++ b/src/server/api/fs/delete.rs @@ -0,0 +1,43 @@ +use std::{fs, path::Path}; + +use axum::{Extension, Json}; +use serde::Serialize; + +use crate::{ + config::Config, + server::{ + error::*, + utils::{ + path::{validate_path, PathQuery}, + token::Token, + }, + }, +}; + +pub async fn delete( + Extension(config): Extension, + path: PathQuery, + Token(user): Token, +) -> Result> { + let path = validate_path(path)?; + + let path = format!("{}/{}", user.user_dir(&config.storage.path), path); + let path = Path::new(&path); + + if !path.exists() { + return Err(Error::NotFound); + } + + if path.is_dir() { + fs::remove_dir(&path).map_err(|_| Error::DeleteDirectory)?; + } else { + fs::remove_file(&path).map_err(|_| Error::DeleteFile)?; + } + + Ok(Json(Response { success: true })) +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub success: bool, +} diff --git a/src/server/api/fs/list.rs b/src/server/api/fs/list.rs new file mode 100644 index 0000000..4f801b3 --- /dev/null +++ b/src/server/api/fs/list.rs @@ -0,0 +1,86 @@ +use std::{fs, time::SystemTime}; + +use axum::{Extension, Json}; +use byte_unit::Byte; +use serde::Serialize; + +use crate::{ + config::Config, + server::{ + error::*, + utils::{ + path::{validate_path, PathQuery}, + token::Token, + }, + }, +}; + +pub async fn list( + Extension(config): Extension, + path: PathQuery, + Token(user): Token, +) -> Result> { + let path = validate_path(path)?; + + let mut response = Response::default(); + + let path = format!("{}/{}", user.user_dir(&config.storage.path), path); + + let paths = fs::read_dir(&path).map_err(|_| Error::FailedReadDirectory)?; + + for dir_entry in paths { + let dir_entry = dir_entry.unwrap(); + + let metadata = dir_entry.metadata().unwrap(); + + let name = dir_entry + .path() + .display() + .to_string() + .replace(&format!("{path}/"), "") + .replace(&path, ""); + + if metadata.is_dir() { + // TODO: add size and modification date + response.dirs.push(Entry::new(name, 0.to_string(), 0)) + } else { + let size = Byte::from_bytes(metadata.len().into()) + .get_appropriate_unit(true) + .to_string(); + + let modified = metadata + .modified() + .unwrap() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + + response.files.push(Entry::new(name, size, modified)); + } + } + + Ok(Json(response)) +} + +#[derive(Debug, Default, Serialize)] +pub struct Response { + pub dirs: Vec, + pub files: Vec, +} + +#[derive(Debug, Serialize)] +pub struct Entry { + pub name: String, + pub size: String, + pub modified: u64, +} + +impl Entry { + pub fn new(name: String, size: String, modified: u64) -> Self { + Self { + name, + size, + modified, + } + } +} diff --git a/src/server/api/fs/mod.rs b/src/server/api/fs/mod.rs new file mode 100644 index 0000000..d1e312b --- /dev/null +++ b/src/server/api/fs/mod.rs @@ -0,0 +1,14 @@ +mod create_dir; +mod delete; +mod list; +mod upload; + +use axum::routing::*; + +pub fn app() -> Router { + Router::new() + .route("/list", get(list::list)) + .route("/createDir", get(create_dir::create_dir)) + .route("/delete", delete(delete::delete)) + .route("/upload", post(upload::upload)) +} diff --git a/src/server/api/fs/upload.rs b/src/server/api/fs/upload.rs new file mode 100644 index 0000000..9ff21a6 --- /dev/null +++ b/src/server/api/fs/upload.rs @@ -0,0 +1,64 @@ +use std::{fs, io::Write, path::Path}; + +use axum::{extract::Multipart, Extension, Json}; +use futures_util::TryStreamExt; +use serde::Serialize; + +use crate::{ + config::Config, + server::{ + error::*, + utils::{ + path::{validate_path, PathQuery}, + token::Token, + }, + }, +}; + +pub async fn upload( + Extension(config): Extension, + path: PathQuery, + Token(user): Token, + mut multipart: Multipart, +) -> Result> { + let path = validate_path(path)?; + + let path = format!("{}/{}", user.user_dir(&config.storage.path), path); + let path = Path::new(&path); + + if path.exists() { + return Err(Error::AlreadyExists); + } + + // create a directory where the file will be placed + // e.g. path ==> `/secret/files/images/screenshot.png` + // directories up to `{storage dir}/{username}/secret/files/images/` will be created + if let Some(prefix) = path.parent() { + fs::create_dir_all(&prefix).map_err(|_| Error::CreateDirectory)? + } + + let field = multipart + .next_field() + .await + .map_err(|_| Error::Multipart)? + .ok_or(Error::Multipart)?; + + let file = fs::File::create(path).map_err(|_| Error::CreateFile)?; + + field + .try_fold((file, 0), |(mut file, written_len), bytes| async move { + file.write_all(bytes.as_ref()) + .expect("failed to write chunk to file"); + + Ok((file, written_len + bytes.len() as u64)) + }) + .await + .map_err(|_| Error::WriteFile)?; + + Ok(Json(Response { success: true })) +} + +#[derive(Debug, Serialize)] +pub struct Response { + pub success: bool, +} diff --git a/src/server/api/mod.rs b/src/server/api/mod.rs new file mode 100644 index 0000000..ab87416 --- /dev/null +++ b/src/server/api/mod.rs @@ -0,0 +1,15 @@ +mod auth; +mod fs; + +use axum::routing::*; + +pub async fn health() -> &'static str { + "I am working!" +} + +pub fn app() -> Router { + Router::new() + .nest("/auth", auth::app()) + .nest("/fs", fs::app()) + .route("/health", get(health)) +} diff --git a/src/server/error.rs b/src/server/error.rs new file mode 100644 index 0000000..079e779 --- /dev/null +++ b/src/server/error.rs @@ -0,0 +1,77 @@ +use serde::Serialize; +use thiserror::Error; +use tracing::error; + +#[derive(Debug, Clone, Error)] +pub enum Error { + // auth error + #[error("User not found")] + UserNotFound, + #[error("User already exists")] + UserAlreadyExists, + #[error("Failed to generate access token")] + GenerateToken, + #[error("An error occurred in database request")] + Database, + #[error("Username is too short")] + UsernameTooShort, + #[error("Username is too short")] + UsernameTooLong, + #[error("Password is too short")] + PasswordTooShort, + #[error("Invalid token")] + InvalidToken, + #[error("`{0}` header is missing")] + MissingHeader(&'static str), + #[error("`Authorization` header must be a bearer token")] + MissingBearer, + // fs error + #[error("Invalid `path` query parameter")] + InvalidPath, + #[error("Failed to read files from directory")] + FailedReadDirectory, + #[error("Failed to create directory")] + CreateDirectory, + #[error("Failed to create file")] + CreateFile, + #[error("File or directory already exists")] + AlreadyExists, + #[error("File or directory does not exists")] + NotFound, + #[error("Failed to delete directory (directory is not empty?)")] + DeleteDirectory, + #[error("Failed to delete file")] + DeleteFile, + #[error("Multipart error")] + Multipart, + #[error("Write to file failed")] + WriteFile, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "kebab-case")] +enum ResponseError { + Error(String), +} + +impl axum::response::IntoResponse for Error { + fn into_response(self) -> axum::response::Response { + use axum::http::StatusCode; + + #[cfg(debug_assertions)] + error!("Error: {:?}", self); + + let status = match self { + Error::GenerateToken => StatusCode::INTERNAL_SERVER_ERROR, + Error::Database => StatusCode::INTERNAL_SERVER_ERROR, + _ => StatusCode::BAD_REQUEST, + }; + + let mut response = axum::Json(ResponseError::Error(self.to_string())).into_response(); + *response.status_mut() = status; + + response + } +} + +pub type Result = std::result::Result; diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..8505166 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,30 @@ +mod api; +pub mod error; +pub mod utils; + +use anyhow::anyhow; +use axum::{routing::get, Extension, Router, Server}; +use tracing::info; + +use crate::{config::Config, database::Database}; + +pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> { + info!( + "🚀 Server has launched on http://{}:{}", + config.http.host, config.http.port + ); + + let host = format!("{}:{}", config.http.host, config.http.port); + + let app = Router::new() + .nest("/api", api::app()) + .route("/", get(api::health)) + .layer(Extension(config)) + .layer(Extension(db)); + + Server::bind(&host.parse()?) + .serve(app.into_make_service()) + .await?; + + Err(anyhow!("Server unexpected stopped!")) +} diff --git a/src/server/utils/mod.rs b/src/server/utils/mod.rs new file mode 100644 index 0000000..7663a74 --- /dev/null +++ b/src/server/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod token; +pub mod path; diff --git a/src/server/utils/path.rs b/src/server/utils/path.rs new file mode 100644 index 0000000..6ed402b --- /dev/null +++ b/src/server/utils/path.rs @@ -0,0 +1,29 @@ +use axum::extract::Query; +use serde::Deserialize; + +use crate::server::error::{Error, Result}; + +pub type PathQuery = Query; + +#[derive(Debug, Clone, Deserialize)] +pub struct Path { + pub path: String, +} + +pub fn validate_path(path: PathQuery) -> Result { + let path = path.path.clone(); + + // `path` can't contain `..` + // to prevent attack attempts because by using a `..` you can access the previous folder + if path.contains("..") { + return Err(Error::InvalidPath); + } + + // `path` can't contain `~` + // to prevent attack attempts because `~` can get up a directory on `$HOME` + if path.contains('~') { + return Err(Error::InvalidPath); + } + + Ok(path) +} diff --git a/src/server/utils/token.rs b/src/server/utils/token.rs new file mode 100644 index 0000000..4d7a827 --- /dev/null +++ b/src/server/utils/token.rs @@ -0,0 +1,60 @@ +use axum::{ + async_trait, + extract::FromRequestParts, + http::{header::AUTHORIZATION, request::Parts}, +}; +use crypto_utils::jsonwebtoken; + +use crate::{ + config::Config, + database::{error::Error as DatabaseError, Database, User}, + server::error::Error, +}; + +pub struct Token(pub User); + +#[async_trait] +impl FromRequestParts for Token +where + S: Send + Sync, +{ + type Rejection = Error; + + async fn from_request_parts(req: &mut Parts, _state: &S) -> Result { + let extensions = &req.extensions; + + let config = extensions.get::().unwrap(); + let db = extensions.get::().unwrap(); + + // Get authorisation header + let authorisation = req + .headers + .get(AUTHORIZATION) + .ok_or(Error::MissingHeader("Authorization"))? + .to_str() + .map_err(|_| Error::InvalidToken)?; + + // Check that its a well-formed bearer + let split = authorisation.split_once(' '); + + let token = match split { + Some((name, contents)) if name == "Bearer" => contents.to_string(), + _ => return Err(Error::MissingBearer), + }; + + let token = match jsonwebtoken::Token::decode(config.jwt.secret.as_bytes(), token) { + Ok(token) => token, + Err(_) => return Err(Error::InvalidToken), + }; + + let user = match db.find_user_by_id(&token.claims.sub).await { + Ok(user) => user, + Err(err) => match err { + DatabaseError::UserNotFound => return Err(Error::UserNotFound), + _ => return Err(Error::Database), + }, + }; + + Ok(Self(user)) + } +} diff --git a/src/types/config.rs b/src/types/config.rs new file mode 100644 index 0000000..b49c5fd --- /dev/null +++ b/src/types/config.rs @@ -0,0 +1,64 @@ +//! [Configuration file types](Config). +use std::fs; + +use serde::{Deserialize, Serialize}; + +/// Settings configuration. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + /// HTTP Settings. + pub http: ConfigHTTP, + /// Json Web Token Settings. + pub jwt: ConfigJWT, + /// Storage Settings. + pub storage: ConfigStorage, +} + +/// HTTP Settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConfigHTTP { + /// HTTP Host. + pub host: String, + /// Port HTTP Port. + pub port: u16, + /// [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Domains (e.g ["site1.example.com", "site2.example.com"]). + pub cors: Vec, +} + +/// Json Web Token Settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConfigJWT { + /// JWT Secret string (used to sign tokens). + pub secret: String, + /// Token expiration time in hours. + pub expires: i64, +} + +/// Storage Settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConfigStorage { + /// Directory where user files will be stored. + pub path: String, +} + +impl Config { + /// Parse configuration file. + pub fn parse(path: &str) -> anyhow::Result { + let config_str = fs::read_to_string(path)?; + + let config = toml::from_str(&config_str)?; + + Ok(config) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test default configuration file. + #[test] + fn test_config() { + Config::parse("./config.toml").expect("Failed to parse configuration file"); + } +} diff --git a/types/Cargo.toml b/types/Cargo.toml deleted file mode 100644 index a03309f..0000000 --- a/types/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "homedisk-types" -version = "0.0.0" -edition = "2021" - -[features] -config = ["toml", "dirs"] -database = ["crypto-utils", "hex", "sqlx"] - -[dependencies] -thiserror = "1.0.32" -uuid = "1.1.2" -anyhow = "1.0.62" -serde = { version = "1.0.144", features = ["derive"] } -axum = { version = "0.5.15", optional = true } -toml = { version = "0.5.9", optional = true } -dirs = { version = "4.0.0", optional = true } -crypto-utils = { version = "0.4.0", features = ["sha"], optional = true } -hex = { version = "0.4.3", optional = true } -sqlx = { version = "0.6.1", features = ["sqlite"], optional = true } diff --git a/types/src/auth/login.rs b/types/src/auth/login.rs deleted file mode 100644 index bf30f7b..0000000 --- a/types/src/auth/login.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! HTTP `/auth/login` Request and Response types - -use serde::{Deserialize, Serialize}; - -/// HTTP `/auth/login` Request -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Request { - pub username: String, - pub password: String, -} - -/// HTTP `/auth/login` Response -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Response { - pub access_token: String, -} diff --git a/types/src/auth/mod.rs b/types/src/auth/mod.rs deleted file mode 100644 index 6ccd5d1..0000000 --- a/types/src/auth/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! HTTP `/auth/*` types for Request and Response - -pub mod login; -pub mod whoami; diff --git a/types/src/auth/whoami.rs b/types/src/auth/whoami.rs deleted file mode 100644 index 9ae6092..0000000 --- a/types/src/auth/whoami.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! HTTP `/auth/whoami` Response type - -use serde::{Deserialize, Serialize}; - -/// HTTP `/auth/whoami` Response -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Response { - /// Logged user username - pub username: String, -} diff --git a/types/src/config/mod.rs b/types/src/config/mod.rs deleted file mode 100644 index 66508a9..0000000 --- a/types/src/config/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! # Configuration file -//! -//! Path to a config file is `config.toml` in the work directory. -//! -//! ## Example config -//! -//! ```toml -//! [http] -//! host = "0.0.0.0" # HTTP Host -//! port = 8080 # HTTP Port -//! cors = ["homedisk.medzik.xyz"] # Domains allowed for CORS -//! -//! [jwt] -//! secret = "secret key used to sign tokens" # JWT Secret string (used to sign tokens) -//! expires = 24 # Token expiration time (in hours) -//! -//! [storage] -//! path = "/home/homedisk" # # Directory where user files will be stored -//! ``` -//! -//! ### External docs -//! - [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - -mod types; - -pub use types::*; diff --git a/types/src/config/types.rs b/types/src/config/types.rs deleted file mode 100644 index 9ee7d61..0000000 --- a/types/src/config/types.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::fs; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - /// Configure HTTP settings - pub http: ConfigHTTP, - /// Configure Json Web Token settings - pub jwt: ConfigJWT, - /// Configure storage settings - pub storage: ConfigStorage, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConfigHTTP { - /// HTTP Host - pub host: String, - /// Port HTTP Port - pub port: u16, - /// [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Domains (e.g ["site1.example.com", "site2.example.com"]) - pub cors: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConfigJWT { - /// JWT Secret string (used to sign tokens) - pub secret: String, - /// Token expiration time in hours - pub expires: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConfigStorage { - /// Directory where user files will be stored - pub path: String, -} - -#[cfg(feature = "config")] -impl Config { - /// Parse configuration file. - /// - /// ```no_run - /// use homedisk_types::config::Config; - /// - /// let config = Config::parse().unwrap(); - /// ``` - pub fn parse() -> anyhow::Result { - let config_str = fs::read_to_string("config.toml")?; - - let config = toml::from_str(&config_str)?; - - Ok(config) - } -} diff --git a/types/src/database/mod.rs b/types/src/database/mod.rs deleted file mode 100644 index d10f673..0000000 --- a/types/src/database/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Types for a database - -mod user; - -pub use user::*; diff --git a/types/src/database/user.rs b/types/src/database/user.rs deleted file mode 100644 index a4e57e9..0000000 --- a/types/src/database/user.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crypto_utils::sha::{Algorithm, CryptographicHash}; -use uuid::Uuid; - -/// SQL user table -#[derive(Debug, sqlx::FromRow)] -pub struct User { - /// UUID of the user - pub id: String, - /// Username - pub username: String, - /// Encryped user password - pub password: String, -} - -impl User { - /// The function create a unique user UUID and create SHA-512 hash from salted user password - /// and returns the [User] type. - /// - /// **Note: This doesn't create a user in the database!** - /// - /// ``` - /// use homedisk_types::database::User; - /// - /// let user = User::new("medzik", "SuperSecretPassword123!"); - /// - /// # assert_eq!(user.username, "medzik") - /// ``` - pub fn new(username: &str, password: &str) -> Self { - // change username to lowercase - let username = username.to_lowercase(); - - // salting the password - let password = format!("{username}${password}"); - - // hash password using SHA-512 and encode it to String from Vec - let password = hex::encode(CryptographicHash::hash( - Algorithm::SHA512, - password.as_bytes(), - )); - - // generate a user UUID - let id_sha1 = CryptographicHash::hash( - Algorithm::SHA1, - (format!("{username}${password}")).as_bytes(), - ); - let id = Uuid::new_v5(&Uuid::NAMESPACE_X500, &id_sha1).to_string(); - - // return `User` - Self { - id, - username, - password, - } - } - - /// The function returns the directory where the user file is located. - /// - /// ``` - /// use homedisk_types::database::User; - /// - /// let user = User::new("medzik", "whatever"); - /// - /// let dir = user.user_dir("/storage"); // will return `/storage/medzik` - /// - /// assert_eq!(dir, "/storage/medzik") - /// ``` - pub fn user_dir(&self, storage: &str) -> String { - // get a user storage path - let path = format!( - "{path}/{username}", - path = storage, - username = self.username, - ); - - // return user storage path - path - } -} - -#[cfg(test)] -mod tests { - use crypto_utils::sha::{Algorithm, CryptographicHash}; - - use super::User; - - /// Check if the id is reproducable - #[test] - fn check_id_reproducable() { - // example user data - let username = "test"; - let password = "password"; - - let user_a = User::new(username, password); - let user_b = User::new(username, password); - - assert_eq!(user_a.id, user_b.id) - } - - /// Check if the username is in lowercase - #[test] - fn check_username_is_in_lowercase() { - // example user data - let username = "mEDZIk"; - let password = "password"; - - // username in lowercase (expected username) - let username_expected = "medzik"; - - // create a new `User` type - let user = User::new(username, password); - - // username validation with expected username - assert_eq!(user.username, username_expected) - } - - /// Check that the password is a checksum with a salt - #[test] - fn check_if_password_is_hashed_and_salted() { - // example user data - let username = "username"; - let password = "password"; - - // create a new `User` type - let user = User::new(username, password); - - // expected password salt (string) - let password_expected_salt = format!("{username}${password}"); - - // expected password (hashed) - let password_expected = hex::encode(CryptographicHash::hash( - Algorithm::SHA512, - password_expected_salt.as_bytes(), - )); - - // password validation with expected password salt - assert_eq!(user.password, password_expected) - } -} diff --git a/types/src/errors/auth.rs b/types/src/errors/auth.rs deleted file mode 100644 index 66b9ad0..0000000 --- a/types/src/errors/auth.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[derive(Debug, Clone, Serialize, Deserialize, Error)] -pub enum Error { - #[error("user not found")] - UserNotFound, - #[error("user already exists")] - UserAlreadyExists, - #[error("username is too short")] - UsernameTooShort, - #[error("username is too long")] - UsernameTooLong, - #[error("password is too short")] - PasswordTooShort, - #[error("failed to generate jwt token")] - TokenGenerate, - #[error("invalid jwt token")] - InvalidToken, - #[error("other error - {0}")] - Other(String), -} diff --git a/types/src/errors/fs.rs b/types/src/errors/fs.rs deleted file mode 100644 index 9b20dfc..0000000 --- a/types/src/errors/fs.rs +++ /dev/null @@ -1,30 +0,0 @@ -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -#[derive(Debug, Clone, Serialize, Deserialize, Error)] -pub enum Error { - #[error("file doesn't exists")] - FileDoesNotExist, - #[error("file already exists")] - FileAlreadyExists, - #[error("unexpected multipart error")] - MultipartError, - #[error("failed to create create a file - {0}")] - CreateFile(String), - #[error("failed to create a directory - {0}")] - CreateDirectory(String), - #[error("failed to delete file: {0}")] - DeleteFile(String), - #[error("failed to delete directory: {0}")] - DeleteDirectory(String), - #[error("failed to write content to file: {0}")] - WriteFile(String), - #[error("failed to decode base64: {0}")] - Base64(String), - #[error("failed to read directory: {0}")] - ReadDirectory(String), - #[error("failed to read file content: {0}")] - ReadFile(String), - #[error("other error - {0}")] - Other(String), -} diff --git a/types/src/errors/mod.rs b/types/src/errors/mod.rs deleted file mode 100644 index 1ad6912..0000000 --- a/types/src/errors/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Error types - -mod auth; -#[cfg(feature = "database")] -mod database; -mod fs; -mod server; - -pub use auth::Error as AuthError; -pub use database::{Error as DatabaseError, Result as DatabaseResult}; -pub use fs::Error as FsError; -pub use server::Error as ServerError; diff --git a/types/src/errors/server.rs b/types/src/errors/server.rs deleted file mode 100644 index f4afb74..0000000 --- a/types/src/errors/server.rs +++ /dev/null @@ -1,71 +0,0 @@ -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use super::{AuthError, FsError}; - -/// HTTP Server Error -#[derive(Debug, Clone, Serialize, Deserialize, Error)] -#[serde(tag = "error", content = "error_message", rename_all = "kebab-case")] -pub enum Error { - #[error("auth error: {0}")] - AuthError(#[from] AuthError), - #[error("fs error: {0}")] - FsError(#[from] FsError), - #[error("too may requests, please slow down")] - TooManyRequests, - #[error("invalid Content-Type")] - InvalidContentType, - #[error("failed to deserialize json")] - JsonDataError, - #[error("syntax error in json")] - JsonSyntaxError, - #[error("failed to extract the request body")] - BytesRejection, - #[error("other error: {0}")] - Other(String), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -enum ResponseError { - Error(String), -} - -impl Error { - fn into_response(self) -> ResponseError { - ResponseError::Error(self.to_string()) - } -} - -#[cfg(feature = "axum")] -impl axum::response::IntoResponse for Error { - fn into_response(self) -> axum::response::Response { - use axum::http::StatusCode; - - let status = match self { - Self::AuthError(ref err) => match err { - AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR, - AuthError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, - _ => StatusCode::BAD_REQUEST, - }, - Self::FsError(ref err) => match err { - FsError::CreateFile(_) => StatusCode::INTERNAL_SERVER_ERROR, - FsError::CreateDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR, - FsError::DeleteFile(_) => StatusCode::INTERNAL_SERVER_ERROR, - FsError::DeleteDirectory(_) => StatusCode::INTERNAL_SERVER_ERROR, - FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR, - FsError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, - _ => StatusCode::BAD_REQUEST, - }, - Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS, - Self::BytesRejection => StatusCode::INTERNAL_SERVER_ERROR, - Self::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, - _ => StatusCode::BAD_REQUEST, - }; - - let mut response = axum::Json(self.into_response()).into_response(); - *response.status_mut() = status; - - response - } -} diff --git a/types/src/fs/create_dir.rs b/types/src/fs/create_dir.rs deleted file mode 100644 index 8329dc3..0000000 --- a/types/src/fs/create_dir.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! HTTP `/fs/createdir` Request and Response types - -use serde::{Deserialize, Serialize}; - -/// HTTP `/fs/createdir` Request -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Request { - /// Path to directory wich will be created - pub path: String, -} diff --git a/types/src/fs/delete.rs b/types/src/fs/delete.rs deleted file mode 100644 index a2c8944..0000000 --- a/types/src/fs/delete.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! HTTP `/fs/delete` Request type - -use serde::{Deserialize, Serialize}; - -/// HTTP `/fs/delete` Request -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Request { - /// Path of file/directory to be delete - pub path: String, -} diff --git a/types/src/fs/download.rs b/types/src/fs/download.rs deleted file mode 100644 index d72856f..0000000 --- a/types/src/fs/download.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! HTTP `/fs/download` Request type - -use serde::{Deserialize, Serialize}; - -/// HTTP `/fs/download` Request -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Request { - /// Path of file to be download - pub path: String, -} diff --git a/types/src/fs/list.rs b/types/src/fs/list.rs deleted file mode 100644 index f4b392e..0000000 --- a/types/src/fs/list.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! HTTP `/fs/list` Request, Response, FileInfo and DirInfo types - -use serde::{Deserialize, Serialize}; - -/// HTTP `/fs/list` Request -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Request { - /// Path to directory - pub path: String, -} - -/// HTTP `/fs/list` Response -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Response { - /// Vector with files info - pub files: Vec, - /// Vector with directories info - pub dirs: Vec, -} - -/// Info about a file -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct FileInfo { - /// File name - pub name: String, - /// File size - pub size: String, - /// Latest modification of this file - pub modified: u64, -} - -/// Info about a directory -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct DirInfo { - /// Directory name - pub name: String, - /// Directory size (size of all files in directory) - pub size: String, -} diff --git a/types/src/fs/mod.rs b/types/src/fs/mod.rs deleted file mode 100644 index 8cc9156..0000000 --- a/types/src/fs/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! HTTP `/fs/*` types for Request and Response - -pub mod create_dir; -pub mod delete; -pub mod download; -pub mod list; -pub mod upload; diff --git a/types/src/fs/upload.rs b/types/src/fs/upload.rs deleted file mode 100644 index 694756f..0000000 --- a/types/src/fs/upload.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! HTTP `/fs/upload` Request and Response types - -use serde::{Deserialize, Serialize}; - -/// HTTP `/fs/upload` Queries -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Pagination { - /// Path where the file will be uploaded - pub path: String, -} diff --git a/types/src/lib.rs b/types/src/lib.rs deleted file mode 100644 index 51671c9..0000000 --- a/types/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![doc(html_root_url = "https://homedisk-doc.medzik.xyz")] - -pub mod auth; -pub mod config; -#[cfg(feature = "database")] -pub mod database; -pub mod errors; -pub mod fs;