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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
@ -74,6 +89,22 @@ version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "async-lock"
|
||||
version = "3.4.0"
|
||||
@ -217,6 +248,26 @@ dependencies = [
|
||||
"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]]
|
||||
name = "axum-server"
|
||||
version = "0.7.1"
|
||||
@ -311,6 +362,27 @@ dependencies = [
|
||||
"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]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
@ -341,6 +413,8 @@ version = "1.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@ -490,6 +564,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -520,6 +605,15 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
@ -623,6 +717,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.2.7"
|
||||
@ -750,6 +854,16 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
@ -1396,6 +1510,15 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@ -1718,6 +1841,7 @@ checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1890,6 +2014,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@ -2121,6 +2251,12 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
@ -2397,6 +2533,28 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rpassword"
|
||||
version = "7.3.1"
|
||||
@ -2721,6 +2879,15 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
@ -2795,8 +2962,11 @@ name = "sparse-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum-login",
|
||||
"axum-server",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"codee",
|
||||
"console_error_panic_hook",
|
||||
@ -2816,10 +2986,13 @@ dependencies = [
|
||||
"sqlx",
|
||||
"structopt",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
"tower-http 0.5.2",
|
||||
"tower-sessions",
|
||||
"tower-sessions-sqlx-store",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wasm-bindgen",
|
||||
@ -2894,6 +3067,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
@ -2978,6 +3152,7 @@ dependencies = [
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
@ -3016,6 +3191,7 @@ dependencies = [
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
@ -3040,6 +3216,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
@ -3268,6 +3445,37 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
@ -3304,6 +3512,7 @@ dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
@ -3431,14 +3640,33 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags 2.8.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
@ -3493,6 +3721,71 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
@ -3680,6 +3973,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@ -4189,3 +4488,31 @@ dependencies = [
|
||||
"quote",
|
||||
"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]
|
||||
inherits = "release"
|
||||
strip = true
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
21
packages.nix
21
packages.nix
@ -30,14 +30,6 @@ let
|
||||
|
||||
src = craneLib.cleanCargoSource ./.;
|
||||
|
||||
commonArgs = buildEnvironment // {
|
||||
inherit src;
|
||||
strictDeps = true;
|
||||
|
||||
nativeBuildInputs = buildTools.linux;
|
||||
buildInputs = buildTools.all;
|
||||
};
|
||||
|
||||
fileSetForBeaconCrate = pkgs.lib.fileset.toSource {
|
||||
root = ./.;
|
||||
fileset = pkgs.lib.fileset.unions [
|
||||
@ -97,6 +89,14 @@ let
|
||||
];
|
||||
};
|
||||
|
||||
commonArgs = buildEnvironment // {
|
||||
inherit src;
|
||||
strictDeps = true;
|
||||
|
||||
nativeBuildInputs = buildTools.linux;
|
||||
buildInputs = buildTools.all;
|
||||
};
|
||||
|
||||
freebsdArgs = commonArgs // {
|
||||
# Sigh...
|
||||
# For some reason, crane and cargo don't run the build script for FreeBSD
|
||||
@ -249,8 +249,11 @@ let
|
||||
installPhase = ''
|
||||
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_FREEBSD =
|
||||
|
||||
@ -3,3 +3,10 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
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",
|
||||
"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": {
|
||||
"columns": [
|
||||
{
|
||||
@ -28,5 +28,5 @@
|
||||
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]
|
||||
leptos = { 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"
|
||||
leptos_axum = { version = "^0.7", optional = true }
|
||||
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-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"
|
||||
thiserror = "1"
|
||||
http = "1"
|
||||
@ -37,14 +37,24 @@ pbkdf2 = { version = "0.12", features = ["simple", "sha2"], optional = true }
|
||||
sha2 = { version = "0.10", optional = true }
|
||||
hex = { version = "0.4", optional = true }
|
||||
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]
|
||||
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:axum-login",
|
||||
"dep:tokio",
|
||||
"dep:time",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tower-sessions",
|
||||
"dep:tower-sessions-sqlx-store",
|
||||
"dep:leptos_axum",
|
||||
"dep:axum-server",
|
||||
"dep:tracing-subscriber",
|
||||
@ -60,8 +70,7 @@ ssr = [
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"leptos-use/ssr",
|
||||
"axum/ws"
|
||||
"leptos-use/ssr"
|
||||
]
|
||||
|
||||
[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_router::{
|
||||
components::{A, Route, Router, Routes},
|
||||
StaticSegment,
|
||||
path
|
||||
};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[server]
|
||||
pub async fn test_retrieve() -> Result<u64, ServerFnError> {
|
||||
@ -20,6 +21,58 @@ pub async fn test_retrieve() -> Result<u64, ServerFnError> {
|
||||
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 {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
@ -42,34 +95,71 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
pub fn App() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
|
||||
let user = Resource::new(|| (), |_| async { me().await });
|
||||
|
||||
view! {
|
||||
<Stylesheet id="leptos" href="/pkg/sparse-server.css"/>
|
||||
|
||||
// sets the document title
|
||||
<Title text="Welcome to Leptos"/>
|
||||
<Title text="Sparse Control"/>
|
||||
|
||||
// content for this welcome page
|
||||
<Router>
|
||||
<nav>
|
||||
<h1>"Sparse control"</h1>
|
||||
<A href="/">"Home"</A>
|
||||
<Suspense fallback=|| ()>
|
||||
<A href="/beacons">"Beacon management"</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>
|
||||
|
||||
<aside class="beacons">
|
||||
</aside>
|
||||
|
||||
<Routes fallback=|| "Page not found.".into_view()>
|
||||
<Route path=StaticSegment("") view=HomePage/>
|
||||
<Route path=StaticSegment("/users") view=crate::users::UserView/>
|
||||
<Route path=path!("users") view=crate::users::UserView />
|
||||
<Route path=path!("login") view=move || view! { <LoginPage /> } />
|
||||
<Route path=path!("") view=HomePage/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn LoginPage() -> impl IntoView {
|
||||
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
use leptos_use::{UseWebSocketReturn, use_websocket};
|
||||
|
||||
// Creates a reactive value to update the button
|
||||
let count = RwSignal::new(0);
|
||||
@ -91,7 +181,11 @@ fn HomePage() -> impl IntoView {
|
||||
let pending = request_time.pending();
|
||||
|
||||
let text_input = RwSignal::new("".to_owned());
|
||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
||||
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");
|
||||
|
||||
@ -108,6 +202,10 @@ fn HomePage() -> impl IntoView {
|
||||
send(&text_input.get());
|
||||
text_input.set("".to_string());
|
||||
};
|
||||
} else {
|
||||
let send_message = move |_| {};
|
||||
}
|
||||
}
|
||||
|
||||
view! {
|
||||
<main class="main">
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{net::SocketAddrV4, path::PathBuf};
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub mod user;
|
||||
@ -25,7 +26,15 @@ pub struct Options {
|
||||
#[structopt()]
|
||||
pub enum Command {
|
||||
/// 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
|
||||
ExtractPubKey {},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use sqlx::{Database, query, sqlite::SqlitePool};
|
||||
use sqlx::{query, sqlite::SqlitePool};
|
||||
|
||||
use crate::cli::UserCommand as UC;
|
||||
|
||||
|
||||
@ -1,15 +1,2 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
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}};
|
||||
use sha2::Sha256;
|
||||
#[derive(Clone)]
|
||||
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;
|
||||
|
||||
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>
|
||||
where
|
||||
E: sqlx::SqliteExecutor<'a>
|
||||
{
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
let key = pbkdf2_hmac_array::<Sha256, 20>(
|
||||
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[..]);
|
||||
let password_string = hash_password(
|
||||
password.as_bytes()
|
||||
).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE users SET password_hash = ?, password_salt = ? WHERE user_id = ?",
|
||||
"UPDATE users SET password_hash = ? WHERE user_id = ?",
|
||||
password_string,
|
||||
salt_string,
|
||||
id
|
||||
)
|
||||
.execute(pool)
|
||||
@ -52,7 +100,7 @@ where
|
||||
tracing::info!("Creating new user {}", name);
|
||||
|
||||
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
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
@ -65,3 +113,68 @@ where
|
||||
|
||||
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)]
|
||||
pub enum Error {
|
||||
Generic(String),
|
||||
UserCreate(String),
|
||||
#[cfg(feature = "ssr")]
|
||||
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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::Generic(err) => {
|
||||
write!(f, "generic error: {err}")
|
||||
}
|
||||
Error::UserCreate(err) => {
|
||||
write!(f, "user create error: {err}")
|
||||
}
|
||||
@ -15,6 +23,14 @@ impl std::fmt::Display for Error {
|
||||
Error::Sqlx(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 {
|
||||
#[cfg(feature = "ssr")]
|
||||
Error::Sqlx(err) => Some(err),
|
||||
#[cfg(feature = "ssr")]
|
||||
Error::TokioJoin(err) => Some(err),
|
||||
_ => 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")]
|
||||
impl From<sqlx::Error> for Error {
|
||||
fn from(err: sqlx::Error) -> Self {
|
||||
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")]
|
||||
pub(crate) mod beacons {
|
||||
#[allow(dead_code)]
|
||||
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"));
|
||||
#[allow(dead_code)]
|
||||
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"));
|
||||
#[allow(dead_code)]
|
||||
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!");
|
||||
|
||||
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");
|
||||
webserver::serve_web(options, pool).await
|
||||
webserver::serve_web(management_address, bind_address, pool).await
|
||||
}
|
||||
Some(cli::Command::ExtractPubKey { }) => {
|
||||
Ok(ExitCode::SUCCESS)
|
||||
@ -89,8 +94,13 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
||||
cli::user::handle_user_command(command, pool).await
|
||||
}
|
||||
None => {
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
|
||||
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 (time_ago, set_time_ago) = signal(user.last_active.map(|active| format_delta(Utc::now() - active)));
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
Effect::watch(
|
||||
move || counter.get(),
|
||||
move |_, _, _| {
|
||||
@ -194,7 +195,7 @@ async fn list_users() -> Result<Vec<PubUser>, ServerFnError> {
|
||||
|
||||
let users = sqlx::query_as!(
|
||||
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)
|
||||
.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 axum::Router;
|
||||
use leptos::logging::log;
|
||||
use leptos::prelude::*;
|
||||
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 {
|
||||
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) {
|
||||
use futures_util::StreamExt;
|
||||
use tracing::info;
|
||||
|
||||
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 addr = conf.leptos_options.site_addr;
|
||||
let leptos_options = conf.leptos_options;
|
||||
// Generate the list of routes in your Leptos 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()
|
||||
.route("/ws", axum::routing::any(websocket))
|
||||
.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())
|
||||
})
|
||||
.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
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
tracing::info!("listening on http://{}", &addr);
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
axum::serve(listener, app.into_make_service()).await?;
|
||||
let management_listener = tokio::net::TcpListener::bind(&management_address).await?;
|
||||
tracing::info!("management interface listening on http://{}", &management_address);
|
||||
|
||||
axum::serve(management_listener, app.into_make_service())
|
||||
.with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle()))
|
||||
.await?;
|
||||
|
||||
deletion_task.await??;
|
||||
|
||||
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