Compare commits
9 Commits
ca8c96306f
...
f8e2dad58a
| Author | SHA1 | Date | |
|---|---|---|---|
|
f8e2dad58a
|
|||
|
2b800c2df4
|
|||
|
9e3d534190
|
|||
|
d74479851f
|
|||
|
4ecbf6da15
|
|||
|
674b58e290
|
|||
|
ef9f88e72c
|
|||
|
97cf3f62ad
|
|||
|
3677b6adfa
|
167
Cargo.lock
generated
167
Cargo.lock
generated
@@ -32,6 +32,15 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arraydeque"
|
name = "arraydeque"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -262,6 +271,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes-utils"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.63"
|
version = "1.2.63"
|
||||||
@@ -541,6 +560,12 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie-factory"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie_store"
|
name = "cookie_store"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -593,6 +618,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc16"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
@@ -1562,6 +1593,15 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -1583,6 +1623,43 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fred"
|
||||||
|
version = "10.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a7b2fd0f08b23315c13b6156f971aeedb6f75fb16a29ac1872d2eabccc1490e"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"bytes-utils",
|
||||||
|
"float-cmp",
|
||||||
|
"fred-macros",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"rand 0.8.6",
|
||||||
|
"redis-protocol",
|
||||||
|
"semver",
|
||||||
|
"socket2 0.5.10",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
|
"url",
|
||||||
|
"urlencoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fred-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1458c6e22d36d61507034d5afecc64f105c1d39712b7ac6ec3b352c423f715cc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -2000,7 +2077,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.6.4",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
@@ -2314,6 +2391,7 @@ dependencies = [
|
|||||||
name = "libretunes"
|
name = "libretunes"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
"diesel",
|
"diesel",
|
||||||
@@ -2321,6 +2399,7 @@ dependencies = [
|
|||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"fred",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
"lucide-dioxus",
|
"lucide-dioxus",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
@@ -2535,6 +2614,12 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -2593,6 +2678,16 @@ dependencies = [
|
|||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2957,7 +3052,7 @@ dependencies = [
|
|||||||
"quinn-udp",
|
"quinn-udp",
|
||||||
"rustc-hash 2.1.2",
|
"rustc-hash 2.1.2",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2 0.6.4",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2994,7 +3089,7 @@ dependencies = [
|
|||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2 0.6.4",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
@@ -3020,13 +3115,24 @@ version = "6.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha 0.3.1",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha 0.9.0",
|
||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3041,6 +3147,16 @@ dependencies = [
|
|||||||
"rand_core 0.10.1",
|
"rand_core 0.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -3051,6 +3167,15 @@ dependencies = [
|
|||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -3072,6 +3197,20 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redis-protocol"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cdba59219406899220fc4cdfd17a95191ba9c9afb719b5fa5a083d63109a9f1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"bytes-utils",
|
||||||
|
"cookie-factory",
|
||||||
|
"crc16",
|
||||||
|
"log",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -3516,6 +3655,16 @@ version = "1.15.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.5.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@@ -3765,7 +3914,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.6.4",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -3801,7 +3950,7 @@ dependencies = [
|
|||||||
"postgres-protocol",
|
"postgres-protocol",
|
||||||
"postgres-types",
|
"postgres-types",
|
||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
"socket2",
|
"socket2 0.6.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"whoami",
|
"whoami",
|
||||||
@@ -4193,6 +4342,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ edition = "2024"
|
|||||||
build = "src/build.rs"
|
build = "src/build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
cfg-if = "1.0.4"
|
||||||
chrono = { version = "0.4.45", features = ["serde"] }
|
chrono = { version = "0.4.45", features = ["serde"] }
|
||||||
config = { version = "0.15.24", optional = true }
|
config = { version = "0.15.24", optional = true }
|
||||||
diesel = { version = "2.3.10", optional = true, features = ["chrono"] }
|
diesel = { version = "2.3.10", optional = true, features = ["chrono"] }
|
||||||
@@ -16,6 +17,7 @@ diesel-async = { version = "0.9.1", optional = true, features = ["postgres", "de
|
|||||||
diesel_migrations = { version = "2.3.2", optional = true }
|
diesel_migrations = { version = "2.3.2", optional = true }
|
||||||
dioxus = { version = "0.7.9", features = ["router", "fullstack"] }
|
dioxus = { version = "0.7.9", features = ["router", "fullstack"] }
|
||||||
dotenvy = { version = "0.15.7", optional = true }
|
dotenvy = { version = "0.15.7", optional = true }
|
||||||
|
fred = { version = "10.1.0", optional = true }
|
||||||
lucide-dioxus = { version = "3.11.0", features = ["notifications"] }
|
lucide-dioxus = { version = "3.11.0", features = ["notifications"] }
|
||||||
pbkdf2 = { version = "0.13.0", optional = true, features = ["getrandom", "phc"] }
|
pbkdf2 = { version = "0.13.0", optional = true, features = ["getrandom", "phc"] }
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
@@ -34,6 +36,7 @@ server = [
|
|||||||
"dep:diesel-async",
|
"dep:diesel-async",
|
||||||
"dep:diesel_migrations",
|
"dep:diesel_migrations",
|
||||||
"dep:dotenvy",
|
"dep:dotenvy",
|
||||||
|
"dep:fred",
|
||||||
"dep:pbkdf2",
|
"dep:pbkdf2",
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
|
pub mod user;
|
||||||
|
|||||||
112
src/models/user.rs
Normal file
112
src/models/user.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
//! Various user types. Some types marked server-only to help prevent
|
||||||
|
//! leaking passwords to the frontend
|
||||||
|
|
||||||
|
/// Standard informational user type, contains no password information
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "server", derive(Queryable, Selectable, Identifiable))]
|
||||||
|
#[cfg_attr(feature = "server", diesel(table_name = crate::schema::users,
|
||||||
|
check_for_backend(diesel::pg::Pg)))]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub username: String,
|
||||||
|
pub created_at: chrono::DateTime<chrono::Local>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plaintext user credentials, used for login/signup form
|
||||||
|
pub struct UserCredentials {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "server")] {
|
||||||
|
|
||||||
|
use diesel::{
|
||||||
|
deserialize::{FromSql, FromSqlRow},
|
||||||
|
expression::AsExpression,
|
||||||
|
prelude::*,
|
||||||
|
serialize::ToSql,
|
||||||
|
sql_types,
|
||||||
|
};
|
||||||
|
use pbkdf2::{PasswordHasher, Pbkdf2};
|
||||||
|
|
||||||
|
/// Newtype for a `String`-represented hashed password
|
||||||
|
#[derive(Clone, Debug, AsExpression, FromSqlRow)]
|
||||||
|
#[diesel(sql_type = sql_types::Text)]
|
||||||
|
pub struct HashedPassword(String);
|
||||||
|
|
||||||
|
impl<DB> FromSql<diesel::sql_types::Text, DB> for HashedPassword
|
||||||
|
where
|
||||||
|
DB: diesel::backend::Backend,
|
||||||
|
String: FromSql<sql_types::Text, DB>,
|
||||||
|
{
|
||||||
|
fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
|
||||||
|
Ok(Self(String::from_sql(bytes)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB> ToSql<diesel::sql_types::Text, DB> for HashedPassword
|
||||||
|
where
|
||||||
|
DB: diesel::backend::Backend,
|
||||||
|
String: ToSql<sql_types::Text, DB>,
|
||||||
|
{
|
||||||
|
fn to_sql<'b>(
|
||||||
|
&'b self,
|
||||||
|
out: &mut diesel::serialize::Output<'b, '_, DB>,
|
||||||
|
) -> diesel::serialize::Result {
|
||||||
|
self.0.to_sql(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User as it appears in the database, with hashed password
|
||||||
|
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = crate::schema::users, check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct DbUser {
|
||||||
|
pub id: i32,
|
||||||
|
pub username: String,
|
||||||
|
pub hashed_password: HashedPassword,
|
||||||
|
pub created_at: chrono::DateTime<chrono::Local>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DbUser> for User {
|
||||||
|
fn from(db_user: DbUser) -> Self {
|
||||||
|
User {
|
||||||
|
id: db_user.id,
|
||||||
|
username: db_user.username,
|
||||||
|
created_at: db_user.created_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User credentials with hashed password
|
||||||
|
#[derive(Clone, Debug, Insertable, Queryable, Selectable)]
|
||||||
|
#[diesel(table_name = crate::schema::users, check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct HashedUserCredentials {
|
||||||
|
username: String,
|
||||||
|
hashed_password: HashedPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DbUser> for HashedUserCredentials {
|
||||||
|
fn from(db_user: DbUser) -> Self {
|
||||||
|
HashedUserCredentials {
|
||||||
|
username: db_user.username,
|
||||||
|
hashed_password: db_user.hashed_password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserCredentials {
|
||||||
|
/// Attempt to convert into `HashedUserCredentials` by hashing the password. Yields a PBKDF2
|
||||||
|
/// error on failure.
|
||||||
|
pub fn try_hash(self) -> Result<HashedUserCredentials, pbkdf2::password_hash::Error> {
|
||||||
|
let hashed_password = Pbkdf2::default().hash_password(self.password.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(HashedUserCredentials {
|
||||||
|
username: self.username,
|
||||||
|
hashed_password: HashedPassword(hashed_password.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,39 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Build a connection URI from parts
|
||||||
|
fn format_uri(
|
||||||
|
scheme: &str,
|
||||||
|
username: &Option<String>,
|
||||||
|
password: &Option<String>,
|
||||||
|
host: &str,
|
||||||
|
port: &Option<u16>,
|
||||||
|
path: &Option<String>,
|
||||||
|
) -> String {
|
||||||
|
let mut url = format!("{scheme}://");
|
||||||
|
|
||||||
|
if let Some(username) = username {
|
||||||
|
url.push_str(username);
|
||||||
|
|
||||||
|
if let Some(password) = password {
|
||||||
|
url.push_str(&format!(":{password}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
url.push('@');
|
||||||
|
}
|
||||||
|
|
||||||
|
url.push_str(host);
|
||||||
|
|
||||||
|
if let Some(port) = port {
|
||||||
|
url.push_str(&format!(":{port}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
url.push_str(&format!("/{path}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct DatabaseConfig {
|
pub struct DatabaseConfig {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@@ -39,39 +73,69 @@ impl DatabaseConnectionConfig {
|
|||||||
database,
|
database,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
} => {
|
} => format_uri("postgres", username, password, host, port, database),
|
||||||
let mut url = "postgres://".to_string();
|
|
||||||
|
|
||||||
if let Some(username) = username {
|
|
||||||
url.push_str(username);
|
|
||||||
|
|
||||||
if let Some(password) = password {
|
|
||||||
url.push_str(&format!(":{password}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
url.push('@');
|
|
||||||
}
|
|
||||||
|
|
||||||
url.push_str(host);
|
|
||||||
|
|
||||||
if let Some(port) = port {
|
|
||||||
url.push_str(&format!(":{port}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(database) = database {
|
|
||||||
url.push_str(&format!("/{database}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
url
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum KeyValStoreConnectionConfig {
|
||||||
|
FromUrl {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
|
FromParts {
|
||||||
|
scheme: Option<String>,
|
||||||
|
host: String,
|
||||||
|
port: Option<u16>,
|
||||||
|
database: Option<String>,
|
||||||
|
username: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyValStoreConnectionConfig {
|
||||||
|
/// Convert this configuration into the Redis connection URI
|
||||||
|
pub fn as_uri(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::FromUrl { url } => url.clone(),
|
||||||
|
Self::FromParts {
|
||||||
|
scheme,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
database,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
} => format_uri(
|
||||||
|
scheme.as_deref().unwrap_or("redis"),
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
database,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct KeyValStoreConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
connection: KeyValStoreConnectionConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyValStoreConfig {
|
||||||
|
/// Get the configured database connection URI
|
||||||
|
pub fn connection_uri(&self) -> String {
|
||||||
|
self.connection.as_uri()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
/// Top-level application configuration
|
/// Top-level application configuration
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub database: DatabaseConfig,
|
pub database: DatabaseConfig,
|
||||||
|
pub key_val_store: KeyValStoreConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse configuration from the expected files and environment variables
|
/// Parse configuration from the expected files and environment variables
|
||||||
|
|||||||
29
src/server/key_val_store.rs
Normal file
29
src/server/key_val_store.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use fred::prelude::*;
|
||||||
|
|
||||||
|
use crate::util::error::{Contextualize, Error, ErrorType};
|
||||||
|
|
||||||
|
const KEY_VAL_POOL_SIZE: usize = 4;
|
||||||
|
|
||||||
|
pub type KeyValPool = Pool;
|
||||||
|
|
||||||
|
pub async fn setup(connection_uri: &str) -> Result<KeyValPool, Error> {
|
||||||
|
let config = Config::from_url(connection_uri)
|
||||||
|
.map_err(|e| ErrorType::KeyValStore(e.to_string()))
|
||||||
|
.err_context("Error creating key-value store config")?;
|
||||||
|
|
||||||
|
let pool = Builder::from_config(config)
|
||||||
|
.build_pool(KEY_VAL_POOL_SIZE)
|
||||||
|
// At time of writing the only error that could occur here is if config is not provided.
|
||||||
|
// Since we're building a pool `from_config`, this shouldn't be possible
|
||||||
|
.map_err(|e| ErrorType::KeyValStore(e.to_string()))
|
||||||
|
.err_context("Error creating pool for key-value store")?;
|
||||||
|
|
||||||
|
tracing::debug!("Establishing connection to key-value store...");
|
||||||
|
|
||||||
|
pool.init()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ErrorType::KeyValStore(e.to_string()))
|
||||||
|
.err_context("Error connecting to key-value store")?;
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::App;
|
use crate::App;
|
||||||
use crate::server::{config, database};
|
use crate::server::{config, database, key_val_store};
|
||||||
use crate::util::error::{Contextualize, Error, Result};
|
use crate::util::error::{Contextualize, Error, Result};
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<()> {
|
||||||
@@ -22,10 +22,16 @@ pub fn main() -> Result<()> {
|
|||||||
.map_err(|e| Error::message_here(e.to_string()))
|
.map_err(|e| Error::message_here(e.to_string()))
|
||||||
.err_context("Failed to create tokio runtime for server setup")?;
|
.err_context("Failed to create tokio runtime for server setup")?;
|
||||||
|
|
||||||
let _db_pool = setup_rt.block_on(async {
|
let (_db_pool, _key_val_pool) = setup_rt.block_on(async {
|
||||||
database::setup(config.database.connection_uri())
|
let db_pool = database::setup(config.database.connection_uri())
|
||||||
.await
|
.await
|
||||||
.err_context("Failed database setup")
|
.err_context("Failed database setup")?;
|
||||||
|
|
||||||
|
let key_val_pool = key_val_store::setup(&config.key_val_store.connection_uri())
|
||||||
|
.await
|
||||||
|
.err_context("Failed key-value store setup")?;
|
||||||
|
|
||||||
|
Ok::<_, Error>((db_pool, key_val_pool))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
tracing::info!("Setup complete, launching web server...");
|
tracing::info!("Setup complete, launching web server...");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
pub mod key_val_store;
|
||||||
pub mod main;
|
pub mod main;
|
||||||
|
|
||||||
pub use main::main;
|
pub use main::main;
|
||||||
|
|||||||
@@ -242,9 +242,8 @@ impl dioxus_fullstack::AsStatusCode for Error {
|
|||||||
ErrorType::Database(msg) if *msg == (diesel::result::Error::NotFound).to_string() => {
|
ErrorType::Database(msg) if *msg == (diesel::result::Error::NotFound).to_string() => {
|
||||||
StatusCode::NOT_FOUND
|
StatusCode::NOT_FOUND
|
||||||
}
|
}
|
||||||
ErrorType::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
ErrorType::Error(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
ErrorType::ServerFnError(e) => e.as_status_code(),
|
ErrorType::ServerFnError(e) => e.as_status_code(),
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,6 +292,11 @@ pub enum ErrorType {
|
|||||||
|
|
||||||
#[error("Server function error: {0}")]
|
#[error("Server function error: {0}")]
|
||||||
ServerFnError(ServerFnError),
|
ServerFnError(ServerFnError),
|
||||||
|
|
||||||
|
// Using string to represent Fred errors, because Fred's Error type is not `Serialize`,
|
||||||
|
// and Fred is only available on the server
|
||||||
|
#[error("Key-value store error: {0}")]
|
||||||
|
KeyValStore(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ErrorType> for Error {
|
impl From<ErrorType> for Error {
|
||||||
@@ -309,3 +313,11 @@ impl From<diesel::result::Error> for Error {
|
|||||||
Error::new_here(ErrorType::Database(format!("{err}")))
|
Error::new_here(ErrorType::Database(format!("{err}")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl From<fred::error::Error> for Error {
|
||||||
|
#[track_caller]
|
||||||
|
fn from(err: fred::error::Error) -> Self {
|
||||||
|
Error::new_here(ErrorType::KeyValStore(format!("{err}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user