From 66c1fe0025ab7e7d8feb0972cad7847b6ad1766e Mon Sep 17 00:00:00 2001 From: Nell Date: Sat, 21 Feb 2026 10:39:52 +0100 Subject: [PATCH] Init --- Cargo.lock | 378 ++++++- Cargo.toml | 21 +- README.md | 3 + build.rs | 3 + .../src/m20220101_000001_create_table.rs | 974 +++++++++--------- src/app/app.rs | 4 + .../http/web => interfaces/http/dto}/auth.rs | 0 src/interfaces/http/dto/category.rs | 35 + src/interfaces/http/dto/channel.rs | 50 + src/interfaces/http/dto/message.rs | 40 + src/interfaces/http/dto/mod.rs | 6 + src/interfaces/http/dto/server.rs | 52 + src/interfaces/http/dto/user.rs | 33 + src/interfaces/http/mod.rs | 1 + src/interfaces/mod.rs | 1 + src/lib.rs | 12 +- src/models/attachment.rs | 2 +- src/models/category.rs | 6 +- src/models/channel.rs | 6 +- src/models/channel_user.rs | 4 +- src/models/message.rs | 6 +- src/models/server.rs | 6 +- src/models/server_user.rs | 6 +- src/models/user.rs | 7 +- src/network/http/router.rs | 2 +- src/network/http/web/api/auth.rs | 105 ++ src/network/http/web/api/category.rs | 33 +- src/network/http/web/api/mod.rs | 3 + src/network/http/web/api/server.rs | 74 +- src/network/http/web/api/user.rs | 42 + src/network/udp/server.rs | 2 + src/repositories/user.rs | 62 +- src/serializers/mod.rs | 14 +- src/serializers/user.rs | 44 + src/utils/mod.rs | 4 +- src/utils/password.rs | 28 + src/utils/ssh_auth.rs | 61 ++ src/utils/toolbox.rs | 29 + 38 files changed, 1543 insertions(+), 616 deletions(-) create mode 100644 README.md create mode 100644 build.rs rename src/{network/http/web => interfaces/http/dto}/auth.rs (100%) create mode 100644 src/interfaces/http/dto/category.rs create mode 100644 src/interfaces/http/dto/channel.rs create mode 100644 src/interfaces/http/dto/message.rs create mode 100644 src/interfaces/http/dto/mod.rs create mode 100644 src/interfaces/http/dto/server.rs create mode 100644 src/interfaces/http/dto/user.rs create mode 100644 src/interfaces/http/mod.rs create mode 100644 src/interfaces/mod.rs create mode 100644 src/network/http/web/api/auth.rs create mode 100644 src/network/http/web/api/user.rs create mode 100644 src/serializers/user.rs create mode 100644 src/utils/password.rs create mode 100644 src/utils/ssh_auth.rs diff --git a/Cargo.lock b/Cargo.lock index bbcebc5..623a9ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -280,9 +292,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "axum-macros", @@ -345,6 +357,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -392,6 +410,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -501,9 +528,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -513,6 +540,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.53" @@ -619,6 +656,18 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -629,6 +678,32 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "darling" version = "0.20.11" @@ -741,6 +816,41 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "sha2", + "subtle", +] + [[package]] name = "either" version = "1.15.0" @@ -750,6 +860,25 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "env_filter" version = "0.1.4" @@ -833,6 +962,22 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.5" @@ -982,6 +1127,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1025,6 +1171,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1179,12 +1336,11 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "bytes", - "futures-core", "http", "http-body", "hyper", @@ -1355,6 +1511,15 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1659,18 +1824,22 @@ dependencies = [ name = "ox_speak_server" version = "0.1.0" dependencies = [ + "argon2", "axum", + "base64", "chrono", "env_logger", "futures-util", "log", "migration", "parking_lot", + "rand 0.9.2", "sea-orm", "serde", "serde_json", "serde_repr", "socket2", + "ssh-key", "tokio", "toml", "tower-http", @@ -1678,6 +1847,44 @@ dependencies = [ "validator", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1707,6 +1914,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1844,6 +2062,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -2044,6 +2271,16 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rkyv" version = "0.7.45" @@ -2087,6 +2324,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", + "sha2", "signature", "spki", "subtle", @@ -2109,6 +2347,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -2155,9 +2402,9 @@ dependencies = [ [[package]] name = "sea-orm" -version = "2.0.0-rc.19" +version = "2.0.0-rc.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6dda57d64724c4c3e2b39ce17ca5f4084561656a3518b65b26edc5b36e4607" +checksum = "f4bb965a287ae073c738851c5d38037ac6da66c9841ac1de7c13c8d08862180a" dependencies = [ "async-stream", "async-trait", @@ -2204,11 +2451,12 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "2.0.0-rc.19" +version = "2.0.0-rc.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7674a565e093a4bfffbfd6d7fd79a5dc8d75463d442ffb44d0fc3a3dcce5a6" +checksum = "b3e208f041129ad7962b6951f0b392e9ff97a8337bd8c7022c61e7b02ab29fe0" dependencies = [ "heck 0.5.0", + "itertools", "pluralizer", "proc-macro2", "quote", @@ -2235,11 +2483,10 @@ dependencies = [ [[package]] name = "sea-query" -version = "1.0.0-rc.20" +version = "1.0.0-rc.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ebab2b9d558deec08e43887a63ed4d96d56b32cb9d98578bd1749e2c8c7e24" +checksum = "c6a067a2f6f13250f615f0bedb5bc3a6c872fec70776d0b43b43caeaa699e232" dependencies = [ - "bigdecimal", "chrono", "inherent", "ordered-float", @@ -2252,9 +2499,9 @@ dependencies = [ [[package]] name = "sea-query-derive" -version = "1.0.0-rc.11" +version = "1.0.0-rc.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365d236217f5daa4f40d3c9998ff3921351b53472da50308e384388162353b3a" +checksum = "8d88ad44b6ad9788c8b9476b6b91f94c7461d1e19d39cd8ea37838b1e6ff5aa8" dependencies = [ "darling", "heck 0.4.1", @@ -2266,18 +2513,12 @@ dependencies = [ [[package]] name = "sea-query-sqlx" -version = "0.8.0-rc.9" +version = "0.8.0-rc.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68873fa1776b4c25a26e7679f8ee22332978c721168ec1b0b32b6583d5a9381d" +checksum = "e4377164b09a11bb692dec6966eb0e6908d63d768defef0be689b39e02cf8544" dependencies = [ - "bigdecimal", - "chrono", - "rust_decimal", "sea-query", - "serde_json", "sqlx", - "time", - "uuid", ] [[package]] @@ -2311,6 +2552,26 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2343,15 +2604,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2523,7 +2784,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", - "bigdecimal", "bytes", "chrono", "crc", @@ -2601,7 +2861,6 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bigdecimal", "bitflags", "byteorder", "bytes", @@ -2648,7 +2907,6 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bigdecimal", "bitflags", "byteorder", "chrono", @@ -2666,7 +2924,6 @@ dependencies = [ "log", "md-5", "memchr", - "num-bigint", "once_cell", "rand 0.8.5", "rust_decimal", @@ -2710,6 +2967,49 @@ dependencies = [ "uuid", ] +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "cipher", + "ssh-encoding", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" +dependencies = [ + "ed25519-dalek", + "num-bigint-dig", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2883,9 +3183,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -3163,14 +3463,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", "rand 0.9.2", - "serde", + "serde_core", "wasm-bindgen", ] @@ -3667,3 +3967,9 @@ dependencies = [ "quote", "syn 2.0.110", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index 349d3dd..8deb4f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "ox_speak_server" version = "0.1.0" edition = "2024" +build = "build.rs" [lib] # The `_lib` suffix may seem redundant but it is necessary @@ -34,13 +35,13 @@ opt-level = 3 # Optimisation maximale pour la vitesse [dependencies] # Async -tokio = {version = "1.48", features = ["full"]} +tokio = { version = "1.49", features = ["full"] } # HTTP #actix-web = "4.12" #poem = "3.1" #poem-openapi = { version="5.1", features = ["swagger-ui", "url", "chrono"]} -axum = { version = "0.8", features = ["macros", "ws"] } +axum = { version = "0.8.8", features = ["macros", "ws"] } #utoipa = "5.4" #utoipa-swagger-ui = { version = "9.0", features = ["axum"] } #tower = "0.5" @@ -50,20 +51,24 @@ tower-http = { version = "0.6", features = ["trace", "cors", "timeout"] } socket2 = "0.6" # db -sea-orm = { version = "2.0.0-rc", features = ["sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "runtime-tokio", "with-chrono", "with-uuid", "with-json", "schema-sync"] } +sea-orm = { version = "2.0.0-rc.30", features = ["sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "runtime-tokio", "with-chrono", "with-uuid", "with-json", "schema-sync"] } migration = { path = "migration" } # logs -log = "0.4.28" +log = "0.4" env_logger = "0.11.8" # utils -chrono = "0.4" +chrono = "0.4.43" parking_lot = "0.12" serde = { version = "1.0", features = ["default", "derive"] } -serde_json = { version = "1.0.145", features = ["default"]} +serde_json = { version = "1.0.149", features = ["default"] } serde_repr = "0.1" -toml = "0.9" +toml = "0.9.8" validator = { version = "0.20", features = ["derive"] } -uuid = {version = "1", features = ["v4", "v7", "fast-rng", "serde"]} +uuid = { version = "1.20", features = ["v4", "v7", "fast-rng", "serde"] } futures-util = "0.3" +rand = "0.9" +ssh-key = { version = "0.6", features = ["default", "crypto"] } +base64 = "0.22" +argon2 = "0.5.3" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9555076 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## TODO + +Terminer le système d'authentification (login, changement de mot de passe...) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..de8e5df --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + let profile = std::env::var("PROFILE").unwrap_or_default(); +} diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index e6e8715..e43c69a 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -7,555 +7,529 @@ pub struct Migration; impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // Create table `server` - manager.create_table( - Table::create() - .table(Alias::new("server")) - .if_not_exists() - .col( - ColumnDef::new("id") - .uuid() - .primary_key() - .not_null() - ) - .col( - ColumnDef::new("name") - .string() - .not_null() - ) - .col( - ColumnDef::new("password") - .string() - .null() - ) - .col( - ColumnDef::new(Alias::new("created_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .col( - ColumnDef::new(Alias::new("updated_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .to_owned(), - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("server")) + .if_not_exists() + .col(ColumnDef::new("id").uuid().primary_key().not_null()) + .col(ColumnDef::new("name").string().not_null()) + .col(ColumnDef::new("password").string().null()) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .to_owned(), + ) + .await?; // Create table `category` - manager.create_table( - Table::create() - .table(Alias::new("category")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("server_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("name")) - .string() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("position")) - .integer() - .not_null() - .default(0) - ) - .col( - ColumnDef::new(Alias::new("created_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .col( - ColumnDef::new(Alias::new("updated_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - // L'index sera créé après via manager.create_index - .foreign_key( - ForeignKey::create() - .name("fk_category_server") - .from(Alias::new("category"), Alias::new("server_id")) - .to(Alias::new("server"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .to_owned(), - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("category")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("server_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("name")).string().not_null()) + .col( + ColumnDef::new(Alias::new("position")) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + // L'index sera créé après via manager.create_index + .foreign_key( + ForeignKey::create() + .name("fk_category_server") + .from(Alias::new("category"), Alias::new("server_id")) + .to(Alias::new("server"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; // Create table `channel` - manager.create_table( - Table::create() - .table(Alias::new("channel")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("server_id")) - .uuid() - .null() - ) - .col( - ColumnDef::new(Alias::new("category_id")) - .uuid() - .null() - ) - .col( - ColumnDef::new(Alias::new("position")) - .integer() - .not_null() - .default(0) - ) - .col( - ColumnDef::new(Alias::new("channel_type")) - .integer() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("name")) - .string() - .null() - ) - .col( - ColumnDef::new(Alias::new("created_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .col( - ColumnDef::new(Alias::new("updated_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - // Indexes créés après - .foreign_key( - ForeignKey::create() - .name("fk_channel_server") - .from(Alias::new("channel"), Alias::new("server_id")) - .to(Alias::new("server"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .name("fk_channel_category") - .from(Alias::new("channel"), Alias::new("category_id")) - .to(Alias::new("category"), Alias::new("id")) - .on_delete(ForeignKeyAction::SetNull) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("channel")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("server_id")).uuid().null()) + .col(ColumnDef::new(Alias::new("category_id")).uuid().null()) + .col( + ColumnDef::new(Alias::new("position")) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Alias::new("channel_type")) + .integer() + .not_null(), + ) + .col(ColumnDef::new(Alias::new("name")).string().null()) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + // Indexes créés après + .foreign_key( + ForeignKey::create() + .name("fk_channel_server") + .from(Alias::new("channel"), Alias::new("server_id")) + .to(Alias::new("server"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("fk_channel_category") + .from(Alias::new("channel"), Alias::new("category_id")) + .to(Alias::new("category"), Alias::new("id")) + .on_delete(ForeignKeyAction::SetNull), + ) + .to_owned(), + ) + .await?; // Create table `user` - manager.create_table( - Table::create() - .table(Alias::new("user")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("username")) - .string() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("pub_key")) - .text() - .not_null() - .unique_key() - ) - .col( - ColumnDef::new(Alias::new("created_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .col( - ColumnDef::new(Alias::new("updated_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("user")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("username")).string().not_null()) + .col(ColumnDef::new(Alias::new("password")).string().not_null()) + .col( + ColumnDef::new(Alias::new("pub_key")) + .text() + .null() + .unique_key(), + ) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .to_owned(), + ) + .await?; // Create table `message` - manager.create_table( - Table::create() - .table(Alias::new("message")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("channel_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("user_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("content")) - .text() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("created_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .col( - ColumnDef::new(Alias::new("edited_at")) - .date_time() - .null() - ) - .col( - ColumnDef::new(Alias::new("reply_to_id")) - .uuid() - .null() - ) - // Indexes créés après - .foreign_key( - ForeignKey::create() - .name("fk_message_channel") - .from(Alias::new("message"), Alias::new("channel_id")) - .to(Alias::new("channel"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .name("fk_message_user") - .from(Alias::new("message"), Alias::new("user_id")) - .to(Alias::new("user"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .name("fk_message_reply_to") - .from(Alias::new("message"), Alias::new("reply_to_id")) - .to(Alias::new("message"), Alias::new("id")) - .on_delete(ForeignKeyAction::SetNull) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("message")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("channel_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("user_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("content")).text().not_null()) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .null(), + ) + .col(ColumnDef::new(Alias::new("reply_to_id")).uuid().null()) + // Indexes créés après + .foreign_key( + ForeignKey::create() + .name("fk_message_channel") + .from(Alias::new("message"), Alias::new("channel_id")) + .to(Alias::new("channel"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("fk_message_user") + .from(Alias::new("message"), Alias::new("user_id")) + .to(Alias::new("user"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("fk_message_reply_to") + .from(Alias::new("message"), Alias::new("reply_to_id")) + .to(Alias::new("message"), Alias::new("id")) + .on_delete(ForeignKeyAction::SetNull), + ) + .to_owned(), + ) + .await?; // Create table `attachment` - manager.create_table( - Table::create() - .table(Alias::new("attachment")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("message_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("filename")) - .string() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("file_size")) - .big_integer() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("mime_type")) - .string() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("created_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - // Index créé après - .foreign_key( - ForeignKey::create() - .name("fk_attachment_message") - .from(Alias::new("attachment"), Alias::new("message_id")) - .to(Alias::new("message"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("attachment")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("message_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("filename")).string().not_null()) + .col( + ColumnDef::new(Alias::new("file_size")) + .big_integer() + .not_null(), + ) + .col(ColumnDef::new(Alias::new("mime_type")).string().not_null()) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + // Index créé après + .foreign_key( + ForeignKey::create() + .name("fk_attachment_message") + .from(Alias::new("attachment"), Alias::new("message_id")) + .to(Alias::new("message"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; // Create M2M table `server_user` - manager.create_table( - Table::create() - .table(Alias::new("server_user")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("server_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("user_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("username")) - .string() - .null() - ) - .col( - ColumnDef::new(Alias::new("joined_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - .col( - ColumnDef::new(Alias::new("updated_at")) - .date_time() - .not_null() - ) - // Indexes créés après - .foreign_key( - ForeignKey::create() - .name("fk_server_user_server") - .from(Alias::new("server_user"), Alias::new("server_id")) - .to(Alias::new("server"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .name("fk_server_user_user") - .from(Alias::new("server_user"), Alias::new("user_id")) - .to(Alias::new("user"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("server_user")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("server_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("user_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("username")).string().null()) + .col( + ColumnDef::new(Alias::new("joined_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .not_null(), + ) + // Indexes créés après + .foreign_key( + ForeignKey::create() + .name("fk_server_user_server") + .from(Alias::new("server_user"), Alias::new("server_id")) + .to(Alias::new("server"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("fk_server_user_user") + .from(Alias::new("server_user"), Alias::new("user_id")) + .to(Alias::new("user"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; // Create M2M table `channel_user` - manager.create_table( - Table::create() - .table(Alias::new("channel_user")) - .if_not_exists() - .col( - ColumnDef::new(Alias::new("id")) - .uuid() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(Alias::new("channel_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("user_id")) - .uuid() - .not_null() - ) - .col( - ColumnDef::new(Alias::new("role")) - .string() - .not_null() - .default("member".to_owned()) - ) - .col( - ColumnDef::new(Alias::new("joined_at")) - .date_time() - .not_null() - .default(Expr::current_timestamp()) - ) - // Indexes créés après - .foreign_key( - ForeignKey::create() - .name("fk_channel_user_channel") - .from(Alias::new("channel_user"), Alias::new("channel_id")) - .to(Alias::new("channel"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .name("fk_channel_user_user") - .from(Alias::new("channel_user"), Alias::new("user_id")) - .to(Alias::new("user"), Alias::new("id")) - .on_delete(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Alias::new("channel_user")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("channel_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("user_id")).uuid().not_null()) + .col( + ColumnDef::new(Alias::new("role")) + .string() + .not_null() + .default("member".to_owned()), + ) + .col( + ColumnDef::new(Alias::new("joined_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + // Indexes créés après + .foreign_key( + ForeignKey::create() + .name("fk_channel_user_channel") + .from(Alias::new("channel_user"), Alias::new("channel_id")) + .to(Alias::new("channel"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("fk_channel_user_user") + .from(Alias::new("channel_user"), Alias::new("user_id")) + .to(Alias::new("user"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; // -------------------------------------------------------------------- // Création des INDEX après les tables // -------------------------------------------------------------------- // category(server_id) - manager.create_index( - Index::create() - .name("idx_category_server_id") - .table(Alias::new("category")) - .col(Alias::new("server_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_category_server_id") + .table(Alias::new("category")) + .col(Alias::new("server_id")) + .to_owned(), + ) + .await?; // channel(server_id) - manager.create_index( - Index::create() - .name("idx_channel_server_id") - .table(Alias::new("channel")) - .col(Alias::new("server_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_channel_server_id") + .table(Alias::new("channel")) + .col(Alias::new("server_id")) + .to_owned(), + ) + .await?; // channel(category_id) - manager.create_index( - Index::create() - .name("idx_channel_category_id") - .table(Alias::new("channel")) - .col(Alias::new("category_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_channel_category_id") + .table(Alias::new("channel")) + .col(Alias::new("category_id")) + .to_owned(), + ) + .await?; // message(channel_id) - manager.create_index( - Index::create() - .name("idx_message_channel_id") - .table(Alias::new("message")) - .col(Alias::new("channel_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_message_channel_id") + .table(Alias::new("message")) + .col(Alias::new("channel_id")) + .to_owned(), + ) + .await?; // message(user_id) - manager.create_index( - Index::create() - .name("idx_message_user_id") - .table(Alias::new("message")) - .col(Alias::new("user_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_message_user_id") + .table(Alias::new("message")) + .col(Alias::new("user_id")) + .to_owned(), + ) + .await?; // message(reply_to_id) - manager.create_index( - Index::create() - .name("idx_message_reply_to_id") - .table(Alias::new("message")) - .col(Alias::new("reply_to_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_message_reply_to_id") + .table(Alias::new("message")) + .col(Alias::new("reply_to_id")) + .to_owned(), + ) + .await?; // attachment(message_id) - manager.create_index( - Index::create() - .name("idx_attachment_message_id") - .table(Alias::new("attachment")) - .col(Alias::new("message_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_attachment_message_id") + .table(Alias::new("attachment")) + .col(Alias::new("message_id")) + .to_owned(), + ) + .await?; // server_user(server_id) - manager.create_index( - Index::create() - .name("idx_server_user_server_id") - .table(Alias::new("server_user")) - .col(Alias::new("server_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_server_user_server_id") + .table(Alias::new("server_user")) + .col(Alias::new("server_id")) + .to_owned(), + ) + .await?; // server_user(user_id) - manager.create_index( - Index::create() - .name("idx_server_user_user_id") - .table(Alias::new("server_user")) - .col(Alias::new("user_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_server_user_user_id") + .table(Alias::new("server_user")) + .col(Alias::new("user_id")) + .to_owned(), + ) + .await?; // unique (server_id, user_id) - manager.create_index( - Index::create() - .name("uk_server_user_server_user") - .table(Alias::new("server_user")) - .col(Alias::new("server_id")) - .col(Alias::new("user_id")) - .unique() - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("uk_server_user_server_user") + .table(Alias::new("server_user")) + .col(Alias::new("server_id")) + .col(Alias::new("user_id")) + .unique() + .to_owned(), + ) + .await?; // channel_user(channel_id) - manager.create_index( - Index::create() - .name("idx_channel_user_channel_id") - .table(Alias::new("channel_user")) - .col(Alias::new("channel_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_channel_user_channel_id") + .table(Alias::new("channel_user")) + .col(Alias::new("channel_id")) + .to_owned(), + ) + .await?; // channel_user(user_id) - manager.create_index( - Index::create() - .name("idx_channel_user_user_id") - .table(Alias::new("channel_user")) - .col(Alias::new("user_id")) - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("idx_channel_user_user_id") + .table(Alias::new("channel_user")) + .col(Alias::new("user_id")) + .to_owned(), + ) + .await?; // unique (channel_id, user_id) - manager.create_index( - Index::create() - .name("uk_channel_user_channel_user") - .table(Alias::new("channel_user")) - .col(Alias::new("channel_id")) - .col(Alias::new("user_id")) - .unique() - .to_owned() - ).await?; + manager + .create_index( + Index::create() + .name("uk_channel_user_channel_user") + .table(Alias::new("channel_user")) + .col(Alias::new("channel_id")) + .col(Alias::new("user_id")) + .unique() + .to_owned(), + ) + .await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(Alias::new("channel_user")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("server_user")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("attachment")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("message")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("channel")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("category")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("user")).to_owned()).await?; - manager.drop_table(Table::drop().table(Alias::new("server")).to_owned()).await?; + manager + .drop_table(Table::drop().table(Alias::new("channel_user")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("server_user")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("attachment")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("message")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("channel")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("category")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("user")).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Alias::new("server")).to_owned()) + .await?; Ok(()) } -} \ No newline at end of file +} diff --git a/src/app/app.rs b/src/app/app.rs index 3c2fd4f..d8669cc 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -85,4 +85,8 @@ impl App { println!("Nettoyage et fermeture de l'application."); } + + async fn shutdown(&self) { + + } } \ No newline at end of file diff --git a/src/network/http/web/auth.rs b/src/interfaces/http/dto/auth.rs similarity index 100% rename from src/network/http/web/auth.rs rename to src/interfaces/http/dto/auth.rs diff --git a/src/interfaces/http/dto/category.rs b/src/interfaces/http/dto/category.rs new file mode 100644 index 0000000..670156c --- /dev/null +++ b/src/interfaces/http/dto/category.rs @@ -0,0 +1,35 @@ +use crate::models::category; +use sea_orm::Set; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct CategoryResponse { + pub id: Uuid, + pub name: String, +} + +impl From for CategoryResponse { + fn from(model: category::Model) -> Self { + Self { + id: model.id, + name: model.name, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateCategoryRequest { + pub server_id: Uuid, + pub name: String, +} + +impl From for category::ActiveModel { + fn from(request: CreateCategoryRequest) -> Self { + Self { + server_id: Set(request.server_id), + name: Set(request.name), + ..Default::default() + } + } +} diff --git a/src/interfaces/http/dto/channel.rs b/src/interfaces/http/dto/channel.rs new file mode 100644 index 0000000..d1050fb --- /dev/null +++ b/src/interfaces/http/dto/channel.rs @@ -0,0 +1,50 @@ +use crate::models::channel; +use crate::models::channel::ChannelType; +use sea_orm::Set; +use serde::Serialize; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct ChannelResponse { + pub id: Uuid, + pub server_id: Option, + pub category_id: Option, + pub position: i32, + pub channel_type: ChannelType, + pub name: Option, +} + +impl From for ChannelResponse { + fn from(model: channel::Model) -> Self { + Self { + id: model.id, + server_id: model.server_id, + category_id: model.category_id, + position: model.position, + channel_type: model.channel_type, + name: model.name, + } + } +} + +#[derive(Debug, Serialize)] +pub struct CreateChannelRequest { + pub server_id: Option, + pub category_id: Option, + pub position: i32, + pub channel_type: ChannelType, + pub name: Option, +} + +impl From for channel::ActiveModel { + fn from(request: CreateChannelRequest) -> Self { + Self { + server_id: Set(request.server_id), + category_id: Set(request.category_id), + position: Set(request.position), + channel_type: Set(request.channel_type), + name: Set(request.name), + ..Default::default() + } + } +} diff --git a/src/interfaces/http/dto/message.rs b/src/interfaces/http/dto/message.rs new file mode 100644 index 0000000..f80bea1 --- /dev/null +++ b/src/interfaces/http/dto/message.rs @@ -0,0 +1,40 @@ +use crate::models::message; +use sea_orm::Set; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct MessageResponse { + pub id: Uuid, + pub channel_id: Uuid, + pub author_id: Uuid, + pub content: String, +} + +impl From for MessageResponse { + fn from(model: message::Model) -> Self { + Self { + id: model.id, + channel_id: model.channel_id, + author_id: model.user_id, + content: model.content, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateMessageRequest { + pub channel_id: Uuid, + pub content: String, +} + +impl From for message::ActiveModel { + fn from(request: CreateMessageRequest) -> Self { + Self { + channel_id: Set(request.channel_id), + user_id: Set(Uuid::new_v4()), + content: Set(request.content), + ..Default::default() + } + } +} diff --git a/src/interfaces/http/dto/mod.rs b/src/interfaces/http/dto/mod.rs new file mode 100644 index 0000000..dede070 --- /dev/null +++ b/src/interfaces/http/dto/mod.rs @@ -0,0 +1,6 @@ +pub mod auth; +pub mod category; +pub mod channel; +pub mod message; +pub mod server; +pub mod user; diff --git a/src/interfaces/http/dto/server.rs b/src/interfaces/http/dto/server.rs new file mode 100644 index 0000000..0ff1e0f --- /dev/null +++ b/src/interfaces/http/dto/server.rs @@ -0,0 +1,52 @@ +// On importe le modèle pour pouvoir mapper +use crate::models::server; +use sea_orm::Set; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct ServerResponse { + pub id: Uuid, + pub name: String, + pub created_at: String, + pub updated_at: String, +} + +impl From for ServerResponse { + fn from(model: server::Model) -> Self { + Self { + id: model.id, + name: model.name, + created_at: model.created_at.to_rfc3339(), + updated_at: model.updated_at.to_rfc3339(), + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateServerRequest { + pub name: String, + pub password: Option, +} + +impl From for server::ActiveModel { + fn from(request: CreateServerRequest) -> Self { + Self { + name: Set(request.name), + password: Set(request.password), + ..Default::default() + } + } +} + +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum TreeItemType { + // todo : faire le CategoryResponse et ChannelResponse +} + +#[derive(Serialize)] +#[serde(transparent)] +pub struct ServerTreeResponse { + items: Vec, +} diff --git a/src/interfaces/http/dto/user.rs b/src/interfaces/http/dto/user.rs new file mode 100644 index 0000000..bca317e --- /dev/null +++ b/src/interfaces/http/dto/user.rs @@ -0,0 +1,33 @@ +use crate::models::user; +use sea_orm::Set; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct UserResponse { + pub id: Uuid, + pub username: String, +} + +impl From for UserResponse { + fn from(model: user::Model) -> Self { + Self { + id: model.id, + username: model.username, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateUserRequest { + pub username: String, +} + +impl From for user::ActiveModel { + fn from(request: CreateUserRequest) -> Self { + Self { + username: Set(request.username), + ..Default::default() + } + } +} diff --git a/src/interfaces/http/mod.rs b/src/interfaces/http/mod.rs new file mode 100644 index 0000000..a07dce5 --- /dev/null +++ b/src/interfaces/http/mod.rs @@ -0,0 +1 @@ +pub mod dto; diff --git a/src/interfaces/mod.rs b/src/interfaces/mod.rs new file mode 100644 index 0000000..3883215 --- /dev/null +++ b/src/interfaces/mod.rs @@ -0,0 +1 @@ +pub mod http; diff --git a/src/lib.rs b/src/lib.rs index f231297..847ad3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ -pub mod config; pub mod app; +pub mod config; pub mod network; pub mod utils; pub mod database; -pub mod models; -pub mod serializers; -pub mod repositories; pub mod event_bus; -pub mod hub; \ No newline at end of file +pub mod hub; +pub mod models; +pub mod repositories; +pub mod serializers; + +pub mod interfaces; diff --git a/src/models/attachment.rs b/src/models/attachment.rs index 3bc9588..f682c12 100644 --- a/src/models/attachment.rs +++ b/src/models/attachment.rs @@ -13,7 +13,7 @@ pub struct Model { pub filename: String, pub file_size: i32, pub mime_type: String, - pub created_at: DateTime, + pub created_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/models/category.rs b/src/models/category.rs index e5b5f7f..12ffbd9 100644 --- a/src/models/category.rs +++ b/src/models/category.rs @@ -12,8 +12,8 @@ pub struct Model { pub server_id: Uuid, pub name: String, pub position: i32, - pub created_at: DateTime, - pub updated_at: DateTime, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -50,4 +50,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/models/channel.rs b/src/models/channel.rs index b4297c2..ab610ec 100644 --- a/src/models/channel.rs +++ b/src/models/channel.rs @@ -27,8 +27,8 @@ pub struct Model { pub position: i32, pub channel_type: ChannelType, pub name: Option, - pub created_at: DateTime, - pub updated_at: DateTime, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -87,4 +87,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/models/channel_user.rs b/src/models/channel_user.rs index fa47835..39accca 100644 --- a/src/models/channel_user.rs +++ b/src/models/channel_user.rs @@ -12,7 +12,7 @@ pub struct Model { pub channel_id: Uuid, pub user_id: Uuid, pub role: String, - pub joined_at: DateTime, + pub joined_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -55,4 +55,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/models/message.rs b/src/models/message.rs index cf2811a..88c15f7 100644 --- a/src/models/message.rs +++ b/src/models/message.rs @@ -13,8 +13,8 @@ pub struct Model { pub user_id: Uuid, #[sea_orm(column_type = "Text")] pub content: String, - pub created_at: DateTime, - pub edited_at: Option, + pub created_at: DateTimeUtc, + pub updated_at: Option, pub reply_to_id: Option, } @@ -74,4 +74,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/models/server.rs b/src/models/server.rs index d09de47..4a97c30 100644 --- a/src/models/server.rs +++ b/src/models/server.rs @@ -11,8 +11,8 @@ pub struct Model { pub id: Uuid, pub name: String, pub password: Option, - pub created_at: DateTime, - pub updated_at: DateTime, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -51,4 +51,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/models/server_user.rs b/src/models/server_user.rs index fb6a8bf..e78205b 100644 --- a/src/models/server_user.rs +++ b/src/models/server_user.rs @@ -12,8 +12,8 @@ pub struct Model { pub server_id: Uuid, pub user_id: Uuid, pub username: Option, - pub joined_at: DateTime, - pub updated_at: DateTime, + pub joined_at: DateTimeUtc, + pub updated_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -56,4 +56,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/models/user.rs b/src/models/user.rs index 26469c1..47053b8 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -10,10 +10,11 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: Uuid, pub username: String, + pub password: String, #[sea_orm(column_type = "Text", unique)] pub pub_key: String, - pub created_at: DateTime, - pub updated_at: DateTime, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -52,4 +53,4 @@ impl ActiveModelBehavior for ActiveModel { ..ActiveModelTrait::default() } } -} \ No newline at end of file +} diff --git a/src/network/http/router.rs b/src/network/http/router.rs index 40ee8ae..e59e35e 100644 --- a/src/network/http/router.rs +++ b/src/network/http/router.rs @@ -8,7 +8,7 @@ use crate::network::http::{web, AppRouter}; pub fn setup_route(app_state: AppState) -> Router { Router::new() .merge(web::setup_route()) - .layer(CorsLayer::permissive()) .layer(middleware::from_fn_with_state(app_state.clone(), context_middleware)) .with_state(app_state) + .layer(CorsLayer::permissive()) } \ No newline at end of file diff --git a/src/network/http/web/api/auth.rs b/src/network/http/web/api/auth.rs new file mode 100644 index 0000000..c25007d --- /dev/null +++ b/src/network/http/web/api/auth.rs @@ -0,0 +1,105 @@ +use crate::app::AppState; +use crate::network::http::{AppRouter, HTTPError}; +use crate::utils::toolbox::ssh_generate_challenge; +use axum::extract::State; +use axum::Json; +use serde::{Deserialize, Serialize}; +use ssh_key::{Algorithm as SshAlgorithm, PublicKey, Signature}; + +fn setup_route() -> AppRouter { + AppRouter::new() +} + +#[derive(Deserialize)] +pub struct SshChallengeRequest { + username: String, +} + +#[derive(Serialize)] +pub struct SshChallengeResponse { + challenge: String, +} + +pub async fn ssh_challenge( + State(state): State, + Json(payload): Json, +) -> Result, HTTPError> { + log::info!( + "POST /auth/ssh-challenge - Challenge request for user: {}", + payload.username + ); + + let user = state + .repositories + .user + .get_by_username(payload.username.clone()) + .await? + .ok_or_else(|| { + log::warn!( + "POST /auth/ssh-challenge - User not found: {}", + payload.username + ); + HTTPError::NotFound + })?; + + let challenge = ssh_generate_challenge(32); + log::info!( + "POST /auth/ssh-challenge - Challenge generated for user: {}", + payload.username + ); + + // todo : stocker le challenge dans AppState + // bien penser à ajouter un délai d'expiration + // songer à mettre une session id ? + // Peut être que l'utilisateur se connectera depuis différent device + // state.store_challenge(&payload.username, challenge.clone()) + + Ok(Json(SshChallengeResponse { challenge })) +} + +#[derive(Deserialize)] +enum SignatureAlgorithm { + #[serde(rename = "rsa")] + Rsa, + #[serde(rename = "ed25519")] + Ed25519, + #[serde(rename = "ecdsa")] + Ecdsa, +} + +impl SignatureAlgorithm { + fn to_ssh_algorithm(&self) -> SshAlgorithm { + match self { + SignatureAlgorithm::Rsa => SshAlgorithm::Rsa { hash: None }, + SignatureAlgorithm::Ed25519 => SshAlgorithm::Ed25519, + SignatureAlgorithm::Ecdsa => SshAlgorithm::Ecdsa { + curve: ssh_key::EcdsaCurve::NistP256, + }, + } + } +} + +#[derive(Deserialize)] +struct SshVerifyRequest { + username: String, + signature: String, + algorithm: SignatureAlgorithm, +} + +#[derive(Serialize)] +struct SshVerifyResponse { + // todo : remplir avec la réponse jwt - à établir après la construction jwt +} + +pub async fn ssh_verify() {} + +#[derive(Deserialize)] +pub struct LoginRequest { + username: String, + password: String, +} + +#[derive(Serialize)] +pub struct SshChallengeResponse { + challenge: String, +} diff --git a/src/network/http/web/api/category.rs b/src/network/http/web/api/category.rs index 68cc52f..220ca6d 100644 --- a/src/network/http/web/api/category.rs +++ b/src/network/http/web/api/category.rs @@ -30,6 +30,8 @@ pub async fn category_list( Extension(_ctx): Extension, Query(query): Query ) -> Result>, HTTPError> { + log::info!("GET /categories/ - Query: server_id={:?}", query.server_id); + let categories = if let Some(server_id) = query.server_id { app_state.repositories.category .get_by_server_id(server_id) @@ -40,7 +42,7 @@ pub async fn category_list( .await? }; - + log::info!("GET /categories/ - Found {} categories", categories.len()); Ok(Json(categories.into_iter().map(CategorySerializer::from).collect())) } @@ -48,11 +50,17 @@ pub async fn category_detail( State(app_state): State, Path(id): Path ) -> Result, HTTPError> { - let category = category::Entity::find_by_id(id) - .one(app_state.db.get_connection()) - .await? - .ok_or(HTTPError::NotFound)?; + log::info!("GET /categories/{id}/ - Fetching category: {}", id); + let category = app_state.repositories.category + .get_by_id(id) + .await? + .ok_or_else(|| { + log::warn!("GET /categories/{id}/ - Category not found: {}", id); + HTTPError::NotFound + })?; + + log::info!("GET /categories/{id}/ - Category found: {}", id); Ok(Json(CategorySerializer::from(category))) } @@ -60,9 +68,12 @@ pub async fn category_create( State(app_state): State, Json(serializer): Json ) -> Result, HTTPError> { + log::info!("POST /categories/ - Creating category: {:?}", serializer.name); + let active = serializer.into_active_model(); let category: category::Model = active.insert(app_state.db.get_connection()).await?; + log::info!("POST /categories/ - Category created with id: {}", category.id); Ok(Json(CategorySerializer::from(category))) } @@ -71,10 +82,15 @@ pub async fn category_update( Path(id): Path, Json(serializer): Json, ) -> Result, HTTPError> { + log::info!("PUT /categories/{id}/ - Updating category: {}", id); + let category = category::Entity::find_by_id(id) .one(app_state.db.get_connection()) .await? - .ok_or(HTTPError::NotFound)?; + .ok_or_else(|| { + log::warn!("PUT /categories/{id}/ - Category not found: {}", id); + HTTPError::NotFound + })?; let active = category.into_active_model(); @@ -82,6 +98,7 @@ pub async fn category_update( .update(app_state.db.get_connection()) .await?; + log::info!("PUT /categories/{id}/ - Category updated: {}", id); Ok(Json(CategorySerializer::from(category))) } @@ -89,13 +106,17 @@ pub async fn category_delete( State(app_state): State, Path(id): Path ) -> Result { + log::info!("DELETE /categories/{id}/ - Deleting category: {}", id); + let result = category::Entity::delete_by_id(id) .exec(app_state.db.get_connection()) .await?; if result.rows_affected == 0 { + log::warn!("DELETE /categories/{id}/ - Category not found: {}", id); Err(HTTPError::NotFound) } else { + log::info!("DELETE /categories/{id}/ - Category deleted: {}", id); Ok(StatusCode::NO_CONTENT) } } \ No newline at end of file diff --git a/src/network/http/web/api/mod.rs b/src/network/http/web/api/mod.rs index 4107db6..bea5b0a 100644 --- a/src/network/http/web/api/mod.rs +++ b/src/network/http/web/api/mod.rs @@ -4,6 +4,8 @@ mod category; mod channel; mod message; mod server; +mod user; +mod auth; pub fn setup_route() -> AppRouter { @@ -12,4 +14,5 @@ pub fn setup_route() -> AppRouter { .nest("/channel", channel::setup_route()) .nest("/message", message::setup_route()) .nest("/server", server::setup_route()) + .nest("/user", user::setup_route()) } \ No newline at end of file diff --git a/src/network/http/web/api/server.rs b/src/network/http/web/api/server.rs index ee75d79..4f53243 100644 --- a/src/network/http/web/api/server.rs +++ b/src/network/http/web/api/server.rs @@ -1,75 +1,84 @@ -use axum::Json; -use axum::extract::{Path, State}; -use axum::http::StatusCode; -use axum::routing::{delete, get, post, put}; -use sea_orm::{IntoActiveModel}; -use uuid::Uuid; use crate::app::AppState; -use crate::models::server; +use crate::interfaces::http::dto::server::ServerResponse; use crate::network::http::{AppRouter, HTTPError}; use crate::serializers::{ServerSerializer, ServerTreeSerializer}; +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::routing::get; +use axum::Json; +use sea_orm::IntoActiveModel; +use uuid::Uuid; pub fn setup_route() -> AppRouter { AppRouter::new() - .route("/servers/", get(server_list)) - .route("/servers/{id}/", get(server_detail)) - .route("/servers/", post(server_create)) - .route("/servers/{id}/", put(server_update)) - .route("/servers/{id}/", delete(server_delete)) + .route("/servers/", get(server_list).post(server_create)) + .route( + "/servers/{id}/", + get(server_detail).put(server_update).delete(server_delete), + ) .route("/servers/{id}/password/", get(server_password)) .route("/servers/{id}/tree/", get(tree)) } pub async fn server_list( - State(state): State -) -> Result>, HTTPError> { + State(state): State, +) -> Result>, HTTPError> { let servers = state.repositories.server.get_all().await?; - Ok(Json(servers.into_iter().map(ServerSerializer::from).collect())) + Ok(Json( + servers.into_iter().map(ServerResponse::from).collect(), + )) } pub async fn server_detail( State(state): State, - Path(id): Path -) -> Result, HTTPError> { - let server = state.repositories.server.get_by_id(id) + Path(id): Path, +) -> Result, HTTPError> { + let server = state + .repositories + .server + .get_by_id(id) .await? .ok_or(HTTPError::NotFound)?; - Ok(Json(ServerSerializer::from(server))) + Ok(Json(ServerResponse::from(server))) } pub async fn server_create( State(state): State, - Json(serializer): Json -) -> Result, HTTPError> { + Json(serializer): Json, +) -> Result, HTTPError> { let active = serializer.into_active_model(); let server = state.repositories.server.create(active).await?; - Ok(Json(ServerSerializer::from(server))) + Ok(Json(ServerResponse::from(server))) } pub async fn server_update( State(state): State, Path(id): Path, Json(serializer): Json, -) -> Result, HTTPError> { - let am_server = state.repositories.server +) -> Result, HTTPError> { + let am_server = state + .repositories + .server .get_by_id(id) .await? .ok_or(HTTPError::NotFound)? .into_active_model(); - let server = state.repositories.server + let server = state + .repositories + .server .update(serializer.apply_to_active_model(am_server)) .await?; - Ok(Json(ServerSerializer::from(server))) + Ok(Json(ServerResponse::from(server))) } pub async fn server_delete( State(state): State, - Path(id): Path + Path(id): Path, ) -> Result { if state.repositories.server.delete(id).await? { Ok(StatusCode::NO_CONTENT) @@ -80,9 +89,12 @@ pub async fn server_delete( pub async fn server_password( State(state): State, - Path(id): Path + Path(id): Path, ) -> Result>, HTTPError> { - let server = state.repositories.server.get_by_id(id) + let server = state + .repositories + .server + .get_by_id(id) .await? .ok_or(HTTPError::NotFound)?; @@ -91,9 +103,9 @@ pub async fn server_password( pub async fn tree( State(state): State, - Path(id): Path + Path(id): Path, ) -> Result, HTTPError> { let layout = state.repositories.server.get_tree(id).await?; Ok(Json(ServerTreeSerializer::from(layout))) -} \ No newline at end of file +} diff --git a/src/network/http/web/api/user.rs b/src/network/http/web/api/user.rs new file mode 100644 index 0000000..d2bbbcc --- /dev/null +++ b/src/network/http/web/api/user.rs @@ -0,0 +1,42 @@ +use axum::{Extension, Json}; +use axum::extract::{Path, State}; +use axum::routing::get; +use uuid::Uuid; +use crate::app::AppState; +use crate::network::http::{AppRouter, HTTPError}; +use crate::serializers::UserSerializer; + +pub fn setup_route() -> AppRouter { + AppRouter::new() + .route("/users/", get(user_list)) + .route("/users/{id}/", get(user_detail)) +} + +pub async fn user_list( + State(app_state): State, + Extension(_ctx): Extension +) -> Result>, HTTPError> { + log::info!("GET /users/ - Fetching user list"); + + let users = app_state.repositories.user.get_all().await?; + log::info!("GET /users/ - Found {} users", users.len()); + + Ok(Json(users.into_iter().map(UserSerializer::from).collect())) +} + +pub async fn user_detail( + State(app_state): State, + Path(id): Path +) -> Result, HTTPError> { + log::info!("GET /users/{id}/ - Fetching user with id: {}", id); + + let user = app_state.repositories.user + .get_by_id(id).await? + .ok_or_else(|| { + log::warn!("GET /users/{id}/ - User not found: {}", id); + HTTPError::NotFound + })?; + + log::info!("GET /users/{id}/ - User found: {}", id); + Ok(Json(UserSerializer::from(user))) +} diff --git a/src/network/udp/server.rs b/src/network/udp/server.rs index e2c9ab0..797d7f5 100644 --- a/src/network/udp/server.rs +++ b/src/network/udp/server.rs @@ -10,7 +10,9 @@ use crate::utils::toolbox::number_of_cpus; #[derive(Clone)] pub struct UDPServer { + // todo : passer sur du arcswap, rwlock rajoute trop de contention. table_router: Arc>>, + bind_addr: SocketAddr } diff --git a/src/repositories/user.rs b/src/repositories/user.rs index 0899f69..e53c686 100644 --- a/src/repositories/user.rs +++ b/src/repositories/user.rs @@ -1,33 +1,83 @@ -use std::sync::Arc; -use sea_orm::{DbErr, EntityTrait, ActiveModelTrait}; use crate::models::user; use crate::repositories::RepositoryContext; +use crate::utils::password; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, QueryFilter, Set, +}; +use std::sync::Arc; #[derive(Clone)] pub struct UserRepository { - pub context: Arc + pub context: Arc, } impl UserRepository { + pub async fn get_all(&self) -> Result, DbErr> { + user::Entity::find().all(&self.context.db).await + } + pub async fn get_by_id(&self, id: uuid::Uuid) -> Result, DbErr> { user::Entity::find_by_id(id).one(&self.context.db).await } + pub async fn get_by_username(&self, username: String) -> Result, DbErr> { + user::Entity::find() + .filter(user::Column::Username.eq(username)) + .one(&self.context.db) + .await + } + + pub async fn check_password( + &self, + username: String, + password: String, + ) -> Result { + let user = self.get_by_username(username).await?; + if let Some(user) = user { + let password_ok = password::verify_password(password.as_str(), user.password.as_str()) + .map_err(|_| DbErr::Custom("Password hashing failed".to_string()))?; + + if password_ok { + return Ok(user); + } + } + Err(DbErr::Custom("Invalid username or password".to_string())) + } + pub async fn update(&self, active: user::ActiveModel) -> Result { let user = active.update(&self.context.db).await?; self.context.events.emit("user_updated", user.clone()); Ok(user) } - pub async fn create(&self, active: user::ActiveModel) -> Result { + pub async fn create(&self, mut active: user::ActiveModel) -> Result { let user = active.insert(&self.context.db).await?; self.context.events.emit("user_created", user.clone()); Ok(user) } + pub async fn set_password(&self, id: uuid::Uuid, password: String) -> Result<(), DbErr> { + let user = self + .get_by_id(id) + .await? + .ok_or_else(|| DbErr::Custom("User not found".to_string()))?; + + let mut active = user.into_active_model(); + let password = password::hash_password(&password) + .map_err(|_| DbErr::Custom("Password hashing failed".to_string()))?; + active.password = Set(password); + + let user = self.update(active).await?; + + self.context.events.emit("user_changed", user); + Ok(()) + } + pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> { - user::Entity::delete_by_id(id).exec(&self.context.db).await?; + user::Entity::delete_by_id(id) + .exec(&self.context.db) + .await?; self.context.events.emit("user_deleted", id); Ok(()) } -} \ No newline at end of file +} diff --git a/src/serializers/mod.rs b/src/serializers/mod.rs index a66a138..8ebbbd8 100644 --- a/src/serializers/mod.rs +++ b/src/serializers/mod.rs @@ -2,8 +2,20 @@ mod server; mod category; mod channel; mod message; +mod user; +trait BaseSerializer +where + M: ActiveModelTrait, +{ + fn into_active_model(self) -> M; + + fn apply_to_active_model(self, active_model: M) -> M; +} + +use sea_orm::ActiveModelTrait; pub use server::*; pub use category::*; pub use channel::*; -pub use message::*; \ No newline at end of file +pub use message::*; +pub use user::*; \ No newline at end of file diff --git a/src/serializers/user.rs b/src/serializers/user.rs new file mode 100644 index 0000000..2bfa4db --- /dev/null +++ b/src/serializers/user.rs @@ -0,0 +1,44 @@ +use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use crate::models::{category, user}; + +#[derive(Serialize, Deserialize)] +pub struct UserSerializer { + #[serde(skip_deserializing)] + pub id: Option, + pub username: String, + pub pub_key: String, + + #[serde(skip_deserializing)] + pub created_at: String, + #[serde(skip_deserializing)] + pub updated_at: String, +} + +impl From for UserSerializer { + fn from(model: user::Model) -> Self { + Self { + id: Some(model.id), + username: model.username, + pub_key: model.pub_key, + created_at: model.created_at.to_string(), + updated_at: model.updated_at.to_string(), + } + } +} + +impl UserSerializer { + pub fn into_active_model(self) -> user::ActiveModel { + user::ActiveModel { + username: Set(self.username), + pub_key: Set(self.pub_key), + ..Default::default() + } + } + + pub fn apply_to_active_model(self, mut active_model: user::ActiveModel) -> user::ActiveModel { active_model.username = Set(self.username); + active_model.pub_key = Set(self.pub_key); + active_model + } +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 1f05afa..edd580d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,3 @@ -pub mod toolbox; \ No newline at end of file +pub mod password; +pub mod ssh_auth; +pub mod toolbox; diff --git a/src/utils/password.rs b/src/utils/password.rs new file mode 100644 index 0000000..fbd27aa --- /dev/null +++ b/src/utils/password.rs @@ -0,0 +1,28 @@ +// src/utils/password.rs +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Algorithm, Argon2, Params, + Version, +}; + +/// Hache un password avec Argon2id +/// Génère automatiquement un salt cryptographiquement sûr +pub fn hash_password(password: &str) -> Result { + let salt = SaltString::generate(OsRng); + let params = Params::new(65540, 18, 1, None)?; + + let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); + + argon2 + .hash_password(password.as_bytes(), &salt) + .map(|hash| hash.to_string()) +} + +/// Vérifie un password contre son hash +pub fn verify_password(password: &str, hash: &str) -> Result { + let parsed_hash = PasswordHash::new(hash)?; + let argon2 = Argon2::default(); + + Ok(argon2 + .verify_password(password.as_bytes(), &parsed_hash) + .is_ok()) +} diff --git a/src/utils/ssh_auth.rs b/src/utils/ssh_auth.rs new file mode 100644 index 0000000..7762325 --- /dev/null +++ b/src/utils/ssh_auth.rs @@ -0,0 +1,61 @@ +use std::collections::HashMap; +use std::sync::Arc; +use chrono::{DateTime, Utc}; +use parking_lot::Mutex; +use crate::utils::toolbox::ssh_generate_challenge; + +#[derive(Clone)] +struct SshAuthManager { + // session_id: String, + challenges: Arc>>, +} + +impl SshAuthManager { + pub fn new() -> Self { + let manager = Self { + challenges: Arc::new(Mutex::new(HashMap::new())) + }; + manager.start_cleanup_task(); + + manager + } + + fn start_cleanup_task(&self) { + let challenges = self.challenges.clone(); + + tokio::spawn(async move { + let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(30)); + loop { + interval.tick().await; + let mut map = challenges.lock(); + let now = Utc::now(); + map.retain(|_, challenge| { + challenge.expires_at > now + }) + } + }); + } + + pub fn add_auth_challenge(&self, session_id: String) -> SshAuthChallenge { + let challenge = ssh_generate_challenge(1024); + let auth_challenge = SshAuthChallenge { + session_id: session_id.clone(), + challenge, + expires_at: Utc::now() + chrono::Duration::minutes(1) + }; + + self.challenges.lock().insert( + session_id, + auth_challenge.clone() + ); + + auth_challenge + } +} + +#[derive(Clone)] +struct SshAuthChallenge { + session_id: String, + challenge: String, + expires_at: DateTime +} diff --git a/src/utils/toolbox.rs b/src/utils/toolbox.rs index 4c56551..f6cb580 100644 --- a/src/utils/toolbox.rs +++ b/src/utils/toolbox.rs @@ -1,3 +1,5 @@ +use rand::Rng; + pub fn number_of_cpus() -> usize { match std::thread::available_parallelism() { Ok(n) => n.get(), @@ -6,4 +8,31 @@ pub fn number_of_cpus() -> usize { 1 } } +} + +pub fn generate_random(size: usize) -> Vec { + let mut rng = rand::rng(); + let value = (0..size).map(|_| rng.random()).collect::>(); + value +} + +pub fn b64_encode(value: &[u8]) -> String { + base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + value + ) +} + +pub fn b64_decode(value: &str) -> Result, std::io::Error> { + base64::Engine::decode( + &base64::engine::general_purpose::STANDARD, + value + ).map_err(|e| { + std::io::Error::new(std::io::ErrorKind::InvalidData, e) + }) +} + +pub fn ssh_generate_challenge(size: usize) -> String { + let challenge = generate_random(size); + b64_encode(&challenge) } \ No newline at end of file