This commit is contained in:
2026-02-21 10:39:52 +01:00
parent f7c975a3f0
commit 66c1fe0025
38 changed files with 1543 additions and 616 deletions

378
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
## TODO
Terminer le système d'authentification (login, changement de mot de passe...)

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
let profile = std::env::var("PROFILE").unwrap_or_default();
}

File diff suppressed because it is too large Load Diff

View File

@@ -85,4 +85,8 @@ impl App {
println!("Nettoyage et fermeture de l'application.");
}
async fn shutdown(&self) {
}
}

View File

@@ -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<category::Model> 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<CreateCategoryRequest> for category::ActiveModel {
fn from(request: CreateCategoryRequest) -> Self {
Self {
server_id: Set(request.server_id),
name: Set(request.name),
..Default::default()
}
}
}

View File

@@ -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<Uuid>,
pub category_id: Option<Uuid>,
pub position: i32,
pub channel_type: ChannelType,
pub name: Option<String>,
}
impl From<channel::Model> 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<Uuid>,
pub category_id: Option<Uuid>,
pub position: i32,
pub channel_type: ChannelType,
pub name: Option<String>,
}
impl From<CreateChannelRequest> 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()
}
}
}

View File

@@ -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<message::Model> 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<CreateMessageRequest> 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()
}
}
}

View File

@@ -0,0 +1,6 @@
pub mod auth;
pub mod category;
pub mod channel;
pub mod message;
pub mod server;
pub mod user;

View File

@@ -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<server::Model> 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<String>,
}
impl From<CreateServerRequest> 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<TreeItemType>,
}

View File

@@ -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<user::Model> 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<CreateUserRequest> for user::ActiveModel {
fn from(request: CreateUserRequest) -> Self {
Self {
username: Set(request.username),
..Default::default()
}
}
}

View File

@@ -0,0 +1 @@
pub mod dto;

1
src/interfaces/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod http;

View File

@@ -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;
pub mod hub;
pub mod models;
pub mod repositories;
pub mod serializers;
pub mod interfaces;

View File

@@ -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)]

View File

@@ -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()
}
}
}
}

View File

@@ -27,8 +27,8 @@ pub struct Model {
pub position: i32,
pub channel_type: ChannelType,
pub name: Option<String>,
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()
}
}
}
}

View File

@@ -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()
}
}
}
}

View File

@@ -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<DateTime>,
pub created_at: DateTimeUtc,
pub updated_at: Option<DateTimeUtc>,
pub reply_to_id: Option<Uuid>,
}
@@ -74,4 +74,4 @@ impl ActiveModelBehavior for ActiveModel {
..ActiveModelTrait::default()
}
}
}
}

View File

@@ -11,8 +11,8 @@ pub struct Model {
pub id: Uuid,
pub name: String,
pub password: Option<String>,
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()
}
}
}
}

View File

@@ -12,8 +12,8 @@ pub struct Model {
pub server_id: Uuid,
pub user_id: Uuid,
pub username: Option<String>,
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()
}
}
}
}

View File

@@ -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()
}
}
}
}

View File

@@ -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())
}

View File

@@ -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<AppState>,
Json(payload): Json<SshChallengeRequest>,
) -> Result<Json<SshChallengeResponse>, 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,
}

View File

@@ -30,6 +30,8 @@ pub async fn category_list(
Extension(_ctx): Extension<RequestContext>,
Query(query): Query<CategoryQuery>
) -> Result<Json<Vec<CategorySerializer>>, 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<AppState>,
Path(id): Path<Uuid>
) -> Result<Json<CategorySerializer>, 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<AppState>,
Json(serializer): Json<CategorySerializer>
) -> Result<Json<CategorySerializer>, 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<Uuid>,
Json(serializer): Json<CategorySerializer>,
) -> Result<Json<CategorySerializer>, 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<AppState>,
Path(id): Path<Uuid>
) -> Result<StatusCode, HTTPError> {
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)
}
}

View File

@@ -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())
}

View File

@@ -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<AppState>
) -> Result<Json<Vec<ServerSerializer>>, HTTPError> {
State(state): State<AppState>,
) -> Result<Json<Vec<ServerResponse>>, 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<AppState>,
Path(id): Path<Uuid>
) -> Result<Json<ServerSerializer>, HTTPError> {
let server = state.repositories.server.get_by_id(id)
Path(id): Path<Uuid>,
) -> Result<Json<ServerResponse>, 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<AppState>,
Json(serializer): Json<ServerSerializer>
) -> Result<Json<ServerSerializer>, HTTPError> {
Json(serializer): Json<ServerSerializer>,
) -> Result<Json<ServerResponse>, 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<AppState>,
Path(id): Path<Uuid>,
Json(serializer): Json<ServerSerializer>,
) -> Result<Json<ServerSerializer>, HTTPError> {
let am_server = state.repositories.server
) -> Result<Json<ServerResponse>, 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<AppState>,
Path(id): Path<Uuid>
Path(id): Path<Uuid>,
) -> Result<StatusCode, HTTPError> {
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<AppState>,
Path(id): Path<Uuid>
Path(id): Path<Uuid>,
) -> Result<Json<Option<String>>, 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<AppState>,
Path(id): Path<Uuid>
Path(id): Path<Uuid>,
) -> Result<Json<ServerTreeSerializer>, HTTPError> {
let layout = state.repositories.server.get_tree(id).await?;
Ok(Json(ServerTreeSerializer::from(layout)))
}
}

View File

@@ -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<AppState>,
Extension(_ctx): Extension<crate::network::http::RequestContext>
) -> Result<Json<Vec<UserSerializer>>, 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<AppState>,
Path(id): Path<Uuid>
) -> Result<Json<UserSerializer>, 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)))
}

View File

@@ -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<RwLock<HashMap<String, SocketAddr>>>,
bind_addr: SocketAddr
}

View File

@@ -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<RepositoryContext>
pub context: Arc<RepositoryContext>,
}
impl UserRepository {
pub async fn get_all(&self) -> Result<Vec<user::Model>, DbErr> {
user::Entity::find().all(&self.context.db).await
}
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<user::Model>, DbErr> {
user::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn get_by_username(&self, username: String) -> Result<Option<user::Model>, 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<user::Model, DbErr> {
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<user::Model, DbErr> {
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<user::Model, DbErr> {
pub async fn create(&self, mut active: user::ActiveModel) -> Result<user::Model, DbErr> {
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(())
}
}
}

View File

@@ -2,8 +2,20 @@ mod server;
mod category;
mod channel;
mod message;
mod user;
trait BaseSerializer<M>
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::*;
pub use message::*;
pub use user::*;

44
src/serializers/user.rs Normal file
View File

@@ -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<Uuid>,
pub username: String,
pub pub_key: String,
#[serde(skip_deserializing)]
pub created_at: String,
#[serde(skip_deserializing)]
pub updated_at: String,
}
impl From<user::Model> 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
}
}

View File

@@ -1 +1,3 @@
pub mod toolbox;
pub mod password;
pub mod ssh_auth;
pub mod toolbox;

28
src/utils/password.rs Normal file
View File

@@ -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<String, argon2::password_hash::Error> {
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<bool, argon2::password_hash::Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
Ok(argon2
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}

61
src/utils/ssh_auth.rs Normal file
View File

@@ -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<Mutex<HashMap<String, SshAuthChallenge>>>,
}
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<Utc>
}

View File

@@ -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<u8> {
let mut rng = rand::rng();
let value = (0..size).map(|_| rng.random()).collect::<Vec<u8>>();
value
}
pub fn b64_encode(value: &[u8]) -> String {
base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
value
)
}
pub fn b64_decode(value: &str) -> Result<Vec<u8>, 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)
}