feat: set up basic sessions
This commit is contained in:
parent
bee66a8d6c
commit
bf879bb081
327
Cargo.lock
generated
327
Cargo.lock
generated
@ -26,6 +26,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloc-no-stdlib"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloc-stdlib"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@ -74,6 +89,22 @@ version = "1.0.95"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compression"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
|
||||||
|
dependencies = [
|
||||||
|
"brotli",
|
||||||
|
"flate2",
|
||||||
|
"futures-core",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"zstd",
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-lock"
|
name = "async-lock"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@ -217,6 +248,26 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-login"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5260ed0ecc8ace8e7e61a7406672faba598c8a86b8f4742fcdde0ddc979a318f"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum",
|
||||||
|
"form_urlencoded",
|
||||||
|
"serde",
|
||||||
|
"subtle",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions",
|
||||||
|
"tracing",
|
||||||
|
"urlencoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-server"
|
name = "axum-server"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -311,6 +362,27 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "brotli"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
"alloc-stdlib",
|
||||||
|
"brotli-decompressor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "brotli-decompressor"
|
||||||
|
version = "4.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
|
||||||
|
dependencies = [
|
||||||
|
"alloc-no-stdlib",
|
||||||
|
"alloc-stdlib",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.16.0"
|
||||||
@ -341,6 +413,8 @@ version = "1.2.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -490,6 +564,17 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@ -520,6 +605,15 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@ -623,6 +717,16 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-where"
|
name = "derive-where"
|
||||||
version = "1.2.7"
|
version = "1.2.7"
|
||||||
@ -750,6 +854,16 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -1396,6 +1510,15 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
@ -1718,6 +1841,7 @@ checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1890,6 +2014,12 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
@ -2121,6 +2251,12 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
@ -2397,6 +2533,28 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp-serde"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"rmp",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rpassword"
|
name = "rpassword"
|
||||||
version = "7.3.1"
|
version = "7.3.1"
|
||||||
@ -2721,6 +2879,15 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@ -2795,8 +2962,11 @@ name = "sparse-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-login",
|
||||||
"axum-server",
|
"axum-server",
|
||||||
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"codee",
|
"codee",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
@ -2816,10 +2986,13 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"structopt",
|
"structopt",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tower-http 0.5.2",
|
"tower-http 0.5.2",
|
||||||
|
"tower-sessions",
|
||||||
|
"tower-sessions-sqlx-store",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@ -2894,6 +3067,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -2978,6 +3152,7 @@ dependencies = [
|
|||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
@ -3016,6 +3191,7 @@ dependencies = [
|
|||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
@ -3040,6 +3216,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@ -3268,6 +3445,37 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@ -3304,6 +3512,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@ -3431,14 +3640,33 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-cookies"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fd0118512cf0b3768f7fcccf0bef1ae41d68f2b45edc1e77432b36c97c56c6d"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum-core",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
@ -3493,6 +3721,71 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65856c81ee244e0f8a55ab0f7b769b72fbde387c235f0a73cd97c579818d05eb"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"http",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions-core",
|
||||||
|
"tower-sessions-memory-store",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-core"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb6abbfcaf6436ec5a772cd9f965401da12db793e404ae6134eac066fa5a04f3"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum-core",
|
||||||
|
"base64",
|
||||||
|
"futures",
|
||||||
|
"http",
|
||||||
|
"parking_lot",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-memory-store"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fad75660c8afbe74f4e7cbbe8e9090171a056b57370ea4d7d5e9eb3e4af3092"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-sessions-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-sqlx-store"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdd38eba51214e99accab78f6b7c8e273e90a9cb57575e86b592c60074e182d7"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"rmp-serde",
|
||||||
|
"sqlx",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"time",
|
||||||
|
"tower-sessions-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.41"
|
||||||
@ -3680,6 +3973,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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"
|
||||||
@ -4189,3 +4488,31 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "7.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.13+zstd.1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|||||||
@ -13,6 +13,7 @@ panic = "abort"
|
|||||||
|
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
strip = true
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
21
packages.nix
21
packages.nix
@ -30,14 +30,6 @@ let
|
|||||||
|
|
||||||
src = craneLib.cleanCargoSource ./.;
|
src = craneLib.cleanCargoSource ./.;
|
||||||
|
|
||||||
commonArgs = buildEnvironment // {
|
|
||||||
inherit src;
|
|
||||||
strictDeps = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = buildTools.linux;
|
|
||||||
buildInputs = buildTools.all;
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSetForBeaconCrate = pkgs.lib.fileset.toSource {
|
fileSetForBeaconCrate = pkgs.lib.fileset.toSource {
|
||||||
root = ./.;
|
root = ./.;
|
||||||
fileset = pkgs.lib.fileset.unions [
|
fileset = pkgs.lib.fileset.unions [
|
||||||
@ -97,6 +89,14 @@ let
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
commonArgs = buildEnvironment // {
|
||||||
|
inherit src;
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
nativeBuildInputs = buildTools.linux;
|
||||||
|
buildInputs = buildTools.all;
|
||||||
|
};
|
||||||
|
|
||||||
freebsdArgs = commonArgs // {
|
freebsdArgs = commonArgs // {
|
||||||
# Sigh...
|
# Sigh...
|
||||||
# For some reason, crane and cargo don't run the build script for FreeBSD
|
# For some reason, crane and cargo don't run the build script for FreeBSD
|
||||||
@ -249,8 +249,11 @@ let
|
|||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
|
|
||||||
cp target/x86_64-unknown-linux-musl/release/sparse-server $out/bin
|
cp target/x86_64-unknown-linux-gnu/release/sparse-server $out/bin
|
||||||
'';
|
'';
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
RUSTFLAGS = "-Ctarget-feature=+crt-static";
|
||||||
|
|
||||||
SPARSE_INSTALLER_LINUX = "${sparse-installer-linux}/bin/sparse-installer";
|
SPARSE_INSTALLER_LINUX = "${sparse-installer-linux}/bin/sparse-installer";
|
||||||
SPARSE_INSTALLER_FREEBSD =
|
SPARSE_INSTALLER_FREEBSD =
|
||||||
|
|||||||
@ -3,3 +3,10 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
[target.x86_64-unknown-linux-gnu]
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["std", "panic_abort", "core", "alloc"]
|
||||||
|
build-std-features = ["panic_immediate_abort"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
rustflags = ["--cfg=has_std"]
|
||||||
|
|||||||
12
sparse-server/.sqlx/query-09801043d7da4a27d3388f289ef8bf040f1279bb1aee533f7ab45d375f6e0b70.json
generated
Normal file
12
sparse-server/.sqlx/query-09801043d7da4a27d3388f289ef8bf040f1279bb1aee533f7ab45d375f6e0b70.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE users SET last_active = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "09801043d7da4a27d3388f289ef8bf040f1279bb1aee533f7ab45d375f6e0b70"
|
||||||
|
}
|
||||||
12
sparse-server/.sqlx/query-36691252e9640a76c9381b00ab14931aaa45f8d1cd1de4697bcd726865719d70.json
generated
Normal file
12
sparse-server/.sqlx/query-36691252e9640a76c9381b00ab14931aaa45f8d1cd1de4697bcd726865719d70.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO users (user_name, password_hash) VALUES (?, \"\")",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "36691252e9640a76c9381b00ab14931aaa45f8d1cd1de4697bcd726865719d70"
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT user_id, user_name, (SELECT MAX(expires) FROM sessions s WHERE s.user_id = u.user_id) as last_active FROM users u",
|
"query": "SELECT user_id, user_name, last_active FROM users",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -28,5 +28,5 @@
|
|||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "ed123391fb7afe255dc30bb1006410e8537b03cc9ff00c248ccc7c34a4a8366c"
|
"hash": "4eeb48b1e4f85bae416b9d91b663d25b9abb6ecb4a31700b95141937c2f8f1f9"
|
||||||
}
|
}
|
||||||
12
sparse-server/.sqlx/query-6bccf4d930b1603d7df48cdbc605dc9095185b0fdcc5bf3613966699a9e67577.json
generated
Normal file
12
sparse-server/.sqlx/query-6bccf4d930b1603d7df48cdbc605dc9095185b0fdcc5bf3613966699a9e67577.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE users SET password_hash = ? WHERE user_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "6bccf4d930b1603d7df48cdbc605dc9095185b0fdcc5bf3613966699a9e67577"
|
||||||
|
}
|
||||||
38
sparse-server/.sqlx/query-7ca12d1edd84924ca65f597196eb618e4a313caf315a90aceaaaa253ff25947b.json
generated
Normal file
38
sparse-server/.sqlx/query-7ca12d1edd84924ca65f597196eb618e4a313caf315a90aceaaaa253ff25947b.json
generated
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT * FROM users WHERE user_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password_hash",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last_active",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "7ca12d1edd84924ca65f597196eb618e4a313caf315a90aceaaaa253ff25947b"
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "INSERT INTO users (user_name, password_salt, password_hash) VALUES (?, \"\", \"\")",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a1833e1eebd2373430b370b6fb3f2bfba2c7451759b741f4e7f5a71a49d76417"
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "UPDATE users SET password_hash = ?, password_salt = ? WHERE user_id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "bcaa134040954a687027e3b9d3cc2d9e9c2ade4c04eee8abea4db9d15db70fce"
|
|
||||||
}
|
|
||||||
38
sparse-server/.sqlx/query-e0951ca9b4ff37ca9d9c8c4ea1ab618ad0dc8cdff118b6d801b568592762a29f.json
generated
Normal file
38
sparse-server/.sqlx/query-e0951ca9b4ff37ca9d9c8c4ea1ab618ad0dc8cdff118b6d801b568592762a29f.json
generated
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT * FROM users WHERE user_name = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password_hash",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last_active",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "e0951ca9b4ff37ca9d9c8c4ea1ab618ad0dc8cdff118b6d801b568592762a29f"
|
||||||
|
}
|
||||||
@ -9,13 +9,13 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
leptos = { version = "^0.7", features = ["nightly"] }
|
leptos = { version = "^0.7", features = ["nightly"] }
|
||||||
leptos_router = { version = "^0.7", features = ["nightly"] }
|
leptos_router = { version = "^0.7", features = ["nightly"] }
|
||||||
axum = { version = "^0.7", optional = true }
|
axum = { version = "^0.7", features = ["ws"], optional = true }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
leptos_axum = { version = "^0.7", optional = true }
|
leptos_axum = { version = "^0.7", optional = true }
|
||||||
leptos_meta = { version = "^0.7" }
|
leptos_meta = { version = "^0.7" }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
tokio = { version = "1", features = ["rt-multi-thread", "signal"], optional = true }
|
||||||
tower = { version = "0.4", optional = true }
|
tower = { version = "0.4", optional = true }
|
||||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
tower-http = { version = "0.5", features = ["fs", "compression-br", "compression-deflate", "compression-gzip", "compression-zstd"], optional = true }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
http = "1"
|
http = "1"
|
||||||
@ -37,14 +37,24 @@ pbkdf2 = { version = "0.12", features = ["simple", "sha2"], optional = true }
|
|||||||
sha2 = { version = "0.10", optional = true }
|
sha2 = { version = "0.10", optional = true }
|
||||||
hex = { version = "0.4", optional = true }
|
hex = { version = "0.4", optional = true }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
axum-login = { version = "0.16.0", optional = true }
|
||||||
|
async-trait = "0.1.85"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
tower-sessions = { version = "0.13.0", optional = true }
|
||||||
|
tower-sessions-sqlx-store = { version = "0.14.0", features = ["sqlite"], optional = true }
|
||||||
|
time = { version = "0.3.37", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
||||||
ssr = [
|
ssr = [
|
||||||
"dep:axum",
|
"dep:axum",
|
||||||
|
"dep:axum-login",
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
|
"dep:time",
|
||||||
"dep:tower",
|
"dep:tower",
|
||||||
"dep:tower-http",
|
"dep:tower-http",
|
||||||
|
"dep:tower-sessions",
|
||||||
|
"dep:tower-sessions-sqlx-store",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
"dep:axum-server",
|
"dep:axum-server",
|
||||||
"dep:tracing-subscriber",
|
"dep:tracing-subscriber",
|
||||||
@ -60,8 +70,7 @@ ssr = [
|
|||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
"leptos-use/ssr",
|
"leptos-use/ssr"
|
||||||
"axum/ws"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.leptos]
|
[package.metadata.leptos]
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE users DROP COLUMN password_salt;-- Add migration script here
|
||||||
|
ALTER TABLE users ADD COLUMN last_active int;
|
||||||
|
|
||||||
|
DROP TABLE sessions;
|
||||||
@ -2,8 +2,9 @@ use leptos::prelude::*;
|
|||||||
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
||||||
use leptos_router::{
|
use leptos_router::{
|
||||||
components::{A, Route, Router, Routes},
|
components::{A, Route, Router, Routes},
|
||||||
StaticSegment,
|
path
|
||||||
};
|
};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn test_retrieve() -> Result<u64, ServerFnError> {
|
pub async fn test_retrieve() -> Result<u64, ServerFnError> {
|
||||||
@ -20,6 +21,58 @@ pub async fn test_retrieve() -> Result<u64, ServerFnError> {
|
|||||||
Ok(since_the_epoch)
|
Ok(since_the_epoch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct User {
|
||||||
|
user_id: i64,
|
||||||
|
user_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn me() -> Result<Option<User>, ServerFnError> {
|
||||||
|
let session: crate::db::user::AuthSession = leptos_axum::extract().await?;
|
||||||
|
|
||||||
|
Ok(session.user.map(|user| User {
|
||||||
|
user_id: user.user_id,
|
||||||
|
user_name: user.user_name
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn login(username: String, password: String, next: Option<String>) -> Result<(), ServerFnError> {
|
||||||
|
use leptos::server_fn::error::NoCustomError;
|
||||||
|
|
||||||
|
let mut session: crate::db::user::AuthSession = leptos_axum::extract().await?;
|
||||||
|
|
||||||
|
let user = match session.authenticate((username, password).clone()).await {
|
||||||
|
Ok(Some(user)) => user,
|
||||||
|
Ok(None) => return Err(ServerFnError::<NoCustomError>::ServerError("Invalid credentials".to_string())),
|
||||||
|
Err(e) => return Err(server_fn::server_fn_error!(e).into())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = session.login(&user).await {
|
||||||
|
return Err(server_fn::server_fn_error!(e).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target) = next {
|
||||||
|
leptos_axum::redirect(&target);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn logout() -> Result<(), ServerFnError> {
|
||||||
|
let mut session: crate::db::user::AuthSession = leptos_axum::extract().await?;
|
||||||
|
|
||||||
|
match session.logout().await {
|
||||||
|
Ok(_) => {
|
||||||
|
leptos_axum::redirect("/login");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(server_fn::server_fn_error!(e).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -42,34 +95,71 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
|||||||
pub fn App() -> impl IntoView {
|
pub fn App() -> impl IntoView {
|
||||||
provide_meta_context();
|
provide_meta_context();
|
||||||
|
|
||||||
|
let user = Resource::new(|| (), |_| async { me().await });
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Stylesheet id="leptos" href="/pkg/sparse-server.css"/>
|
<Stylesheet id="leptos" href="/pkg/sparse-server.css"/>
|
||||||
|
|
||||||
// sets the document title
|
// sets the document title
|
||||||
<Title text="Welcome to Leptos"/>
|
<Title text="Sparse Control"/>
|
||||||
|
|
||||||
// content for this welcome page
|
|
||||||
<Router>
|
<Router>
|
||||||
<nav>
|
<nav>
|
||||||
<h1>"Sparse control"</h1>
|
<h1>"Sparse control"</h1>
|
||||||
<A href="/">"Home"</A>
|
<A href="/">"Home"</A>
|
||||||
|
<Suspense fallback=|| ()>
|
||||||
<A href="/beacons">"Beacon management"</A>
|
<A href="/beacons">"Beacon management"</A>
|
||||||
<A href="/users">"Users"</A>
|
<A href="/users">"Users"</A>
|
||||||
|
{move || user
|
||||||
|
.get()
|
||||||
|
.map(|err| err.ok())
|
||||||
|
.flatten()
|
||||||
|
.flatten()
|
||||||
|
.map(|_| view! {
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
on:click=move |_| {
|
||||||
|
leptos::task::spawn_local(async move {
|
||||||
|
let _ = logout().await;
|
||||||
|
user.refetch();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
>
|
||||||
|
"Log out"
|
||||||
|
</a>
|
||||||
|
})}
|
||||||
|
{move || user
|
||||||
|
.get()
|
||||||
|
.map(|err| err.ok())
|
||||||
|
.flatten()
|
||||||
|
.flatten()
|
||||||
|
.is_none()
|
||||||
|
.then(|| view! {
|
||||||
|
<A href="/login">"Log in"</A>
|
||||||
|
})}
|
||||||
|
</Suspense>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<aside class="beacons">
|
<aside class="beacons">
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<Routes fallback=|| "Page not found.".into_view()>
|
<Routes fallback=|| "Page not found.".into_view()>
|
||||||
<Route path=StaticSegment("") view=HomePage/>
|
<Route path=path!("users") view=crate::users::UserView />
|
||||||
<Route path=StaticSegment("/users") view=crate::users::UserView/>
|
<Route path=path!("login") view=move || view! { <LoginPage /> } />
|
||||||
|
<Route path=path!("") view=HomePage/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn LoginPage() -> impl IntoView {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// Renders the home page of your application.
|
/// Renders the home page of your application.
|
||||||
#[component]
|
#[component]
|
||||||
fn HomePage() -> impl IntoView {
|
fn HomePage() -> impl IntoView {
|
||||||
use leptos_use::{UseWebSocketReturn, use_websocket};
|
|
||||||
|
|
||||||
// Creates a reactive value to update the button
|
// Creates a reactive value to update the button
|
||||||
let count = RwSignal::new(0);
|
let count = RwSignal::new(0);
|
||||||
@ -91,23 +181,31 @@ fn HomePage() -> impl IntoView {
|
|||||||
let pending = request_time.pending();
|
let pending = request_time.pending();
|
||||||
|
|
||||||
let text_input = RwSignal::new("".to_owned());
|
let text_input = RwSignal::new("".to_owned());
|
||||||
|
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
||||||
let (messages, set_messages) = signal(Vec::<String>::new());
|
let (messages, set_messages) = signal(Vec::<String>::new());
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "hydrate")] {
|
||||||
|
use leptos_use::{UseWebSocketReturn, use_websocket};
|
||||||
|
|
||||||
let UseWebSocketReturn { send, message, .. } = use_websocket::<String, String, codee::string::FromToStringCodec>("/ws");
|
let UseWebSocketReturn { send, message, .. } = use_websocket::<String, String, codee::string::FromToStringCodec>("/ws");
|
||||||
|
|
||||||
Effect::new(move |_| {
|
Effect::new(move |_| {
|
||||||
message.with(move |message| {
|
message.with(move |message| {
|
||||||
if let Some(m) = message {
|
if let Some(m) = message {
|
||||||
leptos::logging::log!("got update: {}", m);
|
leptos::logging::log!("got update: {}", m);
|
||||||
set_messages.update(|messages: &mut Vec<_>| messages.push(format!("msg: {}", m)));
|
set_messages.update(|messages: &mut Vec<_>| messages.push(format!("msg: {}", m)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let send_message = move |_| {
|
let send_message = move |_| {
|
||||||
send(&text_input.get());
|
send(&text_input.get());
|
||||||
text_input.set("".to_string());
|
text_input.set("".to_string());
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
let send_message = move |_| {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<main class="main">
|
<main class="main">
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::{net::SocketAddrV4, path::PathBuf};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
pub mod user;
|
pub mod user;
|
||||||
@ -25,7 +26,15 @@ pub struct Options {
|
|||||||
#[structopt()]
|
#[structopt()]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Run the web and API server
|
/// Run the web and API server
|
||||||
Serve {},
|
Serve {
|
||||||
|
/// Address to bind to for the management interface
|
||||||
|
#[structopt(default_value = "127.0.0.1:3000")]
|
||||||
|
management_address: SocketAddrV4,
|
||||||
|
|
||||||
|
/// Public address to bind to for the beacons to call back to
|
||||||
|
#[structopt(default_value = "127.0.0.1:5000")]
|
||||||
|
bind_address: SocketAddrV4,
|
||||||
|
},
|
||||||
|
|
||||||
/// Extract the public key and print it to standard out
|
/// Extract the public key and print it to standard out
|
||||||
ExtractPubKey {},
|
ExtractPubKey {},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use sqlx::{Database, query, sqlite::SqlitePool};
|
use sqlx::{query, sqlite::SqlitePool};
|
||||||
|
|
||||||
use crate::cli::UserCommand as UC;
|
use crate::cli::UserCommand as UC;
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,2 @@
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
pub struct User {
|
|
||||||
pub user_id: i16,
|
|
||||||
pub user_name: String,
|
|
||||||
pub password_salt: String,
|
|
||||||
pub password_hash: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Sessions {
|
|
||||||
pub session_id: String,
|
|
||||||
pub user_id: i16,
|
|
||||||
pub expires: chrono::DateTime<chrono::offset::Local>,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,29 +1,77 @@
|
|||||||
use pbkdf2::{pbkdf2_hmac_array, password_hash::{rand_core::OsRng, SaltString}};
|
#[derive(Clone)]
|
||||||
use sha2::Sha256;
|
pub struct User {
|
||||||
|
pub user_id: i64,
|
||||||
|
pub user_name: String,
|
||||||
|
password_hash: String,
|
||||||
|
pub last_active: Option<i64>
|
||||||
|
}
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use pbkdf2::{Pbkdf2, password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, rand_core::OsRng, SaltString}};
|
||||||
|
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
const PASSWORD_ITERATIONS: u32 = 100_000;
|
impl std::fmt::Debug for User {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("User")
|
||||||
|
.field("user_id", &self.user_id)
|
||||||
|
.field("user_name", &self.user_name)
|
||||||
|
.field("password_hash", &"[redacted]")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthUser for User {
|
||||||
|
type Id = i64;
|
||||||
|
|
||||||
|
fn id(&self) -> Self::Id {
|
||||||
|
self.user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn session_auth_hash(&self) -> &[u8] {
|
||||||
|
self.password_hash.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn hash_password(pass: &[u8]) -> Result<String, Error> {
|
||||||
|
Ok(tokio::task::spawn_blocking({
|
||||||
|
let pass = pass.to_owned();
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
|
||||||
|
move || Pbkdf2.hash_password(
|
||||||
|
&*pass,
|
||||||
|
&salt,
|
||||||
|
).map(|hash| hash.to_string())
|
||||||
|
}).await??)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_password(pass: &str, hash: &str) -> Result<bool, Error> {
|
||||||
|
Ok(tokio::task::spawn_blocking({
|
||||||
|
let pass = pass.to_owned();
|
||||||
|
let hash = hash.to_owned();
|
||||||
|
|
||||||
|
move ||
|
||||||
|
PasswordHash::new(&*hash)
|
||||||
|
.map(|parsed| Pbkdf2.verify_password(
|
||||||
|
&pass.as_bytes(),
|
||||||
|
&parsed
|
||||||
|
).is_ok())
|
||||||
|
}).await??)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn reset_password<'a, E>(pool: E, id: i16, password: String) -> Result<(), crate::error::Error>
|
pub async fn reset_password<'a, E>(pool: E, id: i16, password: String) -> Result<(), crate::error::Error>
|
||||||
where
|
where
|
||||||
E: sqlx::SqliteExecutor<'a>
|
E: sqlx::SqliteExecutor<'a>
|
||||||
{
|
{
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let password_string = hash_password(
|
||||||
|
password.as_bytes()
|
||||||
let key = pbkdf2_hmac_array::<Sha256, 20>(
|
).await?;
|
||||||
password.as_bytes(),
|
|
||||||
salt.as_str().as_bytes(),
|
|
||||||
PASSWORD_ITERATIONS
|
|
||||||
);
|
|
||||||
|
|
||||||
let salt_string = hex::encode(salt.as_str().as_bytes());
|
|
||||||
let password_string = hex::encode(&key[..]);
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"UPDATE users SET password_hash = ?, password_salt = ? WHERE user_id = ?",
|
"UPDATE users SET password_hash = ? WHERE user_id = ?",
|
||||||
password_string,
|
password_string,
|
||||||
salt_string,
|
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
@ -52,7 +100,7 @@ where
|
|||||||
tracing::info!("Creating new user {}", name);
|
tracing::info!("Creating new user {}", name);
|
||||||
|
|
||||||
let new_id = sqlx::query!(
|
let new_id = sqlx::query!(
|
||||||
r#"INSERT INTO users (user_name, password_salt, password_hash) VALUES (?, "", "")"#,
|
r#"INSERT INTO users (user_name, password_hash) VALUES (?, "")"#,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
@ -65,3 +113,68 @@ where
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Backend(SqlitePool);
|
||||||
|
|
||||||
|
impl Backend {
|
||||||
|
pub fn new(db: SqlitePool) -> Self {
|
||||||
|
Self(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AuthnBackend for Backend {
|
||||||
|
type User = User;
|
||||||
|
type Credentials = (String, String);
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
async fn authenticate(
|
||||||
|
&self,
|
||||||
|
creds: Self::Credentials
|
||||||
|
) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
let user: Option<Self::User> = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"SELECT * FROM users WHERE user_name = ?",
|
||||||
|
creds.0
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(user) = user else { return Ok(None); };
|
||||||
|
|
||||||
|
let good_hash = verify_password(
|
||||||
|
&user.password_hash,
|
||||||
|
&creds.1
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
if good_hash {
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE users SET last_active = ?",
|
||||||
|
now
|
||||||
|
)
|
||||||
|
.execute(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(user))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
let user: Option<Self::User> = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"SELECT * FROM users WHERE user_id = ?",
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.0)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AuthSession = axum_login::AuthSession<Backend>;
|
||||||
|
|||||||
@ -1,13 +1,21 @@
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
Generic(String),
|
||||||
UserCreate(String),
|
UserCreate(String),
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
Sqlx(sqlx::Error),
|
Sqlx(sqlx::Error),
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
TokioJoin(tokio::task::JoinError),
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Pbkdf2(pbkdf2::password_hash::errors::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Error::Generic(err) => {
|
||||||
|
write!(f, "generic error: {err}")
|
||||||
|
}
|
||||||
Error::UserCreate(err) => {
|
Error::UserCreate(err) => {
|
||||||
write!(f, "user create error: {err}")
|
write!(f, "user create error: {err}")
|
||||||
}
|
}
|
||||||
@ -15,6 +23,14 @@ impl std::fmt::Display for Error {
|
|||||||
Error::Sqlx(err) => {
|
Error::Sqlx(err) => {
|
||||||
write!(f, "sqlx error: {err:?}")
|
write!(f, "sqlx error: {err:?}")
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Error::TokioJoin(err) => {
|
||||||
|
write!(f, "tokio join error: {err:?}")
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Error::Pbkdf2(err) => {
|
||||||
|
write!(f, "password hash error: {err:?}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,14 +40,38 @@ impl std::error::Error for Error {
|
|||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
Error::Sqlx(err) => Some(err),
|
Error::Sqlx(err) => Some(err),
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Error::TokioJoin(err) => Some(err),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Error {
|
||||||
|
type Err = Self;
|
||||||
|
|
||||||
|
fn from_str(err: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self::Generic(err.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
impl From<sqlx::Error> for Error {
|
impl From<sqlx::Error> for Error {
|
||||||
fn from(err: sqlx::Error) -> Self {
|
fn from(err: sqlx::Error) -> Self {
|
||||||
Self::Sqlx(err)
|
Self::Sqlx(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl From<tokio::task::JoinError> for Error {
|
||||||
|
fn from(err: tokio::task::JoinError) -> Self {
|
||||||
|
Self::TokioJoin(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl From<pbkdf2::password_hash::errors::Error> for Error {
|
||||||
|
fn from(err: pbkdf2::password_hash::errors::Error) -> Self {
|
||||||
|
Self::Pbkdf2(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub(crate) mod beacons {
|
pub(crate) mod beacons {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const LINUX_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX"));
|
pub const LINUX_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX"));
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const FREEBSD_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_FREEBSD"));
|
pub const FREEBSD_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_FREEBSD"));
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const WINDOWS_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_WINDOWS"));
|
pub const WINDOWS_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_WINDOWS"));
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const LINUX_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_LINUX"));
|
pub const LINUX_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_LINUX"));
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const FREEBSD_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
|
pub const FREEBSD_INSTALLER: &'static [u8] = include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,9 +83,9 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
|||||||
tracing::info!("Done running database migrations!");
|
tracing::info!("Done running database migrations!");
|
||||||
|
|
||||||
match options.command.clone() {
|
match options.command.clone() {
|
||||||
Some(cli::Command::Serve { }) => {
|
Some(cli::Command::Serve { management_address, bind_address }) => {
|
||||||
tracing::info!("Performing requested action, acting as web server");
|
tracing::info!("Performing requested action, acting as web server");
|
||||||
webserver::serve_web(options, pool).await
|
webserver::serve_web(management_address, bind_address, pool).await
|
||||||
}
|
}
|
||||||
Some(cli::Command::ExtractPubKey { }) => {
|
Some(cli::Command::ExtractPubKey { }) => {
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
@ -89,8 +94,13 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
|||||||
cli::user::handle_user_command(command, pool).await
|
cli::user::handle_user_command(command, pool).await
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
|
||||||
tracing::info!("Performing default action of acting as web server");
|
tracing::info!("Performing default action of acting as web server");
|
||||||
webserver::serve_web(options, pool).await
|
|
||||||
|
let default_management_ip = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 3000);
|
||||||
|
let default_beacon_ip = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 5000);
|
||||||
|
webserver::serve_web(default_management_ip, default_beacon_ip, pool).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,6 +70,7 @@ pub fn RenderUser(refresh_user_list: Action<(), ()>, user: PubUser) -> impl Into
|
|||||||
let UseIntervalReturn { counter, .. } = use_interval(1000);
|
let UseIntervalReturn { counter, .. } = use_interval(1000);
|
||||||
let (time_ago, set_time_ago) = signal(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
let (time_ago, set_time_ago) = signal(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
||||||
|
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
Effect::watch(
|
Effect::watch(
|
||||||
move || counter.get(),
|
move || counter.get(),
|
||||||
move |_, _, _| {
|
move |_, _, _| {
|
||||||
@ -194,7 +195,7 @@ async fn list_users() -> Result<Vec<PubUser>, ServerFnError> {
|
|||||||
|
|
||||||
let users = sqlx::query_as!(
|
let users = sqlx::query_as!(
|
||||||
DbUser,
|
DbUser,
|
||||||
"SELECT user_id, user_name, (SELECT MAX(expires) FROM sessions s WHERE s.user_id = u.user_id) as last_active FROM users u"
|
"SELECT user_id, user_name, last_active FROM users"
|
||||||
)
|
)
|
||||||
.fetch(&pool)
|
.fetch(&pool)
|
||||||
.map(|user| user.map(|u| PubUser {
|
.map(|user| user.map(|u| PubUser {
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
use std::process::ExitCode;
|
use std::{net::SocketAddrV4, process::ExitCode};
|
||||||
|
|
||||||
use sqlx::sqlite::SqlitePool;
|
use sqlx::sqlite::SqlitePool;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use leptos::logging::log;
|
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||||
use sparse_server::app::*;
|
use tokio::{signal, task::AbortHandle};
|
||||||
|
use tower_sessions::{Expiry, SessionManagerLayer, session_store::ExpiredDeletion};
|
||||||
|
use tower_sessions_sqlx_store::SqliteStore;
|
||||||
|
|
||||||
|
use sparse_server::app::*;
|
||||||
|
|
||||||
pub async fn websocket(ws: axum::extract::ws::WebSocketUpgrade) -> axum::response::Response {
|
pub async fn websocket(ws: axum::extract::ws::WebSocketUpgrade) -> axum::response::Response {
|
||||||
tracing::info!("Handling websocket request to /ws");
|
tracing::info!("Handling websocket request to /ws");
|
||||||
@ -14,7 +16,6 @@ pub async fn websocket(ws: axum::extract::ws::WebSocketUpgrade) -> axum::respons
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_websocket(mut socket: axum::extract::ws::WebSocket) {
|
async fn handle_websocket(mut socket: axum::extract::ws::WebSocket) {
|
||||||
use futures_util::StreamExt;
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
@ -43,13 +44,34 @@ async fn handle_websocket(mut socket: axum::extract::ws::WebSocket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn serve_web(options: crate::cli::Options, db: SqlitePool) -> anyhow::Result<ExitCode> {
|
pub async fn serve_web(management_address: SocketAddrV4, _bind_address: SocketAddrV4, db: SqlitePool) -> anyhow::Result<ExitCode> {
|
||||||
let conf = get_configuration(None).unwrap();
|
let conf = get_configuration(None).unwrap();
|
||||||
let addr = conf.leptos_options.site_addr;
|
|
||||||
let leptos_options = conf.leptos_options;
|
let leptos_options = conf.leptos_options;
|
||||||
// Generate the list of routes in your Leptos App
|
|
||||||
let routes = generate_route_list(App);
|
let routes = generate_route_list(App);
|
||||||
|
|
||||||
|
let session_store = SqliteStore::new(db.clone());
|
||||||
|
session_store.migrate().await?;
|
||||||
|
|
||||||
|
let deletion_task = tokio::task::spawn(
|
||||||
|
session_store
|
||||||
|
.clone()
|
||||||
|
.continuously_delete_expired(tokio::time::Duration::from_secs(60))
|
||||||
|
);
|
||||||
|
|
||||||
|
let session_layer = SessionManagerLayer::new(session_store)
|
||||||
|
.with_secure(false)
|
||||||
|
.with_expiry(Expiry::OnInactivity(time::Duration::minutes(20)));
|
||||||
|
|
||||||
|
|
||||||
|
let backend = crate::db::user::Backend::new(db.clone());
|
||||||
|
let auth_layer = axum_login::AuthManagerLayerBuilder::new(backend, session_layer).build();
|
||||||
|
|
||||||
|
let compression_layer = tower_http::compression::CompressionLayer::new()
|
||||||
|
.gzip(true)
|
||||||
|
.deflate(true)
|
||||||
|
.br(true)
|
||||||
|
.zstd(true);
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/ws", axum::routing::any(websocket))
|
.route("/ws", axum::routing::any(websocket))
|
||||||
.leptos_routes_with_context(
|
.leptos_routes_with_context(
|
||||||
@ -61,13 +83,50 @@ pub async fn serve_web(options: crate::cli::Options, db: SqlitePool) -> anyhow::
|
|||||||
move || shell(leptos_options.clone())
|
move || shell(leptos_options.clone())
|
||||||
})
|
})
|
||||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||||
.with_state(leptos_options);
|
.with_state(leptos_options)
|
||||||
|
.layer(auth_layer)
|
||||||
|
.layer(compression_layer);
|
||||||
|
|
||||||
// run our app with hyper
|
// run our app with hyper
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
// `axum::Server` is a re-export of `hyper::Server`
|
||||||
tracing::info!("listening on http://{}", &addr);
|
let management_listener = tokio::net::TcpListener::bind(&management_address).await?;
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
tracing::info!("management interface listening on http://{}", &management_address);
|
||||||
axum::serve(listener, app.into_make_service()).await?;
|
|
||||||
|
axum::serve(management_listener, app.into_make_service())
|
||||||
|
.with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
deletion_task.await??;
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn shutdown_signal(deletion_task_abort_handle: AbortHandle) {
|
||||||
|
let ctrl_c = async {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install Ctrl+C handler");
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let terminate = async {
|
||||||
|
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||||
|
.expect("failed to install signal handler")
|
||||||
|
.recv()
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let terminate = std::future::pending::<()>();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = ctrl_c => {
|
||||||
|
tracing::info!("Received Ctrl-C");
|
||||||
|
deletion_task_abort_handle.abort()
|
||||||
|
},
|
||||||
|
_ = terminate => {
|
||||||
|
tracing::info!("Received terminate command");
|
||||||
|
deletion_task_abort_handle.abort()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user