diff --git a/.gitignore b/.gitignore
index 79f4470..1d296f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
/logs
/.vscode
node-cmd-history.txt
-**/dist/**
+**/dist
+**/data/*
diff --git a/Cargo.lock b/Cargo.lock
index 467b225..7985c9c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,6 +19,21 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "actix-cors"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331"
+dependencies = [
+ "actix-utils",
+ "actix-web",
+ "derive_more",
+ "futures-util",
+ "log",
+ "once_cell",
+ "smallvec",
+]
+
[[package]]
name = "actix-http"
version = "3.9.0"
@@ -65,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
dependencies = [
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -182,7 +197,7 @@ dependencies = [
"actix-router",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -254,9 +269,9 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.6.15"
+version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -269,43 +284,55 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.8"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
-version = "1.1.1"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.4"
+version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
- "windows-sys 0.52.0",
+ "once_cell",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "any_spawner"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41058deaa38c9d9dd933d6d238d825227cffa668e2839b52879f6619c63eee3b"
+dependencies = [
+ "futures",
+ "thiserror 2.0.11",
+ "wasm-bindgen-futures",
]
[[package]]
name = "anyhow"
-version = "1.0.93"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
+checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "anymap2"
@@ -314,44 +341,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]]
-name = "async-recursion"
-version = "1.1.1"
+name = "async-lock"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
+checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.87",
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
]
[[package]]
name = "attribute-derive"
-version = "0.9.2"
+version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f1ee502851995027b06f99f5ffbeffa1406b38d0b318a1ebfa469332c6cbafd"
+checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54"
dependencies = [
"attribute-derive-macro",
"derive-where",
"manyhow",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "attribute-derive-macro"
-version = "0.9.2"
+version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3601467f634cfe36c4780ca9c75dea9a5b34529c1f2810676a337e7e0997f954"
+checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b"
dependencies = [
"collection_literals",
"interpolator",
"manyhow",
- "proc-macro-utils 0.8.0",
+ "proc-macro-utils",
"proc-macro2",
"quote",
"quote-use",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -398,9 +425,9 @@ dependencies = [
[[package]]
name = "bitflags"
-version = "2.6.0"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "blind-rsa-signatures"
@@ -442,9 +469,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "4.0.1"
+version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
+checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -464,15 +491,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.7.2"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "bytestring"
-version = "1.3.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72"
+checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f"
dependencies = [
"bytes",
]
@@ -485,9 +512,9 @@ checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
[[package]]
name = "cc"
-version = "1.1.30"
+version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
+checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [
"jobserver",
"libc",
@@ -502,15 +529,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
-version = "0.1.1"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -521,38 +548,11 @@ dependencies = [
"windows-targets",
]
-[[package]]
-name = "ciborium"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
-dependencies = [
- "ciborium-io",
- "ciborium-ll",
- "serde",
-]
-
-[[package]]
-name = "ciborium-io"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
-
-[[package]]
-name = "ciborium-ll"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
-dependencies = [
- "ciborium-io",
- "half",
-]
-
[[package]]
name = "clap"
-version = "4.5.20"
+version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
+checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
dependencies = [
"clap_builder",
"clap_derive",
@@ -560,9 +560,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.20"
+version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
+checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [
"anstream",
"anstyle",
@@ -572,21 +572,21 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.18"
+version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "clap_lex"
-version = "0.7.2"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "client"
@@ -597,12 +597,15 @@ dependencies = [
"codee",
"console_error_panic_hook",
"crypto",
+ "futures",
+ "gloo-timers 0.3.0",
"leptos",
"leptos-use",
"protocol",
+ "reqwasm",
"serde",
"serde_json",
- "thiserror 2.0.1",
+ "thiserror 2.0.11",
"tracing",
"tracing-subscriber",
"tracing-subscriber-wasm",
@@ -626,7 +629,7 @@ checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445"
dependencies = [
"serde",
"serde_json",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
]
[[package]]
@@ -637,18 +640,26 @@ checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
[[package]]
name = "colorchoice"
-version = "1.0.2"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
[[package]]
name = "config"
-version = "0.14.0"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be"
+checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf"
dependencies = [
"convert_case 0.6.0",
- "lazy_static",
"nom",
"pathdiff",
"serde",
@@ -673,24 +684,30 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_format"
-version = "0.2.33"
+version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b"
+checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
-version = "0.2.33"
+version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1"
+checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
+[[package]]
+name = "const_str_slice_concat"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b"
+
[[package]]
name = "convert_case"
version = "0.4.0"
@@ -725,9 +742,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
-version = "0.2.14"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@@ -743,24 +760,18 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
-version = "0.5.13"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
+checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
-
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto"
@@ -772,7 +783,7 @@ dependencies = [
"ring",
"serde",
"serde_with",
- "thiserror 2.0.1",
+ "thiserror 2.0.11",
"wasm-bindgen-test",
]
@@ -807,7 +818,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -818,16 +829,17 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "dashmap"
-version = "5.5.3"
+version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
+ "crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
@@ -843,7 +855,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -886,7 +898,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -899,7 +911,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -917,6 +929,7 @@ dependencies = [
name = "digital-voting"
version = "0.1.0"
dependencies = [
+ "actix-cors",
"actix-web",
"anyhow",
"base64",
@@ -927,17 +940,27 @@ dependencies = [
"process_io",
"protocol",
"ring",
- "rustyline",
"serde",
+ "serde_json",
"serde_with",
- "thiserror 2.0.1",
+ "thiserror 2.0.11",
"tokio",
"tracing",
"tracing-actix-web",
- "tracing-appender",
"tracing-subscriber",
]
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
[[package]]
name = "drain_filter_polyfill"
version = "0.1.3"
@@ -950,11 +973,20 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+[[package]]
+name = "either_of"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2dc0006c5cf511f802ddcffc0a6df9dcc1912f5f0e448f6641b3b035f14f43d"
+dependencies = [
+ "pin-project-lite",
+]
+
[[package]]
name = "encoding_rs"
-version = "0.8.34"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
@@ -973,12 +1005,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
-version = "0.3.9"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -987,6 +1019,27 @@ version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
+[[package]]
+name = "event-listener"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
[[package]]
name = "fd-lock"
version = "4.0.2"
@@ -1000,9 +1053,9 @@ dependencies = [
[[package]]
name = "flate2"
-version = "1.0.34"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
+checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1063,6 +1116,7 @@ dependencies = [
"futures-core",
"futures-task",
"futures-util",
+ "num_cpus",
]
[[package]]
@@ -1079,7 +1133,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -1155,7 +1209,7 @@ dependencies = [
"gloo-net 0.3.1",
"gloo-render",
"gloo-storage",
- "gloo-timers",
+ "gloo-timers 0.2.6",
"gloo-utils 0.1.7",
"gloo-worker",
]
@@ -1214,10 +1268,30 @@ dependencies = [
"gloo-events",
"gloo-utils 0.1.7",
"serde",
- "serde-wasm-bindgen 0.5.0",
+ "serde-wasm-bindgen",
"serde_urlencoded",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils 0.1.7",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
"wasm-bindgen",
+ "wasm-bindgen-futures",
"web-sys",
]
@@ -1236,7 +1310,7 @@ dependencies = [
"pin-project",
"serde",
"serde_json",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@@ -1252,12 +1326,12 @@ dependencies = [
"futures-core",
"futures-sink",
"gloo-utils 0.2.0",
- "http 1.1.0",
+ "http 1.2.0",
"js-sys",
"pin-project",
"serde",
"serde_json",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@@ -1283,7 +1357,7 @@ dependencies = [
"js-sys",
"serde",
"serde_json",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
"wasm-bindgen",
"web-sys",
]
@@ -1298,6 +1372,18 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "gloo-timers"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "gloo-utils"
version = "0.1.7"
@@ -1341,6 +1427,12 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "guardian"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "493913a18c0d7bebb75127a26a432162c59edbe06f6cf712001e3e769345e8b5"
+
[[package]]
name = "h2"
version = "0.3.26"
@@ -1353,23 +1445,13 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
- "indexmap 2.6.0",
+ "indexmap 2.7.1",
"slab",
"tokio",
"tokio-util",
"tracing",
]
-[[package]]
-name = "half"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
-dependencies = [
- "cfg-if",
- "crunchy",
-]
-
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1384,9 +1466,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
-version = "0.15.0"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@@ -1408,29 +1490,29 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac-sha256"
-version = "1.1.7"
+version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735"
+checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb"
dependencies = [
"digest",
]
[[package]]
name = "hmac-sha512"
-version = "1.1.5"
+version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a"
+checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c"
dependencies = [
"digest",
]
[[package]]
name = "home"
-version = "0.5.9"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1455,9 +1537,9 @@ dependencies = [
[[package]]
name = "http"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [
"bytes",
"fnv",
@@ -1476,6 +1558,20 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+[[package]]
+name = "hydration_context"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d35485b3dcbf7e044b8f28c73f04f13e7b509c2466fd10cb2a8a447e38f8a93a"
+dependencies = [
+ "futures",
+ "once_cell",
+ "or_poisoned",
+ "pin-project-lite",
+ "serde",
+ "throw_error",
+]
+
[[package]]
name = "iana-time-zone"
version = "0.1.61"
@@ -1499,6 +1595,124 @@ dependencies = [
"cc",
]
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1507,19 +1721,30 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
-version = "0.5.0"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
- "unicode-bidi",
- "unicode-normalization",
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
]
[[package]]
name = "impl-more"
-version = "0.1.8"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0"
+checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
[[package]]
name = "indexmap"
@@ -1534,12 +1759,12 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.6.0"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
- "hashbrown 0.15.0",
+ "hashbrown 0.15.2",
"serde",
]
@@ -1549,12 +1774,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8"
-[[package]]
-name = "inventory"
-version = "0.3.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
-
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@@ -1563,18 +1782,18 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
-version = "1.0.11"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "jobserver"
@@ -1587,10 +1806,11 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.72"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
@@ -1611,17 +1831,34 @@ dependencies = [
[[package]]
name = "leptos"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cbb3237c274dadf00dcc27db96c52601b40375117178fb24a991cda073624f0"
+checksum = "21c31c9d022c77702c53e02830d08b28320aca9c0899a19c443096c114623fa5"
dependencies = [
+ "any_spawner",
"cfg-if",
+ "either_of",
+ "futures",
+ "getrandom",
+ "hydration_context",
"leptos_config",
"leptos_dom",
+ "leptos_hot_reload",
"leptos_macro",
- "leptos_reactive",
"leptos_server",
+ "oco_ref",
+ "or_poisoned",
+ "paste",
+ "reactive_graph",
+ "rustc-hash",
+ "send_wrapper",
+ "serde",
+ "serde_qs",
"server_fn",
+ "slotmap",
+ "tachys",
+ "thiserror 2.0.11",
+ "throw_error",
"tracing",
"typed-builder",
"typed-builder-macro",
@@ -1631,18 +1868,20 @@ dependencies = [
[[package]]
name = "leptos-use"
-version = "0.13.8"
+version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17a16cc63083779fe1081dd714b99a856e46d5ce427e7ce94300b0199a3ccb12"
+checksum = "e63ce77018c615075541d944d678853b5bb40c79510e39edf695cadb9b128e13"
dependencies = [
"cfg-if",
+ "chrono",
"codee",
"default-struct-builder",
"js-sys",
"lazy_static",
"leptos",
"paste",
- "thiserror 1.0.65",
+ "send_wrapper",
+ "thiserror 2.0.11",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@@ -1650,70 +1889,56 @@ dependencies = [
[[package]]
name = "leptos_config"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62ed778611380ddea47568ac6ad6ec5158d39b5bd59e6c4dcd24efc15dc3dc0d"
+checksum = "5d874993c7664d757677d056c8f46b5cb5365fe622005e1bf26050f4996e7e52"
dependencies = [
"config",
"regex",
"serde",
- "thiserror 1.0.65",
+ "thiserror 2.0.11",
"typed-builder",
]
[[package]]
name = "leptos_dom"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8401c46c86c1f4c16dcb7881ed319fcdca9cda9b9e78a6088955cb423afcf119"
+checksum = "a462aaeec85bc4ecfb26bf324437b92690bf3add1e30eb29b3acc08b20e8b4cb"
dependencies = [
- "async-recursion",
- "cfg-if",
- "drain_filter_polyfill",
- "futures",
- "getrandom",
- "html-escape",
- "indexmap 2.6.0",
- "itertools",
"js-sys",
- "leptos_reactive",
- "once_cell",
- "pad-adapter",
- "paste",
- "rustc-hash",
- "serde",
- "serde_json",
- "server_fn",
- "smallvec",
+ "or_poisoned",
+ "reactive_graph",
+ "send_wrapper",
+ "tachys",
"tracing",
"wasm-bindgen",
- "wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "leptos_hot_reload"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cb53d4794240b684a2f4be224b84bee9e62d2abc498cf2bcd643cd565e01d96"
+checksum = "07eb295ad2f3b2af190da62af339b84fd01ce3c71702f09eb69a57310fcf0c6d"
dependencies = [
"anyhow",
"camino",
- "indexmap 2.6.0",
+ "indexmap 2.7.1",
"parking_lot",
"proc-macro2",
"quote",
"rstml",
"serde",
- "syn 2.0.87",
+ "syn 2.0.96",
"walkdir",
]
[[package]]
name = "leptos_macro"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b13bc3db70715cd8218c4535a5af3ae3c0e5fea6f018531fc339377b36bc0e0"
+checksum = "90291b25ee576bc9c299d3371cc8f09bf60ea939a8de61fa8b744650aff76e24"
dependencies = [
"attribute-derive",
"cfg-if",
@@ -1727,71 +1952,61 @@ dependencies = [
"quote",
"rstml",
"server_fn_macro",
- "syn 2.0.87",
+ "syn 2.0.96",
"tracing",
"uuid",
]
[[package]]
-name = "leptos_reactive"
-version = "0.6.15"
+name = "leptos_server"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4161acbf80f59219d8d14182371f57302bc7ff81ee41aba8ba1ff7295727f23"
+checksum = "18caffe32c245ddb35697edd898ccb3393efce67672a707a14eebd0db2e8249a"
dependencies = [
+ "any_spawner",
"base64",
- "cfg-if",
+ "codee",
"futures",
- "indexmap 2.6.0",
- "js-sys",
- "oco_ref",
- "paste",
- "pin-project",
- "rustc-hash",
- "self_cell",
+ "hydration_context",
+ "or_poisoned",
+ "reactive_graph",
+ "send_wrapper",
"serde",
- "serde-wasm-bindgen 0.6.5",
"serde_json",
- "slotmap",
- "thiserror 1.0.65",
- "tracing",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
-[[package]]
-name = "leptos_server"
-version = "0.6.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a97eb90a13f71500b831c7119ddd3bdd0d7ae0a6b0487cade4fddeed3b8c03f"
-dependencies = [
- "inventory",
- "lazy_static",
- "leptos_macro",
- "leptos_reactive",
- "serde",
"server_fn",
- "thiserror 1.0.65",
+ "tachys",
"tracing",
]
[[package]]
name = "libc"
-version = "0.2.161"
+version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libm"
-version = "0.2.8"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+
+[[package]]
+name = "linear-map"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee"
[[package]]
name = "linux-raw-sys"
-version = "0.4.14"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "litemap"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "local-channel"
@@ -1822,29 +2037,29 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.22"
+version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "manyhow"
-version = "0.10.4"
+version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64"
+checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587"
dependencies = [
"manyhow-macros",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "manyhow-macros"
-version = "0.10.4"
+version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead"
+checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495"
dependencies = [
- "proc-macro-utils 0.8.0",
+ "proc-macro-utils",
"proc-macro2",
"quote",
]
@@ -1863,9 +2078,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minicov"
-version = "0.3.5"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169"
+checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
dependencies = [
"cc",
"walkdir",
@@ -1879,20 +2094,19 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.8.0"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
- "hermit-abi",
"libc",
"log",
"wasi",
@@ -1905,6 +2119,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577"
+[[package]]
+name = "next_tuple"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28"
+
[[package]]
name = "nibble_vec"
version = "0.1.0"
@@ -1916,9 +2136,9 @@ dependencies = [
[[package]]
name = "nix"
-version = "0.28.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
@@ -2000,23 +2220,33 @@ dependencies = [
"libm",
]
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
[[package]]
name = "object"
-version = "0.36.5"
+version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "oco_ref"
-version = "0.1.1"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708"
+checksum = "64b94982fe39a861561cf67ff17a7849f2cedadbbad960a797634032b7abb998"
dependencies = [
"serde",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
]
[[package]]
@@ -2025,6 +2255,12 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+[[package]]
+name = "or_poisoned"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd"
+
[[package]]
name = "overload"
version = "0.1.1"
@@ -2032,10 +2268,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
-name = "pad-adapter"
-version = "0.1.1"
+name = "parking"
+version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
@@ -2068,9 +2304,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "pem-rfc7468"
@@ -2089,29 +2325,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
-version = "1.1.6"
+version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec"
+checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.1.6"
+version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8"
+checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "pin-project-lite"
-version = "0.2.14"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@@ -2164,35 +2400,12 @@ dependencies = [
[[package]]
name = "prettyplease"
-version = "0.2.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
-dependencies = [
- "proc-macro2",
- "syn 2.0.87",
-]
-
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
dependencies = [
"proc-macro2",
- "quote",
- "version_check",
+ "syn 2.0.96",
]
[[package]]
@@ -2214,17 +2427,7 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
-]
-
-[[package]]
-name = "proc-macro-utils"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8"
-dependencies = [
- "proc-macro2",
- "quote",
- "smallvec",
+ "syn 2.0.96",
]
[[package]]
@@ -2240,9 +2443,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.88"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
@@ -2255,7 +2458,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
"version_check",
"yansi",
]
@@ -2266,7 +2469,10 @@ version = "0.1.0"
dependencies = [
"rustyline",
"shellwords",
- "thiserror 2.0.1",
+ "thiserror 2.0.11",
+ "tracing",
+ "tracing-appender",
+ "tracing-subscriber",
]
[[package]]
@@ -2277,15 +2483,15 @@ dependencies = [
"chrono",
"crypto",
"serde",
- "thiserror 2.0.1",
+ "thiserror 2.0.11",
"wasm-bindgen-test",
]
[[package]]
name = "quote"
-version = "1.0.37"
+version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@@ -2306,10 +2512,10 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35"
dependencies = [
- "proc-macro-utils 0.10.0",
+ "proc-macro-utils",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -2352,20 +2558,70 @@ dependencies = [
"getrandom",
]
+[[package]]
+name = "reactive_graph"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbf210c04505e128fb7f64acecc23c71f82f56c7d481b190e1010b7bada2cb9"
+dependencies = [
+ "any_spawner",
+ "async-lock",
+ "futures",
+ "guardian",
+ "hydration_context",
+ "or_poisoned",
+ "pin-project-lite",
+ "rustc-hash",
+ "send_wrapper",
+ "serde",
+ "slotmap",
+ "thiserror 2.0.11",
+ "tracing",
+ "web-sys",
+]
+
+[[package]]
+name = "reactive_stores"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80bb1913eeb71f74028213455ee971550c2b3cb91b6acd5efa8a0f8dc59f5039"
+dependencies = [
+ "guardian",
+ "itertools",
+ "or_poisoned",
+ "paste",
+ "reactive_graph",
+ "reactive_stores_macro",
+ "rustc-hash",
+]
+
+[[package]]
+name = "reactive_stores_macro"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d86e4f08f361b05d11422398cef4bc4cf356f2fdd2f06a96646b0e9cd902226"
+dependencies = [
+ "convert_case 0.6.0",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
[[package]]
name = "redox_syscall"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
-version = "1.11.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@@ -2375,9 +2631,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.8"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@@ -2396,6 +2652,15 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+[[package]]
+name = "reqwasm"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb"
+dependencies = [
+ "gloo-net 0.1.0",
+]
+
[[package]]
name = "ring"
version = "0.17.8"
@@ -2434,16 +2699,17 @@ dependencies = [
[[package]]
name = "rstml"
-version = "0.11.2"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77"
+checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56"
dependencies = [
+ "derive-where",
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
"syn_derive",
- "thiserror 1.0.65",
+ "thiserror 2.0.11",
]
[[package]]
@@ -2454,9 +2720,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
-version = "1.1.0"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "rustc_version"
@@ -2469,22 +2735,28 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.38.37"
+version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
+[[package]]
+name = "rustversion"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
+
[[package]]
name = "rustyline"
-version = "14.0.0"
+version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
+checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f"
dependencies = [
"bitflags",
"cfg-if",
@@ -2500,18 +2772,18 @@ dependencies = [
"unicode-segmentation",
"unicode-width",
"utf8parse",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "rustyline-derive"
-version = "0.10.0"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5af959c8bf6af1aff6d2b463a57f71aae53d1332da58419e30ad8dc7011d951"
+checksum = "327e9d075f6df7e25fbf594f1be7ef55cf0d567a6cb5112eeccbbd51ceb48e0d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -2529,29 +2801,17 @@ dependencies = [
"winapi-util",
]
-[[package]]
-name = "scoped-tls"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
-
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "self_cell"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
-
[[package]]
name = "semver"
-version = "1.0.23"
+version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]]
name = "send_wrapper"
@@ -2564,9 +2824,9 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.214"
+version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
@@ -2582,33 +2842,22 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "serde-wasm-bindgen"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
-dependencies = [
- "js-sys",
- "serde",
- "wasm-bindgen",
-]
-
[[package]]
name = "serde_derive"
-version = "1.0.214"
+version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "serde_json"
-version = "1.0.132"
+version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
+checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [
"itoa",
"memchr",
@@ -2618,13 +2867,13 @@ dependencies = [
[[package]]
name = "serde_qs"
-version = "0.12.0"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
+checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
dependencies = [
"percent-encoding",
"serde",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
]
[[package]]
@@ -2650,15 +2899,15 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.11.0"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
+checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
- "indexmap 2.6.0",
+ "indexmap 2.7.1",
"serde",
"serde_derive",
"serde_json",
@@ -2668,37 +2917,38 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.11.0"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
+checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "server_fn"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe"
+checksum = "f5dd7fcccd3ef2081da086c1f8595b506627abbbbc9f64be0141d2251219570e"
dependencies = [
"bytes",
- "ciborium",
"const_format",
"dashmap",
"futures",
"gloo-net 0.6.0",
- "http 1.1.0",
+ "http 1.2.0",
"js-sys",
"once_cell",
+ "pin-project-lite",
"send_wrapper",
"serde",
"serde_json",
"serde_qs",
"server_fn_macro_default",
- "thiserror 1.0.65",
+ "thiserror 2.0.11",
+ "throw_error",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -2709,26 +2959,26 @@ dependencies = [
[[package]]
name = "server_fn_macro"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1"
+checksum = "e0bbac4f01a714b0490247ac625bdb7055548210556c39e8f56a2dbbe3abc70b"
dependencies = [
"const_format",
"convert_case 0.6.0",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
"xxhash-rust",
]
[[package]]
name = "server_fn_macro_default"
-version = "0.6.15"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440"
+checksum = "f07dfd1744a5f5612f00f69fe035b0bfafdf12bb46d76e785673078a9e56b170"
dependencies = [
"server_fn_macro",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -2801,7 +3051,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
dependencies = [
- "serde",
"version_check",
]
@@ -2813,9 +3062,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys 0.52.0",
@@ -2837,6 +3086,12 @@ dependencies = [
"der",
]
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -2862,9 +3117,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.87"
+version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
@@ -2873,54 +3128,98 @@ dependencies = [
[[package]]
name = "syn_derive"
-version = "0.1.8"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219"
+dependencies = [
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
- "proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "tachys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d777e4426a597296b020edcb5c3d8f25a3ccd8adfd22eb5154ac81da946aef9f"
+dependencies = [
+ "any_spawner",
+ "const_str_slice_concat",
+ "drain_filter_polyfill",
+ "either_of",
+ "futures",
+ "html-escape",
+ "indexmap 2.7.1",
+ "itertools",
+ "js-sys",
+ "linear-map",
+ "next_tuple",
+ "oco_ref",
+ "once_cell",
+ "or_poisoned",
+ "parking_lot",
+ "paste",
+ "reactive_graph",
+ "reactive_stores",
+ "rustc-hash",
+ "send_wrapper",
+ "slotmap",
+ "throw_error",
+ "tracing",
+ "wasm-bindgen",
+ "web-sys",
]
[[package]]
name = "thiserror"
-version = "1.0.65"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "thiserror-impl 1.0.65",
+ "thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
-version = "2.0.1"
+version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07c1e40dd48a282ae8edc36c732cbc219144b87fb6a4c7316d611c6b1f06ec0c"
+checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
- "thiserror-impl 2.0.1",
+ "thiserror-impl 2.0.11",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.65"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.1"
+version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "874aa7e446f1da8d9c3a5c95b1c5eb41d800045252121dc7f8e0ba370cee55f5"
+checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -2933,11 +3232,20 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "throw_error"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ef8bf264c6ae02a065a4a16553283f0656bd6266fc1fcb09fd2e6b5e91427b"
+dependencies = [
+ "pin-project-lite",
+]
+
[[package]]
name = "time"
-version = "0.3.36"
+version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@@ -2956,34 +3264,29 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
-version = "0.2.18"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
-name = "tinyvec"
-version = "1.8.0"
+name = "tinystr"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
- "tinyvec_macros",
+ "displaydoc",
+ "zerovec",
]
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-
[[package]]
name = "tokio"
-version = "1.41.1"
+version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
+checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes",
@@ -2999,20 +3302,20 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "tokio-util"
-version = "0.7.12"
+version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
@@ -3048,7 +3351,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
- "indexmap 2.6.0",
+ "indexmap 2.7.1",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3057,9 +3360,9 @@ dependencies = [
[[package]]
name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
@@ -3069,9 +3372,9 @@ dependencies = [
[[package]]
name = "tracing-actix-web"
-version = "0.7.14"
+version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b87073920bcce23e9f5cb0d2671e9f01d6803bb5229c159b2f5ce6806d73ffc"
+checksum = "54a9f5c1aca50ebebf074ee665b9f99f2e84906dcf6b993a0d0090edb835166d"
dependencies = [
"actix-web",
"mutually_exclusive_features",
@@ -3087,27 +3390,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
- "thiserror 1.0.65",
+ "thiserror 1.0.69",
"time",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
@@ -3126,9 +3429,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"nu-ansi-term",
"sharded-slab",
@@ -3151,22 +3454,22 @@ dependencies = [
[[package]]
name = "typed-builder"
-version = "0.18.2"
+version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add"
+checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75"
dependencies = [
"typed-builder-macro",
]
[[package]]
name = "typed-builder-macro"
-version = "0.18.2"
+version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63"
+checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
@@ -3175,26 +3478,11 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
-[[package]]
-name = "unicode-bidi"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
-
[[package]]
name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
-
-[[package]]
-name = "unicode-normalization"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
-dependencies = [
- "tinyvec",
-]
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-segmentation"
@@ -3204,9 +3492,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
-version = "0.1.14"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "unicode-xid"
@@ -3222,21 +3510,33 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.5.2"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
[[package]]
name = "utf8-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -3245,18 +3545,18 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.11.0"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
+checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom",
]
[[package]]
name = "valuable"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version_check"
@@ -3282,47 +3582,50 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
+ "rustversion",
+ "serde",
+ "serde_json",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.45"
+version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
+ "once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3330,33 +3633,34 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
name = "wasm-bindgen-test"
-version = "0.3.45"
+version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426"
+checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
dependencies = [
- "console_error_panic_hook",
"js-sys",
"minicov",
- "scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
@@ -3364,20 +3668,20 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
-version = "0.3.45"
+version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0"
+checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
]
[[package]]
name = "wasm-streams"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
@@ -3388,9 +3692,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.70"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3520,18 +3824,30 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
-version = "0.6.20"
+version = "0.6.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
dependencies = [
"memchr",
]
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
[[package]]
name = "xxhash-rust"
-version = "0.8.12"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984"
+checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
[[package]]
name = "yansi"
@@ -3539,6 +3855,30 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "synstructure",
+]
+
[[package]]
name = "zerocopy"
version = "0.7.35"
@@ -3557,7 +3897,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.87",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "synstructure",
]
[[package]]
@@ -3566,6 +3927,28 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
[[package]]
name = "zstd"
version = "0.13.2"
diff --git a/Cargo.toml b/Cargo.toml
index 35cfaae..aa58609 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,9 +8,15 @@ A system for both private and transparent electronic elections
publish = false
license = "Apache-2.0"
repository = "https://github.com/ThrasherLT/digital-voting"
-default-run = "digital-voting"
+default-run = "node"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[[bin]]
+name = "node"
+path = "src/main.rs"
+
+[[bin]]
+name = "authority"
+path = "src/bin/mock-authority/main.rs"
[workspace]
resolver = "2"
@@ -19,29 +25,28 @@ members = [
]
[workspace.dependencies]
-thiserror = "2.0.1"
-chrono = { version = "0.4.38", features = ["serde"] }
-serde = { version = "1.0.214", features = ["derive"] }
-tracing = "0.1.40"
+thiserror = "2.0.11"
+chrono = { version = "0.4.39", features = ["serde"] }
+serde = { version = "1.0.217", features = ["derive"] }
+tracing = "0.1.41"
ring = "0.17.8"
base64 = "0.22.1"
-serde_with = { version = "3.11.0", features = ["base64"] }
-tracing-subscriber = "0.3.18"
-wasm-bindgen-test = "0.3.45"
+serde_with = { version = "3.12.0", features = ["base64"] }
+tracing-subscriber = "0.3.19"
+wasm-bindgen-test = "0.3.50"
bincode = "1.3.3"
+serde_json = "1.0.137"
crypto = { version = "*", path = "./subcrates/crypto" }
protocol = { version = "*", path = "./subcrates/protocol" }
process_io = { version = "*", path = "./subcrates/process_io" }
[dependencies]
-anyhow = "1.0.93"
+anyhow = "1.0.95"
actix-web = "4.9.0"
-tokio = { version = "1.41.1", features = ["full", "test-util"] }
-tracing-appender = "0.2.3"
-tracing-actix-web = "0.7.14"
-clap = { version = "4.5.20", features = ["derive"] }
-rustyline = { version = "14.0.0", features = ["derive"] }
+tokio = { version = "1.43.0", features = ["full", "test-util"] }
+tracing-actix-web = "0.7.15"
+clap = { version = "4.5.27", features = ["derive"] }
ring.workspace = true
chrono.workspace = true
@@ -52,10 +57,12 @@ serde_with.workspace = true
base64.workspace = true
tracing-subscriber.workspace = true
bincode.workspace = true
+serde_json.workspace = true
crypto.workspace = true
protocol.workspace = true
process_io.workspace = true
+actix-cors = "0.7.0"
[profile.release]
lto = true
diff --git a/deny.toml b/deny.toml
index e67a64a..98b0771 100644
--- a/deny.toml
+++ b/deny.toml
@@ -71,7 +71,6 @@ db-urls = ["https://github.com/rustsec/advisory-db"]
# output a note when they are encountered.
# TODO make sure these are figured out before any serious release:
ignore = [
- { id = "RUSTSEC-2024-0370", reason = "`leptos` depends on `proc-macro-error`, so will have to wait for `leptos` to be updated" },
{ id = "RUSTSEC-2023-0071", reason = "The Marvin Attack only affects deterministicly padded RSA, while this crate uses PSS under the hood" },
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
@@ -95,13 +94,13 @@ allow = [
"MIT",
"Apache-2.0",
"ISC",
- "Unicode-DFS-2016",
"OpenSSL",
"Apache-2.0 WITH LLVM-exception",
"BSD-3-Clause",
"BSD-2-Clause",
"BSL-1.0",
"Zlib",
+ "Unicode-3.0"
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
diff --git a/src/api/server_cli.rs b/src/api/cli.rs
similarity index 56%
rename from src/api/server_cli.rs
rename to src/api/cli.rs
index 49fc341..0a724d3 100644
--- a/src/api/server_cli.rs
+++ b/src/api/cli.rs
@@ -3,22 +3,40 @@
// TODO daemonize at least on Unix systems.
+use std::path::PathBuf;
+
use clap::Parser;
-use crypto::signature::blind_sign;
+
+// TODO make sure paths exist and won't cause an error
/// Command line arguments for the node.
/// All the stuff required to start the node.
#[derive(Parser, Clone, Debug)]
pub struct Args {
/// The address the node will listen on.
- #[clap(short = 'a', long = "address", default_value = "127.0.0.1:8080")]
+ #[clap(
+ long = "address",
+ default_value = "0.0.0.0:8080",
+ help = "The socket address on which the http server of the node will be hosted"
+ )]
pub socket_addr: std::net::SocketAddr,
- /// The public key of the election authority used to verify that the voters are eligible.
- #[clap(short = 'p', long = "authority-public-key")]
- pub authority_pk: blind_sign::PublicKey,
+ /// Path to where the log, config and etc files are stored.
+ #[clap(
+ long = "data-path",
+ default_value = "./data",
+ help = "Path to where log, config and similar files will be stored"
+ )]
+ pub data_path: PathBuf,
+ /// Don't start a CLI interface for this node.
+ #[clap(
+ long = "no-cli",
+ default_value_t = false,
+ help = "Only run HTTP server and do not start a CLI interface"
+ )]
+ pub no_cli: bool,
/// The command to execute. See `Cmd` for more details.
#[clap(subcommand)]
- pub cmd: Cmd,
+ pub cmd: Option,
}
/// The command that the node should execute on startup.
diff --git a/src/api/config.rs b/src/api/config.rs
new file mode 100644
index 0000000..7baff94
--- /dev/null
+++ b/src/api/config.rs
@@ -0,0 +1,14 @@
+use std::path::Path;
+
+use anyhow::Result;
+use protocol::config::BlockchainConfig;
+use tokio::fs;
+
+pub async fn load_from_file(path: &Path) -> Result {
+ if let Some(parent) = path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ let file_content = fs::read_to_string(path).await?;
+
+ Ok(serde_json::from_str(&file_content)?)
+}
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 19a99c8..3662a7e 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -1,2 +1,3 @@
+pub mod cli;
+pub mod config;
pub mod server;
-pub mod server_cli;
diff --git a/src/api/server.rs b/src/api/server.rs
index 7fdc31f..8fe3ba6 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -1,46 +1,85 @@
use std::net::SocketAddr;
-use actix_web::{post, routes, web, App, HttpServer, Responder};
+use actix_cors::Cors;
+use actix_web::{get, post, routes, web, App, HttpResponse, HttpServer, Responder};
+use anyhow::{bail, Result};
+use tokio::{select, sync::oneshot, task::JoinHandle};
use tracing::info;
use tracing_actix_web::TracingLogger;
-use thiserror::Error;
-
use protocol::vote::Vote;
-#[derive(Error, Debug)]
-pub enum Error {
- #[error("Actix error: {0}")]
- ActixError(#[from] std::io::Error),
-}
-type Result = std::result::Result;
-
-pub async fn run(addr: SocketAddr) -> Result<()> {
- println!("starting HTTP server at http://localhost:8080");
-
- HttpServer::new(|| {
- App::new()
- // enable logger
- .wrap(TracingLogger::default())
- .service(greet)
- .service(vote)
- })
- .bind(addr)?
- .run()
- .await?;
-
- Ok(())
+use crate::state::State;
+
+pub type Handle = (oneshot::Sender<()>, JoinHandle>);
+
+pub fn run(state: State, addr: SocketAddr) -> Result {
+ let (tx, rx) = oneshot::channel::<()>();
+ let state = web::Data::new(state);
+ println!("starting HTTP server at {addr}");
+
+ let handle = tokio::spawn(async move {
+ let server = HttpServer::new(move || {
+ App::new()
+ // Actix web takes an app state factory here and uses an Arc internally.
+ // It will error in runtime, if state is passed inside an Arc.
+ // Also this closure is called once for every worker, meaning that, if you
+ // pass State::new(), it'll create a new instance for each worker.
+ // https://actix.rs/docs/application#shared-mutable-state
+ .app_data(state.clone())
+ // Enable logger
+ .wrap(TracingLogger::default())
+ .wrap(
+ Cors::default()
+ // TODO Probably should be more specific, even though it's a browser extension:
+ .allow_any_origin()
+ .allow_any_header()
+ .allowed_methods(vec!["GET", "POST"])
+ .max_age(3600),
+ )
+ .service(greet)
+ .service(vote)
+ .service(config)
+ .service(health)
+ })
+ .bind(addr)?;
+
+ select! {
+ _ = rx => {
+ Ok(())
+ },
+ _ = server.run() => {
+ bail!("Server stopped")
+ }
+ }
+ });
+
+ Ok((tx, handle))
}
#[routes]
#[get("/")]
#[get("/index.html")]
async fn greet() -> impl Responder {
- "Hello! Please send a POST request to /vote with a JSON body, containing a public key, a vote, a timestamp, and a signature.\n"
+ HttpResponse::Ok().body("Welcome to the digital voting blockchain! (WIP).\n")
}
#[post("/vote")]
pub async fn vote(vote: web::Json) -> impl Responder {
info!("POST: /vote {vote:?}");
- vote
+ HttpResponse::Ok()
+}
+
+#[get("/config")]
+pub async fn config(state: web::Data) -> impl Responder {
+ if let Ok(json) = serde_json::to_string(state.get_blockchain_config()) {
+ HttpResponse::Ok().body(json)
+ } else {
+ HttpResponse::InternalServerError().body("Failed to get config")
+ }
+}
+
+#[get("/health")]
+pub async fn health() -> impl Responder {
+ HttpResponse::Ok()
}
diff --git a/src/batcher/mod.rs b/src/batcher/mod.rs
index 0648038..9c0d59d 100644
--- a/src/batcher/mod.rs
+++ b/src/batcher/mod.rs
@@ -10,8 +10,6 @@ use tokio::sync::{
Notify,
};
-use protocol::timestamp::Timestamp;
-
/// The Batcher instance itself holding the context for batching and the batched items.
/// The intended way to add items to the batcher is through the Sender returned by the new function.
/// This is done so that a single Batcher instance shouldn't be shared between multiple threads.
@@ -24,7 +22,7 @@ pub struct Batcher {
/// full batch had not yet been accumulated.
batch_time_interval: Duration,
/// Variable to track when the next batch should be returned.
- next_batch_time: Timestamp,
+ next_batch_time: chrono::DateTime,
/// Notify instance to notify the batcher that the batch is full and ready to be
/// returned without waiting for the `batch_time_interval`.
batch_ready_notify: Arc,
diff --git a/src/bin/mock-authority/main.rs b/src/bin/mock-authority/main.rs
new file mode 100644
index 0000000..3e70acd
--- /dev/null
+++ b/src/bin/mock-authority/main.rs
@@ -0,0 +1,277 @@
+//! This is a mock of the election authorities servers which will be responsible for ensuring the
+//! eligibility of the voters by signing their public keys. This is only used for testing purposes.
+
+use std::{
+ io::Write,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+
+use actix_web::{get, post, routes, web, App, HttpResponse, HttpServer, Responder};
+use anyhow::{anyhow, bail, Result};
+use clap::Parser;
+use serde::{self, Deserialize, Serialize};
+
+use crypto::signature::blind_sign;
+use process_io::{cli::StdioReader, logging::start_logger};
+use tokio::{select, sync::oneshot, task::JoinHandle};
+use tracing::trace;
+
+#[derive(Parser, Clone, Debug)]
+pub struct Args {
+ #[clap(
+ long = "address",
+ default_value = "0.0.0.0:8080",
+ help = "Specify the ip:port on which to host the mock election authority HTTP server"
+ )]
+ pub addr: std::net::SocketAddr,
+ #[clap(
+ long = "new-keys",
+ default_value_t = false,
+ help = "Generate new blind signer keys instead of loading them from FS"
+ )]
+ pub new_keys: bool,
+ #[clap(
+ long = "no-http",
+ default_value_t = false,
+ help = "Only run CLI and do not start an http server"
+ )]
+ pub no_http_server: bool,
+ #[clap(
+ long = "no-cli",
+ default_value_t = false,
+ help = "Only run HTTP server and do not start a CLI interface"
+ )]
+ pub no_cli: bool,
+ #[clap(
+ long = "data-path",
+ default_value = "./data",
+ help = "Path to where log, config and similar files will be stored"
+ )]
+ pub data_path: PathBuf,
+}
+
+#[derive(Parser, Clone, Debug)]
+pub enum Cmd {
+ #[clap(about = "Blind sign a blinded message")]
+ BlindSign {
+ blinded_msg: blind_sign::BlindedMessage,
+ },
+ #[clap(about = "Get blinder public key")]
+ GetPubkey,
+ #[clap(about = "Shut down the mock authority")]
+ Quit,
+}
+
+#[derive(Debug, serde::Serialize, serde::Deserialize)]
+struct AuthorityConfig {
+ pk: blind_sign::PublicKey,
+ sk: blind_sign::SecretKey,
+}
+
+impl AuthorityConfig {
+ fn load_from_fs(path: &Path) -> Result {
+ Ok(serde_json::from_slice(&std::fs::read(path)?)?)
+ }
+
+ fn save_to_fs(&self, path: &Path) -> Result<()> {
+ std::fs::OpenOptions::new()
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(path)?
+ .write_all(&serde_json::to_vec(self)?)?;
+
+ Ok(())
+ }
+}
+
+struct AppState {
+ blind_signer: Arc,
+}
+
+fn new_blind_signer(path: &Path) -> Result {
+ let blind_signer = blind_sign::BlindSigner::new()?;
+ AuthorityConfig {
+ pk: blind_signer.get_public_key()?,
+ sk: blind_signer.get_secret_key()?,
+ }
+ .save_to_fs(path)?;
+
+ Ok(blind_signer)
+}
+
+fn setup_blind_signer(
+ new_keys: bool,
+ authority_config_path: &Path,
+) -> Result {
+ if let Some(parent) = authority_config_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ if new_keys {
+ if let Err(err) = std::fs::remove_file(authority_config_path) {
+ // It's not an error if the file actually doesn't exist, since we're deleting it anyway.
+ if err.kind() != std::io::ErrorKind::NotFound {
+ anyhow::bail!("Failed to delete old blind signer cfg file: {}", err);
+ }
+ }
+ }
+
+ match load_blind_signer_from_fs(authority_config_path) {
+ Ok(blind_signer) => Ok(blind_signer),
+ Err(_) => Ok(new_blind_signer(authority_config_path)?),
+ }
+}
+
+fn load_blind_signer_from_fs(path: &Path) -> Result {
+ let config = AuthorityConfig::load_from_fs(path)?;
+ Ok(blind_sign::BlindSigner::new_from_keys(
+ config.pk, config.sk,
+ )?)
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let args = Args::parse();
+ let _tracing_worker_guard = start_logger(&args.data_path.join("authority.log"))?;
+ let blind_signer = Arc::new(setup_blind_signer(
+ args.new_keys,
+ &args.data_path.join("authority-config.json"),
+ )?);
+
+ match (args.no_cli, args.no_http_server) {
+ (true, true) => bail!("Authority needs at least CLI interface or HTTP server to run"),
+ (true, false) => {
+ let (_stop_server, handle) = run_server(blind_signer, args.addr);
+ handle.await??;
+ }
+ (false, true) => run_cli(
+ &blind_signer,
+ args.data_path.join("authority-cmd-history.txt"),
+ )?,
+ (false, false) => {
+ let _server_shutdown = run_server(blind_signer.clone(), args.addr);
+ run_cli(
+ &blind_signer,
+ args.data_path.join("authority-cmd-history.txt"),
+ )?;
+ }
+ }
+ Ok(())
+}
+
+fn run_cli(blind_signer: &blind_sign::BlindSigner, cmd_history_path: PathBuf) -> Result<()> {
+ let mut stdio_reader = StdioReader::new(cmd_history_path)?;
+
+ loop {
+ let line = match stdio_reader.read_stdio_blocking() {
+ Ok(line) => line,
+ Err(e) => {
+ println!("Quitting: {e:?}");
+ break;
+ }
+ };
+ let res = match Cmd::try_parse_from(line) {
+ Ok(Cmd::BlindSign { blinded_msg }) => blind_signer
+ .bling_sign(&blinded_msg)
+ .map_err(std::convert::Into::into)
+ .map(|blinded_signature| blinded_signature.to_string()),
+ Ok(Cmd::GetPubkey) => blind_signer
+ .get_public_key()
+ .map_err(std::convert::Into::into)
+ .map(|blinder_pk| blinder_pk.to_string()),
+ Ok(Cmd::Quit) => break,
+ Err(e) => Err(anyhow!("Unsupported command: {e}")),
+ };
+
+ match res {
+ Ok(res) => println!("{res}"),
+ Err(error) => println!("ERROR: {error}"),
+ }
+ }
+
+ Ok(())
+}
+
+type Handle = (oneshot::Sender<()>, JoinHandle>);
+
+fn run_server(blind_signer: Arc, addr: std::net::SocketAddr) -> Handle {
+ let (tx, rx) = oneshot::channel::<()>();
+
+ let handle = tokio::spawn(async move {
+ let server = HttpServer::new(move || {
+ App::new()
+ .app_data(web::Data::new(AppState {
+ blind_signer: blind_signer.clone(),
+ }))
+ .wrap(
+ actix_cors::Cors::default()
+ // TODO Probably should be more specific:
+ .allow_any_origin()
+ .allow_any_header()
+ .allowed_methods(vec!["GET", "POST"])
+ .max_age(3600),
+ )
+ .service(greet)
+ .service(authenticate)
+ .service(get_pkey)
+ .service(health)
+ })
+ .bind(addr)?;
+ trace!("Starting server");
+
+ select! {
+ _ = rx => {
+ Ok(())
+ },
+ _ = server.run() => {
+ bail!("Server stopped")
+ }
+ }
+ });
+
+ (tx, handle)
+}
+
+#[routes]
+#[get("/")]
+#[get("/index.html")]
+async fn greet() -> impl Responder {
+ trace!("GET root request");
+ HttpResponse::Ok().body("Hello, mock authority!\n")
+}
+
+#[post("/authenticate")]
+pub async fn authenticate(
+ verification_request: web::Json,
+ data: web::Data,
+) -> impl Responder {
+ trace!("POST /authenticate request");
+ match data
+ .blind_signer
+ .bling_sign(&verification_request.blinded_pkey)
+ {
+ Ok(blind_signature) => HttpResponse::Ok().json(blind_signature),
+ Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")),
+ }
+}
+
+#[get("/pkey")]
+pub async fn get_pkey(data: web::Data) -> impl Responder {
+ trace!("POST /pkey request");
+ match data.blind_signer.get_public_key() {
+ Ok(pkey) => HttpResponse::Ok().json(pkey.to_string()),
+ Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")),
+ }
+}
+
+#[get("/health")]
+pub async fn health() -> impl Responder {
+ trace!("GET /health request");
+ HttpResponse::Ok()
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct VerificationRequest {
+ blinded_pkey: blind_sign::BlindedMessage,
+}
diff --git a/src/bin/mock_authority.rs b/src/bin/mock_authority.rs
deleted file mode 100644
index 047f95b..0000000
--- a/src/bin/mock_authority.rs
+++ /dev/null
@@ -1,208 +0,0 @@
-//! This is a mock of the election authorities servers which will be responsible for ensuring the
-//! eligibility of the voters by signing their public keys. This is only used for testing purposes.
-
-use std::{io::Write, sync::Arc};
-
-use actix_web::{get, post, routes, web, App, HttpResponse, HttpServer, Responder};
-use anyhow::{anyhow, Result};
-use clap::Parser;
-use serde::{self, Deserialize, Serialize};
-
-use crypto::signature::blind_sign;
-use digital_voting::logging::start_logger;
-use process_io::cli::StdioReader;
-
-#[derive(Parser, Clone, Debug)]
-pub struct Args {
- #[clap(
- short = 'a',
- long = "address",
- default_value = "127.0.0.1:8081",
- help = "Specify the ip:port on which to host the mock election authority HTTP server"
- )]
- pub addr: std::net::SocketAddr,
- #[clap(
- short = 'k',
- long = "new-keys",
- default_value_t = false,
- help = "Generate new blind signer keys instead of loading them from FS"
- )]
- pub new_keys: bool,
- #[clap(
- short = 'n',
- long = "no-http",
- default_value_t = false,
- help = "Only run CLI and do not start an http server"
- )]
- pub no_http_server: bool,
-}
-
-#[derive(Parser, Clone, Debug)]
-pub enum Cmd {
- #[clap(about = "Blind sign a blinded message")]
- BlindSign {
- blinded_msg: blind_sign::BlindedMessage,
- },
- #[clap(about = "Get blinder public key")]
- GetPubkey,
-}
-
-struct AppState {
- blind_signer: Arc,
-}
-
-fn new_blind_signer(path: &str) -> Result {
- let blind_signer = blind_sign::BlindSigner::new()?;
- let mut blind_signer_cfg_file = std::fs::OpenOptions::new()
- .write(true)
- .create(true)
- .truncate(true)
- .open(path)?;
-
- writeln!(blind_signer_cfg_file, "{}", blind_signer.get_public_key()?)?;
- writeln!(blind_signer_cfg_file, "{}", blind_signer.get_secret_key()?)?;
-
- Ok(blind_signer)
-}
-
-fn setup_blind_signer(arg_new_keys: bool) -> Result {
- let blind_signer_cfg_path = "authority-blind-signer-cfg";
-
- if arg_new_keys {
- if let Err(err) = std::fs::remove_file(blind_signer_cfg_path) {
- // It's not an error if the file actually doesn't exist, since we're deleting it anyway.
- if err.kind() != std::io::ErrorKind::NotFound {
- anyhow::bail!("Failed to delete old blind signer cfg file: {}", err);
- }
- }
- }
-
- match load_blind_signer_from_fs(blind_signer_cfg_path) {
- Ok(blind_signer) => Ok(blind_signer),
- Err(_) => Ok(new_blind_signer(blind_signer_cfg_path)?),
- }
-}
-
-fn load_blind_signer_from_fs(path: &str) -> Result {
- if std::path::Path::new(path).exists() {
- let blind_signer_cfg = std::fs::read_to_string(path)?;
- let mut blind_signer_cfg = blind_signer_cfg.lines().take(2);
- let (pk, sk) = (
- blind_signer_cfg
- .next()
- .ok_or(anyhow!("Failed to parse blind signer public key"))?
- .parse()?,
- blind_signer_cfg
- .next()
- .ok_or(anyhow!("Failed to parse blind signer secret key"))?
- .parse()?,
- );
- Ok(blind_sign::BlindSigner::new_from_keys(pk, sk)?)
- } else {
- Err(anyhow!("Blind signer config not found"))
- }
-}
-
-#[tokio::main]
-async fn main() -> Result<()> {
- let _tracing_worker_guard = start_logger("mock_authority.log")?;
- let args = Args::parse();
- let blind_signer = Arc::new(setup_blind_signer(args.new_keys)?);
-
- println!("Starting mock authority server on: {}...", args.addr);
- println!("With authority PK:\n{}", blind_signer.get_public_key()?);
- if args.no_http_server {
- run_cli(&blind_signer)?;
- } else {
- let blind_signer_clone = blind_signer.clone();
- tokio::task::spawn_blocking(move || run_cli(&blind_signer_clone));
-
- run_server(blind_signer, args).await?;
- }
-
- Ok(())
-}
-
-fn run_cli(blind_signer: &blind_sign::BlindSigner) -> Result<()> {
- let mut stdio_reader = StdioReader::new()?;
-
- loop {
- let line = match stdio_reader.read_stdio_blocking() {
- Ok(line) => line,
- Err(e) => {
- // TODO
- println!("Quitting: {e:?}, send interrupt again to kill the server (WIP)");
- break;
- }
- };
- let res = match Cmd::try_parse_from(line) {
- Ok(Cmd::BlindSign { blinded_msg }) => blind_signer
- .bling_sign(&blinded_msg)
- .map_err(std::convert::Into::into)
- .map(|blinded_signature| blinded_signature.to_string()),
- Ok(Cmd::GetPubkey) => blind_signer
- .get_public_key()
- .map_err(std::convert::Into::into)
- .map(|blinder_pk| blinder_pk.to_string()),
- Err(e) => Err(anyhow!("Unsupported command: {e}")),
- };
-
- match res {
- Ok(res) => println!("{res}"),
- Err(error) => println!("ERROR: {error}"),
- }
- }
-
- Ok(())
-}
-
-async fn run_server(blind_signer: Arc, args: Args) -> Result<()> {
- HttpServer::new(move || {
- App::new()
- .app_data(web::Data::new(AppState {
- blind_signer: blind_signer.clone(),
- }))
- .service(greet)
- .service(authenticate)
- .service(get_pkey)
- })
- .bind(args.addr)?
- .run()
- .await?;
-
- Ok(())
-}
-
-#[routes]
-#[get("/")]
-#[get("/index.html")]
-async fn greet() -> impl Responder {
- "Hello! Please send a POST request to /authenticate with a JSON body, containing a public key, a vote, some mock authentication data, and a signature.\n"
-}
-
-#[post("/authenticate")]
-pub async fn authenticate(
- verification_request: web::Json,
- data: web::Data,
-) -> impl Responder {
- match data
- .blind_signer
- .bling_sign(&verification_request.blinded_pkey)
- {
- Ok(blind_signature) => HttpResponse::Ok().json(blind_signature),
- Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")),
- }
-}
-
-#[get("/pkey")]
-pub async fn get_pkey(data: web::Data) -> impl Responder {
- match data.blind_signer.get_public_key() {
- Ok(pkey) => HttpResponse::Ok().json(pkey.to_string()),
- Err(e) => HttpResponse::InternalServerError().body(format!("Error: {e}")),
- }
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-struct VerificationRequest {
- blinded_pkey: blind_sign::BlindedMessage,
-}
diff --git a/src/lib.rs b/src/lib.rs
index 468278d..99f2957 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,13 +6,13 @@ use std::{collections::HashMap, fmt::Display};
use thiserror::Error;
pub mod api;
+pub mod state;
pub mod batcher;
-pub mod logging;
mod blockchain;
use blockchain::{BlockValue, Blockchain, Error as BlockchainError};
-use protocol::{candidate_id::CandidateId, vote::Vote};
+use protocol::{config::CandidateId, vote::Vote};
#[derive(Error, Debug)]
pub enum Error {
@@ -57,7 +57,7 @@ impl VotingSystem {
self.blockchain.iter().for_each(|values| {
for vote in values {
- let count = tally.entry(vote.get_candidate().clone()).or_insert(0);
+ let count = tally.entry(*vote.get_candidate()).or_insert(0);
*count += 1;
}
});
diff --git a/src/main.rs b/src/main.rs
index b8aa47a..d72f982 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,31 +1,46 @@
-use anyhow::Result;
+use std::{path::PathBuf, str::FromStr};
+
+use anyhow::{anyhow, Result};
use clap::Parser;
-use digital_voting::{api::server_cli::Args, logging::start_logger};
-use process_io::cli::StdioReader;
+use digital_voting::{
+ api::{cli::Args, config},
+ state::State,
+};
+use process_io::{cli::StdioReader, logging::start_logger};
+use tokio::task::JoinHandle;
+use tracing::trace;
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
- println!("Args: {args:?}");
- let _tracing_worker_guard = start_logger("digital_voting.log")?;
-
- tokio::task::spawn_blocking(|| {
- let mut stdio_reader = StdioReader::new().unwrap();
- loop {
- let line = match stdio_reader.read_stdio_blocking() {
- Ok(line) => line,
- Err(e) => {
- // TODO
- println!("Quitting: {e:?}, send interrupt again to kill the server (WIP)");
- break;
- }
- };
- println!("Read line: {line:?}");
- }
- });
+ let _tracing_worker_guard = start_logger(&args.data_path.join("node.log"))?;
+ let blockchain_config = config::load_from_file(&args.data_path.join("blockchain-config.json"))
+ .await
+ .map_err(|e| anyhow!("Failed to load blockchain config: {e}"))?;
+ let state = State::new(blockchain_config);
+ trace!("Config loaded");
- digital_voting::api::server::run(args.socket_addr).await?;
+ let (stop_server, server_handle) = digital_voting::api::server::run(state, args.socket_addr)?;
+ if !args.no_cli {
+ let _cli_handle: JoinHandle> = tokio::task::spawn_blocking(|| {
+ let mut stdio_reader = StdioReader::new(PathBuf::from_str("node-cmd-history.txt")?)?;
+ loop {
+ let line = match stdio_reader.read_stdio_blocking() {
+ Ok(line) => line,
+ Err(e) => {
+ println!("Quitting: {e:?}");
+ if let Err(()) = stop_server.send(()) {
+ println!("Server already down");
+ }
+ break Ok(());
+ }
+ };
+ println!("Read line: {line:?}");
+ }
+ });
+ }
+ server_handle.await??;
Ok(())
}
diff --git a/src/state.rs b/src/state.rs
new file mode 100644
index 0000000..046bc8e
--- /dev/null
+++ b/src/state.rs
@@ -0,0 +1,17 @@
+use protocol::config::BlockchainConfig;
+
+pub struct State {
+ blockchain_config: BlockchainConfig,
+}
+
+impl State {
+ #[must_use]
+ pub fn new(blockchain_config: BlockchainConfig) -> Self {
+ Self { blockchain_config }
+ }
+
+ #[must_use]
+ pub fn get_blockchain_config(&self) -> &BlockchainConfig {
+ &self.blockchain_config
+ }
+}
diff --git a/subcrates/client/Cargo.toml b/subcrates/client/Cargo.toml
index fcb0f7a..591331d 100644
--- a/subcrates/client/Cargo.toml
+++ b/subcrates/client/Cargo.toml
@@ -10,20 +10,22 @@ license = "Apache-2.0"
repository = "https://github.com/ThrasherLT/digital-voting"
[dependencies]
-leptos = { version = "0.6.15", features = ["csr", "tracing"] }
+leptos = { version = "0.7.4", features = ["csr", "tracing"] }
console_error_panic_hook = "0.1.7"
-leptos-use = { version = "0.13.8", default-features = false, features = ["use_clipboard", "storage"] }
+leptos-use = { version = "0.15.5", default-features = false, features = ["use_clipboard", "storage"] }
tracing-subscriber-wasm = "0.1.0"
-serde_json = "1.0.132"
codee = { version = "0.2.0", features = ["json_serde"] }
anyhow = "1.0.91"
-# reqwasm = "0.5.0"
+reqwasm = "0.5.0"
+futures = "0.3.31"
+gloo-timers = { version = "0.3.0", features = ["futures"] }
thiserror.workspace = true
serde.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
chrono.workspace = true
+serde_json.workspace = true
crypto.workspace = true
protocol.workspace = true
diff --git a/subcrates/client/src/authentication.rs b/subcrates/client/src/authentication.rs
index a6f3929..359b743 100644
--- a/subcrates/client/src/authentication.rs
+++ b/subcrates/client/src/authentication.rs
@@ -3,35 +3,38 @@
// TODO Docummentation
// TODO local storage only works on pages that have the same origin
-use crate::state::State;
+use crate::states::user::User;
use leptos::{
- component, create_node_ref, create_signal, event_target_checked, expect_context, view,
- IntoView, NodeRef, Show, SignalGet, SignalSet,
+ component,
+ prelude::{
+ event_target_checked, signal, ClassAttribute, ElementChild, Get, NodeRef, NodeRefAttribute,
+ OnAttribute, Read, RwSignal, Set, Show,
+ },
+ view, IntoView,
};
#[component]
-pub fn User() -> impl IntoView {
+pub fn Authentication(user_state: RwSignal>) -> impl IntoView {
view! {
-
-
+
+
}
}
#[component]
-fn Login() -> impl IntoView {
- let mut state = expect_context::();
- let (get_error, set_error) = create_signal(None);
+fn Login(user_state: RwSignal>) -> impl IntoView {
+ let (get_error, set_error) = signal(None);
- let (password_visible, set_password_visible) = create_signal(false);
+ let (password_visible, set_password_visible) = signal(false);
let get_password_visibility = move || {
- if password_visible.get() {
+ if *password_visible.read() {
"text"
} else {
"password"
}
};
- let username_ref: NodeRef = create_node_ref();
- let password_ref: NodeRef = create_node_ref();
+ let username_ref: NodeRef = NodeRef::new();
+ let password_ref: NodeRef = NodeRef::new();
let on_submit = move |ev: leptos::ev::SubmitEvent| {
ev.prevent_default();
let username = username_ref
@@ -42,9 +45,9 @@ fn Login() -> impl IntoView {
.get()
.expect("Password should be mounted")
.value();
- if let Err(e) = state.login_user(&username, &password) {
- // TODO Better error reporting:
- set_error.set(Some(format!("Error occured: {e}")));
+ match User::login(username, &password) {
+ Ok(user) => user_state.set(Some(user)),
+ Err(e) => set_error.set(Some(format!("Error occured: {e}"))),
}
};
@@ -78,27 +81,26 @@ fn Login() -> impl IntoView {
Login
-
+
{get_error.get().expect("Error to be some")}
}
}
#[component]
-fn Register() -> impl IntoView {
- let (get_error, set_error) = create_signal(None);
- let (password_visible, set_password_visible) = create_signal(false);
+fn Register(user_state: RwSignal>) -> impl IntoView {
+ let (get_error, set_error) = signal(None);
+ let (password_visible, set_password_visible) = signal(false);
let get_password_visibility = move || {
- if password_visible.get() {
+ if *password_visible.read() {
"text"
} else {
"password"
}
};
- let username_ref: NodeRef = create_node_ref();
- let password_ref: NodeRef = create_node_ref();
- let repeat_password_ref: NodeRef = create_node_ref();
- let mut state = expect_context::();
+ let username_ref: NodeRef = NodeRef::new();
+ let password_ref: NodeRef = NodeRef::new();
+ let repeat_password_ref: NodeRef = NodeRef::new();
let on_submit = move |ev: leptos::ev::SubmitEvent| {
ev.prevent_default();
let username = username_ref
@@ -121,9 +123,9 @@ fn Register() -> impl IntoView {
set_error.set(Some("Username and password cannot be empty".to_owned()));
return;
}
- if let Err(e) = state.register_user(&username, &password) {
- // TODO Better error reporting:
- set_error.set(Some(format!("Error occured: {e}")));
+ match User::register(username, &password) {
+ Ok(user) => user_state.set(Some(user)),
+ Err(e) => set_error.set(Some(format!("Error occured: {e}"))),
}
};
@@ -163,7 +165,7 @@ fn Register() -> impl IntoView {
Register
-
+
{get_error.get().expect("Error to be some")}
}
diff --git a/subcrates/client/src/blockchain_selection.rs b/subcrates/client/src/blockchain_selection.rs
new file mode 100644
index 0000000..28a8344
--- /dev/null
+++ b/subcrates/client/src/blockchain_selection.rs
@@ -0,0 +1,148 @@
+use std::time::Duration;
+
+use anyhow::{anyhow, bail, Result};
+use leptos::{
+ component,
+ prelude::{
+ signal, ClassAttribute, ElementChild, For, Get, NodeRef, NodeRefAttribute, OnAttribute,
+ Read, RwSignal, Set, Show, Signal, Update, WriteSignal,
+ },
+ task::spawn_local,
+ view, IntoView,
+};
+use protocol::config::BlockchainConfig;
+
+use crate::{fetch, states::user::User};
+
+fn new_blockchain(
+ new_blockchain_addr: String,
+ set_user: RwSignal>,
+ blockchain_config: BlockchainConfig,
+) -> Result<()> {
+ let mut res = Ok(());
+
+ set_user.update(|user| {
+ if let Some(user) = user {
+ if let Err(e) = user.add_blockchain(new_blockchain_addr.clone(), blockchain_config) {
+ res = Err(anyhow!(format!("Error fetching blockchain configs: {e}")));
+ }
+ } else {
+ res = Err(anyhow!("Internal user error"));
+ }
+ });
+
+ res
+}
+
+fn delete_blockchain(blockchain_addr: &str, set_user: &mut Option) -> Result<()> {
+ let Some(user) = set_user else {
+ bail!("Internal user error");
+ };
+ user.remove_blockchain(blockchain_addr)
+}
+
+#[component]
+pub fn SelectBlockchain(
+ user: RwSignal>,
+ set_blockchain: WriteSignal,
+) -> impl IntoView {
+ view! {
+ "Blockchain Node Selection"
+
+
+ }
+}
+
+#[component]
+fn NewBlockchain(user: RwSignal>) -> impl IntoView {
+ let (get_error, set_error) = signal(Option::::None);
+ let new_blockchain_addr_ref: NodeRef = NodeRef::new();
+
+ let on_submit = move |ev: leptos::ev::SubmitEvent| {
+ ev.prevent_default();
+ let new_blockchain_addr = new_blockchain_addr_ref
+ .get()
+ .expect("New blockchain input should be mounted")
+ .value();
+
+ spawn_local(async move {
+ match fetch::blockchain_config(new_blockchain_addr.clone(), Duration::from_secs(5))
+ .await
+ {
+ Ok(blockchain_config) => {
+ if let Err(e) = new_blockchain(new_blockchain_addr, user, blockchain_config) {
+ set_error.set(Some(format!("Error setting up new blockchain: {e}")))
+ } else {
+ set_error.set(None);
+ }
+ }
+ Err(e) => set_error.set(Some(format!("Error fetching blockchain configs: {e}"))),
+ };
+ })
+ };
+
+ view! {
+
+
+
+ {get_error.get().expect("Error to be some")}
+
+ }
+}
+
+#[component]
+fn BlockchainList(
+ user: RwSignal>,
+ set_blockchain: WriteSignal,
+) -> impl IntoView {
+ let (get_error, set_error) = signal(None);
+
+ let blockchain_list = Signal::derive(move || {
+ let blockchain_list: Vec> = user
+ .read()
+ .as_ref()
+ .map(|user| user.blockchains.clone())
+ .unwrap_or_default()
+ .iter()
+ .map(|blockchain| RwSignal::new(blockchain.clone()))
+ .collect();
+ blockchain_list
+ });
+
+ view! {
+
+
+
+ // TODO create separate component for buttons with confirmation prompt.
+ {child}
+ "Delete"
+
+
+
+
+
+ {get_error.get().expect("Error to be some")}
+
+ }
+}
diff --git a/subcrates/client/src/candidate_selection.rs b/subcrates/client/src/candidate_selection.rs
new file mode 100644
index 0000000..cdcc990
--- /dev/null
+++ b/subcrates/client/src/candidate_selection.rs
@@ -0,0 +1,125 @@
+use std::{str::FromStr, time::Duration};
+
+use anyhow::{bail, Result};
+use leptos::{
+ component,
+ prelude::{
+ event_target_value, signal, ClassAttribute, CollectView, ElementChild, Get, OnAttribute,
+ Read, ReadSignal, Set, Show, Signal, WriteSignal,
+ },
+ task::spawn_local,
+ view, IntoView,
+};
+use protocol::{config::CandidateId, vote::Vote};
+
+use crate::{
+ fetch,
+ states::{
+ access_tokens::AccessTokens, candidate::Candidate, config::Config, signature::Signature,
+ user::User,
+ },
+};
+
+fn send_vote(
+ user: Signal,
+ blockchain: ReadSignal,
+ candidate_id: ReadSignal>,
+ access_tokens: ReadSignal,
+ set_candidate: WriteSignal>,
+ set_error: WriteSignal >,
+) -> Result<()> {
+ let Some(candidate_id) = candidate_id.get() else {
+ bail!("Candidate ID is not selected");
+ };
+
+ let vote = Vote::new(
+ &Signature::load(&user.read(), &blockchain.read())?.signer,
+ candidate_id,
+ chrono::Utc::now(),
+ access_tokens.read().prepare()?,
+ )?;
+
+ // For some reason `leptos` throws a "time not implemented on this platform" error,
+ // if we read from any signal within the `spawn_local` future.
+ let addr = blockchain.get().to_string();
+ let user = user.read();
+
+ spawn_local(async move {
+ if let Err(e) = fetch::submit_vote(addr.clone(), Duration::from_secs(5), vote).await {
+ set_error.set(Some(format!("Failed to submit vote: {e}")));
+ } else {
+ match Candidate::choose(candidate_id, &user, &addr) {
+ Ok(candidate) => set_candidate.set(Some(candidate)),
+ Err(e) => set_error.set(Some(format!("Failed to save candidate: {e}"))),
+ }
+ }
+ });
+
+ Ok(())
+}
+
+#[component]
+pub fn CandidateSelection(
+ user: Signal,
+ blockchain: ReadSignal,
+ set_candidate: WriteSignal>,
+ access_tokens: ReadSignal,
+) -> impl IntoView {
+ let (get_error, set_error) = signal(None);
+ let (selected, set_selected) = signal(None);
+
+ let config =
+ Config::load(&user.read(), &blockchain.read()).expect("Config to exist at this point");
+ let candidates = config
+ .blockchain_config
+ .candidates
+ .into_iter()
+ .map(|candidate| {
+ view! {
+
+ {candidate.name}
+ set_selected.set(Some(candidate_id)),
+ Err(e) => {
+ set_error.set(Some(format!("Candidate selection failed: {e}")))
+ }
+ }
+ }
+ />
+
+ }
+ })
+ .collect_view();
+
+ view! {
+ "Election Candidate Selection"
+
+ "Select the candidate for whom you wish to vote for:" {candidates}
+
+
+ "Confirm Candidate"
+
+
+ {move || get_error.get().expect("Error to be some")}
+
+ }
+}
diff --git a/subcrates/client/src/fetch.rs b/subcrates/client/src/fetch.rs
new file mode 100644
index 0000000..b9a69f8
--- /dev/null
+++ b/subcrates/client/src/fetch.rs
@@ -0,0 +1,68 @@
+use std::{pin::pin, time::Duration};
+
+use anyhow::{anyhow, bail, Result};
+use futures::future::{select, Either};
+use leptos::logging::log;
+use protocol::{config::BlockchainConfig, vote::Vote};
+use reqwasm::http::Response;
+
+pub async fn blockchain_config(addr: String, timeout: Duration) -> Result {
+ let addr = format!("{addr}/config");
+ let response = get(&addr, timeout).await?;
+ if response.status() != 200 {
+ bail!("Error code {}", response.status());
+ }
+ let config: BlockchainConfig = response.json().await?;
+
+ Ok(config)
+}
+
+pub async fn submit_vote(addr: String, timeout: Duration, vote: Vote) -> Result<()> {
+ let addr = format!("{addr}/vote");
+ let vote = serde_json::to_string(&vote)?;
+ log!("{}", vote);
+
+ let response = post(vote, &addr, timeout).await?;
+ if response.status() != 200 {
+ bail!("Error code {}", response.status());
+ }
+
+ Ok(())
+}
+
+async fn get(addr: &str, timeout: Duration) -> Result {
+ let fetch_future = pin!(async {
+ match reqwasm::http::Request::get(&addr).send().await {
+ Ok(response) => Ok(response),
+ Err(_) => Err(anyhow!("Request failed".to_string())),
+ }
+ });
+
+ let timeout_future = gloo_timers::future::TimeoutFuture::new(timeout.as_millis().try_into()?);
+
+ match select(fetch_future, timeout_future).await {
+ Either::Left((result, _)) => result,
+ Either::Right(_) => Err(anyhow!("Request timed out".to_string())),
+ }
+}
+
+async fn post(payload: String, addr: &str, timeout: Duration) -> Result {
+ let fetch_future = pin!(async {
+ match reqwasm::http::Request::post(&addr)
+ .header("Content-Type", "application/json")
+ .body(payload)
+ .send()
+ .await
+ {
+ Ok(response) => Ok(response),
+ Err(_) => Err(anyhow!("Request failed".to_string())),
+ }
+ });
+
+ let timeout_future = gloo_timers::future::TimeoutFuture::new(timeout.as_millis().try_into()?);
+
+ match select(fetch_future, timeout_future).await {
+ Either::Left((result, _)) => result,
+ Either::Right(_) => Err(anyhow!("Request timed out".to_string())),
+ }
+}
diff --git a/subcrates/client/src/main.rs b/subcrates/client/src/main.rs
index 1f47dcd..5b1b387 100644
--- a/subcrates/client/src/main.rs
+++ b/subcrates/client/src/main.rs
@@ -1,23 +1,23 @@
-//! This is the main entry point of the client for the whole blockchain.
-//! The client is implemented as a browser extension, but may be conveniently
-//! build into a website for quick development and testing with hot reloading.
-//! The browser extension had been set up to work with manifest version 3.
-
use leptos::{
- component, expect_context, mount_to_body, provide_context, view, IntoView, Show, Signal,
- SignalWith,
+ component,
+ mount::mount_to_body,
+ prelude::{ElementChild, Read, RwSignal, Show, Signal},
+ view, IntoView,
};
+use states::user::User;
mod authentication;
+mod blockchain_selection;
+mod candidate_selection;
+mod fetch;
mod settings;
-mod state;
+mod states;
mod storage;
mod utils;
mod validation;
+mod verification;
mod vote;
-use state::{State, Status};
-
// Configuration for wasm-bindgen-test to run tests in browser.
#[cfg(test)]
mod tests {
@@ -40,23 +40,36 @@ fn main() {
#[must_use]
#[component]
pub fn App() -> impl IntoView {
- provide_context(State::new());
- let state = expect_context::();
-
- let status = Signal::derive(move || state.get_status());
+ let blockchain = RwSignal::new(String::new());
+ let user_state = RwSignal::new(Option::::None);
+ let get_user = Signal::derive(move || {
+ user_state
+ .read_only()
+ .read()
+ .as_ref()
+ .expect("User to have existed by now")
+ .to_owned()
+ });
view! {
-
-
+ "Untitled Voting System"
+
+
- Status::LoggedOut }) fallback=|| ()>
-
+
+
-
-
+
+
-
-
+
+
}
}
diff --git a/subcrates/client/src/settings.rs b/subcrates/client/src/settings.rs
index cd343f6..74865ef 100644
--- a/subcrates/client/src/settings.rs
+++ b/subcrates/client/src/settings.rs
@@ -1,13 +1,15 @@
//! File containing code which handles the user settings for the browser extension.
-use crate::state::State;
+use crate::states::user::User;
use leptos::{
- component, create_signal, expect_context, view, IntoView, Show, SignalGet, SignalSet,
+ component,
+ prelude::{signal, ElementChild, Get, OnAttribute, RwSignal, Set, Show, Update},
+ view, IntoView,
};
#[component]
-pub fn SettingsPanel() -> impl IntoView {
- let (show_settings, set_show_settings) = create_signal(false);
+pub fn SettingsPanel(user: RwSignal>) -> impl IntoView {
+ let (show_settings, set_show_settings) = signal(false);
view! {
impl IntoView {
"Hide settings"
-
+
+
+
}
}
#[component]
-pub fn User() -> impl IntoView {
- let (double_check, set_double_check) = create_signal(false);
+fn User(user: RwSignal >) -> impl IntoView {
+ let (double_check, set_double_check) = signal(false);
view! {
();
- state.logout();
+ user.set(None);
}>"Logout"
impl IntoView {
}>"Delete User"
"Are you sure? This cannot be undone!"
();
- state.delete_user();
+ user.update(|user| {
+ let mut user = user.take();
+ user.map(|mut user| user.delete());
+ });
}>"Yes, I'm sure"
{
- $(
- let $var = $var.read_only();
- )*
- };
-}
-
-/// Status of the client.
-/// Used to select which part of the UI to show to the user.
-#[derive(PartialEq, PartialOrd)]
-pub enum Status {
- LoggedOut,
- LoggedIn,
- Validated,
- Voted,
-}
-
-// All members of the `State` struct must be reactive signals otherwise they
-// won't get loaded when Leptos retrieves them from context.
-/// The global state of the client.
-#[derive(Clone, Default)]
-pub struct State {
- username: RwSignal>,
- encryption: RwSignal >,
- signer: RwSignal >,
- authority_key: RwSignal >,
- blinded_pk: RwSignal >,
- unblinder: RwSignal >,
- access_token: RwSignal >,
- candidate: RwSignal >,
-}
-
-impl State {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn register_user(&mut self, username: &str, password: &str) -> Result<()> {
- if Storage::load(username).is_some() {
- bail!("User already exists")
- }
- let encryption = symmetric::Encryption::new(password.as_bytes())?;
- let signer = digital_sign::Signer::new()?;
-
- KeyStore {
- signer_sk: Some(signer.get_secret_key().to_owned()),
- authority_key: None,
- unblinding_secret: None,
- access_token: None,
- candidate: None,
- }
- .encrypt(&encryption)?
- .save(username);
-
- self.username.set(Some(username.to_owned()));
- self.encryption.set(Some(encryption));
- self.signer.set(Some(signer));
-
- Ok(())
- }
-
- pub fn login_user(&mut self, username: &str, password: &str) -> Result<()> {
- let storage = Storage::load(username).ok_or(anyhow!("User or password are incorrect"))?;
- let encryption = symmetric::Encryption::load(password.as_bytes(), storage.get_metadata())?;
- let key_store = storage.decrypt(&encryption)?;
- let signer = if let Some(signer_sk) = key_store.signer_sk {
- Some(digital_sign::Signer::from_secret_key(signer_sk)?)
- } else {
- None
- };
- let unblinder = if let (Some(authority_key), Some(unblinding_secret)) =
- (&key_store.authority_key, key_store.unblinding_secret)
- {
- Some(blind_sign::Unblinder::from_pk_and_secret(
- authority_key.to_owned(),
- unblinding_secret,
- )?)
- } else {
- None
- };
-
- self.username.set(Some(username.to_owned()));
- self.encryption.set(Some(encryption));
- self.signer.set(signer);
- self.authority_key.set(key_store.authority_key);
- // Upon logging in, it is assumed that the user will press the blinding button again to display the
- // blinded pk, if he needs it:
- self.blinded_pk.set(None);
- self.unblinder.set(unblinder);
- self.access_token.set(key_store.access_token);
- self.candidate.set(key_store.candidate);
-
- Ok(())
- }
-
- pub fn logout(&mut self) {
- self.username.set(None);
- self.encryption.set(None);
- self.signer.set(None);
- self.authority_key.set(None);
- self.blinded_pk.set(None);
- self.unblinder.set(None);
- self.access_token.set(None);
- self.candidate.set(None);
- }
-
- // TODO Ensure that keys cannot be read from garbage after user had logged out.
- pub fn delete_user(&mut self) {
- self.username.with(|username| {
- username.as_ref().map(|username| {
- Storage::delete(username);
- })
- });
-
- self.logout();
- }
-
- pub fn get_status(&self) -> Status {
- if self.candidate.with(Option::is_some) {
- Status::Voted
- } else if self.access_token.with(Option::is_some) {
- Status::Validated
- } else if self.signer.with(Option::is_some) {
- Status::LoggedIn
- } else {
- Status::LoggedOut
- }
- }
-
- pub fn blind(&mut self, authority_key: blind_sign::PublicKey) -> Result<()> {
- let signer_pub_key = self
- .signer
- .with(|signer| signer.as_ref().map(digital_sign::Signer::get_public_key))
- .ok_or(anyhow!("User is not logged in"))?;
- let blinder = blind_sign::Blinder::new(authority_key.clone())?;
- let (blinded_pk, unblinder) = blinder.blind(&signer_pub_key)?;
- self.authority_key.set(Some(authority_key));
- self.unblinder.set(Some(unblinder));
- self.blinded_pk.set(Some(blinded_pk));
- self.save()?;
-
- Ok(())
- }
-
- pub fn get_blinded_pk(&self) -> RwSignal > {
- self.blinded_pk
- }
-
- pub fn unblind(&mut self, blind_signature: blind_sign::BlindSignature) -> Result<()> {
- let Self {
- signer, unblinder, ..
- } = self;
- apply_read_only!(signer, unblinder);
- let access_token = with!(|signer, unblinder| {
- match (signer, unblinder) {
- (Some(signer), Some(unblinder)) => unblinder
- .unblind_signature(blind_signature, &signer.get_public_key())
- .map_err(std::convert::Into::into),
- _ => Err(anyhow!("State is corrupted")),
- }
- })?;
- self.access_token.set(Some(access_token));
- self.save()?;
-
- Ok(())
- }
-
- pub fn vote(&mut self, candidate: &str, _blockchain_addr: &str) -> Result<()> {
- let candidate = CandidateId::new(candidate.parse()?);
- let Self {
- signer,
- access_token,
- ..
- } = self;
- apply_read_only!(signer, access_token);
-
- let _vote = with!(|signer, access_token| {
- match (signer, access_token) {
- (Some(signer), Some(access_token)) => {
- Vote::new(signer, candidate.clone(), chrono::Utc::now(), access_token)
- .map_err(std::convert::Into::into)
- }
- _ => Err(anyhow!("State is corrupted")),
- }
- })?;
-
- self.candidate.set(Some(candidate.clone()));
- self.save()?;
-
- Ok(())
- }
-
- fn save(&self) -> Result<()> {
- let Self {
- username,
- encryption,
- signer,
- authority_key,
- unblinder,
- access_token,
- candidate,
- ..
- } = self;
- apply_read_only!(signer, authority_key, unblinder, access_token, candidate);
- let key_store = with!(
- |signer, authority_key, unblinder, access_token, candidate| {
- KeyStore {
- signer_sk: signer
- .as_ref()
- .map(|signer| signer.get_secret_key().clone()),
- authority_key: authority_key.clone(),
- unblinding_secret: unblinder
- .as_ref()
- .map(|unblinder| unblinder.get_unblinding_secret().clone()),
- access_token: access_token.clone(),
- candidate: candidate.clone(),
- }
- }
- );
- let storage = encryption
- .with(|encryption| {
- encryption
- .as_ref()
- .map(|encryption| key_store.encrypt(encryption))
- })
- .ok_or(anyhow!("User is not logged in"))??;
- username.with(|username| {
- if let Some(username) = username.as_ref() {
- storage.save(username)
- }
- });
-
- Ok(())
- }
-}
-
-#[cfg(target_arch = "wasm32")]
-#[cfg(test)]
-mod tests {
- use super::*;
- use leptos::SignalGet;
-
- fn logout_login(_state: State, username: &str, password: &str) -> State {
- let mut state = State::new();
- state.login_user(username, password).unwrap();
-
- state
- }
-
- #[wasm_bindgen_test::wasm_bindgen_test]
- #[test]
- // State contains Leptos signals which can only be used on WASM targets.
- fn test_state() {
- let username = "Admin";
- let password = "Password";
- let candidate = "5";
- let blockchain_addr = "www.blockchain.com";
-
- let authority_signer = blind_sign::BlindSigner::new().unwrap();
-
- let mut state = State::new();
- assert!(matches!(state.get_status(), Status::LoggedOut));
-
- state.register_user(username, password).unwrap();
- assert!(matches!(state.get_status(), Status::LoggedIn));
- let mut state = logout_login(state, username, password);
- assert!(matches!(state.get_status(), Status::LoggedIn));
-
- state
- .blind(authority_signer.get_public_key().unwrap())
- .unwrap();
- // User is expected to press blinding button upon login at this point, so no need to test relogin here.
-
- let blind_signature = authority_signer
- .bling_sign(&state.blinded_pk.get().unwrap())
- .unwrap();
-
- state.unblind(blind_signature).unwrap();
- let mut state = logout_login(state, username, password);
- assert!(matches!(state.get_status(), Status::Validated));
-
- state.vote(candidate, blockchain_addr).unwrap();
- assert!(matches!(state.get_status(), Status::Voted));
- }
-}
diff --git a/subcrates/client/src/states/access_tokens.rs b/subcrates/client/src/states/access_tokens.rs
new file mode 100644
index 0000000..992e4cd
--- /dev/null
+++ b/subcrates/client/src/states/access_tokens.rs
@@ -0,0 +1,78 @@
+use anyhow::{anyhow, bail, Result};
+use crypto::signature::blind_sign;
+
+use crate::storage::Storage;
+
+use super::user::User;
+
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct AccessTokens(Vec >);
+
+impl AccessTokens {
+ fn storage_key(username: &str, blockchain: &str) -> String {
+ format!("{}/{}/access_tokens", username, blockchain)
+ }
+
+ pub fn new(user: &User, blockchain: &str, count: usize) -> Result {
+ let access_tokens = AccessTokens(vec![None; count]);
+
+ Storage::encrypt(&user.encryption, &access_tokens)?
+ .save(&Self::storage_key(&user.username, blockchain));
+ Ok(access_tokens)
+ }
+
+ pub fn load(user: &User, blockchain: &str) -> Result {
+ let access_tokens_storage = Storage::load(&Self::storage_key(&user.username, blockchain))
+ .ok_or(anyhow!("User or password are incorrect"))?;
+ let access_tokens: Self = access_tokens_storage.decrypt(&user.encryption)?;
+
+ Ok(access_tokens)
+ }
+
+ pub fn set(
+ &mut self,
+ user: &User,
+ blockchain: &str,
+ index: usize,
+ access_token: Option,
+ ) -> Result<()> {
+ if self.0.len() <= index {
+ bail!("Access token index does not exist");
+ }
+ self.0[index] = access_token;
+ Storage::encrypt(&user.encryption, self)?
+ .save(&Self::storage_key(&user.username, blockchain));
+
+ Ok(())
+ }
+
+ pub fn get(&self, index: usize) -> Result> {
+ if self.0.len() <= index {
+ bail!("Access token index does not exist");
+ }
+
+ Ok(self.0[index].clone())
+ }
+
+ pub fn is_complete(&self) -> bool {
+ !self.0.is_empty() && !self.0.contains(&None)
+ }
+
+ pub fn prepare(&self) -> Result> {
+ let mut access_tokens = Vec::new();
+
+ for access_token in &self.0 {
+ access_tokens.push(
+ access_token
+ .clone()
+ .ok_or(anyhow!("An access token is missing"))?,
+ );
+ }
+
+ Ok(access_tokens)
+ }
+
+ pub fn delete(user: &User, blockchain: &str) {
+ Storage::delete(&Self::storage_key(&user.username, blockchain));
+ }
+}
diff --git a/subcrates/client/src/states/blockchain.rs b/subcrates/client/src/states/blockchain.rs
new file mode 100644
index 0000000..447d678
--- /dev/null
+++ b/subcrates/client/src/states/blockchain.rs
@@ -0,0 +1,33 @@
+use anyhow::Result;
+
+use protocol::config::BlockchainConfig;
+
+use super::{
+ access_tokens::AccessTokens, config::Config, signature::Signature, user::User,
+ validators::Validators,
+};
+
+pub fn delete_from_storage(blockchain_addr: &str, user: &mut User) {
+ Signature::delete(&user, blockchain_addr);
+ Config::delete(&user, blockchain_addr);
+ Validators::delete(&user, blockchain_addr);
+ AccessTokens::delete(&user, blockchain_addr);
+ // TODO Delete candidate too.
+}
+
+pub fn create_in_storage(
+ blockchain_addr: String,
+ user: &mut User,
+ blockchain_config: BlockchainConfig,
+) -> Result<()> {
+ let signature = Signature::new(&user, &blockchain_addr)?;
+ Validators::new(
+ &blockchain_config,
+ signature.signer.get_public_key(),
+ &user,
+ &blockchain_addr,
+ )?;
+ let _ = AccessTokens::new(&user, &blockchain_addr, blockchain_config.authorities.len())?;
+ Config::save(blockchain_config, &user, &blockchain_addr)
+ // TODO Add candidate too.
+}
diff --git a/subcrates/client/src/states/candidate.rs b/subcrates/client/src/states/candidate.rs
new file mode 100644
index 0000000..95155d5
--- /dev/null
+++ b/subcrates/client/src/states/candidate.rs
@@ -0,0 +1,37 @@
+use anyhow::Result;
+
+use protocol::config::CandidateId;
+
+use crate::{states::user::User, storage::Storage};
+
+// TODO Add timestamp to candidate.
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
+pub struct Candidate(pub CandidateId);
+
+impl Candidate {
+ fn storage_key(username: &str, blockchain: &str) -> String {
+ format!("{}/{}/candidate", username, blockchain)
+ }
+
+ pub fn choose(candidate: CandidateId, user: &User, blockchain: &str) -> Result {
+ let candidate = Candidate(candidate);
+
+ Storage::encrypt(&user.encryption, &candidate)?
+ .save(&Self::storage_key(&user.username, blockchain));
+ Ok(candidate)
+ }
+
+ pub fn load(user: &User, blockchain: &str) -> Result> {
+ match Storage::load(&Self::storage_key(&user.username, blockchain)) {
+ Some(candidate_storage) => {
+ let candidate: Self = candidate_storage.decrypt(&user.encryption)?;
+ Ok(Some(candidate))
+ }
+ None => Ok(None),
+ }
+ }
+
+ pub fn delete(user: &User, blockchain: &str) {
+ Storage::delete(&Self::storage_key(&user.username, blockchain));
+ }
+}
diff --git a/subcrates/client/src/states/config.rs b/subcrates/client/src/states/config.rs
new file mode 100644
index 0000000..5570f6b
--- /dev/null
+++ b/subcrates/client/src/states/config.rs
@@ -0,0 +1,44 @@
+use anyhow::{anyhow, Result};
+
+use protocol::config::BlockchainConfig;
+
+use crate::{states::user::User, storage::Storage};
+
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct Config {
+ pub blockchain_config: BlockchainConfig,
+}
+
+impl Config {
+ fn storage_key(username: &str, blockchain: &str) -> String {
+ format!("{}/{}/config", username, blockchain)
+ }
+
+ pub fn save(blockchain_config: BlockchainConfig, user: &User, blockchain: &str) -> Result<()> {
+ let config = Config { blockchain_config };
+
+ Storage::encrypt(&user.encryption, &config)?
+ .save(&Self::storage_key(&user.username, blockchain));
+ Ok(())
+ }
+
+ pub fn load(user: &User, blockchain: &str) -> Result {
+ let config_storage = Storage::load(&Self::storage_key(&user.username, blockchain))
+ .ok_or(anyhow!("Failed to load blockchain config"))?;
+ let config: Self = config_storage.decrypt(&user.encryption)?;
+
+ Ok(config)
+ }
+
+ pub fn get_authorities(&self) -> Vec {
+ self.blockchain_config
+ .authorities
+ .iter()
+ .map(|auth| auth.addr.clone())
+ .collect()
+ }
+
+ pub fn delete(user: &User, blockchain: &str) {
+ Storage::delete(&Self::storage_key(&user.username, blockchain));
+ }
+}
diff --git a/subcrates/client/src/states/mod.rs b/subcrates/client/src/states/mod.rs
new file mode 100644
index 0000000..21f6c73
--- /dev/null
+++ b/subcrates/client/src/states/mod.rs
@@ -0,0 +1,8 @@
+pub mod access_tokens;
+pub mod candidate;
+pub mod config;
+pub mod signature;
+pub mod user;
+pub mod validators;
+
+mod blockchain;
diff --git a/subcrates/client/src/states/signature.rs b/subcrates/client/src/states/signature.rs
new file mode 100644
index 0000000..f5f21cb
--- /dev/null
+++ b/subcrates/client/src/states/signature.rs
@@ -0,0 +1,59 @@
+use anyhow::{anyhow, Result};
+
+use crypto::signature::digital_sign;
+
+use crate::{states::user::User, storage::Storage};
+
+#[derive(serde::Serialize, serde::Deserialize)]
+pub struct Signature {
+ #[serde(with = "signer_serde")]
+ pub signer: digital_sign::Signer,
+}
+
+mod signer_serde {
+ use crypto::signature::digital_sign;
+ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
+
+ pub fn serialize(signer: &digital_sign::Signer, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ signer.get_secret_key().clone().serialize(serializer)
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ let signer_sk = digital_sign::SecretKey::deserialize(deserializer)?;
+ digital_sign::Signer::from_secret_key(signer_sk).map_err(de::Error::custom)
+ }
+}
+
+impl Signature {
+ fn storage_key(username: &str, blockchain: &str) -> String {
+ format!("{}/{}/signature", username, blockchain)
+ }
+
+ pub fn new(user: &User, blockchain: &str) -> Result {
+ let signer = digital_sign::Signer::new()?;
+
+ let signature = Signature { signer };
+ Storage::encrypt(&user.encryption, &signature)?
+ .save(&Self::storage_key(&user.username, blockchain));
+
+ Ok(signature)
+ }
+
+ pub fn load(user: &User, blockchain: &str) -> Result {
+ let signature_storage = Storage::load(&Self::storage_key(&user.username, blockchain))
+ .ok_or(anyhow!("User or password are incorrect"))?;
+ let signature: Self = signature_storage.decrypt(&user.encryption)?;
+
+ Ok(signature)
+ }
+
+ pub fn delete(user: &User, blockchain: &str) {
+ Storage::delete(&Self::storage_key(&user.username, blockchain));
+ }
+}
diff --git a/subcrates/client/src/states/user.rs b/subcrates/client/src/states/user.rs
new file mode 100644
index 0000000..990196e
--- /dev/null
+++ b/subcrates/client/src/states/user.rs
@@ -0,0 +1,118 @@
+use anyhow::{anyhow, bail, Result};
+
+use crypto::encryption::symmetric;
+use protocol::config::BlockchainConfig;
+
+use crate::storage::Storage;
+
+use super::blockchain;
+
+/// Storage for list of blockchains that the user had added to his profile.
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
+pub struct UserBlockchains {
+ pub blockchains: Vec,
+}
+
+/// State of the user related data.
+#[derive(Clone)]
+pub struct User {
+ pub username: String,
+ pub encryption: symmetric::Encryption,
+ pub blockchains: Vec,
+}
+
+impl User {
+ /// Login as an existing user in the browser's local storage.
+ pub fn login(username: String, password: &str) -> Result {
+ let user_blockchains_storage =
+ Storage::load(&username).ok_or(anyhow!("User or password are incorrect"))?;
+ let encryption = symmetric::Encryption::load(
+ password.as_bytes(),
+ user_blockchains_storage.get_metadata(),
+ )?;
+ let UserBlockchains { blockchains } = user_blockchains_storage.decrypt(&encryption)?;
+
+ Ok(Self {
+ username,
+ encryption,
+ blockchains,
+ })
+ }
+
+ /// Register a new user to the browser local storage.
+ pub fn register(username: String, password: &str) -> Result {
+ if Storage::load(&username).is_some() {
+ bail!("User already exists")
+ }
+
+ let encryption = symmetric::Encryption::new(password.as_bytes())?;
+ Storage::encrypt(
+ &encryption,
+ &UserBlockchains {
+ blockchains: Vec::new(),
+ },
+ )?
+ .save(&username);
+
+ Ok(Self {
+ username,
+ encryption,
+ blockchains: Vec::new(),
+ })
+ }
+
+ // TODO this leaks storage:
+ /// Remove the user from browser local storage.
+ pub fn delete(mut self) -> Result<()> {
+ for blockchain in std::mem::take(&mut self.blockchains) {
+ blockchain::delete_from_storage(&blockchain, &mut self);
+ }
+ Storage::delete(&self.username);
+
+ Ok(())
+ }
+
+ /// Add a blockchain address to the user.
+ pub fn add_blockchain(
+ &mut self,
+ blockchain: String,
+ blockchain_config: BlockchainConfig,
+ ) -> Result<()> {
+ if self.blockchains.contains(&blockchain) {
+ bail!("Blockchain already added");
+ }
+ self.blockchains.push(blockchain.clone());
+ Storage::encrypt(
+ &self.encryption,
+ &UserBlockchains {
+ blockchains: self.blockchains.clone(),
+ },
+ )?
+ .save(&self.username);
+
+ blockchain::create_in_storage(blockchain, self, blockchain_config)
+ }
+
+ /// Remove a blockchain address from the user.
+ pub fn remove_blockchain(&mut self, blockchain: &str) -> Result<()> {
+ if let Some(index) = self
+ .blockchains
+ .iter()
+ .position(|existing_blockchain| existing_blockchain == blockchain)
+ {
+ self.blockchains.remove(index);
+ Storage::encrypt(
+ &self.encryption,
+ &UserBlockchains {
+ blockchains: self.blockchains.clone(),
+ },
+ )?
+ .save(&self.username);
+ blockchain::delete_from_storage(blockchain, self);
+
+ Ok(())
+ } else {
+ bail!("User is not a member of this blockchain");
+ }
+ }
+}
diff --git a/subcrates/client/src/states/validators.rs b/subcrates/client/src/states/validators.rs
new file mode 100644
index 0000000..d5f68dd
--- /dev/null
+++ b/subcrates/client/src/states/validators.rs
@@ -0,0 +1,109 @@
+use anyhow::{anyhow, bail, Result};
+
+use crypto::signature::{blind_sign, digital_sign};
+use protocol::config::BlockchainConfig;
+
+use crate::{states::user::User, storage::Storage};
+
+use super::signature::Signature;
+
+pub struct Validators(Vec);
+
+struct Validator {
+ blinded_pk: blind_sign::BlindedMessage,
+ unblinder: blind_sign::Unblinder,
+}
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct ValidatorStorage {
+ blinded_pk: blind_sign::BlindedMessage,
+ unblinding_secret: blind_sign::UnblindingSecret,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Default)]
+struct ValidatorsStorage(Vec);
+
+impl Validators {
+ fn storage_key(username: &str, blockchain: &str) -> String {
+ format!("{}/{}/validators", username, blockchain)
+ }
+
+ pub fn new(
+ blockchain_config: &BlockchainConfig,
+ signer_pk: digital_sign::PublicKey,
+ user: &User,
+ blockchain: &str,
+ ) -> Result {
+ let mut validators = Self(Vec::new());
+ let mut validators_storage = ValidatorsStorage::default();
+
+ for auth in &blockchain_config.authorities {
+ let (blinded_pk, unblinder) =
+ blind_sign::Blinder::new(auth.authority_key.clone())?.blind(&signer_pk)?;
+ validators_storage.0.push(ValidatorStorage {
+ blinded_pk: blinded_pk.clone(),
+ unblinding_secret: unblinder.get_unblinding_secret(),
+ });
+ validators.0.push(Validator {
+ blinded_pk,
+ unblinder,
+ });
+ }
+ Storage::encrypt(&user.encryption, &validators_storage)?
+ .save(&Self::storage_key(&user.username, blockchain));
+
+ Ok(validators)
+ }
+
+ pub fn load(
+ blockchain_config: &BlockchainConfig,
+ user: &User,
+ blockchain: &str,
+ ) -> Result {
+ let validators_storage = Storage::load(&Self::storage_key(&user.username, blockchain))
+ .ok_or(anyhow!("User or password are incorrect"))?;
+ let validators_storage: ValidatorsStorage = validators_storage.decrypt(&user.encryption)?;
+
+ let mut validators = Validators(Vec::new());
+
+ for (i, validator_storage) in validators_storage.0.iter().enumerate() {
+ validators.0.push(Validator {
+ blinded_pk: validator_storage.blinded_pk.clone(),
+ unblinder: blind_sign::Unblinder::from_pk_and_secret(
+ blockchain_config.authorities[i].authority_key.clone(),
+ validator_storage.unblinding_secret.clone(),
+ )?,
+ });
+ }
+
+ Ok(validators)
+ }
+
+ pub fn validate(
+ &self,
+ signature: &Signature,
+ index: usize,
+ blind_signature: blind_sign::BlindSignature,
+ ) -> Result {
+ if self.0.len() <= index {
+ bail!("Validator index does not exist");
+ }
+
+ let access_token = self.0[index]
+ .unblinder
+ .unblind_signature(blind_signature.clone(), &signature.signer.get_public_key())?;
+
+ Ok(access_token)
+ }
+
+ pub fn get_blinded_pks(&self) -> Vec {
+ self.0
+ .iter()
+ .map(|validator| validator.blinded_pk.clone())
+ .collect()
+ }
+
+ pub fn delete(user: &User, blockchain: &str) {
+ Storage::delete(&Self::storage_key(&user.username, blockchain));
+ }
+}
diff --git a/subcrates/client/src/storage.rs b/subcrates/client/src/storage.rs
index 9457a9c..2582232 100644
--- a/subcrates/client/src/storage.rs
+++ b/subcrates/client/src/storage.rs
@@ -2,72 +2,78 @@
use anyhow::Result;
use codee::string::JsonSerdeCodec;
-use crypto::{
- encryption::symmetric,
- signature::{blind_sign, digital_sign},
+use crypto::encryption::symmetric;
+use leptos::{
+ logging::log,
+ prelude::{Get, Set},
};
-use leptos::{SignalGet, SignalSet};
use leptos_use::storage::use_local_storage;
-use protocol::candidate_id::CandidateId;
-// TODO Add documentation.
+// TODO Update codee version without breaking.
+// TODO Make sure in place encryption doesn't leak keys.
+// TODO Dynamically import this file so that `states` module could be used in a non-wasm environment.
-#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
-pub struct KeyStore {
- pub signer_sk: Option,
- pub authority_key: Option,
- pub unblinding_secret: Option,
- pub access_token: Option,
- pub candidate: Option,
+/// Encrypted storage containing metadata and all of the storage related operations.
+#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq)]
+pub struct Storage {
+ metadata: symmetric::MetaData,
+ encrypted_bytes: Vec,
}
-impl KeyStore {
- pub fn encrypt(self, encryption: &symmetric::Encryption) -> Result {
- let mut storage = serde_json::to_vec(&self)?;
+impl Storage {
+ /// Encrypt any serializable struct.
+ pub fn encrypt(encryption: &symmetric::Encryption, to_encrypt: &T) -> Result
+ where
+ T: serde::Serialize,
+ {
+ let mut storage = serde_json::to_vec(to_encrypt)?;
let metadata = encryption.encrypt(&mut storage)?;
- Ok(Storage {
+ Ok(Self {
metadata,
encrypted_bytes: storage,
})
}
-}
-#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq)]
-pub struct Storage {
- metadata: symmetric::MetaData,
- encrypted_bytes: Vec,
-}
+ /// Decrypt any deserializable owned struct.
+ pub fn decrypt(self, encryption: &symmetric::Encryption) -> Result
+ where
+ T: serde::de::DeserializeOwned,
+ {
+ let mut encrypted_bytes = self.encrypted_bytes.clone();
+ let decrypted_bytes = encryption.decrypt(&mut encrypted_bytes, &self.metadata)?;
+ let decrypted_value: T = serde_json::from_slice(decrypted_bytes)?;
-impl Storage {
+ Ok(decrypted_value)
+ }
+
+ /// Return metadata of the encrypted data.
pub fn get_metadata(&self) -> &symmetric::MetaData {
&self.metadata
}
- pub fn load(username: &str) -> Option {
- let (storage, _, _) = use_local_storage::, JsonSerdeCodec>(username);
+ /// Load encrypoted data from browser's local storage.
+ pub fn load(storage_key: &str) -> Option {
+ let (storage, _, _) = use_local_storage::, JsonSerdeCodec>(storage_key);
storage.get()
}
- pub fn save(self, username: &str) {
- let (_, set_storage, _) = use_local_storage:: , JsonSerdeCodec>(username);
+ /// Save encrypted data to browser's local storage.
+ pub fn save(self, storage_key: &str) {
+ let (_, set_storage, _) = use_local_storage:: , JsonSerdeCodec>(storage_key);
set_storage.set(Some(self));
}
- pub fn delete(username: &str) {
- let (_, _, clear) = use_local_storage:: , JsonSerdeCodec>(username);
+ /// Delete encrypted data from browser's local storage.
+ pub fn delete(storage_key: &str) {
+ let (_, _, clear) = use_local_storage:: , JsonSerdeCodec>(storage_key);
// TODO Make sure data doesn't stay in leftover garbage:
clear();
- }
-
- pub fn decrypt(self, encryption: &symmetric::Encryption) -> Result {
- // TODO This might be a major hazzard so should rethink the whole decryption in place thing:
- // Cloning to avoid decrypting in storage.
- let mut encrypted_bytes = self.encrypted_bytes.clone();
- let decrypted = encryption.decrypt(&mut encrypted_bytes, &self.metadata)?;
- let key_store: KeyStore = serde_json::from_slice(decrypted)?;
- Ok(key_store)
+ // TODO Figure out why this workaround is necessary:
+ if let Some(_) = Self::load(storage_key) {
+ log!("Known bug: local storage deletion failed!");
+ }
}
}
diff --git a/subcrates/client/src/utils.rs b/subcrates/client/src/utils.rs
index 682cfaf..a9eaed0 100644
--- a/subcrates/client/src/utils.rs
+++ b/subcrates/client/src/utils.rs
@@ -1,11 +1,15 @@
//! File containing common util code.
-use leptos::{component, view, IntoView, Show, Signal, SignalGet, SignalWith};
+use leptos::{
+ component,
+ prelude::{ElementChild, Get, OnAttribute, ReadSignal, Show, With},
+ view, IntoView,
+};
use leptos_use::{use_clipboard, UseClipboardReturn};
#[allow(non_snake_case)]
#[component]
-pub fn Copyable(value: Signal>) -> impl IntoView {
+pub fn Copyable(value: ReadSignal >) -> impl IntoView {
let UseClipboardReturn {
is_supported,
copied,
@@ -28,6 +32,3 @@ pub fn Copyable(value: Signal >) -> impl IntoView {
}
}
-
-// TODO leptos_use doesn't currently really support creating a paste button, but it shouldn't be too
-// complicated to either create a PR for it or create it here locally.
diff --git a/subcrates/client/src/validation.rs b/subcrates/client/src/validation.rs
index 8d72e46..ed0998d 100644
--- a/subcrates/client/src/validation.rs
+++ b/subcrates/client/src/validation.rs
@@ -1,124 +1,110 @@
-//! This file contains the logic for validating the voter's right to vote.
-
use std::str::FromStr;
use crypto::signature::blind_sign;
use leptos::{
- component, create_node_ref, create_signal, expect_context, html, view, IntoView, NodeRef, Show,
- Signal, SignalGet, SignalSet, SignalWith,
+ component,
+ html::{self, ElementChild},
+ prelude::{
+ signal, ClassAttribute, CollectView, Get, NodeRef, NodeRefAttribute, OnAttribute, Read,
+ ReadSignal, RwSignal, Set, Show, Signal, Update,
+ },
+ view, IntoView,
};
-use crate::state::State;
-use crate::utils;
-
-#[must_use]
-#[component]
-pub fn ValidateVoter() -> impl IntoView {
- let state = expect_context::();
-
- view! {
-
-
-
-
-
- }
-}
-
-#[must_use]
-#[component]
-pub fn AuthKeyInput() -> impl IntoView {
- let mut state = expect_context::();
- let (get_error, set_error) = create_signal(None);
-
- let authority_pk_ref: NodeRef = create_node_ref();
- let on_submit = move |ev: leptos::ev::SubmitEvent| {
- // stop the page from reloading:
- ev.prevent_default();
- let authority_pk = authority_pk_ref
- .get()
- .expect("Authority public key input should be mounted")
- .value();
-
- match blind_sign::PublicKey::from_str(&authority_pk) {
- Ok(authority_pk) => {
- if let Err(e) = state.blind(authority_pk) {
- // TODO better error reporting:
- set_error.set(Some(format!("Error had occured: {e}")));
- }
- }
- Err(e) => {
- set_error.set(Some(format!("Invalid election authority public key: {e}")));
- }
- }
- };
-
- view! {
-
-
- {get_error.get().expect("Error to be some")}
-
- }
-}
+use crate::{
+ states::{
+ access_tokens::AccessTokens, config::Config, signature::Signature, user::User,
+ validators::Validators,
+ },
+ utils,
+};
-#[must_use]
-#[component]
-pub fn BlindedPkDisplay() -> impl IntoView {
- let state = expect_context::();
- // TODO Check if nothing is overwriting this value, saw some flicker on the screen.
- let display_value =
- Signal::derive(move || state.get_blinded_pk().get().map(|val| val.to_string()));
+// TODO Figure out a clean way to handle errors and excepts.
+// TODO Blockchain validation must apply to all nodes of the same blockchain.
- view! { }
-}
-
-#[must_use]
#[component]
-pub fn UnblindAccessToken() -> impl IntoView {
- let mut state = expect_context::();
- let (get_error, set_error) = create_signal(None);
-
- let input_ref: NodeRef = create_node_ref();
- let on_submit = move |ev: leptos::ev::SubmitEvent| {
- // stop the page from reloading!
- ev.prevent_default();
-
- // here, we'll extract the value from the input
- let blind_signature = input_ref.get().expect("Input to be mounted").value();
-
- match blind_sign::BlindSignature::from_str(&blind_signature) {
- Ok(blind_signature) => {
- if let Err(e) = state.unblind(blind_signature) {
- // TODO better error reporting:
- set_error.set(Some(format!("Error had occured: {e}")));
+pub fn Validation(
+ user: Signal,
+ blockchain: ReadSignal,
+ access_tokens: RwSignal,
+) -> impl IntoView {
+ let config = Config::load(&user.read(), &blockchain.read()).expect("Config to be loaded");
+ let (signature, _) =
+ signal(Signature::load(&user.read(), &blockchain.read()).expect("Signature to be loaded"));
+
+ let (validators, _) = signal(
+ Validators::load(&config.blockchain_config, &user.read(), &blockchain.read())
+ .expect("Validators to be loaded"),
+ );
+
+ let entries = config
+ .get_authorities()
+ .into_iter()
+ .zip(validators.read().get_blinded_pks())
+ .enumerate()
+ .map(|(i, (authority, blinded_pk))| {
+ let (blinded_pk, _) = signal(Some(format!("{}", blinded_pk)));
+ let (get_error, set_error) = signal(None);
+ let blind_signature_ref: NodeRef = NodeRef::new();
+
+ let on_submit = move |ev: leptos::ev::SubmitEvent| {
+ // Stop the page from reloading:
+ ev.prevent_default();
+ let blind_signature = blind_signature_ref
+ .get()
+ .expect("Input to be mounted")
+ .value();
+
+ match blind_sign::BlindSignature::from_str(&blind_signature) {
+ Ok(blind_signature) => {
+ match validators
+ .read()
+ .validate(&signature.read(), i, blind_signature)
+ {
+ Ok(access_token) => access_tokens.update(|access_tokens| {
+ if let Err(e) = access_tokens.set(
+ &user.read(),
+ &blockchain.read(),
+ i,
+ Some(access_token),
+ ) {
+ set_error.set(Some(format!("Failed to save access token: {e}")))
+ }
+ }),
+ Err(e) => set_error.set(Some(format!(
+ "Invalid valid blind_signature in position {i}: {e}"
+ ))),
+ }
+ }
+ Err(e) => set_error.set(Some(format!(
+ "Not a valid blind_signature in position {i}: {e}"
+ ))),
}
+ };
+ let link = authority.clone();
+ view! {
+
+ {authority}
+
+ "Access token acquired!"
}
+ >
+
+
+
+ {move || get_error.get().expect("Error to be some")}
+
+
}
- Err(e) => {
- set_error.set(Some(format!("Invalid blinded authority signature: {e}")));
- }
- }
- };
+ })
+ .collect_view();
- view! {
-
-
- {get_error.get().expect("Error to be some")}
-
- }
+ view! { }
}
diff --git a/subcrates/client/src/verification.rs b/subcrates/client/src/verification.rs
new file mode 100644
index 0000000..59142b0
--- /dev/null
+++ b/subcrates/client/src/verification.rs
@@ -0,0 +1,17 @@
+use leptos::{
+ component,
+ html::ElementChild,
+ prelude::{Get, ReadSignal},
+ view, IntoView,
+};
+
+use crate::states::candidate::Candidate;
+
+#[component]
+pub fn Verification(candidate: ReadSignal>) -> impl IntoView {
+ view! {
+ "Election Verification"
+ {format!("You have successfully voted for {:?}", candidate.get())}
+ "WIP"
+ }
+}
diff --git a/subcrates/client/src/vote.rs b/subcrates/client/src/vote.rs
index feaa85e..9cfa5c6 100644
--- a/subcrates/client/src/vote.rs
+++ b/subcrates/client/src/vote.rs
@@ -1,81 +1,56 @@
-//! This file contains the logic for casting an actual vote.
-
use leptos::{
- component, create_node_ref, create_signal, event_target_value, expect_context, html, view,
- IntoView, NodeRef, Show, SignalGet, SignalSet,
+ component,
+ prelude::{signal, ElementChild, Get, OnAttribute, Read, RwSignal, Set, Show, Signal},
+ view, IntoView,
};
-use crate::state::State;
+use crate::{
+ candidate_selection,
+ states::{access_tokens::AccessTokens, candidate::Candidate, user::User},
+ validation, verification,
+};
+#[must_use]
#[component]
-pub fn Cast() -> impl IntoView {
- let mut state = expect_context::();
- let (get_error, set_error) = create_signal(None);
- // Temporarily holding the selected candidate in this signal before commiting it to the state.
- let (candidate, set_candidate) = create_signal(Option::::None);
-
- let blockchain_addr_ref: NodeRef = create_node_ref();
-
- let on_submit = move |ev: leptos::ev::SubmitEvent| {
- // stop the page from reloading:
- ev.prevent_default();
- let blockchain_addr = blockchain_addr_ref
- .get()
- .expect("Blockchain URL input should be mounted")
- .value();
-
- if blockchain_addr.is_empty() {
- set_error.set(Some("Blockchain URL cannot be empty".to_owned()));
- return;
- }
- let selected_candidate = candidate.get();
- match selected_candidate {
- Some(selected_candidate) => {
- if let Err(e) = state.vote(&selected_candidate, &blockchain_addr) {
- set_error.set(Some(format!("Failed to vote: {e}")));
- }
- }
- None => set_error.set(Some("Candidate not selected".to_owned())),
- }
- };
+pub fn Vote(user: Signal, blockchain: RwSignal) -> impl IntoView {
+ let access_tokens = RwSignal::new(
+ AccessTokens::load(&user.read(), &blockchain.read()).expect("Access tokens to be loaded"),
+ );
+ let (candidate, set_candidate) =
+ signal(Candidate::load(&user.read(), &blockchain.read()).expect("Candidate to load"));
view! {
-
-
-
-
- {get_error.get().expect("Error to be some")}
+ "DEBUG: Reset candidate"
+
+ "Back to blockchain select"
+
+
+
+
+
+
+
+
+
- }
-}
-
-#[component]
-fn Config() -> impl IntoView {
- // TODO For now candidate config is hardcoded:
- view! {
- "First Candidate"
- "Second Candidate"
- "Third Candidate"
}
}
diff --git a/subcrates/crypto/src/encryption/symmetric.rs b/subcrates/crypto/src/encryption/symmetric.rs
index a3d0aa3..dff7d72 100644
--- a/subcrates/crypto/src/encryption/symmetric.rs
+++ b/subcrates/crypto/src/encryption/symmetric.rs
@@ -36,6 +36,7 @@ type Result = std::result::Result;
const SALT_LEN: usize = 32;
/// Newtype for unique SALT generated for each user and used for deriving salt for encryption key.
+#[derive(Clone)]
struct Salt([u8; SALT_LEN]);
impl Salt {
@@ -165,6 +166,7 @@ impl AsRef<[u8]> for MetaData {
}
/// Encryption state structure.
+#[derive(Clone)]
pub struct Encryption {
/// The AEAD key used to encrypt and decrypt messages.
key: aead::LessSafeKey,
diff --git a/subcrates/process_io/Cargo.toml b/subcrates/process_io/Cargo.toml
index e7bd527..21cdfa0 100644
--- a/subcrates/process_io/Cargo.toml
+++ b/subcrates/process_io/Cargo.toml
@@ -10,7 +10,10 @@ license = "Apache-2.0"
repository = "https://github.com/ThrasherLT/digital-voting"
[dependencies]
-rustyline = { version = "14.0.0", features = ["derive"] }
+rustyline = { version = "15.0.0", features = ["derive"] }
shellwords = "1.1.0"
+tracing-appender = "0.2.3"
+tracing-subscriber.workspace = true
+tracing = { workspace = true, features = ["max_level_trace", "release_max_level_trace"] }
thiserror.workspace = true
diff --git a/subcrates/process_io/src/cli.rs b/subcrates/process_io/src/cli.rs
index f99a84c..e79bdd3 100644
--- a/subcrates/process_io/src/cli.rs
+++ b/subcrates/process_io/src/cli.rs
@@ -3,13 +3,16 @@
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
- highlight::{Highlighter, MatchingBracketHighlighter},
+ highlight::{CmdKind, Highlighter, MatchingBracketHighlighter},
hint::HistoryHinter,
history::DefaultHistory,
validate::MatchingBracketValidator,
Completer, CompletionType, Config, EditMode, Editor, Helper, Hinter, Validator,
};
-use std::borrow::Cow::{self, Borrowed, Owned};
+use std::{
+ borrow::Cow::{self, Borrowed, Owned},
+ path::PathBuf,
+};
use thiserror::Error;
// Error type of the CLI module.
@@ -24,6 +27,9 @@ pub enum Error {
/// There was an error reading while reading the name of the current executable.
#[error(transparent)]
MismatchedQuotes(#[from] shellwords::MismatchedQuotes),
+ /// The provided path was invalid.
+ #[error("Invalid path")]
+ Path,
}
type Result = std::result::Result;
@@ -36,7 +42,9 @@ pub struct StdioReader {
/// The name of the executable.
/// Used for adding to the input command, so that `clap` can parse it.
/// Storing this here to avoid the extra operations needed to retrieve it.
- exec_name: String,
+ exec_path: String,
+ /// Path to where the command history file will be stored.
+ cmd_history_path: PathBuf,
}
impl StdioReader {
@@ -49,7 +57,7 @@ impl StdioReader {
/// # Errors
///
/// If there was an error creating the Editor for Rustyline.
- pub fn new() -> Result {
+ pub fn new(cmd_history_path: PathBuf) -> Result {
let config = Config::builder()
.completion_type(CompletionType::List)
.auto_add_history(true)
@@ -62,13 +70,20 @@ impl StdioReader {
colored_prompt: String::new(),
validator: MatchingBracketValidator::new(),
};
- let exec_name = std::env::current_exe()?;
- let exec_name = exec_name.to_string_lossy().to_string();
+ let exec_path = std::env::current_exe()?;
+ let exec_path = exec_path.to_string_lossy().to_string();
let mut rl = Editor::with_config(config)?;
rl.set_helper(Some(h));
- let _ = rl.load_history("node-cmd-history.txt");
+ if let Some(parent) = cmd_history_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ let _ = rl.load_history(&cmd_history_path);
- Ok(Self { rl, exec_name })
+ Ok(Self {
+ rl,
+ exec_path,
+ cmd_history_path,
+ })
}
/// Read a line from stdio. This function blocks until a line is read.
@@ -91,7 +106,7 @@ impl StdioReader {
let line = self.rl.readline(&prompt)?;
let mut line = shellwords::split(&line)?;
- line.insert(0, self.exec_name.clone());
+ line.insert(0, self.exec_path.clone());
Ok(line)
}
@@ -101,7 +116,7 @@ impl Drop for StdioReader {
/// The command history is saved to a file when the `StdioReader` is dropped.
/// So `StdioReader` should only really be dropped when the program is exiting.
fn drop(&mut self) {
- let _ = self.rl.save_history("node-cmd-history.txt");
+ let _ = self.rl.save_history(&self.cmd_history_path);
}
}
@@ -139,7 +154,7 @@ impl Highlighter for MyHelper {
self.highlighter.highlight(line, pos)
}
- fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
- self.highlighter.highlight_char(line, pos, forced)
+ fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
+ self.highlighter.highlight_char(line, pos, kind)
}
}
diff --git a/subcrates/process_io/src/lib.rs b/subcrates/process_io/src/lib.rs
index 4f77372..2b8e049 100644
--- a/subcrates/process_io/src/lib.rs
+++ b/subcrates/process_io/src/lib.rs
@@ -1 +1,2 @@
pub mod cli;
+pub mod logging;
diff --git a/src/logging.rs b/subcrates/process_io/src/logging.rs
similarity index 71%
rename from src/logging.rs
rename to subcrates/process_io/src/logging.rs
index edd65ad..9f5afdc 100644
--- a/src/logging.rs
+++ b/subcrates/process_io/src/logging.rs
@@ -1,11 +1,21 @@
+use std::path::Path;
+
use tracing::level_filters::LevelFilter;
use tracing_appender::non_blocking::WorkerGuard as TracingWorkerGuard;
/// Start a logger that writes traces to a file without blocking.
-pub fn start_logger(log_filename: &str) -> std::io::Result {
+///
+/// # Errors
+///
+/// If the path to the log file is invalid.
+/// If initializing the `tracing_subscriber` fails.
+pub fn start_logger(log_path: &Path) -> std::io::Result {
+ if let Some(parent) = log_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+
+ let log_file = std::fs::File::create(log_path)?;
// Set up a rolling file appender
- std::fs::create_dir_all("logs")?;
- let log_file = std::fs::File::create(format!("logs/{log_filename}"))?;
// Do not let _tracing_worker_guard go out of scope, or the logging thread will be terminated.
let (non_blocking_tracing_writer, tracing_worker_guard) =
tracing_appender::non_blocking(log_file);
diff --git a/subcrates/protocol/src/config.rs b/subcrates/protocol/src/config.rs
new file mode 100644
index 0000000..e64bacc
--- /dev/null
+++ b/subcrates/protocol/src/config.rs
@@ -0,0 +1,23 @@
+use crypto::signature::blind_sign;
+
+/// This configurably defines what underlying primitive type will be used for the candidate ID.
+pub type CandidateId = u8;
+
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
+pub struct Authority {
+ pub addr: String,
+ pub authority_key: blind_sign::PublicKey,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
+pub struct Candidate {
+ pub name: String,
+ pub id: CandidateId,
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
+pub struct BlockchainConfig {
+ pub authorities: Vec,
+ pub candidates: Vec,
+}
diff --git a/subcrates/protocol/src/lib.rs b/subcrates/protocol/src/lib.rs
index 16b39b6..eb4bd06 100644
--- a/subcrates/protocol/src/lib.rs
+++ b/subcrates/protocol/src/lib.rs
@@ -1,6 +1,6 @@
//! Crate which describes the protocol and fundamental operation of the blockchain.
-pub mod candidate_id;
+pub mod config;
pub mod timestamp;
pub mod vote;
diff --git a/subcrates/protocol/src/vote.rs b/subcrates/protocol/src/vote.rs
index bbd8189..582c70b 100644
--- a/subcrates/protocol/src/vote.rs
+++ b/subcrates/protocol/src/vote.rs
@@ -8,8 +8,10 @@ use crypto::{
use serde::{Deserialize, Serialize};
use thiserror::Error;
-use crate::candidate_id::CandidateId;
-use crate::timestamp::{Limits as TimestampLimits, Timestamp};
+use crate::{
+ config::CandidateId,
+ timestamp::{Limits as TimestampLimits, Timestamp},
+};
/// Errors that can occur when working with election votes.
#[derive(Error, Debug)]
@@ -41,7 +43,7 @@ pub struct Vote {
/// The public key of this signature is the public key of the election authority.
/// Each access token on the blockchain must be unique.
timestamp: Timestamp,
- access_token: blind_sign::Signature,
+ access_tokens: Vec,
/// Digital signature corresponding to the `public_key`.
/// It signs all previous fields.
signature: digital_sign::Signature,
@@ -67,16 +69,16 @@ impl Vote {
signer: &digital_sign::Signer,
candidate: CandidateId,
timestamp: Timestamp,
- access_token: &blind_sign::Signature,
+ access_tokens: Vec,
) -> Result {
let public_key = signer.get_public_key();
- let to_sign = Self::signed_bytes(&public_key, &candidate, ×tamp, access_token)?;
+ let to_sign = Self::signed_bytes(&public_key, candidate, timestamp, &access_tokens)?;
Ok(Self {
public_key,
candidate,
timestamp,
- access_token: access_token.clone(),
+ access_tokens,
signature: signer.sign(&to_sign),
})
}
@@ -99,15 +101,22 @@ impl Vote {
/// A new Vote instance.
fn signed_bytes(
public_key: &digital_sign::PublicKey,
- candidate: &CandidateId,
- timestamp: &Timestamp,
- access_token: &blind_sign::Signature,
+ candidate: CandidateId,
+ timestamp: Timestamp,
+ access_tokens: &Vec,
) -> Result> {
- let mut to_sign =
- Vec::with_capacity(public_key.len() + candidate.as_ref().len() + access_token.len());
+ let mut access_tokens_total_len = 0;
+ for access_token in access_tokens {
+ access_tokens_total_len += access_token.len();
+ }
+ let mut to_sign = Vec::with_capacity(
+ public_key.len() + std::mem::size_of::() + access_tokens_total_len,
+ );
to_sign.extend_from_slice(public_key.as_ref());
- to_sign.extend_from_slice(candidate.as_ref());
- to_sign.extend_from_slice(access_token.as_ref());
+ to_sign.extend_from_slice(&candidate.to_le_bytes());
+ for access_token in access_tokens {
+ to_sign.extend_from_slice(access_token.as_ref());
+ }
to_sign.append(&mut bincode::serialize(×tamp)?);
Ok(to_sign)
@@ -117,25 +126,28 @@ impl Vote {
///
/// # Arguments
///
- /// - `access_token_verifyer` - Verifyer of the blind signature of the election authority.
+ /// - `access_token_verifiers` - A list of verifiers of the access tokens.
+ /// - `timestamp_limits` - The limits of an acceptable timestamp.
///
/// # Errors
///
/// If the vote is invalid or corrupted.
pub fn verify(
&self,
- access_token_verifyer: &blind_sign::Verifier,
+ access_token_verifiers: &[blind_sign::Verifier],
timestamp_limits: &TimestampLimits,
) -> Result<()> {
if !timestamp_limits.verify(self.timestamp) {
return Err(Error::InvalidTimestmap(self.timestamp));
}
- access_token_verifyer.verify_signature(self.access_token.clone(), &self.public_key)?;
+ for (i, access_token) in self.access_tokens.iter().enumerate() {
+ access_token_verifiers[i].verify_signature(access_token.clone(), &self.public_key)?;
+ }
let signed_bytes = Self::signed_bytes(
&self.public_key,
- &self.candidate,
- &self.timestamp,
- &self.access_token,
+ self.candidate,
+ self.timestamp,
+ &self.access_tokens,
)?;
Ok(digital_sign::verify(
&signed_bytes,
@@ -150,9 +162,7 @@ impl std::fmt::Display for Vote {
write!(
f,
"{} voted for candidate {} on {}",
- self.public_key,
- self.candidate,
- self.timestamp.format("%Y-%m-%d %H:%M:%S")
+ self.public_key, self.candidate, self.timestamp,
)
}
}
@@ -166,35 +176,47 @@ mod tests {
fn generate_vote_for_testing(
timestamp: Timestamp,
candidate: CandidateId,
- ) -> (Vote, blind_sign::PublicKey) {
- let blind_signer = blind_sign::BlindSigner::new().unwrap();
- let authority_pubkey = blind_signer.get_public_key().unwrap();
+ authority_count: usize,
+ ) -> (Vote, Vec) {
let digital_signer = digital_sign::Signer::new().unwrap();
- let msg = digital_signer.get_public_key();
- let blinder = blind_sign::Blinder::new(blind_signer.get_public_key().unwrap()).unwrap();
- let (blind_msg, unblinder) = blinder.blind(&msg).unwrap();
- let blind_signature = blind_signer.bling_sign(&blind_msg).unwrap();
- let access_token = unblinder
- .unblind_signature(blind_signature.clone(), &msg)
- .unwrap();
- let vote = Vote::new(&digital_signer, candidate, timestamp, &access_token).unwrap();
-
- (vote, authority_pubkey)
+ let mut access_tokens = Vec::new();
+ let mut authority_pubkeys = Vec::new();
+
+ for _ in 0..authority_count - 1 {
+ let blind_signer = blind_sign::BlindSigner::new().unwrap();
+ authority_pubkeys.push(blind_signer.get_public_key().unwrap());
+ let msg = digital_signer.get_public_key();
+ let blinder = blind_sign::Blinder::new(blind_signer.get_public_key().unwrap()).unwrap();
+ let (blind_msg, unblinder) = blinder.blind(&msg).unwrap();
+ let blind_signature = blind_signer.bling_sign(&blind_msg).unwrap();
+
+ let access_token = unblinder
+ .unblind_signature(blind_signature.clone(), &msg)
+ .unwrap();
+ access_tokens.push(access_token);
+ }
+ let vote = Vote::new(&digital_signer, candidate, timestamp, access_tokens).unwrap();
+
+ (vote, authority_pubkeys)
}
// TODO Not sure if it's a good idea to couple this test to crypto subcrate.
#[wasm_bindgen_test]
#[test]
fn test_vote() {
+ let authority_count = 3;
let timestamp = chrono::Utc::now();
- let (vote, access_token) = generate_vote_for_testing(timestamp, CandidateId::new(1));
-
- let verifier = blind_sign::Verifier::new(access_token).unwrap();
+ let (vote, authority_pubkeys) =
+ generate_vote_for_testing(timestamp, 2.into(), authority_count);
let timestamp_limits = TimestampLimits::new(
timestamp - std::time::Duration::from_secs(1),
timestamp + std::time::Duration::from_secs(1),
)
.unwrap();
- vote.verify(&verifier, ×tamp_limits).unwrap();
+ let authorities: Vec = authority_pubkeys
+ .iter()
+ .map(|pk| blind_sign::Verifier::new(pk.clone()).unwrap())
+ .collect();
+ vote.verify(&authorities, ×tamp_limits).unwrap();
}
}
diff --git a/testing/.env b/testing/.env
new file mode 100644
index 0000000..a4fe9d0
--- /dev/null
+++ b/testing/.env
@@ -0,0 +1,3 @@
+export APP_PORT=8080
+# Using this deterministic port range for more convenient debugging for now:
+export HOST_PORT=32912-32920
diff --git a/testing/Dockerfile b/testing/Dockerfile
new file mode 100644
index 0000000..0ef38b4
--- /dev/null
+++ b/testing/Dockerfile
@@ -0,0 +1,4 @@
+FROM ubuntu:latest
+
+# You can specify the internal dependencies for the containers here:
+RUN apt-get update && apt-get install -y curl jq
diff --git a/testing/aggregate-blockchain-config.sh b/testing/aggregate-blockchain-config.sh
new file mode 100755
index 0000000..5a4d464
--- /dev/null
+++ b/testing/aggregate-blockchain-config.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# This used to generate the blockchain config file from the authority config files.
+
+authorities=$(jq -n '[]')
+
+for dir in $(find ./data/ -type d -iname "*authority*"); do
+ PUBKEY=$(jq -r ".pk" "$dir/authority-config.json")
+ PORT=$(cat "$dir/port")
+
+ new_authority=$(jq -n \
+ --arg addr "http://${HOSTNAME}:${PORT}" \
+ --arg authority_key "$PUBKEY" \
+ '{addr: $addr, authority_key: $authority_key}')
+ authorities=$(jq --argjson new_authority "$new_authority" '. += [$new_authority]' <<< "$authorities")
+done
+
+candidates='[{"name": "Ricky", "id": 0}, {"name": "Randy", "id": 1}]'
+
+blockchain_config=$(jq -n \
+ --argjson candidates "$candidates" \
+ --argjson authorities "$authorities" \
+ '{candidates: $candidates, authorities: $authorities}')
+
+echo "$blockchain_config" > data/blockchain-config.json
diff --git a/testing/clean.sh b/testing/clean.sh
new file mode 100755
index 0000000..1dace9b
--- /dev/null
+++ b/testing/clean.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Use this script to clean all files from the data directory.
+# This is useful if you want to generate a new blockchain configuration.
+
+sudo rm -rf data/*
diff --git a/testing/docker-compose.yml b/testing/docker-compose.yml
new file mode 100644
index 0000000..27ebb17
--- /dev/null
+++ b/testing/docker-compose.yml
@@ -0,0 +1,39 @@
+version: '3.8'
+
+x-common: &common
+ build:
+ context: .
+ ports:
+ - "${HOST_PORT}:${APP_PORT}"
+ volumes:
+ - ./data:/data:rw
+ - ./../target/:/exec:ro
+ - ./startup.sh:/startup.sh
+ - /var/run/docker.sock:/var/run/docker.sock
+ entrypoint: ["/startup.sh"]
+ stdin_open: true
+ tty: true
+ healthcheck:
+ test: ["CMD", "curl", "http://localhost:${APP_PORT}/health"]
+ interval: 10s
+ retries: 5
+ start_period: 4s
+ timeout: 4s
+
+
+services:
+ authority:
+ deploy:
+ replicas: 3
+ environment:
+ - APP=authority
+ - APP_PORT=${APP_PORT}
+ <<: *common
+
+ node:
+ deploy:
+ replicas: 5
+ environment:
+ - APP=node
+ - APP_PORT=${APP_PORT}
+ <<: *common
diff --git a/testing/readme.md b/testing/readme.md
new file mode 100644
index 0000000..31cb7a8
--- /dev/null
+++ b/testing/readme.md
@@ -0,0 +1,36 @@
+# Testing Environment
+
+This is a simple testing environment based on docker compose.
+
+## Platform Support
+
+Currently this testing environment is only supported on linux
+
+## Setup
+
+In order to run the testing environment, follow these steps:
+
+- Run `./requirements.sh` to install the host dependencies (note that this currently is only supported on Ubuntu, but it's easy to install the dependencies manually by referencing the script).
+- Build the project using `cargo build`. The containers will mount the `target/debug/` dir and run the executables from there.
+- Run `./run.sh` to run the testing environment (note that this could take up to half a minute if the configs are being generated).
+
+## Usage
+
+You can check the `./data` directory for port numbers and logs of all the containers.
+You can also edit the `docker-compose.yml` file to adjust the number of nodes and authorities.
+
+## Shutdown
+
+If you want to stop the testing environment, follow these steps:
+
+- Run `./stop.sh` to stop the containers (note that this could take up to a minute).
+- If you want to clear configs (so new configs could be generated on the next run), you can run `sudo ./clean.sh`.
+- The HTTP endpoints of the authority containers can be found in the `data/blockchain-config.json` file.
+
+## TODO
+
+The following features will/should/might be implemented:
+
+- Multiplatform `./requirements.sh` support.
+- Faster `./stop.sh` operation.
+- Configurable candidates.
diff --git a/testing/requirements.sh b/testing/requirements.sh
new file mode 100644
index 0000000..242413e
--- /dev/null
+++ b/testing/requirements.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Use this file to install the necessary dependencies for running the test environment.
+
+sudo apt-get update
+sudo apt-get install -y docker-compose
diff --git a/testing/run.sh b/testing/run.sh
new file mode 100755
index 0000000..f34221a
--- /dev/null
+++ b/testing/run.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Use this file to startup the testing environment.
+
+DATA_DIR="data/"
+
+# If docker-compose creates the data dir, it's owner will be root and the services won't
+# be able to write anything to it.
+if [ ! -d "$DATA_DIR" ]; then
+ echo "Error: Directory '$DATA_DIR' must exist and be owned by the current user."
+ exit 1
+fi
+
+dir_owner=$(stat -c '%U' $DATA_DIR)
+if [ "$dir_owner" != $(whoami) ]; then
+ echo "Error: directory '$DATA_DIR' exists, but must be owned by the current user"
+ exit 1
+fi
+
+check_health() {
+ for container in $(docker-compose ps -q authority); do
+ health_status=$(docker inspect --format '{{.State.Health.Status}}' $container)
+ if [ "$health_status" != "healthy" ]; then
+ return 1
+ fi
+ done
+ return 0
+}
+
+docker-compose up -d authority
+
+# We need to wait for containers to be healthy, because `docker-compose up` exits when the
+# containers have started, but the internal services might not have started yet, which means
+# that the `authority-config.json` files might not have been created yet.
+echo "Waiting for all authority containers to be healthy..."
+until check_health; do
+ echo "..."
+ sleep 2
+done
+
+./aggregate-blockchain-config.sh
+
+docker-compose up -d node
diff --git a/testing/startup.sh b/testing/startup.sh
new file mode 100755
index 0000000..bac0c8d
--- /dev/null
+++ b/testing/startup.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# This is the script which is used to initialize the containers.
+
+# Docker does not expose the service name as an environment variable.
+# So we have to retrieve it through the docker.socket API.
+export NAME=\
+$(curl -X GET --unix-socket /var/run/docker.sock -s "http://v1.43/containers/$HOSTNAME/json" \
+| jq -r '.Name' \
+| sed "s/['\"/\\/]//g") # Sanitize output from extra symbols like '/' or '\"'
+
+# Same for the host port to which the container's `APP_PORT` port is bound to.
+export PORT=\
+$(curl -X GET --unix-socket /var/run/docker.sock -s "http://v1.43/containers/$HOSTNAME/json" \
+| jq -r --arg app_port "$APP_PORT" '.NetworkSettings.Ports[$app_port + "/tcp"][0].HostPort' \
+| sed "s/['\"/\\/]//g")
+
+mkdir -p /data/$NAME
+cd /data/$NAME
+echo "$PORT" > port
+if [ "$APP" == "node" ]; then
+ cp ../blockchain-config.json .
+fi
+
+/exec/debug/${APP} --data-path .
diff --git a/testing/stop.sh b/testing/stop.sh
new file mode 100755
index 0000000..be3103e
--- /dev/null
+++ b/testing/stop.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# This stops all the runnning containers.
+# Keep in mind, this may take up to a minute.
+
+docker-compose down