From 23d6870f77d390f7d5d3945d59557acd2b921ddc Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 10 Mar 2022 11:09:26 +0200 Subject: [PATCH 01/66] home template is working --- Cargo.lock | 1644 ++++++++----------------------- peach-web/Cargo.toml | 16 +- peach-web/rouille_refactor | 19 + peach-web/src/context/mod.rs | 6 +- peach-web/src/main.rs | 85 +- peach-web/src/routes/mod.rs | 10 +- peach-web/src/templates/base.rs | 23 + peach-web/src/templates/home.rs | 118 +++ peach-web/src/templates/mod.rs | 3 + peach-web/src/templates/nav.rs | 59 ++ peach-web/src/utils.rs | 50 +- 11 files changed, 700 insertions(+), 1333 deletions(-) create mode 100644 peach-web/rouille_refactor create mode 100644 peach-web/src/templates/base.rs create mode 100644 peach-web/src/templates/home.rs create mode 100644 peach-web/src/templates/mod.rs create mode 100644 peach-web/src/templates/nav.rs diff --git a/Cargo.lock b/Cargo.lock index 084f32d..6b02ada 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,58 +18,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aead" -version = "0.3.2" +name = "adler32" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "aes" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" -dependencies = [ - "aes-soft", - "aesni", - "cipher", -] - -[[package]] -name = "aes-gcm" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aes-soft" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" -dependencies = [ - "cipher", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aesni" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" -dependencies = [ - "cipher", - "opaque-debug 0.3.0", -] +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "aho-corasick" @@ -80,6 +32,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -89,14 +56,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ascii" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" + [[package]] name = "async-attributes" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -126,9 +99,9 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +checksum = "c026b7e44f1316b567ee750fea85103f87fcb80792b860e979f221259796ca0a" dependencies = [ "async-channel", "async-executor", @@ -161,9 +134,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -206,7 +179,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.6", + "crossbeam-utils 0.8.7", "futures-channel", "futures-core", "futures-io", @@ -250,8 +223,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -261,35 +234,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "async-task" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" - -[[package]] -name = "async-trait" -version = "0.1.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg 1.0.1", -] +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "atomic-waker" @@ -321,21 +274,24 @@ dependencies = [ [[package]] name = "autocfg" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", @@ -346,12 +302,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base-x" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" - [[package]] name = "base64" version = "0.9.3" @@ -374,12 +324,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - [[package]] name = "bitflags" version = "0.3.3" @@ -412,18 +356,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-buffer" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array 0.14.5", ] @@ -451,6 +386,27 @@ dependencies = [ "once_cell", ] +[[package]] +name = "brotli" +version = "3.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "0.2.17" @@ -460,6 +416,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.9.1" @@ -524,9 +490,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -554,35 +520,10 @@ dependencies = [ ] [[package]] -name = "chrono-tz" -version = "0.6.1" +name = "chunked_transfer" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array 0.14.5", -] +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "clap" @@ -617,35 +558,12 @@ dependencies = [ "cache-padded", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "cookie" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" -dependencies = [ - "aes-gcm", - "base64 0.13.0", - "hkdf", - "percent-encoding 2.1.0", - "rand 0.8.4", - "sha2 0.9.9", - "subtle", - "time 0.2.27", - "version_check 0.9.4", -] - [[package]] name = "cpufeatures" version = "0.2.1" @@ -656,10 +574,13 @@ dependencies = [ ] [[package]] -name = "cpuid-bool" -version = "0.2.0" +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] [[package]] name = "crossbeam-channel" @@ -687,7 +608,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "cfg-if 0.1.10", "crossbeam-utils 0.7.2", "lazy_static", @@ -723,16 +644,16 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "cfg-if 0.1.10", "lazy_static", ] [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -748,33 +669,14 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" -dependencies = [ - "generic-array 0.14.5", - "subtle", -] - [[package]] name = "ctor" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ - "quote 1.0.14", - "syn 1.0.85", -] - -[[package]] -name = "ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" -dependencies = [ - "cipher", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -787,6 +689,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "deflate" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f95bf05dffba6e6cce8dfbb30def788154949ccd9aed761b472119c21e01c70" +dependencies = [ + "adler32", + "gzip-header", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -795,48 +707,9 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "rustc_version 0.4.0", - "syn 1.0.85", -] - -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - -[[package]] -name = "devise" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" -dependencies = [ - "devise_core", - "quote 1.0.14", -] - -[[package]] -name = "devise_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" -dependencies = [ - "bitflags 1.3.2", - "proc-macro2 1.0.36", - "proc-macro2-diagnostics", - "quote 1.0.14", - "syn 1.0.85", + "syn 1.0.86", ] [[package]] @@ -848,22 +721,13 @@ dependencies = [ "generic-array 0.12.4", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.5", -] - [[package]] name = "digest" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.2", "crypto-common", ] @@ -877,15 +741,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "4.0.0" @@ -906,12 +761,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "doc-comment" version = "0.3.3" @@ -936,23 +785,14 @@ dependencies = [ [[package]] name = "embedded-hal" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36cfb62ff156596c892272f3015ef952fe1525e85261fa3a7f327bd6b384ab9" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" dependencies = [ "nb 0.1.3", "void", ] -[[package]] -name = "encoding_rs" -version = "0.8.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "env_logger" version = "0.6.2" @@ -1025,8 +865,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "synstructure", ] @@ -1038,27 +878,13 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] -[[package]] -name = "figment" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" -dependencies = [ - "atomic", - "pear", - "serde 1.0.133", - "toml", - "uncased", - "version_check 0.9.4", -] - [[package]] name = "filetime" version = "0.2.15" @@ -1067,7 +893,7 @@ checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.11", "winapi 0.3.9", ] @@ -1078,22 +904,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "fsevent" -version = "0.4.0" +name = "form_urlencoded" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ - "bitflags 1.3.2", - "fsevent-sys", -] - -[[package]] -name = "fsevent-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" -dependencies = [ - "libc", + "matches", + "percent-encoding 2.1.0", ] [[package]] @@ -1215,8 +1032,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -1256,19 +1073,6 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" -dependencies = [ - "cc", - "libc", - "log 0.4.14", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "generic-array" version = "0.12.4" @@ -1323,37 +1127,21 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", "libc", "wasi 0.10.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" -dependencies = [ - "opaque-debug 0.3.0", - "polyval", -] - [[package]] name = "gimli" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - [[package]] name = "globset" version = "0.4.8" @@ -1367,17 +1155,6 @@ dependencies = [ "regex", ] -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags 1.3.2", - "ignore", - "walkdir", -] - [[package]] name = "gloo-timers" version = "0.2.3" @@ -1402,9 +1179,9 @@ dependencies = [ "kuska-handshake", "kuska-sodiumoxide", "kuska-ssb", - "serde 1.0.133", + "serde 1.0.136", "serde_json", - "sha2 0.10.2", + "sha2", ] [[package]] @@ -1419,6 +1196,15 @@ dependencies = [ "nix 0.11.1", ] +[[package]] +name = "gzip-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0131feb3d3bb2a5a238d8a4d09f6353b7ebfdc52e77bccbf4ea6eaa751dde639" +dependencies = [ + "crc32fast", +] + [[package]] name = "h2" version = "0.1.26" @@ -1437,25 +1223,6 @@ dependencies = [ "tokio-io", ] -[[package]] -name = "h2" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" -dependencies = [ - "bytes 1.1.0", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.6", - "indexmap", - "slab 0.4.5", - "tokio 1.15.0", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1486,26 +1253,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" -dependencies = [ - "digest 0.9.0", - "hmac", -] - -[[package]] -name = "hmac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - [[package]] name = "http" version = "0.1.21" @@ -1553,9 +1300,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" [[package]] name = "httpdate" @@ -1563,12 +1310,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humansize" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" - [[package]] name = "humantime" version = "1.3.0" @@ -1620,7 +1361,7 @@ dependencies = [ "bytes 0.4.12", "futures 0.1.31", "futures-cpupool", - "h2 0.1.26", + "h2", "http 0.1.21", "http-body 0.1.0", "httparse", @@ -1643,23 +1384,22 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" dependencies = [ "bytes 1.1.0", "futures-channel", "futures-core", "futures-util", - "h2 0.3.10", "http 0.2.6", "http-body 0.4.4", "httparse", "httpdate", - "itoa 0.4.8", + "itoa 1.0.1", "pin-project-lite", "socket2", - "tokio 1.15.0", + "tokio 1.17.0", "tower-service", "tracing", "want 0.3.0", @@ -1689,21 +1429,14 @@ dependencies = [ ] [[package]] -name = "ignore" -version = "0.4.18" +name = "idna" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ - "crossbeam-utils 0.8.6", - "globset", - "lazy_static", - "log 0.4.14", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", + "matches", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -1712,35 +1445,8 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "hashbrown", - "serde 1.0.133", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "inotify" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", ] [[package]] @@ -1792,7 +1498,7 @@ dependencies = [ "futures 0.1.31", "jsonrpc-core 8.0.1", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_json", ] @@ -1821,9 +1527,9 @@ dependencies = [ "jsonrpc-core 18.0.0", "jsonrpc-pubsub 18.0.0", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_json", - "url", + "url 1.7.2", ] [[package]] @@ -1834,7 +1540,7 @@ checksum = "ddf83704f4e79979a424d1082dd2c1e52683058056c9280efa19ac5f6bc9033c" dependencies = [ "futures 0.1.31", "log 0.3.9", - "serde 1.0.133", + "serde 1.0.136", "serde_derive", "serde_json", ] @@ -1847,7 +1553,7 @@ checksum = "97b83fdc5e0218128d0d270f2f2e7a5ea716f3240c8518a58bc89e6716ba8581" dependencies = [ "futures 0.1.31", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_derive", "serde_json", ] @@ -1862,7 +1568,7 @@ dependencies = [ "futures-executor", "futures-util", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_derive", "serde_json", ] @@ -1877,7 +1583,7 @@ dependencies = [ "futures 0.1.31", "jsonrpc-core 11.0.0", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_json", ] @@ -1912,7 +1618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ "futures 0.3.21", - "hyper 0.14.16", + "hyper 0.14.17", "jsonrpc-core 18.0.0", "jsonrpc-server-utils 18.0.0", "log 0.4.14", @@ -1930,7 +1636,7 @@ dependencies = [ "jsonrpc-core 11.0.0", "log 0.4.14", "parking_lot 0.7.1", - "serde 1.0.133", + "serde 1.0.136", ] [[package]] @@ -1945,7 +1651,7 @@ dependencies = [ "log 0.4.14", "parking_lot 0.11.2", "rand 0.7.3", - "serde 1.0.133", + "serde 1.0.136", ] [[package]] @@ -1977,7 +1683,7 @@ dependencies = [ "jsonrpc-core 18.0.0", "lazy_static", "log 0.4.14", - "tokio 1.15.0", + "tokio 1.17.0", "tokio-stream", "tokio-util", "unicase", @@ -1993,7 +1699,7 @@ dependencies = [ "jsonrpc-core-client 11.0.0", "jsonrpc-pubsub 11.0.0", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_json", ] @@ -2007,7 +1713,7 @@ dependencies = [ "jsonrpc-core-client 18.0.0", "jsonrpc-pubsub 18.0.0", "log 0.4.14", - "serde 1.0.133", + "serde 1.0.136", "serde_json", ] @@ -2062,7 +1768,7 @@ checksum = "ae0f8eafdd240b722243787b51fdaf8df6693fb8621d0f7061cdba574214cf88" dependencies = [ "libc", "libsodium-sys", - "serde 1.0.133", + "serde 1.0.136", ] [[package]] @@ -2081,7 +1787,7 @@ dependencies = [ "log 0.4.14", "once_cell", "regex", - "serde 1.0.133", + "serde 1.0.136", "serde_json", "thiserror", ] @@ -2115,9 +1821,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libsodium-sys" @@ -2181,9 +1887,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard 1.1.0", ] @@ -2207,42 +1913,33 @@ dependencies = [ "value-bag", ] -[[package]] -name = "loom" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" -dependencies = [ - "cfg-if 1.0.0", - "generator", - "scoped-tls 1.0.0", - "serde 1.0.133", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - [[package]] name = "matches" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "maud" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7384febb4f8fc970cc2efab1650a6f48cac2e79b0b29587c90497b646fb10e1e" +dependencies = [ + "maud_macros", +] + +[[package]] +name = "maud_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "423430ac467408136d7de93f2929debd8a7bc3e795c92476f45e259b158e3355" +dependencies = [ + "proc-macro-error", + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -2261,7 +1958,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -2270,7 +1967,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -2280,14 +1977,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] -name = "mini-internal" -version = "0.1.18" +name = "mime_guess" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff801412f35cc16309997f9709a7933a3229df170fa9b3a9cae12e95eef0b5" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mini-internal" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9cec2df85e49afd5bd4c4afb21678e2177736ebe31e4b8836b821ffd806bd" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] @@ -2298,9 +2005,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniserde" -version = "0.1.18" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c57dcbb3bace5647e7254cae926a5251bd6243c9e25b70514199f39f7df118b" +checksum = "6b4f376d897db27bdf4e318542322eca8fd41839cc56a12cc8f8d8a05b4386af" dependencies = [ "itoa 1.0.1", "mini-internal", @@ -2314,7 +2021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -2338,9 +2045,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log 0.4.14", @@ -2400,23 +2107,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9796ba90f2e7187d7837ea05033ed6dff5320cbf2944fe2dc1da53569396ca07" [[package]] -name = "multer" -version = "2.0.2" +name = "multipart" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" dependencies = [ - "bytes 1.1.0", - "encoding_rs", - "futures-util", - "http 0.2.6", + "buf_redux", "httparse", "log 0.4.14", - "memchr", "mime", - "spin", - "tokio 1.15.0", - "tokio-util", - "version_check 0.9.4", + "mime_guess", + "quick-error", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", ] [[package]] @@ -2554,38 +2259,11 @@ dependencies = [ "version_check 0.9.4", ] -[[package]] -name = "normpath" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04aaf5e9cb0fbf883cc0423159eacdf96a9878022084b35c462c428cab73bcaf" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "notify" -version = "4.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" -dependencies = [ - "bitflags 1.3.2", - "filetime", - "fsevent", - "fsevent-sys", - "inotify", - "libc", - "mio 0.6.23", - "mio-extras", - "walkdir", - "winapi 0.3.9", -] - [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi 0.3.9", ] @@ -2596,7 +2274,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-traits 0.2.14", ] @@ -2615,7 +2293,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -2628,6 +2306,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.27.1" @@ -2645,9 +2332,9 @@ checksum = "2069a3ae3dad97a4ae47754e8f47e5d2f1fd32ab7ad8a84bb31d051faa59cc3c" [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "opaque-debug" @@ -2655,12 +2342,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "owning_ref" version = "0.4.1" @@ -2704,7 +2385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.5", + "lock_api 0.4.6", "parking_lot_core 0.8.5", ] @@ -2745,20 +2426,11 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.10", - "smallvec 1.7.0", + "redox_syscall 0.2.11", + "smallvec 1.8.0", "winapi 0.3.9", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - [[package]] name = "peach-buttons" version = "0.1.3" @@ -2785,7 +2457,7 @@ dependencies = [ "peach-lib", "regex", "rpassword", - "serde 1.0.133", + "serde 1.0.136", "serde_json", "snafu 0.6.10", "structopt", @@ -2828,7 +2500,7 @@ dependencies = [ "log 0.4.14", "nanorand", "regex", - "serde 1.0.133", + "serde 1.0.136", "serde_json", "serde_yaml", "sha3", @@ -2847,7 +2519,7 @@ dependencies = [ "jsonrpc-http-server 11.0.0", "log 0.4.14", "peach-lib", - "serde 1.0.133", + "serde 1.0.136", "serde_json", "ws", ] @@ -2872,7 +2544,7 @@ dependencies = [ "miniserde", "probes 0.4.1", "regex", - "serde 1.0.133", + "serde 1.0.136", "wpactrl", ] @@ -2888,7 +2560,7 @@ dependencies = [ "linux-embedded-hal", "log 0.4.14", "nix 0.11.1", - "serde 1.0.133", + "serde 1.0.136", "ssd1306", "tinybmp", ] @@ -2900,7 +2572,7 @@ dependencies = [ "log 0.4.14", "miniserde", "probes 0.4.1", - "serde 1.0.133", + "serde 1.0.136", "systemstat", ] @@ -2914,42 +2586,15 @@ dependencies = [ "golgi", "lazy_static", "log 0.4.14", - "nest", + "maud", "peach-lib", "peach-network", "peach-stats", - "rocket", - "rocket_dyn_templates", - "serde 1.0.133", - "serde_json", + "rouille", "temporary", - "tera", "xdg", ] -[[package]] -name = "pear" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" -dependencies = [ - "proc-macro2 1.0.36", - "proc-macro2-diagnostics", - "quote 1.0.14", - "syn 1.0.85", -] - [[package]] name = "percent-encoding" version = "1.0.1" @@ -2962,88 +2607,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", -] - -[[package]] -name = "pest_meta" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -dependencies = [ - "maplit", - "pest", - "sha-1", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared", - "rand 0.8.4", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", - "uncased", -] - [[package]] name = "pin-project-lite" version = "0.2.8" @@ -3075,17 +2638,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "polyval" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" -dependencies = [ - "cpuid-bool", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.16" @@ -3099,7 +2651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216" dependencies = [ "libc", - "time 0.2.27", + "time 0.3.7", ] [[package]] @@ -3120,8 +2672,8 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "version_check 0.9.4", ] @@ -3132,16 +2684,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "version_check 0.9.4", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" version = "0.4.30" @@ -3160,19 +2706,6 @@ dependencies = [ "unicode-xid 0.2.2", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", - "version_check 0.9.4", - "yansi", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -3190,9 +2723,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2 1.0.36", ] @@ -3226,7 +2759,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "libc", "rand_chacha 0.1.1", "rand_core 0.4.2", @@ -3254,14 +2787,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", - "rand_hc 0.3.1", ] [[package]] @@ -3270,7 +2802,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "rand_core 0.3.1", ] @@ -3324,7 +2856,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.5", ] [[package]] @@ -3345,15 +2877,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "rand_isaac" version = "0.1.1" @@ -3394,7 +2917,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "rand_core 0.4.2", ] @@ -3430,9 +2953,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" dependencies = [ "bitflags 1.3.2", ] @@ -3443,50 +2966,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.3", - "redox_syscall 0.2.10", -] - -[[package]] -name = "ref-cast" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "getrandom 0.2.5", + "redox_syscall 0.2.11", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - [[package]] name = "regex-syntax" version = "0.6.25" @@ -3512,101 +3006,28 @@ dependencies = [ ] [[package]] -name = "rocket" -version = "0.5.0-rc.1" +name = "rouille" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +checksum = "18b2380c42510ef4a28b5f228a174c801e0dec590103e215e60812e2e2f34d05" dependencies = [ - "async-stream 0.3.2", - "async-trait", - "atomic", - "atty", - "binascii", - "bytes 1.1.0", - "either", - "figment", - "futures 0.3.21", - "indexmap", - "log 0.4.14", - "memchr", - "multer", + "base64 0.13.0", + "brotli", + "chrono", + "deflate", + "filetime", + "multipart", "num_cpus", - "parking_lot 0.11.2", - "pin-project-lite", - "rand 0.8.4", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde 1.0.133", - "serde_json", - "state", - "tempfile", - "time 0.2.27", - "tokio 1.15.0", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check 0.9.4", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2 1.0.36", - "quote 1.0.14", - "rocket_http", - "syn 1.0.85", - "unicode-xid 0.2.2", -] - -[[package]] -name = "rocket_dyn_templates" -version = "0.1.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83f1287ad8fa034410928297a91db37518d5c46d7cc7e1e1b4a77aec0cd8807" -dependencies = [ - "glob", - "normpath", - "notify", - "rocket", - "serde 1.0.133", - "serde_json", - "tera", -] - -[[package]] -name = "rocket_http" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" -dependencies = [ - "cookie", - "either", - "http 0.2.6", - "hyper 0.14.16", - "indexmap", - "log 0.4.14", - "memchr", - "mime", - "parking_lot 0.11.2", - "pear", "percent-encoding 2.1.0", - "pin-project-lite", - "ref-cast", - "serde 1.0.133", - "smallvec 1.7.0", - "stable-pattern", - "state", - "time 0.2.27", - "tokio 1.15.0", - "uncased", + "rand 0.8.5", + "serde 1.0.136", + "serde_derive", + "serde_json", + "sha1", + "threadpool", + "time 0.3.7", + "tiny_http", + "url 2.2.2", ] [[package]] @@ -3649,15 +3070,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.6", ] -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - [[package]] name = "ryu" version = "1.0.9" @@ -3685,12 +3100,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "0.3.3" @@ -3720,9 +3129,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" [[package]] name = "semver-parser" @@ -3738,9 +3147,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] @@ -3760,25 +3169,25 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "serde_json" -version = "1.0.74" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "indexmap", "itoa 1.0.1", "ryu", - "serde 1.0.133", + "serde 1.0.136", ] [[package]] @@ -3798,7 +3207,7 @@ checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" dependencies = [ "indexmap", "ryu", - "serde 1.0.133", + "serde 1.0.136", "yaml-rust", ] @@ -3811,27 +3220,23 @@ dependencies = [ "block-buffer 0.7.3", "digest 0.8.1", "fake-simd", - "opaque-debug 0.2.3", + "opaque-debug", ] [[package]] name = "sha1" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] [[package]] -name = "sha2" -version = "0.9.9" +name = "sha1_smol" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" @@ -3846,23 +3251,14 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" dependencies = [ "digest 0.10.3", "keccak", ] -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - [[package]] name = "signal-hook" version = "0.3.13" @@ -3882,12 +3278,6 @@ dependencies = [ "libc", ] -[[package]] -name = "siphasher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" - [[package]] name = "slab" version = "0.3.0" @@ -3900,15 +3290,6 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - [[package]] name = "smallvec" version = "0.2.1" @@ -3926,9 +3307,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "snafu" @@ -3990,15 +3371,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi 0.3.9", @@ -4015,12 +3396,6 @@ dependencies = [ "nix 0.6.0", ] -[[package]] -name = "spin" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" - [[package]] name = "ssd1306" version = "0.2.6" @@ -4031,88 +3406,12 @@ dependencies = [ "embedded-hal", ] -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check 0.9.4", -] - -[[package]] -name = "state" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" -dependencies = [ - "loom", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version 0.2.3", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "serde 1.0.133", - "serde_derive", - "syn 1.0.85", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2 1.0.36", - "quote 1.0.14", - "serde 1.0.133", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.85", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "string" version = "0.2.1" @@ -4130,9 +3429,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ "clap", "lazy_static", @@ -4148,16 +3447,10 @@ dependencies = [ "heck", "proc-macro-error", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "syn" version = "0.15.44" @@ -4171,12 +3464,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", + "quote 1.0.15", "unicode-xid 0.2.2", ] @@ -4187,8 +3480,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "unicode-xid 0.2.2", ] @@ -4240,7 +3533,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.11", "remove_dir_all", "winapi 0.3.9", ] @@ -4254,33 +3547,11 @@ dependencies = [ "random", ] -[[package]] -name = "tera" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3cac831b615c25bcef632d1cabf864fa05813baad3d526829db18eb70e8b58d" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding 2.1.0", - "pest", - "pest_derive", - "rand 0.8.4", - "regex", - "serde 1.0.133", - "serde_json", - "slug", - "unic-segment", -] - [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] @@ -4310,17 +3581,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", ] [[package]] -name = "thread_local" -version = "1.1.3" +name = "threadpool" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ - "once_cell", + "num_cpus", ] [[package]] @@ -4336,40 +3607,25 @@ dependencies = [ [[package]] name = "time" -version = "0.2.27" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ - "const_fn", "libc", - "standback", - "stdweb", - "time-macros", - "version_check 0.9.4", - "winapi 0.3.9", + "num_threads", ] [[package]] -name = "time-macros" -version = "0.1.1" +name = "tiny_http" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +checksum = "9ce51b50006056f590c9b7c3808c3bd70f0d1101666629713866c227d6e58d39" dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2 1.0.36", - "quote 1.0.14", - "standback", - "syn 1.0.85", + "ascii", + "chrono", + "chunked_transfer", + "log 0.4.14", + "url 2.2.2", ] [[package]] @@ -4431,19 +3687,17 @@ dependencies = [ [[package]] name = "tokio" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.7.14", + "mio 0.8.0", "num_cpus", - "once_cell", "pin-project-lite", - "signal-hook-registry", - "tokio-macros", + "socket2", "winapi 0.3.9", ] @@ -4480,7 +3734,7 @@ dependencies = [ "iovec", "log 0.4.14", "mio 0.6.23", - "scoped-tls 0.1.2", + "scoped-tls", "tokio 0.1.22", "tokio-executor", "tokio-io", @@ -4530,17 +3784,6 @@ dependencies = [ "log 0.4.14", ] -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", -] - [[package]] name = "tokio-proto" version = "0.1.1" @@ -4595,7 +3838,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", - "tokio 1.15.0", + "tokio 1.17.0", ] [[package]] @@ -4695,7 +3938,7 @@ dependencies = [ "futures-sink", "log 0.4.14", "pin-project-lite", - "tokio 1.15.0", + "tokio 1.17.0", ] [[package]] @@ -4705,7 +3948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "indexmap", - "serde 1.0.133", + "serde 1.0.136", ] [[package]] @@ -4716,65 +3959,24 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" -dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", -] - [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" dependencies = [ "lazy_static", ] -[[package]] -name = "tracing-log" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" -dependencies = [ - "lazy_static", - "log 0.4.14", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d81bfa81424cc98cb034b837c985b7a290f592e5b4322f353f94a0ab0f9f594" -dependencies = [ - "ansi_term", - "lazy_static", - "matchers", - "regex", - "sharded-slab", - "smallvec 1.7.0", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - [[package]] name = "try-lock" version = "0.1.0" @@ -4787,87 +3989,21 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "ubyte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" -dependencies = [ - "serde 1.0.133", -] - -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - -[[package]] -name = "uncased" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" -dependencies = [ - "serde 1.0.133", - "version_check 0.9.4", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.6.0" @@ -4894,9 +4030,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -4916,27 +4052,29 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "universal-hash" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array 0.14.5", - "subtle", -] - [[package]] name = "url" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" dependencies = [ - "idna", + "idna 0.1.5", "matches", "percent-encoding 1.0.1", ] +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna 0.2.3", + "matches", + "percent-encoding 2.1.0", +] + [[package]] name = "value-bag" version = "1.0.0-alpha.8" @@ -5052,8 +4190,8 @@ dependencies = [ "lazy_static", "log 0.4.14", "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "wasm-bindgen-shared", ] @@ -5075,7 +4213,7 @@ version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ - "quote 1.0.14", + "quote 1.0.15", "wasm-bindgen-macro-support", ] @@ -5086,8 +4224,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.14", - "syn 1.0.85", + "quote 1.0.15", + "syn 1.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5184,7 +4322,7 @@ dependencies = [ "rand 0.6.5", "sha-1", "slab 0.4.5", - "url", + "url 1.7.2", ] [[package]] @@ -5199,11 +4337,11 @@ dependencies = [ [[package]] name = "xdg" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" dependencies = [ - "dirs 3.0.2", + "dirs 4.0.0", ] [[package]] @@ -5214,9 +4352,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map 0.5.4", ] - -[[package]] -name = "yansi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 4e29dbd..97c4a92 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -38,21 +38,13 @@ maintenance = { status = "actively-developed" } base64 = "0.13.0" dirs = "4.0.0" env_logger = "0.8" -#golgi = "0.1.0" golgi = { path = "/home/glyph/Projects/playground/rust/golgi" } lazy_static = "1.4.0" log = "0.4" -nest = "1.0.0" +maud = "0.23.0" peach-lib = { path = "../peach-lib" } -peach-network = { path = "../peach-network", features = ["serde_support"] } -peach-stats = { path = "../peach-stats", features = ["serde_support"] } -rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +peach-network = { path = "../peach-network" } +peach-stats = { path = "../peach-stats" } +rouille = "3.5.0" temporary = "0.6.4" -tera = { version = "1.12.1", features = ["builtins"] } xdg = "2.2.0" - -[dependencies.rocket_dyn_templates] -version = "0.1.0-rc.1" -features = ["tera"] diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor new file mode 100644 index 0000000..3f651c4 --- /dev/null +++ b/peach-web/rouille_refactor @@ -0,0 +1,19 @@ + + +go slow and steady. + +optimise for few dependencies and short compilation times. + +we do not need to be super fast or feature-rich. + +[ architecture ] + + - use the one-file-per-route patten + +[ tasks ] + + - write the nav and base templates + - get the homepage loading properly + - route handler + - template + - file loading (static assets) diff --git a/peach-web/src/context/mod.rs b/peach-web/src/context/mod.rs index 019dd88..65581c1 100644 --- a/peach-web/src/context/mod.rs +++ b/peach-web/src/context/mod.rs @@ -1,3 +1,3 @@ -pub mod dns; -pub mod network; -pub mod scuttlebutt; +//pub mod dns; +//pub mod network; +//pub mod scuttlebutt; diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 8150710..7389d1a 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -8,39 +8,34 @@ //! ## Design //! //! `peach-web` is written primarily in Rust and presents a web interface for -//! interacting with the device. The stack currently consists of Rocket (Rust -//! web framework), Tera (Rust template engine inspired by Jinja2 and the Django -//! template language), HTML, CSS and JavaScript. Additional functionality is -//! provided by JSON-RPC clients for the `peach-network` and `peach-stats` -//! microservices. -//! -//! HTML is rendered server-side. Request handlers call JSON-RPC microservices -//! and serve HTML and assets. A JSON API is exposed for remote calls and -//! dynamic client-side content updates via vanilla JavaScript following -//! unobstructive design principles. Each Tera template is passed a context -//! object. In the case of Rust, this object is a `struct` and must implement -//! `Serialize`. The fields of the context object are available in the context -//! of the template to be rendered. +//! interacting with the device. The stack currently consists of Rouille (Rust +//! micro-web-framework), Maud (an HTML template engine for Rust), HTML and +//! CSS. -mod context; +//mod context; pub mod error; -mod router; -pub mod routes; -#[cfg(test)] -mod tests; +//mod router; +//pub mod routes; +//#[cfg(test)] +//mod tests; +mod templates; pub mod utils; -use std::{process, sync::RwLock}; +use std::sync::RwLock; use lazy_static::lazy_static; -use log::{debug, error, info}; -use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG}; -use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket}; +//use log::{debug, error, info}; +use log::info; +//use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG}; +//use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket}; + +use rouille::{router, Response}; use utils::Theme; pub type BoxError = Box; +/* /// Application configuration parameters. /// These values are extracted from Rocket's default configuration provider: /// `Config::figment()`. As such, the values are drawn from `Rocket.toml` or @@ -52,14 +47,16 @@ pub struct RocketConfig { disable_auth: bool, standalone_mode: bool, } +*/ lazy_static! { static ref THEME: RwLock = RwLock::new(Theme::Light); } -static WLAN_IFACE: &str = "wlan0"; -static AP_IFACE: &str = "ap0"; +//static WLAN_IFACE: &str = "wlan0"; +//static AP_IFACE: &str = "ap0"; +/* pub fn init_rocket() -> Rocket { info!("Initializing Rocket"); // build a basic rocket instance @@ -84,13 +81,16 @@ pub fn init_rocket() -> Rocket { info!("Attaching application configuration to managed state"); mounted_rocket.attach(AdHoc::config::()) } +*/ -/// Launch the peach-web rocket server. -#[rocket::main] -async fn main() { +const HOSTNAME_AND_PORT: &str = "localhost:8000"; + +/// Launch the peach-web server. +fn main() { // initialize logger env_logger::init(); + /* // check if /var/lib/peachcloud/config.yml exists if !std::path::Path::new(PEACH_CONFIG).exists() { info!("PeachCloud configuration file not found; loading default values"); @@ -102,14 +102,29 @@ async fn main() { // this ensures a config file is created if it does not already exist config_manager::save_peach_config(config).expect("peachcloud configuration saving failed"); } + */ - // initialize rocket - let rocket = init_rocket(); + info!("Launching web server..."); - // launch rocket - info!("Launching Rocket"); - if let Err(e) = rocket.launch().await { - error!("Error in Rocket application: {}", e); - process::exit(1); - } + // the `start_server` starts listening forever on the given address. + rouille::start_server(HOSTNAME_AND_PORT, move |request| { + info!("Now listening on {}", HOSTNAME_AND_PORT); + + // static file server + // matches on assets in the `static` directory + let response = rouille::match_assets(&request, "static"); + if response.is_success() { + return response; + } + + router!(request, + (GET) (/) => { + Response::html(templates::home::build()) + }, + + // The code block is called if none of the other blocks matches the request. + // We return an empty response with a 404 status code. + _ => Response::empty_404() + ) + }); } diff --git a/peach-web/src/routes/mod.rs b/peach-web/src/routes/mod.rs index 830e23b..d7078c2 100644 --- a/peach-web/src/routes/mod.rs +++ b/peach-web/src/routes/mod.rs @@ -1,6 +1,6 @@ -pub mod authentication; -pub mod catchers; +//pub mod authentication; +//pub mod catchers; pub mod index; -pub mod scuttlebutt; -pub mod settings; -pub mod status; +//pub mod scuttlebutt; +//pub mod settings; +//pub mod status; diff --git a/peach-web/src/templates/base.rs b/peach-web/src/templates/base.rs new file mode 100644 index 0000000..48430af --- /dev/null +++ b/peach-web/src/templates/base.rs @@ -0,0 +1,23 @@ +use maud::{html, PreEscaped, DOCTYPE}; + +/// Base template builder. +/// +/// Takes an HTML body as input and splices it into the base template. +pub fn build(body: PreEscaped) -> PreEscaped { + html! { + (DOCTYPE) + html lang="en" data-theme="light"; + head { + meta charset="utf-8"; + meta name="description" content="PeachCloud web interface"; + meta name="author" content="glyph and notplants"; + meta name="viewport" content="width=devide-width, initial-scale=1.0"; + link rel="stylesheet" href="/css/peachcloud.css"; + link rel="stylesheet" href="/css/_variables.css"; + title { "PeachCloud" } + } + body { + (body) + } + } +} diff --git a/peach-web/src/templates/home.rs b/peach-web/src/templates/home.rs new file mode 100644 index 0000000..2dc1223 --- /dev/null +++ b/peach-web/src/templates/home.rs @@ -0,0 +1,118 @@ +use maud::{html, PreEscaped}; +use peach_lib::sbot::SbotStatus; + +use crate::templates; + +/// Read the state of the go-sbot process and define status-related +/// elements accordingly. +fn render_status_elements<'a>() -> (&'a str, &'a str, &'a str) { + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read(); + + // conditionally render the center circle class, center circle text and + // status circle class color based on the go-sbot process state + if let Ok(status) = sbot_status { + if status.state == Some("active".to_string()) { + ( + "circle circle-large circle-success", + "^_^", + "circle circle-small border-circle-small border-success", + ) + } else if status.state == Some("inactive".to_string()) { + ( + "circle circle-large circle-warning", + "z_z", + "circle circle-small border-circle-small border-warning", + ) + } else { + ( + "circle circle-large circle-danger", + "x_x", + "circle circle-small border-circle-small border-danger", + ) + } + } else { + ( + "circle circle-large circle-danger", + "x_x", + "circle circle-small border-circle-small border-danger", + ) + } +} + +/// Home template builder. +pub fn build<'a>() -> PreEscaped { + let (center_circle_class, center_circle_text, status_circle_class) = render_status_elements(); + + // render the home template html + let home_template = html! { + (PreEscaped("")) + div class="grid" { + (PreEscaped("")) + (PreEscaped("")) + a class="top-left" href="/scuttlebutt/peers" title="Scuttlebutt Peers" { + div class="circle circle-small border-circle-small border-ssb" { + img class="icon-medium" src="/icons/users.svg"; + } + } + (PreEscaped("")) + (PreEscaped("")) + a class="top-middle" href="/scuttlebutt/profile" title="Profile" { + div class="circle circle-small border-circle-small border-ssb" { + img class="icon-medium" src="/icons/user.svg"; + } + } + (PreEscaped("")) + (PreEscaped("")) + a class="top-right" href="/scuttlebutt/private" title="Private Messages" { + div class="circle circle-small border-circle-small border-ssb" { + img class="icon-medium" src="/icons/envelope.svg"; + } + } + (PreEscaped("")) + a class="middle" { + div class=(center_circle_class) { + p style="font-size: 4rem; color: var(--near-black);" { + (center_circle_text) + } + } + } + (PreEscaped("")) + (PreEscaped("")) + a class="bottom-left" href="/status/scuttlebutt" title="Status" { + div class=(status_circle_class) { + img class="icon-medium" src="/icons/heart-pulse.svg"; + } + } + /* + TODO: render the path of the status circle button based on the mode + {%- if standalone_mode == true -%} + + {% else -%} + + {%- endif -%} + */ + (PreEscaped("")) + (PreEscaped("")) + a class="bottom-middle" href="/guide" title="Guide" { + div class="circle circle-small border-circle-small border-info" { + img class="icon-medium" src="/icons/book.svg"; + } + } + (PreEscaped("")) + (PreEscaped("")) + a class="bottom-right" href="/settings" title="Settings" { + div class="circle circle-small border-circle-small border-settings" { + img class="icon-medium" src="/icons/cog.svg"; + } + } + } + }; + + // wrap the nav bars around the home template content + // title is "" and back button link is `None` because this is the homepage + let body = templates::nav::build(home_template, "", None); + + // render the base template with the provided body + templates::base::build(body) +} diff --git a/peach-web/src/templates/mod.rs b/peach-web/src/templates/mod.rs new file mode 100644 index 0000000..7bfc9fc --- /dev/null +++ b/peach-web/src/templates/mod.rs @@ -0,0 +1,3 @@ +mod base; +pub mod home; +pub mod nav; diff --git a/peach-web/src/templates/nav.rs b/peach-web/src/templates/nav.rs new file mode 100644 index 0000000..7c67ff2 --- /dev/null +++ b/peach-web/src/templates/nav.rs @@ -0,0 +1,59 @@ +use maud::{html, PreEscaped}; + +use crate::utils; + +/// Navigation template builder. +/// +/// Takes the main HTML content as input and splices it into the navigation template. +pub fn build(main: PreEscaped, title: &str, back: Option<&str>) -> PreEscaped { + // retrieve the current theme value + let theme = utils::get_theme(); + + // conditionally render the hermies icon and theme-switcher icon with correct link + let (hermies, switcher) = match theme.as_str() { + // if we're using the dark theme, render light icons and "light" query param + "dark" => ( + "/icons/hermies_hex_light.svg", + html! { + a class="nav-item" href="/theme?theme=light" { + img class="icon-medium nav-icon-right icon-active" title="Toggle theme" src="/icons/sun.png" alt="Sun"; + } + }, + ), + // otherwise, assume we're using light mode + _ => ( + "/icons/hermies_hex.svg", + html! { + a class="nav-item" href="/theme?theme=dark" { + img class="icon-medium nav-icon-right icon-active" title="Toggle theme" src="/icons/moon.png" alt="Moon"; + } + }, + ), + }; + + html! { + (PreEscaped("")) + nav class="nav-bar" { + a class="nav-item" href=[back] title="Back" { + img class="icon-medium nav-icon-left icon-active" src="/icons/back.svg" alt="Back"; + } + h1 class="nav-title" { (title) } + a class="nav-item" id="logoutButton" href="/logout" title="Logout" { + img class="icon-medium nav-icon-right icon-active" src="/icons/enter.svg" alt="Enter"; + } + } + (PreEscaped("")) + main { (main) } + (PreEscaped("")) + nav class="nav-bar" { + a class="nav-item" href="https://scuttlebutt.nz/" { + img class="icon-medium nav-icon-left" title="Scuttlebutt Website" src=(hermies) alt="Secure Scuttlebutt"; + } + a class="nav-item" href="/" { + img class="icon nav-icon-left" src="/icons/peach-icon.png" alt="PeachCloud" title="Home"; + } + // render the pre-defined theme-switcher icon + (switcher) + } + } +} diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index 5b85099..fc63575 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -1,3 +1,30 @@ +use log::info; + +use crate::THEME; + +// THEME FUNCTIONS + +#[derive(Debug, Copy, Clone)] +pub enum Theme { + Light, + Dark, +} + +pub fn get_theme() -> String { + let current_theme = THEME.read().unwrap(); + match *current_theme { + Theme::Dark => "dark".to_string(), + _ => "light".to_string(), + } +} + +pub fn set_theme(theme: Theme) { + info!("set ui theme to: {:?}", theme); + let mut writable_theme = THEME.write().unwrap(); + *writable_theme = theme; +} + +/* pub mod monitor; use std::io::prelude::*; @@ -84,28 +111,6 @@ pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result String { - let current_theme = THEME.read().unwrap(); - match *current_theme { - Theme::Dark => "dark".to_string(), - _ => "light".to_string(), - } -} - -pub fn set_theme(theme: Theme) { - info!("set ui theme to: {:?}", theme); - let mut writable_theme = THEME.write().unwrap(); - *writable_theme = theme; -} - // HELPER FUNCTIONS #[derive(Debug, Serialize)] @@ -122,3 +127,4 @@ pub enum TemplateOrRedirect { Template(Template), Redirect(Redirect), } +*/ -- 2.40.1 From 6b145d66f8e343f6c6ee66f7bcda78ed39f49f45 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Mar 2022 10:19:00 +0200 Subject: [PATCH 02/66] add basic application config and parser --- peach-web/config | 2 ++ peach-web/src/config.rs | 54 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 peach-web/config create mode 100644 peach-web/src/config.rs diff --git a/peach-web/config b/peach-web/config new file mode 100644 index 0000000..aa7ad74 --- /dev/null +++ b/peach-web/config @@ -0,0 +1,2 @@ +disable_auth=false +standalone_mode=true diff --git a/peach-web/src/config.rs b/peach-web/src/config.rs new file mode 100644 index 0000000..00d7239 --- /dev/null +++ b/peach-web/src/config.rs @@ -0,0 +1,54 @@ +// Kind thanks to Alex Wennerberg (https://alexwennerberg.com/) for writing +// this code and offered it under the 0BSD (BDS 0-Clause) license. + +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; + +// Ini-like key=value configuration with global config only (no subsections). +#[derive(Debug)] +pub struct Config { + pub disable_auth: bool, + pub standalone_mode: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + disable_auth: false, + standalone_mode: true, + } + } +} + +impl Config { + pub fn match_kv(&mut self, key: &str, value: &str) { + match key { + "disable_auth" => self.disable_auth = value.parse().unwrap(), + "standalone_mode" => self.standalone_mode = value.parse().unwrap(), + _ => {} + } + } +} + +impl Config { + pub fn from_file>(path: P) -> Result { + let file = File::open(path)?; + let mut conf = Config::default(); + + for l in io::BufReader::new(file).lines() { + let line = l?; + if line.len() == 0 { + continue; + } + if let Some(i) = line.find('=') { + let key = &line[..i]; + let value = &line[i + 1..]; + conf.match_kv(key, value); + } else { + // panic!("Invalid config") + } + } + Ok(conf) + } +} -- 2.40.1 From ec288658f3930715d763875a49a403484c244601 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Mar 2022 14:27:40 +0200 Subject: [PATCH 03/66] implement basic config --- peach-web/src/config.rs | 2 +- peach-web/src/main.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/peach-web/src/config.rs b/peach-web/src/config.rs index 00d7239..7fd85ad 100644 --- a/peach-web/src/config.rs +++ b/peach-web/src/config.rs @@ -6,7 +6,7 @@ use std::io::{self, BufRead}; use std::path::Path; // Ini-like key=value configuration with global config only (no subsections). -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Config { pub disable_auth: bool, pub standalone_mode: bool, diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 7389d1a..e86c2f5 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -13,9 +13,10 @@ //! CSS. //mod context; +mod config; pub mod error; //mod router; -//pub mod routes; +mod routes; //#[cfg(test)] //mod tests; mod templates; @@ -31,6 +32,8 @@ use log::info; use rouille::{router, Response}; +// crate-local dependencies +use config::Config; use utils::Theme; pub type BoxError = Box; @@ -49,7 +52,10 @@ pub struct RocketConfig { } */ +static CONFIG_PATH: &str = "./config"; + lazy_static! { + static ref CONFIG: Config = Config::from_file(CONFIG_PATH).expect("failed to read config file"); static ref THEME: RwLock = RwLock::new(Theme::Light); } @@ -119,7 +125,11 @@ fn main() { router!(request, (GET) (/) => { - Response::html(templates::home::build()) + Response::html(routes::home::build()) + }, + + (GET) (/settings) => { + Response::html(routes::settings::menu::build()) }, // The code block is called if none of the other blocks matches the request. -- 2.40.1 From 07c18ea64dda81872d980475fdaf088a9856d21e Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Mar 2022 14:28:31 +0200 Subject: [PATCH 04/66] create settings menu route and move home route --- peach-web/src/{templates => routes}/home.rs | 0 peach-web/src/routes/mod.rs | 5 +- peach-web/src/routes/settings/menu.rs | 55 ++++++++++----------- peach-web/src/routes/settings/mod.rs | 10 ++-- peach-web/src/templates/mod.rs | 3 +- 5 files changed, 34 insertions(+), 39 deletions(-) rename peach-web/src/{templates => routes}/home.rs (100%) diff --git a/peach-web/src/templates/home.rs b/peach-web/src/routes/home.rs similarity index 100% rename from peach-web/src/templates/home.rs rename to peach-web/src/routes/home.rs diff --git a/peach-web/src/routes/mod.rs b/peach-web/src/routes/mod.rs index d7078c2..1c94673 100644 --- a/peach-web/src/routes/mod.rs +++ b/peach-web/src/routes/mod.rs @@ -1,6 +1,7 @@ //pub mod authentication; //pub mod catchers; -pub mod index; +//pub mod index; +pub mod home; //pub mod scuttlebutt; -//pub mod settings; +pub mod settings; //pub mod status; diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs index b93c645..d7f520c 100644 --- a/peach-web/src/routes/settings/menu.rs +++ b/peach-web/src/routes/settings/menu.rs @@ -1,35 +1,30 @@ -use rocket::{get, request::FlashMessage, State}; -use rocket_dyn_templates::{tera::Context, Template}; +use maud::{html, PreEscaped}; -use crate::routes::authentication::Authenticated; -use crate::utils; -use crate::RocketConfig; +use crate::{templates, CONFIG}; -// HELPERS AND ROUTES FOR /settings - -/// View and delete currently configured admin. -#[get("/settings")] -pub fn settings_menu( - _auth: Authenticated, - flash: Option, - config: &State, -) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Settings".to_string())); - - // pass in mode from managed state so we can conditionally render html elements - context.insert("standalone_mode", &config.standalone_mode); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); +// TODO: flash message implementation for rouille +// +/// Main settings menu template builder. +pub fn build() -> PreEscaped { + let menu_template = html! { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div id="settingsButtons" { + // render the network settings button if we're not in standalone mode + @if !CONFIG.standalone_mode { + a id="network" class="button button-primary center" href="/settings/network" title="Network Settings" { "Network" } + } + a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings" { "Scuttlebutt" } + a id="admin" class="button button-primary center" href="/settings/admin" title="Administrator Settings" { "Administration" } + } + } }; - Template::render("settings/menu", &context.into_json()) + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build(menu_template, "Settings", Some("/")); + + // render the base template with the provided body + templates::base::build(body) } diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index 0356538..b9a9b54 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -1,6 +1,6 @@ -pub mod admin; -pub mod dns; +//pub mod admin; +//pub mod dns; pub mod menu; -pub mod network; -pub mod scuttlebutt; -pub mod theme; +//pub mod network; +//pub mod scuttlebutt; +//pub mod theme; diff --git a/peach-web/src/templates/mod.rs b/peach-web/src/templates/mod.rs index 7bfc9fc..137668d 100644 --- a/peach-web/src/templates/mod.rs +++ b/peach-web/src/templates/mod.rs @@ -1,3 +1,2 @@ -mod base; -pub mod home; +pub mod base; pub mod nav; -- 2.40.1 From eba15605c25f145a7563b2418d1d73d86fcb5e12 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Mar 2022 15:33:04 +0200 Subject: [PATCH 05/66] add scuttlebutt settings menu --- peach-web/src/main.rs | 4 + peach-web/src/routes/settings/menu.rs | 2 +- peach-web/src/routes/settings/mod.rs | 2 +- peach-web/src/routes/settings/scuttlebutt.rs | 285 ------------------ .../src/routes/settings/scuttlebutt/menu.rs | 51 ++++ .../src/routes/settings/scuttlebutt/mod.rs | 1 + 6 files changed, 58 insertions(+), 287 deletions(-) delete mode 100644 peach-web/src/routes/settings/scuttlebutt.rs create mode 100644 peach-web/src/routes/settings/scuttlebutt/menu.rs create mode 100644 peach-web/src/routes/settings/scuttlebutt/mod.rs diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index e86c2f5..8bd8977 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -132,6 +132,10 @@ fn main() { Response::html(routes::settings::menu::build()) }, + (GET) (/settings/scuttlebutt) => { + Response::html(routes::settings::scuttlebutt::menu::build()) + }, + // The code block is called if none of the other blocks matches the request. // We return an empty response with a 404 status code. _ => Response::empty_404() diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs index d7f520c..121aefd 100644 --- a/peach-web/src/routes/settings/menu.rs +++ b/peach-web/src/routes/settings/menu.rs @@ -4,7 +4,7 @@ use crate::{templates, CONFIG}; // TODO: flash message implementation for rouille // -/// Main settings menu template builder. +/// Settings menu template builder. pub fn build() -> PreEscaped { let menu_template = html! { (PreEscaped("")) diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index b9a9b54..0b2a088 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -2,5 +2,5 @@ //pub mod dns; pub mod menu; //pub mod network; -//pub mod scuttlebutt; +pub mod scuttlebutt; //pub mod theme; diff --git a/peach-web/src/routes/settings/scuttlebutt.rs b/peach-web/src/routes/settings/scuttlebutt.rs deleted file mode 100644 index 54b66f7..0000000 --- a/peach-web/src/routes/settings/scuttlebutt.rs +++ /dev/null @@ -1,285 +0,0 @@ -use std::{ - io, - process::{Command, Output}, -}; - -use log::{info, warn}; -use peach_lib::sbot::{SbotConfig, SbotStatus}; -use rocket::{ - form::{Form, FromForm}, - get, post, - request::FlashMessage, - response::{Flash, Redirect}, -}; -use rocket_dyn_templates::{tera::Context, Template}; - -use crate::routes::authentication::Authenticated; -use crate::utils; - -#[derive(Debug, FromForm)] -pub struct SbotConfigForm { - /// Directory path for the log and indexes. - repo: String, - /// Directory path for writing debug output. - debugdir: String, - /// Secret-handshake app-key (aka. network key). - shscap: String, - /// HMAC hash used to sign messages. - hmac: String, - /// Replication hops (1: friends, 2: friends of friends). - hops: u8, - /// IP address to listen on. - lis_ip: String, - /// Port to listen on. - lis_port: String, - /// Address to listen on for WebSocket connections. - wslis: String, - /// Address to for metrics and pprof HTTP server. - debuglis: String, - /// Enable sending local UDP broadcasts. - localadv: bool, - /// Enable listening for UDP broadcasts and connecting. - localdiscov: bool, - /// Enable syncing by using epidemic-broadcast-trees (EBT). - enable_ebt: bool, - /// Bypass graph auth and fetch remote's feed (useful for pubs that are restoring their data - /// from peer; user beware - caveats about). - promisc: bool, - /// Disable the UNIX socket RPC interface. - nounixsock: bool, - /// Run the go-sbot on system start-up (systemd service enabled). - startup: bool, - /// Attempt to repair the filesystem before starting. - repair: bool, -} - -// HELPERS AND ROUTES FOR /settings/scuttlebutt - -/// Scuttlebutt settings menu. -#[get("/")] -pub fn ssb_settings_menu(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read().ok(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("sbot_status", &sbot_status); - context.insert("back", &Some("/settings".to_string())); - context.insert("title", &Some("Scuttlebutt Settings".to_string())); - - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/scuttlebutt/menu", &context.into_json()) -} - -/// Sbot configuration page (includes form for updating configuration parameters). -#[get("/configure")] -pub fn configure_sbot(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read().ok(); - let run_on_startup = sbot_status.map(|status| status.boot_state); - - // retrieve sbot config parameters - let sbot_config = SbotConfig::read().ok(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings/scuttlebutt".to_string())); - context.insert("title", &Some("Sbot Configuration".to_string())); - context.insert("sbot_config", &sbot_config); - context.insert("run_on_startup", &Some(run_on_startup)); - - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/scuttlebutt/configure_sbot", &context.into_json()) -} - -// TODO: consider using `Contextual` here to collect all submitted form -// fields to re-render forms with submitted values on error - -/// Receive the sbot configuration form data and save it to file. -#[post("/configure?", data = "")] -pub fn configure_sbot_post( - restart: bool, - config: Form, - _auth: Authenticated, -) -> Flash { - // call `into_inner()` to take ownership of the `config` data - let owned_config = config.into_inner(); - - // concat the ip and port for listen address - let lis = format!("{}:{}", owned_config.lis_ip, owned_config.lis_port); - - // instantiate `SbotConfig` from form data - let config = SbotConfig { - lis, - hops: owned_config.hops, - repo: owned_config.repo, - debugdir: owned_config.debugdir, - shscap: owned_config.shscap, - localadv: owned_config.localadv, - localdiscov: owned_config.localdiscov, - hmac: owned_config.hmac, - wslis: owned_config.wslis, - debuglis: owned_config.debuglis, - enable_ebt: owned_config.enable_ebt, - promisc: owned_config.promisc, - nounixsock: owned_config.nounixsock, - repair: owned_config.repair, - }; - - match owned_config.startup { - true => { - info!("Enabling go-sbot.service"); - if let Err(e) = systemctl_sbot_cmd("enable") { - warn!("Failed to enable go-sbot.service: {}", e) - } - } - false => { - info!("Disabling go-sbot.service"); - if let Err(e) = systemctl_sbot_cmd("disable") { - warn!("Failed to disable go-sbot.service: {}", e) - } - } - }; - - // write config to file - match SbotConfig::write(config) { - Ok(_) => { - // if `restart` query parameter is `true`, attempt sbot process (re)start - if restart { - restart_sbot_process( - // redirect url - "/settings/scuttlebutt/configure", - // success flash msg - "Updated configuration and restarted the sbot process", - // first failed flash msg - "Updated configuration but failed to start the sbot process", - // second failed flash msg - "Updated configuration but failed to stop the sbot process", - ) - } else { - Flash::success( - Redirect::to("/settings/scuttlebutt/configure"), - "Updated configuration", - ) - } - } - Err(e) => Flash::error( - Redirect::to("/settings/scuttlebutt/configure"), - format!("Failed to update configuration: {}", e), - ), - } -} - -/// Set default configuration parameters for the go-sbot and save them to file. -#[get("/configure/default")] -pub fn configure_sbot_default(_auth: Authenticated) -> Flash { - let default_config = SbotConfig::default(); - - // write default config to file - match SbotConfig::write(default_config) { - Ok(_) => Flash::success( - Redirect::to("/settings/scuttlebutt/configure"), - "Restored default configuration", - ), - Err(e) => Flash::error( - Redirect::to("/settings/scuttlebutt/configure"), - format!("Failed to restore default configuration: {}", e), - ), - } -} - -/// Attempt to start the go-sbot.service process. -/// Redirect to the Scuttlebutt settings menu and communicate the outcome of -/// the attempt via a flash message. -#[get("/start")] -pub fn start_sbot(_auth: Authenticated) -> Flash { - info!("Starting go-sbot.service"); - match systemctl_sbot_cmd("start") { - Ok(_) => Flash::success( - Redirect::to("/settings/scuttlebutt"), - "Sbot process has been started", - ), - Err(_) => Flash::error( - Redirect::to("/settings/scuttlebutt"), - "Failed to start the sbot process", - ), - } -} - -/// Attempt to stop the go-sbot.service process. -/// Redirect to the Scuttlebutt settings menu and communicate the outcome of -/// the attempt via a flash message. -#[get("/stop")] -pub fn stop_sbot(_auth: Authenticated) -> Flash { - info!("Stopping go-sbot.service"); - match systemctl_sbot_cmd("stop") { - Ok(_) => Flash::success( - Redirect::to("/settings/scuttlebutt"), - "Sbot process has been stopped", - ), - Err(_) => Flash::error( - Redirect::to("/settings/scuttlebutt"), - "Failed to stop the sbot process", - ), - } -} - -/// Attempt to restart the go-sbot.service process. -/// Redirect to the Scuttlebutt settings menu and communicate the outcome of -/// the attempt via a flash message. -#[get("/restart")] -pub fn restart_sbot(_auth: Authenticated) -> Flash { - restart_sbot_process( - "/settings/scuttlebutt", - "Sbot process has been restarted", - "Failed to start the sbot process", - "Failed to stop the sbot process", - ) -} - -// HELPER FUNCTIONS - -/// Executes a systemctl command for the go-sbot.service process. -pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result { - Command::new("systemctl") - .arg("--user") - .arg(cmd) - .arg("go-sbot.service") - .output() -} - -/// Executes a systemctl stop command followed by start command. -/// Returns a redirect with a flash message stating the output of the restart attempt. -fn restart_sbot_process( - redirect_url: &str, - success_msg: &str, - start_failed_msg: &str, - stop_failed_msg: &str, -) -> Flash { - let url = redirect_url.to_string(); - - info!("Restarting go-sbot.service"); - match systemctl_sbot_cmd("stop") { - // if stop was successful, try to start the process - Ok(_) => match systemctl_sbot_cmd("start") { - Ok(_) => Flash::success(Redirect::to(url), success_msg), - - Err(e) => Flash::error(Redirect::to(url), format!("{}: {}", start_failed_msg, e)), - }, - Err(e) => Flash::error(Redirect::to(url), format!("{}: {}", stop_failed_msg, e)), - } -} diff --git a/peach-web/src/routes/settings/scuttlebutt/menu.rs b/peach-web/src/routes/settings/scuttlebutt/menu.rs new file mode 100644 index 0000000..c8b35df --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/menu.rs @@ -0,0 +1,51 @@ +use maud::{html, PreEscaped}; +use peach_lib::sbot::SbotStatus; + +use crate::templates; + +// TODO: flash message implementation for rouille +// + +/// Read the status of the go-sbot service and render buttons accordingly. +fn render_process_buttons() -> PreEscaped { + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read(); + + html! { + // render the stop and restart buttons if sbot process is currently active + @if let Ok(status) = sbot_status { + @if status.state == Some("active".to_string()) { + a id="stop" class="button button-primary center" href="/settings/scuttlebutt/stop" title="Stop Sbot" { "Stop Sbot" } + a id="restart" class="button button-primary center" href="/settings/scuttlebutt/restart" title="Restart Sbot" { "Restart Sbot" } + // render the start button if sbot process is currently inactive + } @else { + a id="start" class="button button-primary center" href="/settings/scuttlebutt/start" title="Start Sbot" { "Start Sbot" } + } + // render the start button if an error was returned by the status query + } @else { + a id="start" class="button button-primary center" href="/settings/scuttlebutt/start" title="Start Sbot" { "Start Sbot" } + } + } +} + +/// Scuttlebutt settings menu template builder. +pub fn build() -> PreEscaped { + let menu_template = html! { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div id="settingsButtons" { + a id="configureSbot" class="button button-primary center" href="/settings/scuttlebutt/configure" title="Configure Sbot" { "Configure Sbot" } + // conditionally render the start / stop / restart buttons + (render_process_buttons()) + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build(menu_template, "Scuttlebutt Settings", Some("/settings")); + + // render the base template with the provided body + templates::base::build(body) +} diff --git a/peach-web/src/routes/settings/scuttlebutt/mod.rs b/peach-web/src/routes/settings/scuttlebutt/mod.rs new file mode 100644 index 0000000..b9a0e3e --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/mod.rs @@ -0,0 +1 @@ +pub mod menu; -- 2.40.1 From 4d06eb167f8586781cba953e57636854b1073d38 Mon Sep 17 00:00:00 2001 From: glyph Date: Sat, 12 Mar 2022 10:36:40 +0200 Subject: [PATCH 06/66] incomplete sbot config route --- Cargo.lock | 1 + peach-web/rouille_refactor | 17 +- .../settings/scuttlebutt/configure_sbot.rs | 156 ++++++++++++++++++ .../src/routes/settings/scuttlebutt/mod.rs | 1 + 4 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs diff --git a/Cargo.lock b/Cargo.lock index 6b02ada..6320894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1774,6 +1774,7 @@ dependencies = [ [[package]] name = "kuska-ssb" version = "0.4.0" +source = "git+https://github.com/mycognosist/ssb.git?branch=private_messages#4ae3e9ea24b828b9f11cb3af6866e8656924b9d0" dependencies = [ "async-std", "async-stream 0.2.1", diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor index 3f651c4..e120b0c 100644 --- a/peach-web/rouille_refactor +++ b/peach-web/rouille_refactor @@ -12,8 +12,15 @@ we do not need to be super fast or feature-rich. [ tasks ] - - write the nav and base templates - - get the homepage loading properly - - route handler - - template - - file loading (static assets) + - write the settings route(s) + x menu + x scuttlebutt menu + - configure_sbot + x template + - sbot_config data + + x write the nav and base templates + x get the homepage loading properly + x route handler + x template + x file loading (static assets) diff --git a/peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs b/peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs new file mode 100644 index 0000000..6a5dd17 --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs @@ -0,0 +1,156 @@ +use maud::{html, PreEscaped}; +use peach_lib::sbot::SbotStatus; + +use crate::templates; + +// TODO: flash message implementation for rouille +// + +/* +{# ASSIGN VARIABLES #} + {# ---------------- #} + {%- if sbot_config.hops -%} + {% set hops = sbot_config.hops -%} + {%- else -%} + {% set hops = "X" -%} + {%- endif -%} + {%- if sbot_config.lis -%} + {%- set listen_addr = sbot_config.lis | split(pat=":") -%} + {%- else -%} + {%- set listen_addr = ["", ""] -%} + {%- endif -%} +*/ + +// TODO: read the sbot config and assign variables accordingly +// pass those variables into the template builder + +/// Scuttlebutt settings menu template builder. +pub fn build() -> PreEscaped { + let menu_template = html! { + (PreEscaped("")) + div class="card center" { + form id="sbotConfig" class="center" action="/settings/scuttlebutt/configure" method="post" { + div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Number of hops to replicate" { + label for="hops" class="label-small font-gray" { "HOPS" } + div id="hops" style="display: flex; justify-content: space-evenly;" { + div { + @if hops == 0 { + input type="radio" id="hops_0" name="hops" value="0" checked; + } @else { + input type="radio" id="hops_0" name="hops" value="0"; + } + label class="font-normal" for="hops_0" { "0" } + } + div { + @if hops == 1 { + input type="radio" id="hops_1" name="hops" value="1" checked; + } @else { + input type="radio" id="hops_1" name="hops" value="1"; + } + label class="font-normal" for="hops_1" { "1" } + } + div { + @if hops == 2 { + input type="radio" id="hops_2" name="hops" value="2" checked; + } @else { + input type="radio" id="hops_2" name="hops" value="2"; + } + label class="font-normal" for="hops_2" { "2" } + } + div { + @if hops == 3 { + input type="radio" id="hops_3" name="hops" value="3" checked; + } @else { + input type="radio" id="hops_3" name="hops" value="3"; + } + label class="font-normal" for="hops_3" { "3" } + } + div { + @if hops == 4 { + input type="radio" id="hops_4" name="hops" value="4" checked; + } @else { + input type="radio" id="hops_4" name="hops" value="4"; + } + label class="font-normal" for="hops_4" { "4" } + } + } + } + div class="center" style="display: flex; justify-content: space-between;" { + div style="display: flex; flex-direction: column; width: 60%; margin-bottom: 2rem;" title="IP address on which the sbot runs" { + label for="ip" class="label-small font-gray" { "IP ADDRESS" } + input type="text" id="ip" name="lis_ip" value="{{ listen_addr.0 }}"; + } + div style="display: flex; flex-direction: column; width: 20%; margin-bottom: 2rem;" title="Port on which the sbot runs" { + label for="port" class="label-small font-gray" { "PORT" } + input type="text" id="port" name="lis_port" value="{{ listen_addr.1 }}"; + } + } + div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Network key (aka 'caps key') to define the Scuttleverse in which the sbot operates in" { + label for="network_key" class="label-small font-gray" { "NETWORK KEY" } + input type="text" id="network_key" name="shscap" value="{{ sbot_config.shscap }}"; + } + div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Directory in which the sbot database is saved" { + label for="database_dir" class="label-small font-gray" { "DATABASE DIRECTORY" } + input type="text" id="database_dir" name="repo" value="{{ sbot_config.repo }}"; + } + div class="center" { + @if sbot_config.localadv == true { + input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv" checked; + } @else { + input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv"; + } + label class="font-normal" for="lanBroadcast" title="Broadcast the IP and port of this sbot instance so that local peers can discovery it and attempt to connect" { + "Enable LAN Broadcasting" + } + br; + @if sbot_config.localdiscov == true { + input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov" checked; + } @else { + input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov"; + } + label class="font-normal" for="lanDiscovery" title="Listen for the presence of local peers and attempt to connect if found" { "Enable LAN Discovery" } + br; + @if run_on_startup == "enabled" { + input type="checkbox" id="startup" style="margin-bottom: 1rem;" name="startup" checked; + } @else { + input type="checkbox" id="startup" style="margin-bottom: 1rem;" name="startup"; + } + label class="font-normal" for="startup" title="Run the pub automatically on system startup" { "Run pub when computer starts" } + br; + @if sbot_config.repair == true { + input type="checkbox" id="repair" name="repair" checked; + } @else { + input type="checkbox" id="repair" name="repair"; + } + label class="font-normal" for="repair" title="Attempt to repair the filesystem when starting the pub" { "Attempt filesystem repair when pub starts" } + } + (PreEscaped("")) + input type="hidden" id="debugdir" name="debugdir" value=(sbot_config.debugdir); + input type="hidden" id="hmac" name="hmac" value=(sbot_config.hmac); + input type="hidden" id="wslis" name="wslis" value=(sbot_config.wslis); + input type="hidden" id="debuglis" name="debuglis" value=(sbot_config.debuglis); + input type="hidden" id="enable_ebt" name="enable_ebt" value=(sbot_config.enable_ebt); + input type="hidden" id="promisc" name="promisc" value=(sbot_config.promisc); + input type="hidden" id="nounixsock" name="nounixsock" value=(sbot_config.nounixsock); + (PreEscaped("")) + input id="saveConfig" class="button button-primary center" style="margin-top: 2rem;" type="submit" title="Save configuration parameters to file" value="Save"; + input id="saveRestartConfig" class="button button-primary center" type="submit" title="Save configuration parameters to file and then (re)start the pub" value="Save & Restart" formaction="/settings/scuttlebutt/configure?restart=true"; + a id="restoreDefaults" class="button button-warning center" href="/settings/scuttlebutt/configure/default" title="Restore default configuration parameters and save them to file" { "Restore Defaults" } + } + (PreEscaped("")) + // TODO: flash message + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build(menu_template, "Scuttlebutt Settings", Some("/settings")); + + // render the base template with the provided body + templates::base::build(body) +} + +/* +{%- extends "nav" -%} +{%- block card %} + */ diff --git a/peach-web/src/routes/settings/scuttlebutt/mod.rs b/peach-web/src/routes/settings/scuttlebutt/mod.rs index b9a0e3e..a87cc1a 100644 --- a/peach-web/src/routes/settings/scuttlebutt/mod.rs +++ b/peach-web/src/routes/settings/scuttlebutt/mod.rs @@ -1 +1,2 @@ +pub mod configure_sbot; pub mod menu; -- 2.40.1 From c794d398b8775e2dee857d3903c87f6f63f6e2dd Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 13 Mar 2022 11:09:00 +0200 Subject: [PATCH 07/66] add sbot settings config route --- .../{configure_sbot.rs => configure.rs} | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) rename peach-web/src/routes/settings/scuttlebutt/{configure_sbot.rs => configure.rs} (81%) diff --git a/peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs b/peach-web/src/routes/settings/scuttlebutt/configure.rs similarity index 81% rename from peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs rename to peach-web/src/routes/settings/scuttlebutt/configure.rs index 6a5dd17..fdbdc1c 100644 --- a/peach-web/src/routes/settings/scuttlebutt/configure_sbot.rs +++ b/peach-web/src/routes/settings/scuttlebutt/configure.rs @@ -1,31 +1,49 @@ use maud::{html, PreEscaped}; -use peach_lib::sbot::SbotStatus; +use peach_lib::sbot::{SbotConfig, SbotStatus}; use crate::templates; // TODO: flash message implementation for rouille -// -/* -{# ASSIGN VARIABLES #} - {# ---------------- #} - {%- if sbot_config.hops -%} - {% set hops = sbot_config.hops -%} - {%- else -%} - {% set hops = "X" -%} - {%- endif -%} - {%- if sbot_config.lis -%} - {%- set listen_addr = sbot_config.lis | split(pat=":") -%} - {%- else -%} - {%- set listen_addr = ["", ""] -%} - {%- endif -%} -*/ +/// Read the status and configuration of the sbot. +/// Define fallback values if an error is returned from either read function. +fn read_status_and_config() -> (String, SbotConfig, String, String) { + // retrieve go-sbot systemd process status + let run_on_startup = if let Ok(status) = SbotStatus::read() { + // if the read is ok, return the value or "disabled" if no value is set + match status.boot_state { + Some(state) => state, + None => "disabled".to_string(), + } + } else { + // if the read returns an error, set the value to "disabled" + "disabled".to_string() + }; -// TODO: read the sbot config and assign variables accordingly -// pass those variables into the template builder + // retrieve sbot config parameters + let sbot_config = match SbotConfig::read() { + Ok(config) => config, + // build default config if an error is returned from the read attempt + Err(_) => SbotConfig::default(), + }; + + // split the listen address into ip and port + let (ip, port) = match sbot_config.lis.find(':') { + Some(index) => { + let (ip, port) = sbot_config.lis.split_at(index); + (ip.to_string(), port.to_string()) + } + // if no ':' separator is found, assume an ip has been configured (without port) + None => (sbot_config.lis.to_string(), String::new()), + }; + + (run_on_startup, sbot_config, ip, port) +} /// Scuttlebutt settings menu template builder. pub fn build() -> PreEscaped { + let (run_on_startup, sbot_config, ip, port) = read_status_and_config(); + let menu_template = html! { (PreEscaped("")) div class="card center" { @@ -34,7 +52,7 @@ pub fn build() -> PreEscaped { label for="hops" class="label-small font-gray" { "HOPS" } div id="hops" style="display: flex; justify-content: space-evenly;" { div { - @if hops == 0 { + @if sbot_config.hops == 0 { input type="radio" id="hops_0" name="hops" value="0" checked; } @else { input type="radio" id="hops_0" name="hops" value="0"; @@ -42,7 +60,7 @@ pub fn build() -> PreEscaped { label class="font-normal" for="hops_0" { "0" } } div { - @if hops == 1 { + @if sbot_config.hops == 1 { input type="radio" id="hops_1" name="hops" value="1" checked; } @else { input type="radio" id="hops_1" name="hops" value="1"; @@ -50,7 +68,7 @@ pub fn build() -> PreEscaped { label class="font-normal" for="hops_1" { "1" } } div { - @if hops == 2 { + @if sbot_config.hops == 2 { input type="radio" id="hops_2" name="hops" value="2" checked; } @else { input type="radio" id="hops_2" name="hops" value="2"; @@ -58,7 +76,7 @@ pub fn build() -> PreEscaped { label class="font-normal" for="hops_2" { "2" } } div { - @if hops == 3 { + @if sbot_config.hops == 3 { input type="radio" id="hops_3" name="hops" value="3" checked; } @else { input type="radio" id="hops_3" name="hops" value="3"; @@ -66,7 +84,7 @@ pub fn build() -> PreEscaped { label class="font-normal" for="hops_3" { "3" } } div { - @if hops == 4 { + @if sbot_config.hops == 4 { input type="radio" id="hops_4" name="hops" value="4" checked; } @else { input type="radio" id="hops_4" name="hops" value="4"; @@ -78,20 +96,20 @@ pub fn build() -> PreEscaped { div class="center" style="display: flex; justify-content: space-between;" { div style="display: flex; flex-direction: column; width: 60%; margin-bottom: 2rem;" title="IP address on which the sbot runs" { label for="ip" class="label-small font-gray" { "IP ADDRESS" } - input type="text" id="ip" name="lis_ip" value="{{ listen_addr.0 }}"; + input type="text" id="ip" name="lis_ip" value=(ip); } div style="display: flex; flex-direction: column; width: 20%; margin-bottom: 2rem;" title="Port on which the sbot runs" { label for="port" class="label-small font-gray" { "PORT" } - input type="text" id="port" name="lis_port" value="{{ listen_addr.1 }}"; + input type="text" id="port" name="lis_port" value=(port); } } div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Network key (aka 'caps key') to define the Scuttleverse in which the sbot operates in" { label for="network_key" class="label-small font-gray" { "NETWORK KEY" } - input type="text" id="network_key" name="shscap" value="{{ sbot_config.shscap }}"; + input type="text" id="network_key" name="shscap" value=(sbot_config.shscap); } div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Directory in which the sbot database is saved" { label for="database_dir" class="label-small font-gray" { "DATABASE DIRECTORY" } - input type="text" id="database_dir" name="repo" value="{{ sbot_config.repo }}"; + input type="text" id="database_dir" name="repo" value=(sbot_config.repo); } div class="center" { @if sbot_config.localadv == true { @@ -149,8 +167,3 @@ pub fn build() -> PreEscaped { // render the base template with the provided body templates::base::build(body) } - -/* -{%- extends "nav" -%} -{%- block card %} - */ -- 2.40.1 From 580771ebf21cf130688e636a2e54ae618fa06456 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 13 Mar 2022 11:09:39 +0200 Subject: [PATCH 08/66] update notes and add sbot settings config route to router --- peach-web/rouille_refactor | 3 ++- peach-web/src/main.rs | 4 ++++ peach-web/src/routes/settings/scuttlebutt/mod.rs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor index e120b0c..1fcf234 100644 --- a/peach-web/rouille_refactor +++ b/peach-web/rouille_refactor @@ -17,7 +17,8 @@ we do not need to be super fast or feature-rich. x scuttlebutt menu - configure_sbot x template - - sbot_config data + x sbot_config data + - might need some thought...render elements or input data x write the nav and base templates x get the homepage loading properly diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 8bd8977..9ca6149 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -136,6 +136,10 @@ fn main() { Response::html(routes::settings::scuttlebutt::menu::build()) }, + (GET) (/settings/scuttlebutt/configure) => { + Response::html(routes::settings::scuttlebutt::configure::build()) + }, + // The code block is called if none of the other blocks matches the request. // We return an empty response with a 404 status code. _ => Response::empty_404() diff --git a/peach-web/src/routes/settings/scuttlebutt/mod.rs b/peach-web/src/routes/settings/scuttlebutt/mod.rs index a87cc1a..485da58 100644 --- a/peach-web/src/routes/settings/scuttlebutt/mod.rs +++ b/peach-web/src/routes/settings/scuttlebutt/mod.rs @@ -1,2 +1,2 @@ -pub mod configure_sbot; +pub mod configure; pub mod menu; -- 2.40.1 From 7c98cfcd5dc25d8f97fa1655f9d58a0fadae4d26 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 14 Mar 2022 09:17:31 +0200 Subject: [PATCH 09/66] add admin menu and config routes; start thinking about flash msgs --- Cargo.lock | 2 +- peach-web/rouille_refactor | 16 +++-- peach-web/src/main.rs | 8 +++ .../settings/{admin.rs => admin.rs_old} | 2 + .../src/routes/settings/admin/configure.rs | 59 +++++++++++++++++++ peach-web/src/routes/settings/admin/menu.rs | 26 ++++++++ peach-web/src/routes/settings/admin/mod.rs | 2 + peach-web/src/routes/settings/mod.rs | 2 +- peach-web/src/templates/flash.rs | 20 +++++++ peach-web/src/templates/mod.rs | 1 + peach-web/src/utils.rs | 4 ++ 11 files changed, 135 insertions(+), 7 deletions(-) rename peach-web/src/routes/settings/{admin.rs => admin.rs_old} (99%) create mode 100644 peach-web/src/routes/settings/admin/configure.rs create mode 100644 peach-web/src/routes/settings/admin/menu.rs create mode 100644 peach-web/src/routes/settings/admin/mod.rs create mode 100644 peach-web/src/templates/flash.rs diff --git a/Cargo.lock b/Cargo.lock index 6320894..d8c939c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1774,7 +1774,7 @@ dependencies = [ [[package]] name = "kuska-ssb" version = "0.4.0" -source = "git+https://github.com/mycognosist/ssb.git?branch=private_messages#4ae3e9ea24b828b9f11cb3af6866e8656924b9d0" +source = "git+https://github.com/Kuska-ssb/ssb#fb7062de606e7c9cae8dd4df402a122db46c1b77" dependencies = [ "async-std", "async-stream 0.2.1", diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor index 1fcf234..8d71a21 100644 --- a/peach-web/rouille_refactor +++ b/peach-web/rouille_refactor @@ -19,9 +19,15 @@ we do not need to be super fast or feature-rich. x template x sbot_config data - might need some thought...render elements or input data + - admin + x menu + - configure + - change + - reset - x write the nav and base templates - x get the homepage loading properly - x route handler - x template - x file loading (static assets) + - write getter, setter and unsetter for flash messages + - from rocket docs + - A “removal” cookie is a cookie that has the same name as the original cookie but has an empty value, a max-age of 0, and an expiration date far in the past. + - use Response::with_additional_header() method to set cookie + - https://docs.rs/rouille/latest/rouille/struct.Response.html#method.with_additional_header + - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 9ca6149..e1ba8dd 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -140,6 +140,14 @@ fn main() { Response::html(routes::settings::scuttlebutt::configure::build()) }, + (GET) (/settings/admin) => { + Response::html(routes::settings::admin::menu::build()) + }, + + (GET) (/settings/admin/configure) => { + Response::html(routes::settings::admin::configure::build()) + }, + // The code block is called if none of the other blocks matches the request. // We return an empty response with a 404 status code. _ => Response::empty_404() diff --git a/peach-web/src/routes/settings/admin.rs b/peach-web/src/routes/settings/admin.rs_old similarity index 99% rename from peach-web/src/routes/settings/admin.rs rename to peach-web/src/routes/settings/admin.rs_old index 89b296f..c5d437a 100644 --- a/peach-web/src/routes/settings/admin.rs +++ b/peach-web/src/routes/settings/admin.rs_old @@ -1,3 +1,4 @@ +/* use rocket::{ form::{Form, FromForm}, get, post, @@ -120,3 +121,4 @@ pub fn delete_admin_post( ), } } +*/ diff --git a/peach-web/src/routes/settings/admin/configure.rs b/peach-web/src/routes/settings/admin/configure.rs new file mode 100644 index 0000000..d0f7329 --- /dev/null +++ b/peach-web/src/routes/settings/admin/configure.rs @@ -0,0 +1,59 @@ +use maud::{html, PreEscaped}; +use peach_lib::config_manager; + +use crate::templates; + +/// Administrator settings menu template builder. +pub fn build() -> PreEscaped { + // attempt to load peachcloud config file + let ssb_admins = config_manager::load_peach_config() + .ok() + .map(|config| config.ssb_admin_ids); + + let menu_template = html! { + (PreEscaped("")) + div class="card center" { + div class="capsule capsule-profile center-text font-normal border-info" style="font-family: var(--sans-serif); font-size: var(--font-size-6); margin-bottom: 1.5rem;" { + "Administrators are identified and added by their Scuttlebutt public keys. These accounts will be sent private messages on Scuttlebutt when a password reset is requested." + } + @if let Some(ref ssb_admin_ids) = ssb_admins { + @for admin in ssb_admin_ids { + form class="center" action="/settings/admin/delete" method="post" { + div class="center" style="display: flex; justify-content: space-between;" { + input type="hidden" name="ssb_id" value=(admin); + p class="label-small label-ellipsis font-gray" style="user-select: all;" { (admin) } + input style="width: 30%;" type="submit" class="button button-warning" value="Delete" title="Delete SSB administrator"; + } + } + } + } @else { + div class="card-text" { + "There are no currently configured admins." + } + } + form id="addAdmin" class="center" style="margin-top: 2rem;" action="/settings/admin/add" method="post" { + div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Public key (ID) of a desired administrator" { + label for="publicKey" class="label-small font-gray" { "PUBLIC KEY" } + input type="text" id="publicKey" name="ssb_id" placeholder="@xYz...=.ed25519" autofocus; + } + (PreEscaped("")) + input class="button button-primary center" type="submit" title="Add SSB administrator" value="Add Admin"; + (PreEscaped("")) + @if ssb_admins.is_none() { + (templates::flash::build("error", "Failed to read PeachCloud configuration file")) + } + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build( + menu_template, + "Configure Administrators", + Some("/settings/admin"), + ); + + // render the base template with the provided body + templates::base::build(body) +} diff --git a/peach-web/src/routes/settings/admin/menu.rs b/peach-web/src/routes/settings/admin/menu.rs new file mode 100644 index 0000000..24f57b3 --- /dev/null +++ b/peach-web/src/routes/settings/admin/menu.rs @@ -0,0 +1,26 @@ +use maud::{html, PreEscaped}; + +use crate::templates; + +/// Administrator settings menu template builder. +pub fn build() -> PreEscaped { + let menu_template = html! { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div id="settingsButtons" { + a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin" { "Configure Admin" } + a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password" { "Change Password" } + a id="reset" class="button button-primary center" href="/settings/admin/forgot_password" title="Reset Password" { "Reset Password" } + + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build(menu_template, "Administrator Settings", Some("/settings")); + + // render the base template with the provided body + templates::base::build(body) +} diff --git a/peach-web/src/routes/settings/admin/mod.rs b/peach-web/src/routes/settings/admin/mod.rs new file mode 100644 index 0000000..485da58 --- /dev/null +++ b/peach-web/src/routes/settings/admin/mod.rs @@ -0,0 +1,2 @@ +pub mod configure; +pub mod menu; diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index 0b2a088..2088ee3 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -1,4 +1,4 @@ -//pub mod admin; +pub mod admin; //pub mod dns; pub mod menu; //pub mod network; diff --git a/peach-web/src/templates/flash.rs b/peach-web/src/templates/flash.rs new file mode 100644 index 0000000..1a4c08e --- /dev/null +++ b/peach-web/src/templates/flash.rs @@ -0,0 +1,20 @@ +use maud::{html, Markup}; + +/// Flash message template builder. +/// +/// Render a flash elements based on the given flash name and message. +pub fn build(flash_name: &str, flash_msg: &str) -> Markup { + let flash_class = match flash_name { + "success" => "capsule center-text flash-message font-normal border-success", + "info" => "capsule center-text flash-message font-normal border-info", + "warning" => "capsule center-text flash-message font-normal border-warning", + "error" => "capsule center-text flash-message font-normal border-danger", + _ => "", + }; + + html! { + div class=(flash_class) { + (flash_msg) + } + } +} diff --git a/peach-web/src/templates/mod.rs b/peach-web/src/templates/mod.rs index 137668d..30ac0ee 100644 --- a/peach-web/src/templates/mod.rs +++ b/peach-web/src/templates/mod.rs @@ -1,2 +1,3 @@ pub mod base; +pub mod flash; pub mod nav; diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index fc63575..9f99492 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -24,6 +24,10 @@ pub fn set_theme(theme: Theme) { *writable_theme = theme; } +// get_cookie + +// set_cookie + /* pub mod monitor; -- 2.40.1 From 8455e8089c2fbc31529aeb8a478c96b285830275 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 17 Mar 2022 11:01:36 +0200 Subject: [PATCH 10/66] add base templates, settings routes and auth routes --- peach-web/Cargo.toml | 2 + peach-web/rouille_refactor | 25 +- peach-web/src/config.rs | 2 +- peach-web/src/main.rs | 58 ++- peach-web/src/routes/authentication.rs_old | 333 ++++++++++++++++++ peach-web/src/routes/authentication/change.rs | 101 ++++++ peach-web/src/routes/authentication/login.rs | 77 ++++ peach-web/src/routes/authentication/logout.rs | 18 + peach-web/src/routes/authentication/mod.rs | 4 + peach-web/src/routes/authentication/reset.rs | 101 ++++++ peach-web/src/routes/guide.rs | 103 ++++++ peach-web/src/routes/home.rs | 6 +- peach-web/src/routes/mod.rs | 5 +- peach-web/src/routes/settings/admin/add.rs | 33 ++ .../src/routes/settings/admin/configure.rs | 8 +- peach-web/src/routes/settings/admin/delete.rs | 36 ++ peach-web/src/routes/settings/admin/menu.rs | 11 +- peach-web/src/routes/settings/admin/mod.rs | 2 + peach-web/src/routes/settings/menu.rs | 6 +- .../routes/settings/scuttlebutt/configure.rs | 13 +- .../src/routes/settings/scuttlebutt/menu.rs | 7 +- peach-web/src/routes/status/mod.rs | 4 +- peach-web/src/routes/status/scuttlebutt.rs | 282 +++++++++++++-- .../src/routes/status/scuttlebutt.rs_old | 37 ++ peach-web/src/templates/base.rs | 2 +- peach-web/src/templates/flash.rs | 2 +- peach-web/src/templates/nav.rs | 8 +- 27 files changed, 1219 insertions(+), 67 deletions(-) create mode 100644 peach-web/src/routes/authentication.rs_old create mode 100644 peach-web/src/routes/authentication/change.rs create mode 100644 peach-web/src/routes/authentication/login.rs create mode 100644 peach-web/src/routes/authentication/logout.rs create mode 100644 peach-web/src/routes/authentication/mod.rs create mode 100644 peach-web/src/routes/authentication/reset.rs create mode 100644 peach-web/src/routes/guide.rs create mode 100644 peach-web/src/routes/settings/admin/add.rs create mode 100644 peach-web/src/routes/settings/admin/delete.rs create mode 100644 peach-web/src/routes/status/scuttlebutt.rs_old diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 97c4a92..74631dc 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -35,9 +35,11 @@ travis-ci = { repository = "peachcloud/peach-web", branch = "master" } maintenance = { status = "actively-developed" } [dependencies] +async-std = "1.10" base64 = "0.13.0" dirs = "4.0.0" env_logger = "0.8" +futures = "0.3" golgi = { path = "/home/glyph/Projects/playground/rust/golgi" } lazy_static = "1.4.0" log = "0.4" diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor index 8d71a21..debee1a 100644 --- a/peach-web/rouille_refactor +++ b/peach-web/rouille_refactor @@ -14,6 +14,8 @@ we do not need to be super fast or feature-rich. - write the settings route(s) x menu + x guide + - status x scuttlebutt menu - configure_sbot x template @@ -21,9 +23,26 @@ we do not need to be super fast or feature-rich. - might need some thought...render elements or input data - admin x menu - - configure - - change - - reset + x configure + x add + x delete + - auth + x change password + x form + x post + x reset password + x form + x post + x login + x form + x post + x logout + x get + +[ flash messages ] + + - for now, use simple redirects in the handlers + - then add flash messages later - write getter, setter and unsetter for flash messages - from rocket docs diff --git a/peach-web/src/config.rs b/peach-web/src/config.rs index 7fd85ad..5ea7b93 100644 --- a/peach-web/src/config.rs +++ b/peach-web/src/config.rs @@ -38,7 +38,7 @@ impl Config { for l in io::BufReader::new(file).lines() { let line = l?; - if line.len() == 0 { + if line.is_empty() { continue; } if let Some(i) = line.find('=') { diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index e1ba8dd..fa71ecf 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -118,34 +118,78 @@ fn main() { // static file server // matches on assets in the `static` directory - let response = rouille::match_assets(&request, "static"); + let response = rouille::match_assets(request, "static"); if response.is_success() { return response; } router!(request, (GET) (/) => { - Response::html(routes::home::build()) + Response::html(routes::home::build_template()) + }, + + (GET) (/auth/change) => { + Response::html(routes::authentication::change::build_template()) + }, + + (POST) (/auth/change) => { + routes::authentication::change::handle_form(request) + }, + + (GET) (/auth/login) => { + Response::html(routes::authentication::login::build_template()) + }, + + (POST) (/auth/login) => { + routes::authentication::login::handle_form(request) + }, + + (GET) (/auth/logout) => { + routes::authentication::logout::deauthenticate() + }, + + (GET) (/auth/reset) => { + Response::html(routes::authentication::reset::build_template()) + }, + + (POST) (/auth/reset) => { + routes::authentication::reset::handle_form(request) + }, + + (GET) (/guide) => { + Response::html(routes::guide::build_template()) }, (GET) (/settings) => { - Response::html(routes::settings::menu::build()) + Response::html(routes::settings::menu::build_template()) }, (GET) (/settings/scuttlebutt) => { - Response::html(routes::settings::scuttlebutt::menu::build()) + Response::html(routes::settings::scuttlebutt::menu::build_template()) }, (GET) (/settings/scuttlebutt/configure) => { - Response::html(routes::settings::scuttlebutt::configure::build()) + Response::html(routes::settings::scuttlebutt::configure::build_template()) }, (GET) (/settings/admin) => { - Response::html(routes::settings::admin::menu::build()) + Response::html(routes::settings::admin::menu::build_template()) + }, + + (POST) (/settings/admin/add) => { + routes::settings::admin::add::handle_form(request) }, (GET) (/settings/admin/configure) => { - Response::html(routes::settings::admin::configure::build()) + Response::html(routes::settings::admin::configure::build_template()) + }, + + (POST) (/settings/admin/delete) => { + routes::settings::admin::delete::handle_form(request) + }, + + (GET) (/status/scuttlebutt) => { + Response::html(routes::status::scuttlebutt::build_template()) }, // The code block is called if none of the other blocks matches the request. diff --git a/peach-web/src/routes/authentication.rs_old b/peach-web/src/routes/authentication.rs_old new file mode 100644 index 0000000..3ff8f2b --- /dev/null +++ b/peach-web/src/routes/authentication.rs_old @@ -0,0 +1,333 @@ +use log::info; +use rocket::{ + form::{Form, FromForm}, + get, + http::{Cookie, CookieJar, Status}, + post, + request::{self, FlashMessage, FromRequest, Request}, + response::{Flash, Redirect}, +}; +use rocket_dyn_templates::{tera::Context, Template}; + +use peach_lib::{error::PeachError, password_utils}; + +use crate::error::PeachWebError; +use crate::utils; +use crate::utils::TemplateOrRedirect; +use crate::RocketConfig; + +// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES + +pub const AUTH_COOKIE_KEY: &str = "peachweb_auth"; +pub const ADMIN_USERNAME: &str = "admin"; + +/// Note: Currently we use an empty struct for the Authenticated request guard +/// because there is only one user to be authenticated, and no data needs to be stored here. +/// In a multi-user authentication scheme, we would store the user_id in this struct, +/// and retrieve the correct user via the user_id stored in the cookie. +pub struct Authenticated; + +#[derive(Debug)] +pub enum LoginError { + UserNotLoggedIn, +} + +/// Request guard which returns an empty Authenticated struct from the request +/// if and only if the user has a cookie which proves they are authenticated with peach-web. +/// +/// Note that cookies.get_private uses encryption, which means that this private cookie +/// cannot be inspected, tampered with, or manufactured by clients. +#[rocket::async_trait] +impl<'r> FromRequest<'r> for Authenticated { + type Error = LoginError; + + async fn from_request(req: &'r Request<'_>) -> request::Outcome { + // retrieve auth state from managed state (returns `Option`). + // this value is read from the Rocket.toml config file on start-up + let authentication_is_disabled: bool = *req + .rocket() + .state::() + .map(|config| (&config.disable_auth)) + .unwrap_or(&false); + + if authentication_is_disabled { + let auth = Authenticated {}; + request::Outcome::Success(auth) + } else { + let authenticated = req + .cookies() + .get_private(AUTH_COOKIE_KEY) + .and_then(|cookie| cookie.value().parse().ok()) + .map(|_value: String| Authenticated {}); + match authenticated { + Some(auth) => request::Outcome::Success(auth), + None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)), + } + } + } +} + +// HELPERS AND ROUTES FOR /login + +#[get("/login")] +pub fn login(flash: Option) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Login".to_string())); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + }; + + Template::render("login", &context.into_json()) +} + +#[derive(Debug, FromForm)] +pub struct LoginForm { + pub password: String, +} + +/// Takes in a LoginForm and returns Ok(()) if the password is correct. +/// +/// Note: there is currently only one user, therefore we don't need a username. +pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { + password_utils::verify_password(&login_form.password) +} + +#[post("/login", data = "")] +pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> TemplateOrRedirect { + match verify_login_form(login_form.into_inner()) { + Ok(_) => { + // if successful login, add a cookie indicating the user is authenticated + // and redirect to home page + // NOTE: since we currently have just one user, the value of the cookie + // is just admin (this is arbitrary). + // If we had multiple users, we could put the user_id here. + cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); + + TemplateOrRedirect::Redirect(Redirect::to("/")) + } + Err(e) => { + let err_msg = format!("Invalid password: {}", e); + // if unsuccessful login, render /login page again + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Login".to_string())); + context.insert("flash_name", &("error".to_string())); + context.insert("flash_msg", &(err_msg)); + + TemplateOrRedirect::Template(Template::render("login", &context.into_json())) + } + } +} + +// HELPERS AND ROUTES FOR /logout + +#[get("/logout")] +pub fn logout(cookies: &CookieJar<'_>) -> Flash { + // logout authenticated user + info!("Attempting deauthentication of user."); + cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY)); + Flash::success(Redirect::to("/login"), "Logged out") +} + +// HELPERS AND ROUTES FOR /reset_password + +#[derive(Debug, FromForm)] +pub struct ResetPasswordForm { + pub temporary_password: String, + pub new_password1: String, + pub new_password2: String, +} + +/// Verify, validate and save the submitted password. This function is publicly exposed for users who have forgotten their password. +pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> { + info!( + "reset password!: {} {} {}", + password_form.temporary_password, password_form.new_password1, password_form.new_password2 + ); + password_utils::verify_temporary_password(&password_form.temporary_password)?; + // if the previous line did not throw an error, then the secret_link is correct + password_utils::validate_new_passwords( + &password_form.new_password1, + &password_form.new_password2, + )?; + // if the previous line did not throw an error, then the new password is valid + password_utils::set_new_password(&password_form.new_password1)?; + Ok(()) +} + +/// Password reset request handler. This route is used by a user who is not logged in +/// and is specifically for users who have forgotten their password. +#[get("/reset_password")] +pub fn reset_password(flash: Option) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Reset Password".to_string())); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + }; + + Template::render("settings/admin/reset_password", &context.into_json()) +} + +/// Password reset form request handler. This route is used by a user who is not logged in +/// and is specifically for users who have forgotten their password. +#[post("/reset_password", data = "")] +pub fn reset_password_post(reset_password_form: Form) -> Template { + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Reset Password".to_string())); + + let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { + Ok(_) => ( + "success".to_string(), + "New password has been saved. Return home to login".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to reset password: {}", err), + ), + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/admin/reset_password", &context.into_json()) +} + +// HELPERS AND ROUTES FOR /send_password_reset + +/// Page for users who have forgotten their password. +/// This route is used by a user who is not logged in +/// to initiate the sending of a new password reset. +#[get("/forgot_password")] +pub fn forgot_password_page(flash: Option) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Send Password Reset".to_string())); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + }; + + Template::render("settings/admin/forgot_password", &context.into_json()) +} + +/// Send password reset request handler. This route is used by a user who is not logged in +/// and is specifically for users who have forgotten their password. A successful request results +/// in a Scuttlebutt private message being sent to the account of the device admin. +#[post("/send_password_reset")] +pub fn send_password_reset_post() -> Template { + info!("++ send password reset post"); + let mut context = Context::new(); + context.insert("back", &Some("/".to_string())); + context.insert("title", &Some("Send Password Reset".to_string())); + + let (flash_name, flash_msg) = match password_utils::send_password_reset() { + Ok(_) => ( + "success".to_string(), + "A password reset link has been sent to the admin of this device".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to send password reset link: {}", err), + ), + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/admin/forgot_password", &context.into_json()) +} + +// HELPERS AND ROUTES FOR /settings/change_password + +#[derive(Debug, FromForm)] +pub struct PasswordForm { + pub current_password: String, + pub new_password1: String, + pub new_password2: String, +} + +/// Password save form request handler. This function is for use by a user who is already logged in to change their password. +pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> { + info!( + "change password!: {} {} {}", + password_form.current_password, password_form.new_password1, password_form.new_password2 + ); + password_utils::verify_password(&password_form.current_password)?; + // if the previous line did not throw an error, then the old password is correct + password_utils::validate_new_passwords( + &password_form.new_password1, + &password_form.new_password2, + )?; + // if the previous line did not throw an error, then the new password is valid + password_utils::set_new_password(&password_form.new_password1)?; + Ok(()) +} + +/// Change password request handler. This is used by a user who is already logged in. +#[get("/change_password")] +pub fn change_password(flash: Option, _auth: Authenticated) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/settings/admin".to_string())); + context.insert("title", &Some("Change Password".to_string())); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + }; + + Template::render("settings/admin/change_password", &context.into_json()) +} + +/// Change password form request handler. This route is used by a user who is already logged in. +#[post("/change_password", data = "")] +pub fn change_password_post(password_form: Form, _auth: Authenticated) -> Template { + let mut context = Context::new(); + context.insert("back", &Some("/settings/admin".to_string())); + context.insert("title", &Some("Change Password".to_string())); + + let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { + Ok(_) => ( + "success".to_string(), + "New password has been saved".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!("Failed to save new password: {}", err), + ), + }; + + context.insert("flash_name", &Some(flash_name)); + context.insert("flash_msg", &Some(flash_msg)); + + Template::render("settings/admin/change_password", &context.into_json()) +} diff --git a/peach-web/src/routes/authentication/change.rs b/peach-web/src/routes/authentication/change.rs new file mode 100644 index 0000000..2fdd172 --- /dev/null +++ b/peach-web/src/routes/authentication/change.rs @@ -0,0 +1,101 @@ +use log::info; +use maud::{html, PreEscaped}; +use peach_lib::password_utils; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{error::PeachWebError, templates}; + +// HELPER AND ROUTES FOR /auth/change (GET and POST) + +/// Password change form template builder. +pub fn build_template() -> PreEscaped { + let form_template = html! { + (PreEscaped("")) + div class="card center" { + form id="changePassword" class="center" action="/auth/change" method="post" { + div style="display: flex; flex-direction: column; margin-bottom: 1rem;" { + (PreEscaped("")) + label for="currentPassword" class="center label-small font-gray" style="width: 80%;" { "CURRENT PASSWORD" } + input id="currentPassword" class="center input" name="current_password" type="password" title="Current password" autofocus; + (PreEscaped("")) + label for="newPassword" class="center label-small font-gray" style="width: 80%;" { "NEW PASSWORD" } + input id="newPassword" class="center input" name="new_password1" type="password" title="New password"; + (PreEscaped("")) + label for="newPasswordDuplicate" class="center label-small font-gray" style="width: 80%;" { "RE-ENTER NEW PASSWORD" } + input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" title="New password duplicate"; + (PreEscaped("")) + input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save"; + a class="button button-secondary center" href="/settings/admin" title="Cancel"{ "Cancel" } + } + } + (PreEscaped("")) + // TODO: render flash message + //{% include "snippets/flash_message" %} + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = + templates::nav::build_template(form_template, "Change Password", Some("/settings/admin")); + + // render the base template with the provided body + templates::base::build_template(body) +} + +/// Verify, validate and set a new password, overwriting the current password. +pub fn save_password( + current_password: &str, + new_password1: &str, + new_password2: &str, +) -> Result<(), PeachWebError> { + info!( + "Attempting password change: {} {} {}", + current_password, new_password1, new_password2 + ); + + // check that the supplied value matches the actual current password + password_utils::verify_password(current_password)?; + + // ensure that both new_password values match + password_utils::validate_new_passwords(new_password1, new_password2)?; + + // hash the password and save the hash to file + password_utils::set_new_password(new_password1)?; + + Ok(()) +} + +/// Parse current and new passwords from the submitted form, save the new +/// password hash to file (`/var/lib/peachcloud/config.yml`) and redirect +/// to the change password form URL. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + current_password: String, + new_password1: String, + new_password2: String, + })); + + // save submitted admin id to file + let _result = save_password( + &data.current_password, + &data.new_password1, + &data.new_password2, + ); + + // TODO: match on result and define flash message accordingly + // then send the redirect response + + // redirect to the configure admin page + // TODO: add flash message + Response::redirect_303("/auth/change") +} + +/* + match result { + Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), + Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), + } +*/ diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs new file mode 100644 index 0000000..7c0912e --- /dev/null +++ b/peach-web/src/routes/authentication/login.rs @@ -0,0 +1,77 @@ +use log::info; +use maud::{html, PreEscaped}; +use peach_lib::password_utils; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::templates; + +// HELPER AND ROUTES FOR /auth/login (GET and POST) + +/// Login form template builder. +pub fn build_template() -> PreEscaped { + let form_template = html! { + (PreEscaped("")) + div class="card center" { + form id="login_form" class="center" action="/auth/login" method="post" { + div style="display: flex; flex-direction: column; margin-bottom: 1rem;" { + (PreEscaped("")) + label for="password" class="center label-small font-gray" style="width: 80%;" { "PASSWORD" } + input id="password" name="password" class="center input" type="password" title="Password for given username"; + (PreEscaped("")) + input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login"; + div class="center-text" style="margin-top: 1rem;" { + a href="/settings/admin/forgot_password" class="label-small link font-gray" { "Forgot Password?" } + } + } + (PreEscaped("")) + // TODO: render flash message + //{% include "snippets/flash_message" %} + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build_template(form_template, "Login", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) +} + +/// Parse and verify the submitted password. If verification succeeds, set the +/// auth session cookie and redirect to the home page. If not, set a flash +/// message and redirect to the login page. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { password: String })); + + // TODO: match on result and define flash message accordingly + // then send the redirect response + match password_utils::verify_password(&data.password) { + Ok(_) => { + info!("Successful login attempt"); + // if successful login, add a cookie indicating the user is authenticated + // and redirect to home page + // NOTE: since we currently have just one user, the value of the cookie + // is just admin (this is arbitrary). + // If we had multiple users, we could put the user_id here. + //cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); + + Response::redirect_303("/") + } + Err(_e) => { + info!("Unsuccessful login attempt"); + //let err_msg = format!("Invalid password: {}", e); + // if unsuccessful login, render /login page again + + /* + // TODO: add flash message + context.insert("flash_name", &("error".to_string())); + context.insert("flash_msg", &(err_msg)); + */ + + Response::redirect_303("/auth/login") + } + } +} diff --git a/peach-web/src/routes/authentication/logout.rs b/peach-web/src/routes/authentication/logout.rs new file mode 100644 index 0000000..d3bb81b --- /dev/null +++ b/peach-web/src/routes/authentication/logout.rs @@ -0,0 +1,18 @@ +use log::info; +use rouille::Response; + +// HELPER AND ROUTES FOR /auth/logout (GET) + +/// Deauthenticate the logged-in user by removing the auth cookie. +/// Redirect to the login page. +pub fn deauthenticate() -> Response { + // logout authenticated user + info!("Attempting deauthentication of user."); + // TODO: remove auth cookie + //cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY)); + + // redirect to the login page + // TODO: add flash message + //Flash::success(Redirect::to("/login"), "Logged out") + Response::redirect_303("/auth/login".to_string()) +} diff --git a/peach-web/src/routes/authentication/mod.rs b/peach-web/src/routes/authentication/mod.rs new file mode 100644 index 0000000..f0b9e67 --- /dev/null +++ b/peach-web/src/routes/authentication/mod.rs @@ -0,0 +1,4 @@ +pub mod change; +pub mod login; +pub mod logout; +pub mod reset; diff --git a/peach-web/src/routes/authentication/reset.rs b/peach-web/src/routes/authentication/reset.rs new file mode 100644 index 0000000..37a6ec5 --- /dev/null +++ b/peach-web/src/routes/authentication/reset.rs @@ -0,0 +1,101 @@ +use log::info; +use maud::{html, PreEscaped}; +use peach_lib::password_utils; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{error::PeachWebError, templates}; + +// HELPER AND ROUTES FOR /auth/reset (GET and POST) + +/// Password reset form template builder. +pub fn build_template() -> PreEscaped { + let form_template = html! { + (PreEscaped("")) + div class="card center" { + form id="resetPassword" class="center" action="/auth/reset" method="post" { + div style="display: flex; flex-direction: column; margin-bottom: 1rem;" { + (PreEscaped("")) + label for="temporaryPassword" class="center label-small font-gray" style="width: 80%;" { "TEMPORARY PASSWORD" } + input id="temporaryPassword" class="center input" name="temporary_password" type="password" title="Temporary password" autofocus; + (PreEscaped("")) + label for="newPassword" class="center label-small font-gray" style="width: 80%;" { "NEW PASSWORD" } + input id="newPassword" class="center input" name="new_password1" type="password" title="New password"; + (PreEscaped("")) + label for="newPasswordDuplicate" class="center label-small font-gray" style="width: 80%;" { "RE-ENTER NEW PASSWORD" } + input id="newPasswordDuplicate" class="center input" name="new_password2" type="password" title="New password duplicate"; + (PreEscaped("")) + input id="savePassword" class="button button-primary center" title="Add" type="submit" value="Save"; + a class="button button-secondary center" href="/settings/admin" title="Cancel"{ "Cancel" } + } + } + (PreEscaped("")) + // TODO: render flash message + //{% include "snippets/flash_message" %} + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = + templates::nav::build_template(form_template, "Reset Password", Some("/settings/admin")); + + // render the base template with the provided body + templates::base::build_template(body) +} + +/// Verify, validate and set a new password, overwriting the current password. +pub fn save_password( + temporary_password: &str, + new_password1: &str, + new_password2: &str, +) -> Result<(), PeachWebError> { + info!( + "Attempting password reset: {} {} {}", + temporary_password, new_password1, new_password2 + ); + + // check that the supplied value matches the actual temporary password + password_utils::verify_temporary_password(temporary_password)?; + + // ensure that both new_password values match + password_utils::validate_new_passwords(new_password1, new_password2)?; + + // hash the password and save the hash to file + password_utils::set_new_password(new_password1)?; + + Ok(()) +} + +/// Parse temporary and new passwords from the submitted form, save the new +/// password hash to file (`/var/lib/peachcloud/config.yml`) and redirect +/// to the reset password form URL. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + temporary_password: String, + new_password1: String, + new_password2: String, + })); + + // save submitted admin id to file + let _result = save_password( + &data.temporary_password, + &data.new_password1, + &data.new_password2, + ); + + // TODO: match on result and define flash message accordingly + // then send the redirect response + + // redirect to the configure admin page + // TODO: add flash message + Response::redirect_303("/auth/reset") +} + +/* + match result { + Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), + Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), + } +*/ diff --git a/peach-web/src/routes/guide.rs b/peach-web/src/routes/guide.rs new file mode 100644 index 0000000..2898d0f --- /dev/null +++ b/peach-web/src/routes/guide.rs @@ -0,0 +1,103 @@ +use maud::{html, PreEscaped}; + +use crate::templates; + +/// Guide template builder. +pub fn build_template() -> PreEscaped { + // render the guide template html + let guide_template = html! { + (PreEscaped("")) + div class="card card-wide center" { + div class="capsule capsule-container border-info" { + (PreEscaped("")) + details { + summary class="card-text link" { "Getting started" } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "The Scuttlebutt server (sbot) will be inactive when you first run PeachCloud. This is to allow configuration parameters to be set before it is activated for the first time. Navigate to the " + strong { + a href="/settings/scuttlebutt/configure" class="link font-gray" { + "Sbot Configuration" + } + } + " page to configure your system. The default configuration will be fine for most usecases." + } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "Once the configuration is set, navigate to the " + strong { + a href="/settings/scuttlebutt" class="link font-gray" { + "Scuttlebutt settings menu" + } + } + " to start the sbot. If the server starts successfully, you will see a green smiley face on the home page. If the face is orange and sleeping, that means the sbot is still inactive (ie. the process is not running). If the face is red and dead, that means the sbot failed to start - indicated an error. For now, the best way to gain insight into the problem is to check the systemd log. Open a terminal and enter: " + code { "systemctl --user status go-sbot.service" } + ". The log output may give some clues about the source of the error." + } + } + (PreEscaped("")) + details { + summary class="card-text link" { "Submit a bug report" } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "Bug reports can be submitted by " + strong { + a href="https://git.coopcloud.tech/PeachCloud/peach-workspace/issues/new?template=BUG_TEMPLATE.md" class="link font-gray" { + "filing an issue" + } + } + " on the peach-workspace git repo. Before filing a report, first check to see if an issue already exists for the bug you've encountered. If not, you're invited to submit a new report; the template will guide you through several questions." + } + } + (PreEscaped("")) + details { + summary class="card-text link" { "Share feedback & request support" } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "You're invited to share your thoughts and experiences of PeachCloud in the #peachcloud channel on Scuttlebutt. The channel is also a good place to ask for help." + } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "Alternatively, we have a " + strong { + a href="https://matrix.to/#/#peachcloud:matrix.org" class="link font-gray" { + "Matrix channel" + } + } + " for discussion about PeachCloud and you can also reach out to @glyph " + strong { + a href="mailto:glyph@mycelial.technology" class="link font-gray" { + "via email" + } + } + "." + } + } + (PreEscaped("")) + details { + summary class="card-text link" { "Contribute to PeachCloud" } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "PeachCloud is free, open-source software and relies on donations and grants to fund develop. Donations can be made on our " + strong { + a href="https://opencollective.com/peachcloud" class="link font-gray" { + "Open Collective" + } + } + " page." + } + p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { + "Programmers, designers, artists and writers are also welcome to contribute to the project. Please visit the " + strong { + a href="https://git.coopcloud.tech/PeachCloud/peach-workspace" class="link font-gray" { + "main PeachCloud git repository" + } + } + " to find out more details or contact the team via Scuttlebutt, Matrix or email." + } + } + } + } + }; + + // wrap the nav bars around the home template content + // title is "" and back button link is `None` because this is the homepage + let body = templates::nav::build_template(guide_template, "Guide", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) +} diff --git a/peach-web/src/routes/home.rs b/peach-web/src/routes/home.rs index 2dc1223..76e9b4d 100644 --- a/peach-web/src/routes/home.rs +++ b/peach-web/src/routes/home.rs @@ -41,7 +41,7 @@ fn render_status_elements<'a>() -> (&'a str, &'a str, &'a str) { } /// Home template builder. -pub fn build<'a>() -> PreEscaped { +pub fn build_template() -> PreEscaped { let (center_circle_class, center_circle_text, status_circle_class) = render_status_elements(); // render the home template html @@ -111,8 +111,8 @@ pub fn build<'a>() -> PreEscaped { // wrap the nav bars around the home template content // title is "" and back button link is `None` because this is the homepage - let body = templates::nav::build(home_template, "", None); + let body = templates::nav::build_template(home_template, "", None); // render the base template with the provided body - templates::base::build(body) + templates::base::build_template(body) } diff --git a/peach-web/src/routes/mod.rs b/peach-web/src/routes/mod.rs index 1c94673..37655b6 100644 --- a/peach-web/src/routes/mod.rs +++ b/peach-web/src/routes/mod.rs @@ -1,7 +1,8 @@ -//pub mod authentication; +pub mod authentication; //pub mod catchers; //pub mod index; +pub mod guide; pub mod home; //pub mod scuttlebutt; pub mod settings; -//pub mod status; +pub mod status; diff --git a/peach-web/src/routes/settings/admin/add.rs b/peach-web/src/routes/settings/admin/add.rs new file mode 100644 index 0000000..cc63873 --- /dev/null +++ b/peach-web/src/routes/settings/admin/add.rs @@ -0,0 +1,33 @@ +use peach_lib::config_manager; +use rouille::{post_input, try_or_400, Request, Response}; + +// HELPER AND ROUTES FOR /settings/admin/add + +/// Parse an `admin_id` from the submitted form, save it to file +/// (`/var/lib/peachcloud/config.yml`) and redirect to the administrator +/// configuration URL. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + // the public key of a desired administrator + ssb_id: String, + })); + + // save submitted admin id to file + let _result = config_manager::add_ssb_admin_id(&data.ssb_id); + + // TODO: match on result and define flash message accordingly + // then send the redirect response + + // redirect to the configure admin page + // TODO: add flash message + Response::redirect_303("/settings/admin/configure") +} + +/* + match result { + Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), + Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), + } +*/ diff --git a/peach-web/src/routes/settings/admin/configure.rs b/peach-web/src/routes/settings/admin/configure.rs index d0f7329..7edcb9e 100644 --- a/peach-web/src/routes/settings/admin/configure.rs +++ b/peach-web/src/routes/settings/admin/configure.rs @@ -4,7 +4,7 @@ use peach_lib::config_manager; use crate::templates; /// Administrator settings menu template builder. -pub fn build() -> PreEscaped { +pub fn build_template() -> PreEscaped { // attempt to load peachcloud config file let ssb_admins = config_manager::load_peach_config() .ok() @@ -40,7 +40,7 @@ pub fn build() -> PreEscaped { input class="button button-primary center" type="submit" title="Add SSB administrator" value="Add Admin"; (PreEscaped("")) @if ssb_admins.is_none() { - (templates::flash::build("error", "Failed to read PeachCloud configuration file")) + (templates::flash::build_template("error", "Failed to read PeachCloud configuration file")) } } } @@ -48,12 +48,12 @@ pub fn build() -> PreEscaped { // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build( + let body = templates::nav::build_template( menu_template, "Configure Administrators", Some("/settings/admin"), ); // render the base template with the provided body - templates::base::build(body) + templates::base::build_template(body) } diff --git a/peach-web/src/routes/settings/admin/delete.rs b/peach-web/src/routes/settings/admin/delete.rs new file mode 100644 index 0000000..5a21c36 --- /dev/null +++ b/peach-web/src/routes/settings/admin/delete.rs @@ -0,0 +1,36 @@ +use peach_lib::config_manager; +use rouille::{post_input, try_or_400, Request, Response}; + +// HELPERS AND ROUTES FOR /settings/admin/delete + +/// Parse an `admin_id` from the submitted form, delete it from file +/// (`/var/lib/peachcloud/config.yml`) and redirect to the administrator +/// configuration URL. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + // the public key of a desired administrator + ssb_id: String, + })); + + // remove submitted admin id from file + let _result = config_manager::delete_ssb_admin_id(&data.ssb_id); + + // TODO: match on result and define flash message accordingly + // then send the redirect response + + // redirect to the configure admin page + // TODO: add flash message + Response::redirect_303("/settings/admin/configure") +} + +/* + match result { + Ok(_) => Flash::success(Redirect::to(url), "Removed SSB administrator"), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to remove admin id: {}", e), + ), + } +*/ diff --git a/peach-web/src/routes/settings/admin/menu.rs b/peach-web/src/routes/settings/admin/menu.rs index 24f57b3..036fea0 100644 --- a/peach-web/src/routes/settings/admin/menu.rs +++ b/peach-web/src/routes/settings/admin/menu.rs @@ -3,15 +3,15 @@ use maud::{html, PreEscaped}; use crate::templates; /// Administrator settings menu template builder. -pub fn build() -> PreEscaped { +pub fn build_template() -> PreEscaped { let menu_template = html! { (PreEscaped("")) div class="card center" { (PreEscaped("")) div id="settingsButtons" { a id="configure" class="button button-primary center" href="/settings/admin/configure" title="Configure Admin" { "Configure Admin" } - a id="change" class="button button-primary center" href="/settings/admin/change_password" title="Change Password" { "Change Password" } - a id="reset" class="button button-primary center" href="/settings/admin/forgot_password" title="Reset Password" { "Reset Password" } + a id="change" class="button button-primary center" href="/auth/change" title="Change Password" { "Change Password" } + a id="reset" class="button button-primary center" href="/auth/reset" title="Reset Password" { "Reset Password" } } } @@ -19,8 +19,9 @@ pub fn build() -> PreEscaped { // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build(menu_template, "Administrator Settings", Some("/settings")); + let body = + templates::nav::build_template(menu_template, "Administrator Settings", Some("/settings")); // render the base template with the provided body - templates::base::build(body) + templates::base::build_template(body) } diff --git a/peach-web/src/routes/settings/admin/mod.rs b/peach-web/src/routes/settings/admin/mod.rs index 485da58..93bb64c 100644 --- a/peach-web/src/routes/settings/admin/mod.rs +++ b/peach-web/src/routes/settings/admin/mod.rs @@ -1,2 +1,4 @@ +pub mod add; pub mod configure; +pub mod delete; pub mod menu; diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs index 121aefd..9a3c16c 100644 --- a/peach-web/src/routes/settings/menu.rs +++ b/peach-web/src/routes/settings/menu.rs @@ -5,7 +5,7 @@ use crate::{templates, CONFIG}; // TODO: flash message implementation for rouille // /// Settings menu template builder. -pub fn build() -> PreEscaped { +pub fn build_template() -> PreEscaped { let menu_template = html! { (PreEscaped("")) div class="card center" { @@ -23,8 +23,8 @@ pub fn build() -> PreEscaped { // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build(menu_template, "Settings", Some("/")); + let body = templates::nav::build_template(menu_template, "Settings", Some("/")); // render the base template with the provided body - templates::base::build(body) + templates::base::build_template(body) } diff --git a/peach-web/src/routes/settings/scuttlebutt/configure.rs b/peach-web/src/routes/settings/scuttlebutt/configure.rs index fdbdc1c..c53c1c8 100644 --- a/peach-web/src/routes/settings/scuttlebutt/configure.rs +++ b/peach-web/src/routes/settings/scuttlebutt/configure.rs @@ -41,7 +41,7 @@ fn read_status_and_config() -> (String, SbotConfig, String, String) { } /// Scuttlebutt settings menu template builder. -pub fn build() -> PreEscaped { +pub fn build_template() -> PreEscaped { let (run_on_startup, sbot_config, ip, port) = read_status_and_config(); let menu_template = html! { @@ -112,7 +112,7 @@ pub fn build() -> PreEscaped { input type="text" id="database_dir" name="repo" value=(sbot_config.repo); } div class="center" { - @if sbot_config.localadv == true { + @if sbot_config.localadv { input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv" checked; } @else { input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv"; @@ -121,7 +121,7 @@ pub fn build() -> PreEscaped { "Enable LAN Broadcasting" } br; - @if sbot_config.localdiscov == true { + @if sbot_config.localdiscov { input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov" checked; } @else { input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov"; @@ -135,7 +135,7 @@ pub fn build() -> PreEscaped { } label class="font-normal" for="startup" title="Run the pub automatically on system startup" { "Run pub when computer starts" } br; - @if sbot_config.repair == true { + @if sbot_config.repair { input type="checkbox" id="repair" name="repair" checked; } @else { input type="checkbox" id="repair" name="repair"; @@ -162,8 +162,9 @@ pub fn build() -> PreEscaped { // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build(menu_template, "Scuttlebutt Settings", Some("/settings")); + let body = + templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings")); // render the base template with the provided body - templates::base::build(body) + templates::base::build_template(body) } diff --git a/peach-web/src/routes/settings/scuttlebutt/menu.rs b/peach-web/src/routes/settings/scuttlebutt/menu.rs index c8b35df..58c9822 100644 --- a/peach-web/src/routes/settings/scuttlebutt/menu.rs +++ b/peach-web/src/routes/settings/scuttlebutt/menu.rs @@ -29,7 +29,7 @@ fn render_process_buttons() -> PreEscaped { } /// Scuttlebutt settings menu template builder. -pub fn build() -> PreEscaped { +pub fn build_template() -> PreEscaped { let menu_template = html! { (PreEscaped("")) div class="card center" { @@ -44,8 +44,9 @@ pub fn build() -> PreEscaped { // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build(menu_template, "Scuttlebutt Settings", Some("/settings")); + let body = + templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings")); // render the base template with the provided body - templates::base::build(body) + templates::base::build_template(body) } diff --git a/peach-web/src/routes/status/mod.rs b/peach-web/src/routes/status/mod.rs index a2e0f54..90d533e 100644 --- a/peach-web/src/routes/status/mod.rs +++ b/peach-web/src/routes/status/mod.rs @@ -1,3 +1,3 @@ -pub mod device; -pub mod network; +//pub mod device; +//pub mod network; pub mod scuttlebutt; diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 34aa61d..f2eb965 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -1,37 +1,271 @@ -use rocket::{get, State}; -use rocket_dyn_templates::Template; +use std::error::Error; -use crate::routes::authentication::Authenticated; -use crate::{context::scuttlebutt::StatusContext, RocketConfig}; +use async_std::task; +use futures::stream::TryStreamExt; +use golgi::{messages::SsbMessageValue, Sbot}; +use maud::{html, Markup, PreEscaped}; +use peach_lib::sbot::{SbotConfig, SbotStatus}; -// HELPERS AND ROUTES FOR /status/scuttlebutt +use crate::{error::PeachWebError, templates}; -#[get("/scuttlebutt")] -pub async fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { - let context = StatusContext::build().await; +/* +{# ASSIGN VARIABLES #} +{# ---------------- #} +{%- if sbot_status.memory -%} +{% set mem = sbot_status.memory / 1024 / 1024 | round | int -%} +{%- else -%} +{% set mem = "0" -%} +{%- endif -%} +{%- if sbot_status.blobstore -%} +{% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%} +{%- else -%} +{% set blobs = "0" -%} +{%- endif -%} +*/ - let back = if config.standalone_mode { - // return to home page - Some("/".to_string()) - } else { - // return to status menu - Some("/status".to_string()) +// HELPER FUNCTIONS + +pub async fn init_sbot_with_config( + sbot_config: &Option, +) -> Result { + // initialise sbot connection with ip:port and shscap from config file + let sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None).await? + } + None => Sbot::init(None, None).await?, }; - match context { - Ok(mut context) => { - // define back arrow url based on mode - context.back = back; + Ok(sbot_client) +} - Template::render("status/scuttlebutt", &context) +fn latest_sequence_number() -> Result> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // retrieve the local id + let id = sbot_client.whoami().await?; + + let history_stream = sbot_client.create_history_stream(id).await?; + let mut msgs: Vec = history_stream.try_collect().await?; + + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); + + // return the sequence number of the latest msg + Ok(msgs[0].sequence) + }) +} + +fn downtime_element(downtime: &Option) -> Markup { + match downtime { + Some(time) => { + html! { + label class="label-small font-gray" for="sbotDowntime" title="go-sbot downtime" style="margin-top: 0.5rem;" { "DOWNTIME" } + p id="sbotDowntime" class="card-text" title="Downtime" { (time) } + } } - Err(_) => { - let mut context = StatusContext::default(); + _ => html! { (PreEscaped("")) }, + } +} - // define back arrow url based on mode - context.back = back; +fn uptime_element(uptime: &Option) -> Markup { + match uptime { + Some(time) => { + html! { + label class="label-small font-gray" for="sbotUptime" title="go-sbot uptime" style="margin-top: 0.5rem;" { "UPTIME" } + p id="sbotUptime" class="card-text" title="Uptime" { (time) } + } + } + _ => html! { (PreEscaped("")) }, + } +} - Template::render("status/scuttlebutt", &context) +fn run_on_startup_element(boot_state: &Option) -> Markup { + match boot_state { + Some(state) if state == "enabled" => { + html! { + p id="runOnStartup" class="card-text" title="Enabled" { "Enabled" } + } + } + _ => { + html! { + p id="runOnStartup" class="card-text" title="Disabled" { "Disabled" } + } } } } + +fn database_element(state: &str) -> Markup { + // retrieve the sequence number of the latest message in the sbot database + let sequence_num = latest_sequence_number(); + + if state == "active" && sequence_num.is_ok() { + let number = sequence_num.unwrap(); + html! { + label class="card-text" style="margin-right: 5px;" { (number) } + label class="label-small font-gray" { "MESSAGES IN LOCAL DATABASE" } + } + } else { + html! { label class="label-small font-gray" { "DATABASE UNAVAILABLE" } } + } +} + +/// Read the state of the go-sbot process and define status-related +/// elements accordingly. +fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Markup) { + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read(); + + // conditionally render the following elements: + // state, capsule border class, sbot icon class, uptime or downtime element, + // run on startup element and database (sequence number) element + if let Ok(status) = sbot_status { + match status.state { + Some(state) if state == "active" => ( + "ACTIVE".to_string(), + "capsule capsule-container border-success", + "center icon icon-active", + uptime_element(&status.uptime), + run_on_startup_element(&status.boot_state), + database_element("active"), + ), + Some(state) if state == "inactive" => ( + "INACTIVE".to_string(), + "capsule capsule-container border-warning", + "center icon icon-inactive", + downtime_element(&status.downtime), + run_on_startup_element(&status.boot_state), + database_element("inactive"), + ), + // state is neither active nor inactive (might be failed) + Some(state) => ( + state.to_string(), + "capsule capsule-container border-danger", + "center icon icon-inactive", + downtime_element(&None), + run_on_startup_element(&status.boot_state), + database_element("failed"), + ), + None => ( + "UNAVAILABLE".to_string(), + "capsule capsule-container border-danger", + "center icon icon-inactive", + downtime_element(&None), + run_on_startup_element(&status.boot_state), + database_element("unavailable"), + ), + } + // show an error state if the attempt to read the go-sbot process + // status fails + } else { + ( + "PROCESS QUERY FAILED".to_string(), + "capsule capsule-container border-danger", + "center icon icon-inactive", + downtime_element(&None), + run_on_startup_element(&None), + database_element("error"), + ) + } +} + +/// Scuttlebutt status template builder. +pub fn build_template() -> PreEscaped { + let ( + sbot_state, + capsule_class, + sbot_icon_class, + uptime_downtime_element, + run_on_startup_element, + database_element, + ) = render_status_elements(); + + let status_template = html! { + (PreEscaped("")) + div class="card center" { + (PreEscaped("")) + div class=(capsule_class) { + (PreEscaped("")) + div class="two-grid" title="go-sbot process state" { + (PreEscaped("")) + a class="link two-grid-top-right" href="/settings/scuttlebutt" title="Configure Scuttlebutt settings" { + img id="configureNetworking" class="icon-small icon-active" src="/icons/cog.svg" alt="Configure"; + } + (PreEscaped("")) + (PreEscaped("")) + div class="grid-column-1" { + img id="sbotStateIcon" class=(sbot_icon_class) src="/icons/hermies.svg" alt="Hermies"; + label id="sbotStateLabel" for="sbotStateIcon" class="center label-small font-gray" style="margin-top: 0.5rem;" title="Sbot state" { + (sbot_state) + } + } + (PreEscaped("")) + (PreEscaped("")) + div class="grid-column-2" { + label class="label-small font-gray" for="sbotVersion" title="go-sbot version" { + "VERSION" + } + p id="sbotVersion" class="card-text" title="Version" { + "1.1.0-alpha" + } + (uptime_downtime_element) + label class="label-small font-gray" for="sbotBootState" title="go-sbot boot state" style="margin-top: 0.5rem;" { "RUN ON STARTUP" } + (run_on_startup_element) + } + } + hr style="color: var(--light-gray);"; + div id="middleSection" style="margin-top: 1rem;" { + div id="sbotInfo" class="center" style="display: flex; justify-content: space-between; width: 90%;" { + div class="center" style="display: flex; align-items: last baseline;" { + (database_element) + } + } + } + hr style="color: var(--light-gray);"; + /* + (PreEscaped(" +
+
+ +
+ +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ + + */ + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build_template(status_template, "Settings", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) +} diff --git a/peach-web/src/routes/status/scuttlebutt.rs_old b/peach-web/src/routes/status/scuttlebutt.rs_old new file mode 100644 index 0000000..34aa61d --- /dev/null +++ b/peach-web/src/routes/status/scuttlebutt.rs_old @@ -0,0 +1,37 @@ +use rocket::{get, State}; +use rocket_dyn_templates::Template; + +use crate::routes::authentication::Authenticated; +use crate::{context::scuttlebutt::StatusContext, RocketConfig}; + +// HELPERS AND ROUTES FOR /status/scuttlebutt + +#[get("/scuttlebutt")] +pub async fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { + let context = StatusContext::build().await; + + let back = if config.standalone_mode { + // return to home page + Some("/".to_string()) + } else { + // return to status menu + Some("/status".to_string()) + }; + + match context { + Ok(mut context) => { + // define back arrow url based on mode + context.back = back; + + Template::render("status/scuttlebutt", &context) + } + Err(_) => { + let mut context = StatusContext::default(); + + // define back arrow url based on mode + context.back = back; + + Template::render("status/scuttlebutt", &context) + } + } +} diff --git a/peach-web/src/templates/base.rs b/peach-web/src/templates/base.rs index 48430af..20019a0 100644 --- a/peach-web/src/templates/base.rs +++ b/peach-web/src/templates/base.rs @@ -3,7 +3,7 @@ use maud::{html, PreEscaped, DOCTYPE}; /// Base template builder. /// /// Takes an HTML body as input and splices it into the base template. -pub fn build(body: PreEscaped) -> PreEscaped { +pub fn build_template(body: PreEscaped) -> PreEscaped { html! { (DOCTYPE) html lang="en" data-theme="light"; diff --git a/peach-web/src/templates/flash.rs b/peach-web/src/templates/flash.rs index 1a4c08e..bc597b2 100644 --- a/peach-web/src/templates/flash.rs +++ b/peach-web/src/templates/flash.rs @@ -3,7 +3,7 @@ use maud::{html, Markup}; /// Flash message template builder. /// /// Render a flash elements based on the given flash name and message. -pub fn build(flash_name: &str, flash_msg: &str) -> Markup { +pub fn build_template(flash_name: &str, flash_msg: &str) -> Markup { let flash_class = match flash_name { "success" => "capsule center-text flash-message font-normal border-success", "info" => "capsule center-text flash-message font-normal border-info", diff --git a/peach-web/src/templates/nav.rs b/peach-web/src/templates/nav.rs index 7c67ff2..221aa6b 100644 --- a/peach-web/src/templates/nav.rs +++ b/peach-web/src/templates/nav.rs @@ -5,7 +5,11 @@ use crate::utils; /// Navigation template builder. /// /// Takes the main HTML content as input and splices it into the navigation template. -pub fn build(main: PreEscaped, title: &str, back: Option<&str>) -> PreEscaped { +pub fn build_template( + main: PreEscaped, + title: &str, + back: Option<&str>, +) -> PreEscaped { // retrieve the current theme value let theme = utils::get_theme(); @@ -38,7 +42,7 @@ pub fn build(main: PreEscaped, title: &str, back: Option<&str>) -> PreEs img class="icon-medium nav-icon-left icon-active" src="/icons/back.svg" alt="Back"; } h1 class="nav-title" { (title) } - a class="nav-item" id="logoutButton" href="/logout" title="Logout" { + a class="nav-item" id="logoutButton" href="/auth/logout" title="Logout" { img class="icon-medium nav-icon-right icon-active" src="/icons/enter.svg" alt="Enter"; } } -- 2.40.1 From fe04195030ff486e35c3faf11282b4864333bd72 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 17 Mar 2022 11:02:04 +0200 Subject: [PATCH 11/66] update lockfile and remove old auth routes file --- Cargo.lock | 2 + peach-web/src/routes/authentication.rs | 333 ------------------------- 2 files changed, 2 insertions(+), 333 deletions(-) delete mode 100644 peach-web/src/routes/authentication.rs diff --git a/Cargo.lock b/Cargo.lock index d8c939c..3192727 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2581,9 +2581,11 @@ dependencies = [ name = "peach-web" version = "0.5.0" dependencies = [ + "async-std", "base64 0.13.0", "dirs 4.0.0", "env_logger 0.8.4", + "futures 0.3.21", "golgi", "lazy_static", "log 0.4.14", diff --git a/peach-web/src/routes/authentication.rs b/peach-web/src/routes/authentication.rs deleted file mode 100644 index 3ff8f2b..0000000 --- a/peach-web/src/routes/authentication.rs +++ /dev/null @@ -1,333 +0,0 @@ -use log::info; -use rocket::{ - form::{Form, FromForm}, - get, - http::{Cookie, CookieJar, Status}, - post, - request::{self, FlashMessage, FromRequest, Request}, - response::{Flash, Redirect}, -}; -use rocket_dyn_templates::{tera::Context, Template}; - -use peach_lib::{error::PeachError, password_utils}; - -use crate::error::PeachWebError; -use crate::utils; -use crate::utils::TemplateOrRedirect; -use crate::RocketConfig; - -// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES - -pub const AUTH_COOKIE_KEY: &str = "peachweb_auth"; -pub const ADMIN_USERNAME: &str = "admin"; - -/// Note: Currently we use an empty struct for the Authenticated request guard -/// because there is only one user to be authenticated, and no data needs to be stored here. -/// In a multi-user authentication scheme, we would store the user_id in this struct, -/// and retrieve the correct user via the user_id stored in the cookie. -pub struct Authenticated; - -#[derive(Debug)] -pub enum LoginError { - UserNotLoggedIn, -} - -/// Request guard which returns an empty Authenticated struct from the request -/// if and only if the user has a cookie which proves they are authenticated with peach-web. -/// -/// Note that cookies.get_private uses encryption, which means that this private cookie -/// cannot be inspected, tampered with, or manufactured by clients. -#[rocket::async_trait] -impl<'r> FromRequest<'r> for Authenticated { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - // retrieve auth state from managed state (returns `Option`). - // this value is read from the Rocket.toml config file on start-up - let authentication_is_disabled: bool = *req - .rocket() - .state::() - .map(|config| (&config.disable_auth)) - .unwrap_or(&false); - - if authentication_is_disabled { - let auth = Authenticated {}; - request::Outcome::Success(auth) - } else { - let authenticated = req - .cookies() - .get_private(AUTH_COOKIE_KEY) - .and_then(|cookie| cookie.value().parse().ok()) - .map(|_value: String| Authenticated {}); - match authenticated { - Some(auth) => request::Outcome::Success(auth), - None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)), - } - } - } -} - -// HELPERS AND ROUTES FOR /login - -#[get("/login")] -pub fn login(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Login".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("login", &context.into_json()) -} - -#[derive(Debug, FromForm)] -pub struct LoginForm { - pub password: String, -} - -/// Takes in a LoginForm and returns Ok(()) if the password is correct. -/// -/// Note: there is currently only one user, therefore we don't need a username. -pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { - password_utils::verify_password(&login_form.password) -} - -#[post("/login", data = "")] -pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> TemplateOrRedirect { - match verify_login_form(login_form.into_inner()) { - Ok(_) => { - // if successful login, add a cookie indicating the user is authenticated - // and redirect to home page - // NOTE: since we currently have just one user, the value of the cookie - // is just admin (this is arbitrary). - // If we had multiple users, we could put the user_id here. - cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); - - TemplateOrRedirect::Redirect(Redirect::to("/")) - } - Err(e) => { - let err_msg = format!("Invalid password: {}", e); - // if unsuccessful login, render /login page again - let mut context = Context::new(); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Login".to_string())); - context.insert("flash_name", &("error".to_string())); - context.insert("flash_msg", &(err_msg)); - - TemplateOrRedirect::Template(Template::render("login", &context.into_json())) - } - } -} - -// HELPERS AND ROUTES FOR /logout - -#[get("/logout")] -pub fn logout(cookies: &CookieJar<'_>) -> Flash { - // logout authenticated user - info!("Attempting deauthentication of user."); - cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY)); - Flash::success(Redirect::to("/login"), "Logged out") -} - -// HELPERS AND ROUTES FOR /reset_password - -#[derive(Debug, FromForm)] -pub struct ResetPasswordForm { - pub temporary_password: String, - pub new_password1: String, - pub new_password2: String, -} - -/// Verify, validate and save the submitted password. This function is publicly exposed for users who have forgotten their password. -pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> { - info!( - "reset password!: {} {} {}", - password_form.temporary_password, password_form.new_password1, password_form.new_password2 - ); - password_utils::verify_temporary_password(&password_form.temporary_password)?; - // if the previous line did not throw an error, then the secret_link is correct - password_utils::validate_new_passwords( - &password_form.new_password1, - &password_form.new_password2, - )?; - // if the previous line did not throw an error, then the new password is valid - password_utils::set_new_password(&password_form.new_password1)?; - Ok(()) -} - -/// Password reset request handler. This route is used by a user who is not logged in -/// and is specifically for users who have forgotten their password. -#[get("/reset_password")] -pub fn reset_password(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Reset Password".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/reset_password", &context.into_json()) -} - -/// Password reset form request handler. This route is used by a user who is not logged in -/// and is specifically for users who have forgotten their password. -#[post("/reset_password", data = "")] -pub fn reset_password_post(reset_password_form: Form) -> Template { - let mut context = Context::new(); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Reset Password".to_string())); - - let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { - Ok(_) => ( - "success".to_string(), - "New password has been saved. Return home to login".to_string(), - ), - Err(err) => ( - "error".to_string(), - format!("Failed to reset password: {}", err), - ), - }; - - context.insert("flash_name", &Some(flash_name)); - context.insert("flash_msg", &Some(flash_msg)); - - Template::render("settings/admin/reset_password", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /send_password_reset - -/// Page for users who have forgotten their password. -/// This route is used by a user who is not logged in -/// to initiate the sending of a new password reset. -#[get("/forgot_password")] -pub fn forgot_password_page(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Send Password Reset".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/forgot_password", &context.into_json()) -} - -/// Send password reset request handler. This route is used by a user who is not logged in -/// and is specifically for users who have forgotten their password. A successful request results -/// in a Scuttlebutt private message being sent to the account of the device admin. -#[post("/send_password_reset")] -pub fn send_password_reset_post() -> Template { - info!("++ send password reset post"); - let mut context = Context::new(); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Send Password Reset".to_string())); - - let (flash_name, flash_msg) = match password_utils::send_password_reset() { - Ok(_) => ( - "success".to_string(), - "A password reset link has been sent to the admin of this device".to_string(), - ), - Err(err) => ( - "error".to_string(), - format!("Failed to send password reset link: {}", err), - ), - }; - - context.insert("flash_name", &Some(flash_name)); - context.insert("flash_msg", &Some(flash_msg)); - - Template::render("settings/admin/forgot_password", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /settings/change_password - -#[derive(Debug, FromForm)] -pub struct PasswordForm { - pub current_password: String, - pub new_password1: String, - pub new_password2: String, -} - -/// Password save form request handler. This function is for use by a user who is already logged in to change their password. -pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> { - info!( - "change password!: {} {} {}", - password_form.current_password, password_form.new_password1, password_form.new_password2 - ); - password_utils::verify_password(&password_form.current_password)?; - // if the previous line did not throw an error, then the old password is correct - password_utils::validate_new_passwords( - &password_form.new_password1, - &password_form.new_password2, - )?; - // if the previous line did not throw an error, then the new password is valid - password_utils::set_new_password(&password_form.new_password1)?; - Ok(()) -} - -/// Change password request handler. This is used by a user who is already logged in. -#[get("/change_password")] -pub fn change_password(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings/admin".to_string())); - context.insert("title", &Some("Change Password".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/change_password", &context.into_json()) -} - -/// Change password form request handler. This route is used by a user who is already logged in. -#[post("/change_password", data = "")] -pub fn change_password_post(password_form: Form, _auth: Authenticated) -> Template { - let mut context = Context::new(); - context.insert("back", &Some("/settings/admin".to_string())); - context.insert("title", &Some("Change Password".to_string())); - - let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { - Ok(_) => ( - "success".to_string(), - "New password has been saved".to_string(), - ), - Err(err) => ( - "error".to_string(), - format!("Failed to save new password: {}", err), - ), - }; - - context.insert("flash_name", &Some(flash_name)); - context.insert("flash_msg", &Some(flash_msg)); - - Template::render("settings/admin/change_password", &context.into_json()) -} -- 2.40.1 From 7cdf8c553db4c781815cde1be708f5d26f939a3c Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 17 Mar 2022 16:30:26 +0200 Subject: [PATCH 12/66] complete scuttlebutt status route --- peach-web/src/routes/status/scuttlebutt.rs | 147 ++++++++++++++------- 1 file changed, 101 insertions(+), 46 deletions(-) diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index f2eb965..9e1bde6 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -8,21 +8,6 @@ use peach_lib::sbot::{SbotConfig, SbotStatus}; use crate::{error::PeachWebError, templates}; -/* -{# ASSIGN VARIABLES #} -{# ---------------- #} -{%- if sbot_status.memory -%} -{% set mem = sbot_status.memory / 1024 / 1024 | round | int -%} -{%- else -%} -{% set mem = "0" -%} -{%- endif -%} -{%- if sbot_status.blobstore -%} -{% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%} -{%- else -%} -{% set blobs = "0" -%} -{%- endif -%} -*/ - // HELPER FUNCTIONS pub async fn init_sbot_with_config( @@ -116,9 +101,89 @@ fn database_element(state: &str) -> Markup { } } +fn memory_element(memory: Option) -> Markup { + let (memory, img_class, medium_label_class, small_label_class) = match memory { + Some(mem) => { + // convert memory to mb representation + let memory_rounded = mem / 1024 / 1024; + ( + memory_rounded.to_string(), + "icon icon-active", + "label-medium font-normal", + "label-small font-normal", + ) + } + _ => ( + 0.to_string(), + "icon icon-inactive", + "label-medium font-gray", + "label-small font-gray", + ), + }; + + html! { + div class="stack" { + img class=(img_class) title="Memory" src="/icons/ram.png"; + div class="flex-grid" style="padding-top: 0.5rem;" { + label class=(medium_label_class) style="padding-right: 3px;" title="Memory usage of the go-sbot process in MB" { (memory) } + label class=(small_label_class) { "MB" } + } + label class=(small_label_class) { "MEMORY" } + } + } +} + +fn hops_element() -> Markup { + // retrieve go-sbot systemd process status + let hops = match SbotConfig::read() { + Ok(conf) => conf.hops, + _ => 0, + }; + + html! { + div class="stack" { + img class="icon icon-active" title="Hops" src="/icons/orbits.png"; + div class="flex-grid" style="padding-top: 0.5rem;" { + label class="label-medium font-normal" style="padding-right: 3px;" title="Replication hops" { + (hops) + } + } + label class="label-small font-gray" { "HOPS" } + } + } +} + +fn blobs_element(blobstore: Option) -> Markup { + let blobstore_size = match blobstore { + // convert blobstore size to mb representation + Some(blobs) => blobs / 1024 / 1024, + None => 0, + }; + + html! { + div class="stack" { + img class="icon icon-active" title="Blobs" src="/icons/image-file.png"; + div class="flex-grid" style="padding-top: 0.5rem;" { + label class="label-medium font-normal" style="padding-right: 3px;" title="Blobstore size in MB" { (blobstore_size) } + label class="label-small font-normal" { "MB" } + } + label class="label-small font-gray" { "BLOBSTORE" } + } + } +} + /// Read the state of the go-sbot process and define status-related /// elements accordingly. -fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Markup) { +fn render_status_elements<'a>() -> ( + String, + &'a str, + &'a str, + Markup, + Markup, + Markup, + Markup, + Markup, +) { // retrieve go-sbot systemd process status let sbot_status = SbotStatus::read(); @@ -134,6 +199,8 @@ fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Ma uptime_element(&status.uptime), run_on_startup_element(&status.boot_state), database_element("active"), + memory_element(status.memory), + blobs_element(status.blobstore), ), Some(state) if state == "inactive" => ( "INACTIVE".to_string(), @@ -142,6 +209,8 @@ fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Ma downtime_element(&status.downtime), run_on_startup_element(&status.boot_state), database_element("inactive"), + memory_element(None), + blobs_element(status.blobstore), ), // state is neither active nor inactive (might be failed) Some(state) => ( @@ -151,6 +220,8 @@ fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Ma downtime_element(&None), run_on_startup_element(&status.boot_state), database_element("failed"), + memory_element(None), + blobs_element(status.blobstore), ), None => ( "UNAVAILABLE".to_string(), @@ -159,6 +230,8 @@ fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Ma downtime_element(&None), run_on_startup_element(&status.boot_state), database_element("unavailable"), + memory_element(None), + blobs_element(status.blobstore), ), } // show an error state if the attempt to read the go-sbot process @@ -171,6 +244,8 @@ fn render_status_elements<'a>() -> (String, &'a str, &'a str, Markup, Markup, Ma downtime_element(&None), run_on_startup_element(&None), database_element("error"), + memory_element(None), + blobs_element(None), ) } } @@ -184,8 +259,12 @@ pub fn build_template() -> PreEscaped { uptime_downtime_element, run_on_startup_element, database_element, + memory_element, + blobs_element, ) = render_status_elements(); + let hops_element = hops_element(); + let status_template = html! { (PreEscaped("")) div class="card center" { @@ -228,36 +307,12 @@ pub fn build_template() -> PreEscaped { } } hr style="color: var(--light-gray);"; - /* - (PreEscaped(" -
-
- -
- -
- -
-
- -
- - -
- -
-
- -
- - -
- -
-
- - - */ + (PreEscaped("")) + div class="three-grid card-container" style="margin-top: 1rem;" { + (hops_element) + (blobs_element) + (memory_element) + } } } }; -- 2.40.1 From 7fe919d9a17b664991396f7655d0ac9ff0d9b1ec Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 18 Mar 2022 11:25:53 +0200 Subject: [PATCH 13/66] refine sbot status pattern matching --- peach-web/src/routes/status/scuttlebutt.rs | 102 ++++++++++----------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 9e1bde6..0d6c1b2 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -8,7 +8,7 @@ use peach_lib::sbot::{SbotConfig, SbotStatus}; use crate::{error::PeachWebError, templates}; -// HELPER FUNCTIONS +// SBOT HELPER FUNCTIONS pub async fn init_sbot_with_config( sbot_config: &Option, @@ -47,6 +47,8 @@ fn latest_sequence_number() -> Result> { }) } +// HTML RENDERING FOR ELEMENTS + fn downtime_element(downtime: &Option) -> Markup { match downtime { Some(time) => { @@ -185,59 +187,49 @@ fn render_status_elements<'a>() -> ( Markup, ) { // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read(); - - // conditionally render the following elements: - // state, capsule border class, sbot icon class, uptime or downtime element, - // run on startup element and database (sequence number) element - if let Ok(status) = sbot_status { - match status.state { - Some(state) if state == "active" => ( - "ACTIVE".to_string(), - "capsule capsule-container border-success", - "center icon icon-active", - uptime_element(&status.uptime), - run_on_startup_element(&status.boot_state), - database_element("active"), - memory_element(status.memory), - blobs_element(status.blobstore), - ), - Some(state) if state == "inactive" => ( - "INACTIVE".to_string(), - "capsule capsule-container border-warning", - "center icon icon-inactive", - downtime_element(&status.downtime), - run_on_startup_element(&status.boot_state), - database_element("inactive"), - memory_element(None), - blobs_element(status.blobstore), - ), - // state is neither active nor inactive (might be failed) - Some(state) => ( - state.to_string(), - "capsule capsule-container border-danger", - "center icon icon-inactive", - downtime_element(&None), - run_on_startup_element(&status.boot_state), - database_element("failed"), - memory_element(None), - blobs_element(status.blobstore), - ), - None => ( - "UNAVAILABLE".to_string(), - "capsule capsule-container border-danger", - "center icon icon-inactive", - downtime_element(&None), - run_on_startup_element(&status.boot_state), - database_element("unavailable"), - memory_element(None), - blobs_element(status.blobstore), - ), - } - // show an error state if the attempt to read the go-sbot process - // status fails - } else { - ( + match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => ( + "ACTIVE".to_string(), + "capsule capsule-container border-success", + "center icon icon-active", + uptime_element(&status.uptime), + run_on_startup_element(&status.boot_state), + database_element("active"), + memory_element(status.memory), + blobs_element(status.blobstore), + ), + Ok(status) if status.state == Some("inactive".to_string()) => ( + "INACTIVE".to_string(), + "capsule capsule-container border-warning", + "center icon icon-inactive", + downtime_element(&status.downtime), + run_on_startup_element(&status.boot_state), + database_element("inactive"), + memory_element(None), + blobs_element(status.blobstore), + ), + // state is neither active nor inactive (might be "failed") + Ok(status) if status.state.is_some() => ( + status.state.unwrap(), + "capsule capsule-container border-danger", + "center icon icon-inactive", + downtime_element(&None), + run_on_startup_element(&status.boot_state), + database_element("failed"), + memory_element(None), + blobs_element(status.blobstore), + ), + Ok(status) if status.state.is_none() => ( + "UNAVAILABLE".to_string(), + "capsule capsule-container border-danger", + "center icon icon-inactive", + downtime_element(&None), + run_on_startup_element(&status.boot_state), + database_element("unavailable"), + memory_element(None), + blobs_element(status.blobstore), + ), + _ => ( "PROCESS QUERY FAILED".to_string(), "capsule capsule-container border-danger", "center icon icon-inactive", @@ -246,7 +238,7 @@ fn render_status_elements<'a>() -> ( database_element("error"), memory_element(None), blobs_element(None), - ) + ), } } -- 2.40.1 From 59739cf6e5d6f5719893ca2a5e4bf3f6f4884249 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 18 Mar 2022 11:30:59 +0200 Subject: [PATCH 14/66] further refinement of the sbot status page --- peach-web/src/routes/status/scuttlebutt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 0d6c1b2..36f7f9b 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -210,7 +210,7 @@ fn render_status_elements<'a>() -> ( ), // state is neither active nor inactive (might be "failed") Ok(status) if status.state.is_some() => ( - status.state.unwrap(), + status.state.unwrap().to_uppercase(), "capsule capsule-container border-danger", "center icon icon-inactive", downtime_element(&None), -- 2.40.1 From 729580729c275b3781f1a190c4a64a1dc93203ce Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 18 Mar 2022 11:32:51 +0200 Subject: [PATCH 15/66] add scuttlebutt peers menu and inactive template --- peach-web/rouille_refactor | 10 +++++- peach-web/src/main.rs | 4 +++ peach-web/src/routes/mod.rs | 2 +- peach-web/src/routes/scuttlebutt/mod.rs | 1 + peach-web/src/routes/scuttlebutt/peers.rs | 42 +++++++++++++++++++++++ peach-web/src/templates/inactive.rs | 28 +++++++++++++++ peach-web/src/templates/mod.rs | 1 + 7 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 peach-web/src/routes/scuttlebutt/mod.rs create mode 100644 peach-web/src/routes/scuttlebutt/peers.rs create mode 100644 peach-web/src/templates/inactive.rs diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor index debee1a..06e59ba 100644 --- a/peach-web/rouille_refactor +++ b/peach-web/rouille_refactor @@ -13,9 +13,17 @@ we do not need to be super fast or feature-rich. [ tasks ] - write the settings route(s) + - scuttlebutt + - peers + x menu + - peers list + - invites + - profile + - private x menu x guide - - status + x status + x scuttlebutt x scuttlebutt menu - configure_sbot x template diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index fa71ecf..b30719e 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -188,6 +188,10 @@ fn main() { routes::settings::admin::delete::handle_form(request) }, + (GET) (/scuttlebutt/peers) => { + Response::html(routes::scuttlebutt::peers::build_template()) + }, + (GET) (/status/scuttlebutt) => { Response::html(routes::status::scuttlebutt::build_template()) }, diff --git a/peach-web/src/routes/mod.rs b/peach-web/src/routes/mod.rs index 37655b6..fdc9575 100644 --- a/peach-web/src/routes/mod.rs +++ b/peach-web/src/routes/mod.rs @@ -3,6 +3,6 @@ pub mod authentication; //pub mod index; pub mod guide; pub mod home; -//pub mod scuttlebutt; +pub mod scuttlebutt; pub mod settings; pub mod status; diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs new file mode 100644 index 0000000..d9cebd1 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -0,0 +1 @@ +pub mod peers; diff --git a/peach-web/src/routes/scuttlebutt/peers.rs b/peach-web/src/routes/scuttlebutt/peers.rs new file mode 100644 index 0000000..9c004db --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/peers.rs @@ -0,0 +1,42 @@ +use maud::{html, PreEscaped}; +use peach_lib::sbot::SbotStatus; + +use crate::templates; + +/// Scuttlebutt peer menu template builder. +/// +/// A peer menu which allows navigating to lists of friends, follows, followers +/// and blocks, as well as accessing the invite creation form. +pub fn build_template() -> PreEscaped { + let menu_template = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + // render the scuttlebutt peers menu + html! { + (PreEscaped("")) + div class="card center" { + div class="card-container" { + (PreEscaped("")) + div id="buttons" { + a id="search" class="button button-primary center" href="/scuttlebutt/search" title="Search for a peer" { "Search" } + a id="friends" class="button button-primary center" href="/scuttlebutt/friends" title="List friends" { "Friends" } + a id="follows" class="button button-primary center" href="/scuttlebutt/follows" title="List follows" { "Follows" } + a id="blocks" class="button button-primary center" href="/scuttlebutt/blocks" title="List blocks" { "Blocks" } + a id="invites" class="button button-primary center" href="/scuttlebutt/invites" title="Create invites" { "Invites" } + } + } + } + } + } + _ => { + // the sbot is not active; render a message instead of the menu + templates::inactive::build_template("Social lists and interactions are unavailable.") + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build_template(menu_template, "Scuttlebutt Peers", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) +} diff --git a/peach-web/src/templates/inactive.rs b/peach-web/src/templates/inactive.rs new file mode 100644 index 0000000..5dddc66 --- /dev/null +++ b/peach-web/src/templates/inactive.rs @@ -0,0 +1,28 @@ +use maud::{html, Markup, PreEscaped}; + +/// Sbot inactive template builder. +/// +/// Display a message to the operator when the sbot is inactive and +/// therefore some functionality is not available. +pub fn build_template(unavailable_msg: &str) -> Markup { + html! { + (PreEscaped("")) + div class="card center" { + div class="capsule capsule-container border-warning center-text" { + p class="card-text" style="font-size: var(--font-size-4);" { + "Sbot Inactive" + } + p class="card-text" { (unavailable_msg) } + p class="card-text" { + "Visit the " + strong { + a href="/settings/scuttlebutt" class="link" { + "Scuttlebutt settings menu" + } + } + " to start the Sbot and then try again." + } + } + } + } +} diff --git a/peach-web/src/templates/mod.rs b/peach-web/src/templates/mod.rs index 30ac0ee..d92a585 100644 --- a/peach-web/src/templates/mod.rs +++ b/peach-web/src/templates/mod.rs @@ -1,3 +1,4 @@ pub mod base; pub mod flash; +pub mod inactive; pub mod nav; -- 2.40.1 From 3c49c067dde6cd1a5f9035278432031f69370e97 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 18 Mar 2022 11:33:10 +0200 Subject: [PATCH 16/66] fix big circle background colour when sbot status is failed --- peach-web/src/routes/home.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peach-web/src/routes/home.rs b/peach-web/src/routes/home.rs index 76e9b4d..40e74a5 100644 --- a/peach-web/src/routes/home.rs +++ b/peach-web/src/routes/home.rs @@ -26,14 +26,14 @@ fn render_status_elements<'a>() -> (&'a str, &'a str, &'a str) { ) } else { ( - "circle circle-large circle-danger", + "circle circle-large circle-error", "x_x", "circle circle-small border-circle-small border-danger", ) } } else { ( - "circle circle-large circle-danger", + "circle circle-large circle-error", "x_x", "circle circle-small border-circle-small border-danger", ) -- 2.40.1 From 31628a7155c4d578d83cb6730b59ca0013d204c8 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 18 Mar 2022 11:33:27 +0200 Subject: [PATCH 17/66] remove old scuttlebutt routes file --- peach-web/src/routes/scuttlebutt.rs | 946 ---------------------------- 1 file changed, 946 deletions(-) delete mode 100644 peach-web/src/routes/scuttlebutt.rs diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs deleted file mode 100644 index e9c1d4b..0000000 --- a/peach-web/src/routes/scuttlebutt.rs +++ /dev/null @@ -1,946 +0,0 @@ -//! Routes for Scuttlebutt related functionality. - -use log::debug; -use peach_lib::sbot::{SbotConfig, SbotStatus}; -use rocket::{ - form::{Form, FromForm}, - fs::TempFile, - get, post, - request::FlashMessage, - response::{Flash, Redirect}, - uri, -}; -use rocket_dyn_templates::{tera::Context, Template}; - -use crate::{ - context::{ - scuttlebutt, - scuttlebutt::{ - BlocksContext, FollowsContext, FriendsContext, PrivateContext, ProfileContext, - }, - }, - routes::authentication::Authenticated, - utils, -}; - -// HELPER FUNCTIONS - -/// Check to see if the go-sbot.service process is currently active. -/// Return an error in the form of a `String` if the process -/// check command fails. Otherwise, return the state of the process. -fn is_sbot_active() -> Result { - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => return Err(format!("Failed to read sbot status: {}", e)), - }; - - if sbot_status.state == Some("active".to_string()) { - Ok(true) - } else { - Ok(false) - } -} - -/// Ensure that the given public key is a valid ed25519 key. -fn validate_public_key(public_key: &str, redirect_url: String) -> Result<(), Flash> { - // ensure the id starts with the correct sigil link - if !public_key.starts_with('@') { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: expected '@' sigil as first character", - )); - } - - // find the dot index denoting the start of the algorithm definition tag - let dot_index = match public_key.rfind('.') { - Some(index) => index, - None => { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: no dot index was found", - )) - } - }; - - // check hashing algorithm (must end with ".ed25519") - if !&public_key.ends_with(".ed25519") { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: hashing algorithm must be ed25519", - )); - } - - // obtain the base64 portion (substring) of the public key - let base64_str = &public_key[1..dot_index]; - - // length of a base64 encoded ed25519 public key - if base64_str.len() != 44 { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: base64 data length is incorrect", - )); - } - - Ok(()) -} - -// HELPERS AND ROUTES FOR INVITES - -#[get("/invites")] -pub fn invites(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/scuttlebutt/peers".to_string())); - context.insert("title", &Some("Invites".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - match flash.kind() { - // we've been passed a freshly-generated invite code (redirect from post) - "code" => { - context.insert("invite_code", &Some(flash.message().to_string())); - context.insert("flash_name", &Some("success".to_string())); - context.insert("flash_msg", &Some("Generated invite code".to_string())); - } - _ => { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - } - } - }; - - Template::render("scuttlebutt/invites", &context.into_json()) -} - -#[derive(Debug, FromForm)] -pub struct Invite { - pub uses: u16, -} - -#[post("/invites", data = "")] -pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash { - let uses = invite.uses; - - let url = uri!("/scuttlebutt", invites); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Generating Scuttlebutt invite code"); - match sbot_client.invite_create(uses).await { - // construct a custom flash msg to pass along the invite code - Ok(code) => Flash::new(Redirect::to(url), "code", code), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to create invite code: {}", e), - ), - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. New invite codes cannot be generated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ) - } - // failed to retrieve go-sbot systemd process status - Err(e) => return Flash::error(Redirect::to(url), e) - } -} - -// HELPERS AND ROUTES FOR /private - -/// A private message composition and publication page. -#[get("/private?")] -pub async fn private( - mut public_key: Option, - flash: Option>, - _auth: Authenticated, -) -> Template { - // display a helpful message if the sbot is inactive - if let Ok(false) = is_sbot_active() { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Private Messages".to_string())); - context.insert( - "unavailable_msg", - &Some("Private messages cannot be published.".to_string()), - ); - - // render the "sbot is inactive" template - return Template::render("scuttlebutt/inactive", &context.into_json()); - // otherwise, build the full context and render the private message template - } else { - if let Some(ref key) = public_key { - // `url_decode` replaces '+' with ' ', so we need to revert that - public_key = Some(key.replace(' ', "+")); - } - - // build the private context object - let context = PrivateContext::build(public_key).await; - - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/private", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = PrivateContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - - Template::render("scuttlebutt/private", &context) - } - } - } -} - -#[derive(Debug, FromForm)] -pub struct Private { - pub id: String, - pub text: String, - pub recipient: String, -} - -/// Publish a private message. -#[post("/private", data = "")] -pub async fn private_post(private: Form, _auth: Authenticated) -> Flash { - let url = uri!("/scuttlebutt", private(None::)); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let id = &private.id; - let text = &private.text; - let recipient = &private.recipient; - // now we need to add the local id to the recipients vector, - // otherwise the local id will not be able to read the message. - let recipients = vec![id.to_string(), recipient.to_string()]; - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Publishing a new Scuttlebutt private message"); - match sbot_client - .publish_private(text.to_string(), recipients) - .await - { - Ok(_) => { - Flash::success(Redirect::to(url), format!("Published private message")) - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to publish private message: {}", e), - ), - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. New private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - // failed to retrieve go-sbot systemd process status - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// HELPERS AND ROUTES FOR /search - -/// Search for a peer. -#[get("/search")] -pub fn search(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read().ok(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("sbot_status", &sbot_status); - context.insert("title", &Some("Search")); - context.insert("back", &Some("/scuttlebutt/peers")); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("scuttlebutt/search", &context.into_json()) -} - -#[derive(Debug, FromForm)] -pub struct Peer { - // public key - pub public_key: String, -} - -/// Accept the peer search form and redirect to the profile for that peer. -#[post("/search", data = "")] -pub async fn search_post(peer: Form, _auth: Authenticated) -> Flash { - let public_key = &peer.public_key; - - let search_url = "/scuttlebutt/search".to_string(); - - // validate the key before redirecting to profile url - if let Err(flash) = validate_public_key(&public_key, search_url) { - // redirect with error message - return flash; - } - - // key has not been validated and we can redirect to the profile page - let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); - - Flash::new( - Redirect::to(profile_url), - // this flash msg will not be displayed in the receiving template - "ignore", - "Public key validated for profile lookup", - ) -} - -// HELPERS AND ROUTES FOR /peers - -/// A peer menu which allows navigating to lists of friends, follows, followers -/// and blocks, as well as accessing the invite creation form. -#[get("/peers")] -pub fn peers(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("title", &Some("Scuttlebutt Peers")); - context.insert("back", &Some("/")); - - // display a helpful message if the sbot is inactive - if let Ok(false) = is_sbot_active() { - context.insert( - "unavailable_msg", - &Some("Social lists and interactions are unavailable.".to_string()), - ); - - // render the "sbot is inactive" template - return Template::render("scuttlebutt/inactive", &context.into_json()); - } else { - context.insert("sbot_state", &Some("active".to_string())); - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - } - - Template::render("scuttlebutt/peers", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /post/publish - -#[derive(Debug, FromForm)] -pub struct Post { - pub text: String, -} - -/// Publish a public Scuttlebutt post. -/// Redirects to profile page of the PeachCloud local identity with a flash -/// message describing the outcome of the action (may be successful or -/// unsuccessful). -#[post("/publish", data = "")] -pub async fn publish(post: Form, _auth: Authenticated) -> Flash { - let post_text = &post.text; - let url = uri!("/scuttlebutt", profile(None::)); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Publishing new Scuttlebutt public post"); - match sbot_client.publish_post(post_text).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Published post")), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to publish post: {}", e), - ), - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. New posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// HELPERS AND ROUTES FOR /follow - -/// Follow a Scuttlebutt profile specified by the given public key. -/// Redirects to the appropriate profile page with a flash message describing -/// the outcome of the action (may be successful or unsuccessful). -#[post("/follow", data = "")] -pub async fn follow(peer: Form, _auth: Authenticated) -> Flash { - let public_key = &peer.public_key; - let url = uri!("/scuttlebutt", profile(Some(public_key))); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Following Scuttlebutt peer"); - match sbot_client.follow(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Followed peer")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to follow peer: {}", e)) - } - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// HELPERS AND ROUTES FOR /unfollow - -/// Unfollow a Scuttlebutt profile specified by the given public key. -/// Redirects to the appropriate profile page with a flash message describing -/// the outcome of the action (may be successful or unsuccessful). -#[post("/unfollow", data = "")] -pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash { - let public_key = &peer.public_key; - - let url = uri!("/scuttlebutt", profile(Some(public_key))); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Unfollowing Scuttlebutt peer"); - match sbot_client.unfollow(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Unfollowed peer")), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to unfollow peer: {}", e), - ), - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// HELPERS AND ROUTES FOR /block - -/// Block a Scuttlebutt profile specified by the given public key. -/// Redirects to the appropriate profile page with a flash message describing -/// the outcome of the action (may be successful or unsuccessful). -#[post("/block", data = "")] -pub async fn block(peer: Form, _auth: Authenticated) -> Flash { - let public_key = &peer.public_key; - let url = uri!("/scuttlebutt", profile(Some(public_key))); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Blocking Scuttlebutt peer"); - match sbot_client.block(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Blocked peer")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to block peer: {}", e)) - } - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// HELPERS AND ROUTES FOR /unblock - -/// Unblock a Scuttlebutt profile specified by the given public key. -/// Redirects to the appropriate profile page with a flash message describing -/// the outcome of the action (may be successful or unsuccessful). -#[post("/unblock", data = "")] -pub async fn unblock(peer: Form, _auth: Authenticated) -> Flash { - let public_key = &peer.public_key; - let url = uri!("/scuttlebutt", profile(Some(public_key))); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Unblocking Scuttlebutt peer"); - match sbot_client.unblock(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Unblocked peer")), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to unblock peer: {}", e), - ), - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. Follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// ROUTES FOR /profile - -/// A Scuttlebutt profile, specified by a public key. It may be our own profile -/// or the profile of a peer. If the public key query parameter is not provided, -/// the local profile is displayed (ie. the profile of the public key associated -/// with the local PeachCloud device). -#[get("/profile?")] -pub async fn profile( - mut public_key: Option, - flash: Option>, - _auth: Authenticated, -) -> Template { - // display a helpful message if the sbot is inactive - if let Ok(false) = is_sbot_active() { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Profile".to_string())); - context.insert( - "unavailable_msg", - &Some("Profile data cannot be retrieved.".to_string()), - ); - - // render the "sbot is inactive" template - return Template::render("scuttlebutt/inactive", &context.into_json()); - } else { - if let Some(ref key) = public_key { - // `url_decode` replaces '+' with ' ', so we need to revert that - public_key = Some(key.replace(' ', "+")); - } - - // build the profile context object - let context = ProfileContext::build(public_key).await; - - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/profile", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = ProfileContext::default(); - - // flash name and msg will be `Some` if the sbot is inactive (in - // that case, they are set by the context builder). - // otherwise, we need to assign the name and returned error msg - // to the flash. - if context.flash_name.is_none() || context.flash_msg.is_none() { - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - } - - Template::render("scuttlebutt/profile", &context) - } - } - } -} - -// HELPERS AND ROUTES FOR /profile/update - -/// Serve a form for the purpose of updating the name, description and picture -/// for the local Scuttlebutt profile. -#[get("/profile/update")] -pub async fn update_profile(flash: Option>, _auth: Authenticated) -> Template { - // display a helpful message if the sbot is inactive - if let Ok(false) = is_sbot_active() { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Profile".to_string())); - context.insert( - "unavailable_msg", - &Some("Profile data cannot be retrieved.".to_string()), - ); - - // render the "sbot is inactive" template - return Template::render("scuttlebutt/inactive", &context.into_json()); - } else { - // build the profile context object - let context = ProfileContext::build(None).await; - - match context { - // we were able to build the context without errors - Ok(mut context) => { - context.back = Some("/scuttlebutt/profile".to_string()); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/update_profile", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = ProfileContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - - Template::render("scuttlebutt/update_profile", &context) - } - } - } -} - -#[derive(Debug, FromForm)] -pub struct Profile<'f> { - pub id: String, - pub current_name: String, - pub current_description: String, - pub new_name: String, - pub new_description: String, - pub image: TempFile<'f>, -} - -/// Update the name, description and picture for the local Scuttlebutt profile. -/// Redirects to profile page of the PeachCloud local identity with a flash -/// message describing the outcome of the action (may be successful or -/// unsuccessful). -#[post("/profile/update", data = "")] -pub async fn update_profile_post( - mut profile: Form>, - _auth: Authenticated, -) -> Flash { - let url = uri!("/scuttlebutt", update_profile); - - // we only want to try and interact with the sbot if it's active - match is_sbot_active() { - Ok(true) => { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - // track whether the name, description or image have been updated - let mut name_updated: bool = false; - let mut description_updated: bool = false; - let image_updated: bool; - - // only update the name if it has changed - if profile.new_name != profile.current_name { - debug!("Publishing new Scuttlebutt profile name"); - let publish_name_res = sbot_client.publish_name(&profile.new_name).await; - if publish_name_res.is_err() { - return Flash::error( - Redirect::to(url), - format!("Failed to update name: {}", publish_name_res.unwrap_err()), - ); - } else { - name_updated = true - } - } - - // only update the description if it has changed - if profile.new_description != profile.current_description { - debug!("Publishing new Scuttlebutt profile description"); - let publish_description_res = sbot_client - .publish_description(&profile.new_description) - .await; - - if publish_description_res.is_err() { - return Flash::error( - Redirect::to(url), - format!( - "Failed to update description: {}", - publish_description_res.unwrap_err() - ), - ); - } else { - description_updated = true - } - } - - // only update the image if a file was uploaded - if profile.image.name().is_some() { - match utils::write_blob_to_store(&mut profile.image).await { - Ok(blob_id) => { - // if the file was successfully added to the blobstore, - // publish an about image message with the blob id - let publish_image_res = sbot_client.publish_image(&blob_id).await; - - if publish_image_res.is_err() { - return Flash::error( - Redirect::to(url), - format!( - "Failed to update image: {}", - publish_image_res.unwrap_err() - ), - ); - } else { - image_updated = true - } - } - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to add image to blobstore: {}", e), - ) - } - } - } else { - image_updated = false - } - - if name_updated || description_updated || image_updated { - return Flash::success(Redirect::to(url), "Profile updated"); - } else { - // no updates were made but no errors were encountered either - return Flash::success(Redirect::to(url), "Profile info unchanged"); - } - } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), - } - } - Ok(false) => { - return Flash::warning( - Redirect::to(url), - "The Sbot is inactive. Profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); - } - Err(e) => return Flash::error(Redirect::to(url), e), - } -} - -// HELPERS AND ROUTES FOR /friends - -/// A list of friends (mutual follows), with each list item displaying the -/// name, image and public key of the peer. -#[get("/friends")] -pub async fn friends(flash: Option>, _auth: Authenticated) -> Template { - // build the friends context object - let context = FriendsContext::build().await; - - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = FriendsContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - - Template::render("scuttlebutt/peers_list", &context) - } - } -} - -// HELPERS AND ROUTES FOR /follows - -/// A list of follows (peers we follow who do not follow us), with each list item displaying the name, image and public -/// key of the peer. -#[get("/follows")] -pub async fn follows(flash: Option>, _auth: Authenticated) -> Template { - // build the follows context object - let context = FollowsContext::build().await; - - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = FollowsContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - - Template::render("scuttlebutt/peers_list", &context) - } - } -} - -// HELPERS AND ROUTES FOR /blocks - -/// A list of blocks (peers we've blocked previously), with each list item -/// displaying the name, image and public key of the peer. -#[get("/blocks")] -pub async fn blocks(flash: Option>, _auth: Authenticated) -> Template { - // build the blocks context object - let context = BlocksContext::build().await; - - match context { - // we were able to build the context without errors - Ok(mut context) => { - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) - } - // an error occurred while building the context - Err(e) => { - // build the default context and pass along the error message - let mut context = BlocksContext::default(); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some(e.to_string()); - - Template::render("scuttlebutt/peers_list", &context) - } - } -} -- 2.40.1 From 70f7ad0dc69944b371ca78d63dd7e034896c508f Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 12:26:04 +0200 Subject: [PATCH 18/66] add sbot utils, move theme utils and add flash message trait --- peach-web/src/utils/flash.rs | 44 ++++++++ peach-web/src/utils/mod.rs | 3 + peach-web/src/utils/sbot.rs | 206 +++++++++++++++++++++++++++++++++++ peach-web/src/utils/theme.rs | 134 +++++++++++++++++++++++ 4 files changed, 387 insertions(+) create mode 100644 peach-web/src/utils/flash.rs create mode 100644 peach-web/src/utils/mod.rs create mode 100644 peach-web/src/utils/sbot.rs create mode 100644 peach-web/src/utils/theme.rs diff --git a/peach-web/src/utils/flash.rs b/peach-web/src/utils/flash.rs new file mode 100644 index 0000000..96be9a5 --- /dev/null +++ b/peach-web/src/utils/flash.rs @@ -0,0 +1,44 @@ +use rouille::{input, Request, Response}; + +/// Flash message trait for `Request`. +pub trait FlashRequest { + /// Retrieve the flash message cookie values from a `Request`. + fn retrieve_flash(&self) -> (Option<&str>, Option<&str>); +} + +impl FlashRequest for Request { + fn retrieve_flash(&self) -> (Option<&str>, Option<&str>) { + // check for flash cookies + let flash_name = input::cookies(&self) + .find(|&(n, _)| n == "flash_name") + // return the value of the cookie (key is already known) + .map(|key_val| key_val.1); + let flash_msg = input::cookies(&self) + .find(|&(n, _)| n == "flash_msg") + .map(|key_val| key_val.1); + + (flash_name, flash_msg) + } +} + +/// Flash message trait for `Response`. +pub trait FlashResponse { + /// Add flash message cookies to a `Response`. + fn add_flash(self, flash_name: String, flash_msg: String) -> Response; + /// Reset flash message cookie values for a `Response`. + fn reset_flash(self) -> Response; +} + +impl FlashResponse for Response { + fn add_flash(self, flash_name: String, flash_msg: String) -> Response { + // set the flash cookie headers + self.with_additional_header("Set-Cookie", flash_name) + .with_additional_header("Set-Cookie", flash_msg) + } + + fn reset_flash(self) -> Response { + // set blank cookies to clear the flash msg from the previous request + self.with_additional_header("Set-Cookie", "flash_name=") + .with_additional_header("Set-Cookie", "flash_msg=") + } +} diff --git a/peach-web/src/utils/mod.rs b/peach-web/src/utils/mod.rs new file mode 100644 index 0000000..e02227e --- /dev/null +++ b/peach-web/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod flash; +pub mod sbot; +pub mod theme; diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs new file mode 100644 index 0000000..d916476 --- /dev/null +++ b/peach-web/src/utils/sbot.rs @@ -0,0 +1,206 @@ +use std::io::prelude::*; +use std::{collections::HashMap, error::Error}; +use std::{fs, fs::File, path::Path}; + +use async_std::task; +use dirs; +use futures::stream::TryStreamExt; +use golgi::{blobs, messages::SsbMessageValue, Sbot}; +use log::info; +use peach_lib::sbot::SbotConfig; +use temporary::Directory; + +use crate::error::PeachWebError; + +// SBOT HELPER FUNCTIONS + +pub async fn init_sbot_with_config( + sbot_config: &Option, +) -> Result { + // initialise sbot connection with ip:port and shscap from config file + let sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None).await? + } + None => Sbot::init(None, None).await?, + }; + + Ok(sbot_client) +} + +// FILEPATH FUNCTIONS +// return the path of the ssb-go directory +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + Ok(go_ssb_path) +} + +// check whether a blob is in the blobstore +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + Ok(blob_exists_locally) +} + +/* +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + // write file to temporary path + file.persist_to(&temp_path).await?; + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + Ok(blob_id) +} +*/ + +pub fn latest_sequence_number() -> Result> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // retrieve the local id + let id = sbot_client.whoami().await?; + + let history_stream = sbot_client.create_history_stream(id).await?; + let mut msgs: Vec = history_stream.try_collect().await?; + + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); + + // return the sequence number of the latest msg + Ok(msgs[0].sequence) + }) +} + +pub fn get_blocks_list() -> Result>, Box> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let blocks = sbot_client.get_blocks().await?; + + // we'll use this to store the profile info for each peer whom we block + let mut peer_list = Vec::new(); + + if !blocks.is_empty() { + for peer in blocks.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut peer_info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + peer_info.insert("id".to_string(), key.to_string()); + // we do not even attempt to find the blob for a blocked peer, + // since it may be vulgar to cause distress to the local peer. + peer_info.insert("blob_exists".to_string(), "false".to_string()); + // push profile info to peer_list vec + peer_list.push(peer_info) + } + } + + // return the list of blocked peers + Ok(peer_list) + }) +} + +pub fn get_follows_list() -> Result>, Box> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_list = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut peer_info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + peer_info.insert("id".to_string(), key.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = peer_info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + peer_info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + peer_info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => peer_info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + // push profile info to peer_list vec + peer_list.push(peer_info) + } + } + + // return the list of peers + Ok(peer_list) + }) +} + +pub fn get_friends_list() -> Result, Box> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // return the sequence number of the latest msg + //Ok(msgs[0].sequence) + + Ok(vec!["temp".to_string()]) + }) +} diff --git a/peach-web/src/utils/theme.rs b/peach-web/src/utils/theme.rs new file mode 100644 index 0000000..9f99492 --- /dev/null +++ b/peach-web/src/utils/theme.rs @@ -0,0 +1,134 @@ +use log::info; + +use crate::THEME; + +// THEME FUNCTIONS + +#[derive(Debug, Copy, Clone)] +pub enum Theme { + Light, + Dark, +} + +pub fn get_theme() -> String { + let current_theme = THEME.read().unwrap(); + match *current_theme { + Theme::Dark => "dark".to_string(), + _ => "light".to_string(), + } +} + +pub fn set_theme(theme: Theme) { + info!("set ui theme to: {:?}", theme); + let mut writable_theme = THEME.write().unwrap(); + *writable_theme = theme; +} + +// get_cookie + +// set_cookie + +/* +pub mod monitor; + +use std::io::prelude::*; +use std::{fs, fs::File, path::Path}; + +use dirs; +use golgi::blobs; +use log::info; +use peach_lib::sbot::SbotConfig; +use rocket::{ + fs::TempFile, + response::{Redirect, Responder}, + serde::Serialize, +}; +use rocket_dyn_templates::Template; +use temporary::Directory; + +use crate::{error::PeachWebError, THEME}; + +// FILEPATH FUNCTIONS + +// return the path of the ssb-go directory +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + + Ok(go_ssb_path) +} + +// check whether a blob is in the blobstore +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + + Ok(blob_exists_locally) +} + +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + + // write file to temporary path + file.persist_to(&temp_path).await?; + + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + + Ok(blob_id) +} + +// HELPER FUNCTIONS + +#[derive(Debug, Serialize)] +pub struct FlashContext { + pub flash_name: Option, + pub flash_msg: Option, +} + +/// A helper enum which allows routes to either return a Template or a Redirect +/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066 +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Responder)] +pub enum TemplateOrRedirect { + Template(Template), + Redirect(Redirect), +} +*/ -- 2.40.1 From 40c4f8aaf2b65bf774857fcdc9906145e89dcf73 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 12:27:00 +0200 Subject: [PATCH 19/66] implement flash cookies for auth change routes --- peach-web/rouille_refactor | 16 ++++++ peach-web/src/main.rs | 7 ++- peach-web/src/routes/authentication/change.rs | 51 +++++++++++-------- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor index 06e59ba..f8ad8d7 100644 --- a/peach-web/rouille_refactor +++ b/peach-web/rouille_refactor @@ -10,6 +10,22 @@ we do not need to be super fast or feature-rich. - use the one-file-per-route patten +[ rouille-specific ] + + - logging + - https://docs.rs/rouille/latest/rouille/fn.log_custom.html + x flash message + - https://docs.rs/rouille/latest/rouille/input/fn.cookies.html + - https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#creating_cookies + - https://docs.rs/rouille/latest/rouille/struct.Response.html#method.with_additional_header + - https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie + - file upload + - https://docs.rs/rouille/latest/rouille/input/post/index.html#handling-file-uploads + - auth + - https://github.com/tomaka/rouille/blob/master/examples/login-session.rs + - https://docs.rs/rouille/latest/rouille/struct.Response.html#method.basic_http_auth_login_required + + [ tasks ] - write the settings route(s) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index b30719e..baed99d 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -34,7 +34,7 @@ use rouille::{router, Response}; // crate-local dependencies use config::Config; -use utils::Theme; +use utils::{flash::FlashResponse, theme::Theme}; pub type BoxError = Box; @@ -129,7 +129,10 @@ fn main() { }, (GET) (/auth/change) => { - Response::html(routes::authentication::change::build_template()) + // build the html template + Response::html(routes::authentication::change::build_template(request)) + // reset the flash msg cookies in the response object + .reset_flash() }, (POST) (/auth/change) => { diff --git a/peach-web/src/routes/authentication/change.rs b/peach-web/src/routes/authentication/change.rs index 2fdd172..f85e9e9 100644 --- a/peach-web/src/routes/authentication/change.rs +++ b/peach-web/src/routes/authentication/change.rs @@ -1,14 +1,21 @@ use log::info; use maud::{html, PreEscaped}; use peach_lib::password_utils; -use rouille::{post_input, try_or_400, Request, Response}; +use rouille::{input, post_input, try_or_400, Request, Response}; -use crate::{error::PeachWebError, templates}; +use crate::{ + error::PeachWebError, + templates, + utils::flash::{FlashRequest, FlashResponse}, +}; // HELPER AND ROUTES FOR /auth/change (GET and POST) /// Password change form template builder. -pub fn build_template() -> PreEscaped { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + let form_template = html! { (PreEscaped("")) div class="card center" { @@ -28,9 +35,11 @@ pub fn build_template() -> PreEscaped { a class="button button-secondary center" href="/settings/admin" title="Cancel"{ "Cancel" } } } - (PreEscaped("")) - // TODO: render flash message - //{% include "snippets/flash_message" %} + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } } }; @@ -79,23 +88,23 @@ pub fn handle_form(request: &Request) -> Response { })); // save submitted admin id to file - let _result = save_password( + // match on the result and set flash name and msg accordingly + let (flash_name, flash_msg) = match save_password( &data.current_password, &data.new_password1, &data.new_password2, - ); + ) { + Ok(_) => ( + // = + "flash_name=success".to_string(), + "flash_msg=New password has been saved".to_string(), + ), + Err(err) => ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to save new password: {}", err), + ), + }; - // TODO: match on result and define flash message accordingly - // then send the redirect response - - // redirect to the configure admin page - // TODO: add flash message - Response::redirect_303("/auth/change") + // set the flash cookie headers and redirect to the configure admin page + Response::redirect_303("/auth/change").add_flash(flash_name, flash_msg) } - -/* - match result { - Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), - Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), - } -*/ -- 2.40.1 From cd7c2bc2309a1e75f0ad715ed6df081fc85ffde9 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 12:28:08 +0200 Subject: [PATCH 20/66] move sbot helper functions to utils --- peach-web/src/routes/status/scuttlebutt.rs | 49 +------- peach-web/src/utils.rs | 134 --------------------- 2 files changed, 3 insertions(+), 180 deletions(-) delete mode 100644 peach-web/src/utils.rs diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 36f7f9b..2a59029 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -1,51 +1,8 @@ -use std::error::Error; - -use async_std::task; -use futures::stream::TryStreamExt; -use golgi::{messages::SsbMessageValue, Sbot}; use maud::{html, Markup, PreEscaped}; use peach_lib::sbot::{SbotConfig, SbotStatus}; -use crate::{error::PeachWebError, templates}; - -// SBOT HELPER FUNCTIONS - -pub async fn init_sbot_with_config( - sbot_config: &Option, -) -> Result { - // initialise sbot connection with ip:port and shscap from config file - let sbot_client = match sbot_config { - // TODO: panics if we pass `Some(conf.shscap)` as second arg - Some(conf) => { - let ip_port = conf.lis.clone(); - Sbot::init(Some(ip_port), None).await? - } - None => Sbot::init(None, None).await?, - }; - - Ok(sbot_client) -} - -fn latest_sequence_number() -> Result> { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - task::block_on(async { - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - // retrieve the local id - let id = sbot_client.whoami().await?; - - let history_stream = sbot_client.create_history_stream(id).await?; - let mut msgs: Vec = history_stream.try_collect().await?; - - // reverse the list of messages so we can easily reference the latest one - msgs.reverse(); - - // return the sequence number of the latest msg - Ok(msgs[0].sequence) - }) -} +use crate::templates; +use crate::utils::sbot; // HTML RENDERING FOR ELEMENTS @@ -90,7 +47,7 @@ fn run_on_startup_element(boot_state: &Option) -> Markup { fn database_element(state: &str) -> Markup { // retrieve the sequence number of the latest message in the sbot database - let sequence_num = latest_sequence_number(); + let sequence_num = sbot::latest_sequence_number(); if state == "active" && sequence_num.is_ok() { let number = sequence_num.unwrap(); diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs deleted file mode 100644 index 9f99492..0000000 --- a/peach-web/src/utils.rs +++ /dev/null @@ -1,134 +0,0 @@ -use log::info; - -use crate::THEME; - -// THEME FUNCTIONS - -#[derive(Debug, Copy, Clone)] -pub enum Theme { - Light, - Dark, -} - -pub fn get_theme() -> String { - let current_theme = THEME.read().unwrap(); - match *current_theme { - Theme::Dark => "dark".to_string(), - _ => "light".to_string(), - } -} - -pub fn set_theme(theme: Theme) { - info!("set ui theme to: {:?}", theme); - let mut writable_theme = THEME.write().unwrap(); - *writable_theme = theme; -} - -// get_cookie - -// set_cookie - -/* -pub mod monitor; - -use std::io::prelude::*; -use std::{fs, fs::File, path::Path}; - -use dirs; -use golgi::blobs; -use log::info; -use peach_lib::sbot::SbotConfig; -use rocket::{ - fs::TempFile, - response::{Redirect, Responder}, - serde::Serialize, -}; -use rocket_dyn_templates::Template; -use temporary::Directory; - -use crate::{error::PeachWebError, THEME}; - -// FILEPATH FUNCTIONS - -// return the path of the ssb-go directory -pub fn get_go_ssb_path() -> Result { - let go_ssb_path = match SbotConfig::read() { - Ok(conf) => conf.repo, - // return the default path if unable to read `config.toml` - Err(_) => { - // determine the home directory - let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; - // add the go-ssb subdirectory - home_path.push(".ssb-go"); - // convert the PathBuf to a String - home_path - .into_os_string() - .into_string() - .map_err(|_| PeachWebError::OsString)? - } - }; - - Ok(go_ssb_path) -} - -// check whether a blob is in the blobstore -pub async fn blob_is_stored_locally(blob_path: &str) -> Result { - let go_ssb_path = get_go_ssb_path()?; - let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); - let blob_exists_locally = Path::new(&complete_path).exists(); - - Ok(blob_exists_locally) -} - -// take the path to a file, add it to the blobstore and return the blob id -pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { - // create temporary directory and path - let temp_dir = Directory::new("blob")?; - // we performed a `file.name().is_some()` check before calling `write_blob_to_store` - // so it should be safe to do a simple unwrap here - let filename = file.name().expect("retrieving filename from uploaded file"); - let temp_path = temp_dir.join(filename); - - // write file to temporary path - file.persist_to(&temp_path).await?; - - // open the file and read it into a buffer - let mut file = File::open(&temp_path)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; - - // hash the bytes representing the file - let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; - - // define the blobstore path and blob filename - let (blob_dir, blob_filename) = hex_hash.split_at(2); - let go_ssb_path = get_go_ssb_path()?; - let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); - - // create the blobstore sub-directory - fs::create_dir_all(&blobstore_sub_dir)?; - - // copy the file to the blobstore - let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); - fs::copy(temp_path, blob_path)?; - - Ok(blob_id) -} - -// HELPER FUNCTIONS - -#[derive(Debug, Serialize)] -pub struct FlashContext { - pub flash_name: Option, - pub flash_msg: Option, -} - -/// A helper enum which allows routes to either return a Template or a Redirect -/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066 -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Responder)] -pub enum TemplateOrRedirect { - Template(Template), - Redirect(Redirect), -} -*/ -- 2.40.1 From 976fac973d7f0a920038d533251048c15fdc40d5 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 15:36:24 +0200 Subject: [PATCH 21/66] add flash messages to admin settings and auth routes --- peach-web/src/main.rs | 9 ++-- peach-web/src/routes/authentication/change.rs | 4 +- peach-web/src/routes/authentication/login.rs | 36 ++++++++------- peach-web/src/routes/authentication/reset.rs | 46 +++++++++++-------- peach-web/src/routes/settings/admin/add.rs | 28 +++++------ .../src/routes/settings/admin/configure.rs | 30 ++++++++---- peach-web/src/routes/settings/admin/delete.rs | 35 +++++++------- 7 files changed, 109 insertions(+), 79 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index baed99d..023775c 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -140,7 +140,8 @@ fn main() { }, (GET) (/auth/login) => { - Response::html(routes::authentication::login::build_template()) + Response::html(routes::authentication::login::build_template(request)) + .reset_flash() }, (POST) (/auth/login) => { @@ -152,7 +153,8 @@ fn main() { }, (GET) (/auth/reset) => { - Response::html(routes::authentication::reset::build_template()) + Response::html(routes::authentication::reset::build_template(request)) + .reset_flash() }, (POST) (/auth/reset) => { @@ -184,7 +186,8 @@ fn main() { }, (GET) (/settings/admin/configure) => { - Response::html(routes::settings::admin::configure::build_template()) + Response::html(routes::settings::admin::configure::build_template(request)) + .reset_flash() }, (POST) (/settings/admin/delete) => { diff --git a/peach-web/src/routes/authentication/change.rs b/peach-web/src/routes/authentication/change.rs index f85e9e9..c33abad 100644 --- a/peach-web/src/routes/authentication/change.rs +++ b/peach-web/src/routes/authentication/change.rs @@ -1,7 +1,7 @@ use log::info; use maud::{html, PreEscaped}; use peach_lib::password_utils; -use rouille::{input, post_input, try_or_400, Request, Response}; +use rouille::{post_input, try_or_400, Request, Response}; use crate::{ error::PeachWebError, @@ -105,6 +105,6 @@ pub fn handle_form(request: &Request) -> Response { ), }; - // set the flash cookie headers and redirect to the configure admin page + // set the flash cookie headers and redirect to the change password page Response::redirect_303("/auth/change").add_flash(flash_name, flash_msg) } diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs index 7c0912e..aa643c9 100644 --- a/peach-web/src/routes/authentication/login.rs +++ b/peach-web/src/routes/authentication/login.rs @@ -3,12 +3,18 @@ use maud::{html, PreEscaped}; use peach_lib::password_utils; use rouille::{post_input, try_or_400, Request, Response}; -use crate::templates; +use crate::{ + templates, + utils::flash::{FlashRequest, FlashResponse}, +}; // HELPER AND ROUTES FOR /auth/login (GET and POST) /// Login form template builder. -pub fn build_template() -> PreEscaped { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + let form_template = html! { (PreEscaped("")) div class="card center" { @@ -23,9 +29,11 @@ pub fn build_template() -> PreEscaped { a href="/settings/admin/forgot_password" class="label-small link font-gray" { "Forgot Password?" } } } + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { (PreEscaped("")) - // TODO: render flash message - //{% include "snippets/flash_message" %} + (templates::flash::build_template(name, msg)) } } }; @@ -46,8 +54,6 @@ pub fn handle_form(request: &Request) -> Response { // return a 400 error if the admin_id field is missing let data = try_or_400!(post_input!(request, { password: String })); - // TODO: match on result and define flash message accordingly - // then send the redirect response match password_utils::verify_password(&data.password) { Ok(_) => { info!("Successful login attempt"); @@ -60,18 +66,16 @@ pub fn handle_form(request: &Request) -> Response { Response::redirect_303("/") } - Err(_e) => { + Err(err) => { info!("Unsuccessful login attempt"); - //let err_msg = format!("Invalid password: {}", e); + let err_msg = format!("Invalid password: {}", err); + let (flash_name, flash_msg) = ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to save new password: {}", err_msg), + ); + // if unsuccessful login, render /login page again - - /* - // TODO: add flash message - context.insert("flash_name", &("error".to_string())); - context.insert("flash_msg", &(err_msg)); - */ - - Response::redirect_303("/auth/login") + Response::redirect_303("/auth/login").add_flash(flash_name, flash_msg) } } } diff --git a/peach-web/src/routes/authentication/reset.rs b/peach-web/src/routes/authentication/reset.rs index 37a6ec5..6d19afd 100644 --- a/peach-web/src/routes/authentication/reset.rs +++ b/peach-web/src/routes/authentication/reset.rs @@ -3,12 +3,19 @@ use maud::{html, PreEscaped}; use peach_lib::password_utils; use rouille::{post_input, try_or_400, Request, Response}; -use crate::{error::PeachWebError, templates}; +use crate::{ + error::PeachWebError, + templates, + utils::flash::{FlashRequest, FlashResponse}, +}; // HELPER AND ROUTES FOR /auth/reset (GET and POST) /// Password reset form template builder. -pub fn build_template() -> PreEscaped { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + let form_template = html! { (PreEscaped("")) div class="card center" { @@ -28,9 +35,11 @@ pub fn build_template() -> PreEscaped { a class="button button-secondary center" href="/settings/admin" title="Cancel"{ "Cancel" } } } - (PreEscaped("")) - // TODO: render flash message - //{% include "snippets/flash_message" %} + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } } }; @@ -79,23 +88,22 @@ pub fn handle_form(request: &Request) -> Response { })); // save submitted admin id to file - let _result = save_password( + let (flash_name, flash_msg) = match save_password( &data.temporary_password, &data.new_password1, &data.new_password2, - ); - - // TODO: match on result and define flash message accordingly - // then send the redirect response + ) { + Ok(_) => ( + // = + "flash_name=success".to_string(), + "flash_msg=New password has been saved. Return home to login".to_string(), + ), + Err(err) => ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to reset password: {}", err), + ), + }; // redirect to the configure admin page - // TODO: add flash message - Response::redirect_303("/auth/reset") + Response::redirect_303("/auth/reset").add_flash(flash_name, flash_msg) } - -/* - match result { - Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), - Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), - } -*/ diff --git a/peach-web/src/routes/settings/admin/add.rs b/peach-web/src/routes/settings/admin/add.rs index cc63873..863d3c7 100644 --- a/peach-web/src/routes/settings/admin/add.rs +++ b/peach-web/src/routes/settings/admin/add.rs @@ -1,6 +1,8 @@ use peach_lib::config_manager; use rouille::{post_input, try_or_400, Request, Response}; +use crate::utils::flash::FlashResponse; + // HELPER AND ROUTES FOR /settings/admin/add /// Parse an `admin_id` from the submitted form, save it to file @@ -14,20 +16,20 @@ pub fn handle_form(request: &Request) -> Response { ssb_id: String, })); - // save submitted admin id to file - let _result = config_manager::add_ssb_admin_id(&data.ssb_id); + // TODO: verify that the given ssb_id is valid - // TODO: match on result and define flash message accordingly - // then send the redirect response + // save submitted admin id to file + let (flash_name, flash_msg) = match config_manager::add_ssb_admin_id(&data.ssb_id) { + Ok(_) => ( + "flash_name=success".to_string(), + "flash_msg=Added SSB administrator".to_string(), + ), + Err(err) => ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to add new administrator: {}", err), + ), + }; // redirect to the configure admin page - // TODO: add flash message - Response::redirect_303("/settings/admin/configure") + Response::redirect_303("/settings/admin/configure").add_flash(flash_name, flash_msg) } - -/* - match result { - Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), - Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), - } -*/ diff --git a/peach-web/src/routes/settings/admin/configure.rs b/peach-web/src/routes/settings/admin/configure.rs index 7edcb9e..9b37a1d 100644 --- a/peach-web/src/routes/settings/admin/configure.rs +++ b/peach-web/src/routes/settings/admin/configure.rs @@ -1,14 +1,27 @@ use maud::{html, PreEscaped}; use peach_lib::config_manager; +use rouille::Request; -use crate::templates; +use crate::{templates, utils::flash::FlashRequest}; /// Administrator settings menu template builder. -pub fn build_template() -> PreEscaped { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (mut flash_name, mut flash_msg) = request.retrieve_flash(); + // attempt to load peachcloud config file - let ssb_admins = config_manager::load_peach_config() - .ok() - .map(|config| config.ssb_admin_ids); + let ssb_admins = match config_manager::load_peach_config() { + Ok(config) => Some(config.ssb_admin_ids), + // note: this will overwrite any received flash cookie values + // TODO: find a way to include the `err` in the flash_msg + // currently produces an error because we end up with Some(String) + // instead of Some(str) + Err(_err) => { + flash_name = Some("flash_name=error"); + flash_msg = Some("flash_msg=Failed to read PeachCloud configuration file"); + None + } + }; let menu_template = html! { (PreEscaped("")) @@ -38,10 +51,11 @@ pub fn build_template() -> PreEscaped { } (PreEscaped("")) input class="button button-primary center" type="submit" title="Add SSB administrator" value="Add Admin"; + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { (PreEscaped("")) - @if ssb_admins.is_none() { - (templates::flash::build_template("error", "Failed to read PeachCloud configuration file")) - } + (templates::flash::build_template(name, &msg)) } } }; diff --git a/peach-web/src/routes/settings/admin/delete.rs b/peach-web/src/routes/settings/admin/delete.rs index 5a21c36..06bfc1c 100644 --- a/peach-web/src/routes/settings/admin/delete.rs +++ b/peach-web/src/routes/settings/admin/delete.rs @@ -1,6 +1,8 @@ use peach_lib::config_manager; use rouille::{post_input, try_or_400, Request, Response}; +use crate::utils::flash::FlashResponse; + // HELPERS AND ROUTES FOR /settings/admin/delete /// Parse an `admin_id` from the submitted form, delete it from file @@ -15,22 +17,19 @@ pub fn handle_form(request: &Request) -> Response { })); // remove submitted admin id from file - let _result = config_manager::delete_ssb_admin_id(&data.ssb_id); - - // TODO: match on result and define flash message accordingly - // then send the redirect response - - // redirect to the configure admin page - // TODO: add flash message - Response::redirect_303("/settings/admin/configure") -} - -/* - match result { - Ok(_) => Flash::success(Redirect::to(url), "Removed SSB administrator"), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to remove admin id: {}", e), + // match on the result and set flash name and msg accordingly + let (flash_name, flash_msg) = match config_manager::delete_ssb_admin_id(&data.ssb_id) { + Ok(_) => ( + // = + "flash_name=success".to_string(), + "flash_msg=Removed SSB administrator".to_string(), ), - } -*/ + Err(err) => ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to remove administrator: {}", err), + ), + }; + + // set the flash cookie headers and redirect to the configure admin page + Response::redirect_303("/settings/admin/configure").add_flash(flash_name, flash_msg) +} -- 2.40.1 From cad3fc94c88dce118a60afc5b802ebaf73d04082 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 15:36:37 +0200 Subject: [PATCH 22/66] update theme import --- peach-web/src/templates/nav.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peach-web/src/templates/nav.rs b/peach-web/src/templates/nav.rs index 221aa6b..98136c4 100644 --- a/peach-web/src/templates/nav.rs +++ b/peach-web/src/templates/nav.rs @@ -1,6 +1,6 @@ use maud::{html, PreEscaped}; -use crate::utils; +use crate::utils::theme; /// Navigation template builder. /// @@ -11,7 +11,7 @@ pub fn build_template( back: Option<&str>, ) -> PreEscaped { // retrieve the current theme value - let theme = utils::get_theme(); + let theme = theme::get_theme(); // conditionally render the hermies icon and theme-switcher icon with correct link let (hermies, switcher) = match theme.as_str() { -- 2.40.1 From e8b9cb2cc101c456177da7a2a1824bb31c5dce12 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 15:37:02 +0200 Subject: [PATCH 23/66] remove default rouille features --- Cargo.lock | 72 -------------------------------------------- peach-web/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3192727..1b684a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "aho-corasick" version = "0.7.18" @@ -32,21 +26,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -386,27 +365,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "brotli" -version = "3.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bstr" version = "0.2.17" @@ -573,15 +531,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "crossbeam-channel" version = "0.3.9" @@ -689,16 +638,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "deflate" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f95bf05dffba6e6cce8dfbb30def788154949ccd9aed761b472119c21e01c70" -dependencies = [ - "adler32", - "gzip-header", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -1196,15 +1135,6 @@ dependencies = [ "nix 0.11.1", ] -[[package]] -name = "gzip-header" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0131feb3d3bb2a5a238d8a4d09f6353b7ebfdc52e77bccbf4ea6eaa751dde639" -dependencies = [ - "crc32fast", -] - [[package]] name = "h2" version = "0.1.26" @@ -3015,9 +2945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18b2380c42510ef4a28b5f228a174c801e0dec590103e215e60812e2e2f34d05" dependencies = [ "base64 0.13.0", - "brotli", "chrono", - "deflate", "filetime", "multipart", "num_cpus", diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 74631dc..a4aa206 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -47,6 +47,6 @@ maud = "0.23.0" peach-lib = { path = "../peach-lib" } peach-network = { path = "../peach-network" } peach-stats = { path = "../peach-stats" } -rouille = "3.5.0" +rouille = { version = "3.5.0", default-features = false } temporary = "0.6.4" xdg = "2.2.0" -- 2.40.1 From 60539adf4147ca2da1d26263106ca4f1d38c0eab Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 16:37:53 +0200 Subject: [PATCH 24/66] add max-age and date for flash cookies --- peach-web/src/utils/flash.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/peach-web/src/utils/flash.rs b/peach-web/src/utils/flash.rs index 96be9a5..53c2501 100644 --- a/peach-web/src/utils/flash.rs +++ b/peach-web/src/utils/flash.rs @@ -32,13 +32,19 @@ pub trait FlashResponse { impl FlashResponse for Response { fn add_flash(self, flash_name: String, flash_msg: String) -> Response { // set the flash cookie headers - self.with_additional_header("Set-Cookie", flash_name) - .with_additional_header("Set-Cookie", flash_msg) + self.with_additional_header("Set-Cookie", format!("{}; Max-Age=1", flash_name)) + .with_additional_header("Set-Cookie", format!("{}; Max-Age=1", flash_msg)) } fn reset_flash(self) -> Response { // set blank cookies to clear the flash msg from the previous request - self.with_additional_header("Set-Cookie", "flash_name=") - .with_additional_header("Set-Cookie", "flash_msg=") + self.with_additional_header( + "Set-Cookie", + "flash_name=; Max-Age=0; Expires=Fri, 21 Aug 1987 12:00:00 UTC", + ) + .with_additional_header( + "Set-Cookie", + "flash_msg=; Max-Age=0; Expires=Fri, 21 Aug 1987 12:00:00 UTC", + ) } } -- 2.40.1 From 6db5e7c1691c1f36129f52fea4db80012f908a2a Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 16:38:32 +0200 Subject: [PATCH 25/66] add routes and helpers for starting, stopping and restarting the sbot --- peach-web/src/main.rs | 15 +++- peach-web/src/routes/settings/menu.rs | 4 +- .../src/routes/settings/scuttlebutt/menu.rs | 18 ++-- .../src/routes/settings/scuttlebutt/mod.rs | 3 + .../routes/settings/scuttlebutt/restart.rs | 33 ++++++++ .../src/routes/settings/scuttlebutt/start.rs | 26 ++++++ .../src/routes/settings/scuttlebutt/stop.rs | 26 ++++++ peach-web/src/utils/sbot.rs | 82 +++++++++++++++++-- 8 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 peach-web/src/routes/settings/scuttlebutt/restart.rs create mode 100644 peach-web/src/routes/settings/scuttlebutt/start.rs create mode 100644 peach-web/src/routes/settings/scuttlebutt/stop.rs diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 023775c..351ccff 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -170,7 +170,20 @@ fn main() { }, (GET) (/settings/scuttlebutt) => { - Response::html(routes::settings::scuttlebutt::menu::build_template()) + Response::html(routes::settings::scuttlebutt::menu::build_template(request)) + .reset_flash() + }, + + (GET) (/settings/scuttlebutt/restart) => { + routes::settings::scuttlebutt::restart::restart_sbot() + }, + + (GET) (/settings/scuttlebutt/start) => { + routes::settings::scuttlebutt::start::start_sbot() + }, + + (GET) (/settings/scuttlebutt/stop) => { + routes::settings::scuttlebutt::stop::stop_sbot() }, (GET) (/settings/scuttlebutt/configure) => { diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs index 9a3c16c..dddfd94 100644 --- a/peach-web/src/routes/settings/menu.rs +++ b/peach-web/src/routes/settings/menu.rs @@ -2,8 +2,8 @@ use maud::{html, PreEscaped}; use crate::{templates, CONFIG}; -// TODO: flash message implementation for rouille -// +// ROUTE: /settings + /// Settings menu template builder. pub fn build_template() -> PreEscaped { let menu_template = html! { diff --git a/peach-web/src/routes/settings/scuttlebutt/menu.rs b/peach-web/src/routes/settings/scuttlebutt/menu.rs index 58c9822..8acd000 100644 --- a/peach-web/src/routes/settings/scuttlebutt/menu.rs +++ b/peach-web/src/routes/settings/scuttlebutt/menu.rs @@ -1,10 +1,8 @@ use maud::{html, PreEscaped}; use peach_lib::sbot::SbotStatus; +use rouille::Request; -use crate::templates; - -// TODO: flash message implementation for rouille -// +use crate::{templates, utils::flash::FlashRequest}; /// Read the status of the go-sbot service and render buttons accordingly. fn render_process_buttons() -> PreEscaped { @@ -28,8 +26,13 @@ fn render_process_buttons() -> PreEscaped { } } +// ROUTE: /settings/scuttlebutt + /// Scuttlebutt settings menu template builder. -pub fn build_template() -> PreEscaped { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + let menu_template = html! { (PreEscaped("")) div class="card center" { @@ -39,6 +42,11 @@ pub fn build_template() -> PreEscaped { // conditionally render the start / stop / restart buttons (render_process_buttons()) } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, &msg)) + } } }; diff --git a/peach-web/src/routes/settings/scuttlebutt/mod.rs b/peach-web/src/routes/settings/scuttlebutt/mod.rs index 485da58..e969135 100644 --- a/peach-web/src/routes/settings/scuttlebutt/mod.rs +++ b/peach-web/src/routes/settings/scuttlebutt/mod.rs @@ -1,2 +1,5 @@ pub mod configure; pub mod menu; +pub mod restart; +pub mod start; +pub mod stop; diff --git a/peach-web/src/routes/settings/scuttlebutt/restart.rs b/peach-web/src/routes/settings/scuttlebutt/restart.rs new file mode 100644 index 0000000..3e25b73 --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/restart.rs @@ -0,0 +1,33 @@ +use log::info; +use rouille::Response; + +use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd}; + +// ROUTE: /settings/scuttlebutt/restart + +/// Attempt to restart the go-sbot.service process. +/// Redirect to the Scuttlebutt settings menu and communicate the outcome of +/// the attempt via a flash message. +pub fn restart_sbot() -> Response { + info!("Restarting go-sbot.service"); + let (flash_name, flash_msg) = match systemctl_sbot_cmd("stop") { + // if stop was successful, try to start the process + Ok(_) => match systemctl_sbot_cmd("start") { + Ok(_) => ( + "flash_name=success".to_string(), + "flash_msg=Sbot process has been restarted".to_string(), + ), + Err(e) => ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to start the sbot process: {}", e), + ), + }, + Err(e) => ( + "flash_name=error".to_string(), + format!("flash_msg=Failed to stop the sbot process: {}", e), + ), + }; + + // redirect to the scuttlebutt settings menu + Response::redirect_303("/settings/scuttlebutt").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/settings/scuttlebutt/start.rs b/peach-web/src/routes/settings/scuttlebutt/start.rs new file mode 100644 index 0000000..2ee7f4e --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/start.rs @@ -0,0 +1,26 @@ +use log::info; +use rouille::Response; + +use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd}; + +// ROUTE: /settings/scuttlebutt/start + +/// Attempt to start the go-sbot.service process. +/// Redirect to the Scuttlebutt settings menu and communicate the outcome of +/// the attempt via a flash message. +pub fn start_sbot() -> Response { + info!("Starting go-sbot.service"); + let (flash_name, flash_msg) = match systemctl_sbot_cmd("start") { + Ok(_) => ( + "flash_name=success".to_string(), + "flash_msg=Sbot process has been started".to_string(), + ), + Err(_) => ( + "flash_name=error".to_string(), + "flash_msg=Failed to start the sbot process".to_string(), + ), + }; + + // redirect to the scuttlebutt settings menu + Response::redirect_303("/settings/scuttlebutt").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/settings/scuttlebutt/stop.rs b/peach-web/src/routes/settings/scuttlebutt/stop.rs new file mode 100644 index 0000000..d548698 --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/stop.rs @@ -0,0 +1,26 @@ +use log::info; +use rouille::Response; + +use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd}; + +// ROUTE: /settings/scuttlebutt/stop + +/// Attempt to stop the go-sbot.service process. +/// Redirect to the Scuttlebutt settings menu and communicate the outcome of +/// the attempt via a flash message. +pub fn stop_sbot() -> Response { + info!("Stopping go-sbot.service"); + let (flash_name, flash_msg) = match systemctl_sbot_cmd("stop") { + Ok(_) => ( + "flash_name=success".to_string(), + "flash_msg=Sbot process has been stopped".to_string(), + ), + Err(_) => ( + "flash_name=error".to_string(), + "flash_msg=Failed to stop the sbot process".to_string(), + ), + }; + + // redirect to the scuttlebutt settings menu + Response::redirect_303("/settings/scuttlebutt").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index d916476..5dcf05e 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -1,19 +1,35 @@ -use std::io::prelude::*; -use std::{collections::HashMap, error::Error}; -use std::{fs, fs::File, path::Path}; +use std::{ + collections::HashMap, + error::Error, + fs, + fs::File, + io, + path::Path, + process::{Command, Output}, +}; use async_std::task; use dirs; use futures::stream::TryStreamExt; -use golgi::{blobs, messages::SsbMessageValue, Sbot}; +use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; use log::info; use peach_lib::sbot::SbotConfig; use temporary::Directory; -use crate::error::PeachWebError; +use crate::{error::PeachWebError, utils::sbot}; // SBOT HELPER FUNCTIONS +/// Executes a systemctl command for the go-sbot.service process. +pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result { + Command::new("systemctl") + .arg("--user") + .arg(cmd) + .arg("go-sbot.service") + .output() +} + +/// Initialise an sbot client with the given configuration parameters. pub async fn init_sbot_with_config( sbot_config: &Option, ) -> Result { @@ -191,16 +207,64 @@ pub fn get_follows_list() -> Result>, Box }) } -pub fn get_friends_list() -> Result, Box> { +pub fn get_friends_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); task::block_on(async { let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - // return the sequence number of the latest msg - //Ok(msgs[0].sequence) + let local_id = sbot_client.whoami().await?; - Ok(vec!["temp".to_string()]) + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each friend + let mut peer_list = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let peer_id = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut peer_info = sbot_client.get_profile_info(&peer_id).await?; + // insert the public key of the peer into the info hashmap + peer_info.insert("id".to_string(), peer_id.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = peer_info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + peer_info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match sbot::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + peer_info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => peer_info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + + // check if the peer follows us (making us friends) + let follow_query = RelationshipQuery { + source: peer_id.to_string(), + dest: local_id.clone(), + }; + + // query follow state + match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => { + // only push profile info to peer_list vec if they follow us + peer_list.push(peer_info) + } + _ => (), + }; + } + } + + // return the list of peers + Ok(peer_list) }) } -- 2.40.1 From 4e8d93c3884f29d9811afdcfe218666916fb14b0 Mon Sep 17 00:00:00 2001 From: glyph Date: Sun, 20 Mar 2022 17:17:17 +0200 Subject: [PATCH 26/66] move routes to the router --- peach-web/src/main.rs | 104 ++----------------------------------- peach-web/src/router.rs | 112 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 100 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 351ccff..f5cb47b 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -15,7 +15,7 @@ //mod context; mod config; pub mod error; -//mod router; +mod router; mod routes; //#[cfg(test)] //mod tests; @@ -30,11 +30,9 @@ use log::info; //use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG}; //use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket}; -use rouille::{router, Response}; - // crate-local dependencies use config::Config; -use utils::{flash::FlashResponse, theme::Theme}; +use utils::theme::Theme; pub type BoxError = Box; @@ -123,101 +121,7 @@ fn main() { return response; } - router!(request, - (GET) (/) => { - Response::html(routes::home::build_template()) - }, - - (GET) (/auth/change) => { - // build the html template - Response::html(routes::authentication::change::build_template(request)) - // reset the flash msg cookies in the response object - .reset_flash() - }, - - (POST) (/auth/change) => { - routes::authentication::change::handle_form(request) - }, - - (GET) (/auth/login) => { - Response::html(routes::authentication::login::build_template(request)) - .reset_flash() - }, - - (POST) (/auth/login) => { - routes::authentication::login::handle_form(request) - }, - - (GET) (/auth/logout) => { - routes::authentication::logout::deauthenticate() - }, - - (GET) (/auth/reset) => { - Response::html(routes::authentication::reset::build_template(request)) - .reset_flash() - }, - - (POST) (/auth/reset) => { - routes::authentication::reset::handle_form(request) - }, - - (GET) (/guide) => { - Response::html(routes::guide::build_template()) - }, - - (GET) (/settings) => { - Response::html(routes::settings::menu::build_template()) - }, - - (GET) (/settings/scuttlebutt) => { - Response::html(routes::settings::scuttlebutt::menu::build_template(request)) - .reset_flash() - }, - - (GET) (/settings/scuttlebutt/restart) => { - routes::settings::scuttlebutt::restart::restart_sbot() - }, - - (GET) (/settings/scuttlebutt/start) => { - routes::settings::scuttlebutt::start::start_sbot() - }, - - (GET) (/settings/scuttlebutt/stop) => { - routes::settings::scuttlebutt::stop::stop_sbot() - }, - - (GET) (/settings/scuttlebutt/configure) => { - Response::html(routes::settings::scuttlebutt::configure::build_template()) - }, - - (GET) (/settings/admin) => { - Response::html(routes::settings::admin::menu::build_template()) - }, - - (POST) (/settings/admin/add) => { - routes::settings::admin::add::handle_form(request) - }, - - (GET) (/settings/admin/configure) => { - Response::html(routes::settings::admin::configure::build_template(request)) - .reset_flash() - }, - - (POST) (/settings/admin/delete) => { - routes::settings::admin::delete::handle_form(request) - }, - - (GET) (/scuttlebutt/peers) => { - Response::html(routes::scuttlebutt::peers::build_template()) - }, - - (GET) (/status/scuttlebutt) => { - Response::html(routes::status::scuttlebutt::build_template()) - }, - - // The code block is called if none of the other blocks matches the request. - // We return an empty response with a 404 status code. - _ => Response::empty_404() - ) + // TODO: mount the blobstore fileserver (see old code in src/router.rs) + router::mount_peachpub_routes(request) }); } diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 0872af0..bc35f5a 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -1,3 +1,114 @@ +use rouille::{router, Request, Response}; + +use crate::{routes, utils::flash::FlashResponse}; + +/// Define the PeachPub router. +/// +/// Takes an incoming request and matches on the defined routes, +/// returning either a template or a redirect. +/// +/// Excludes settings and status routes related to networking and the device +/// (memory, hard disk, CPU etc.). +pub fn mount_peachpub_routes(request: &Request) -> Response { + router!(request, + (GET) (/) => { + Response::html(routes::home::build_template()) + }, + + (GET) (/auth/change) => { + // build the html template + Response::html(routes::authentication::change::build_template(request)) + // reset the flash msg cookies in the response object + .reset_flash() + }, + + (POST) (/auth/change) => { + routes::authentication::change::handle_form(request) + }, + + (GET) (/auth/login) => { + Response::html(routes::authentication::login::build_template(request)) + .reset_flash() + }, + + (POST) (/auth/login) => { + routes::authentication::login::handle_form(request) + }, + + (GET) (/auth/logout) => { + routes::authentication::logout::deauthenticate() + }, + + (GET) (/auth/reset) => { + Response::html(routes::authentication::reset::build_template(request)) + .reset_flash() + }, + + (POST) (/auth/reset) => { + routes::authentication::reset::handle_form(request) + }, + + (GET) (/guide) => { + Response::html(routes::guide::build_template()) + }, + + (GET) (/settings) => { + Response::html(routes::settings::menu::build_template()) + }, + + (GET) (/settings/scuttlebutt) => { + Response::html(routes::settings::scuttlebutt::menu::build_template(request)) + .reset_flash() + }, + + (GET) (/settings/scuttlebutt/restart) => { + routes::settings::scuttlebutt::restart::restart_sbot() + }, + + (GET) (/settings/scuttlebutt/start) => { + routes::settings::scuttlebutt::start::start_sbot() + }, + + (GET) (/settings/scuttlebutt/stop) => { + routes::settings::scuttlebutt::stop::stop_sbot() + }, + + (GET) (/settings/scuttlebutt/configure) => { + Response::html(routes::settings::scuttlebutt::configure::build_template()) + }, + + (GET) (/settings/admin) => { + Response::html(routes::settings::admin::menu::build_template()) + }, + + (POST) (/settings/admin/add) => { + routes::settings::admin::add::handle_form(request) + }, + + (GET) (/settings/admin/configure) => { + Response::html(routes::settings::admin::configure::build_template(request)) + .reset_flash() + }, + + (POST) (/settings/admin/delete) => { + routes::settings::admin::delete::handle_form(request) + }, + + (GET) (/scuttlebutt/peers) => { + Response::html(routes::scuttlebutt::peers::build_template()) + }, + + (GET) (/status/scuttlebutt) => { + Response::html(routes::status::scuttlebutt::build_template()) + }, + + // The code block is called if none of the other blocks matches the request. + // We return an empty response with a 404 status code. + _ => Response::empty_404() + ) +} + +/* use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; use rocket_dyn_templates::Template; @@ -123,3 +234,4 @@ pub fn mount_peachcloud_routes(rocket: Rocket) -> Rocket { ) .mount("/status", routes![device_status, network_status]) } +*/ -- 2.40.1 From 0353586705f62b35893989dc636216184b758af9 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 11:17:30 +0200 Subject: [PATCH 27/66] add friends, follows and blocks route handlers and templates --- peach-web/src/router.rs | 12 ++++ peach-web/src/routes/scuttlebutt/blocks.rs | 21 ++++++ peach-web/src/routes/scuttlebutt/follows.rs | 21 ++++++ peach-web/src/routes/scuttlebutt/friends.rs | 21 ++++++ peach-web/src/routes/scuttlebutt/mod.rs | 3 + peach-web/src/templates/mod.rs | 2 + peach-web/src/templates/peers_list.rs | 78 +++++++++++++++++++++ 7 files changed, 158 insertions(+) create mode 100644 peach-web/src/routes/scuttlebutt/blocks.rs create mode 100644 peach-web/src/routes/scuttlebutt/follows.rs create mode 100644 peach-web/src/routes/scuttlebutt/friends.rs create mode 100644 peach-web/src/templates/peers_list.rs diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index bc35f5a..279225f 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -94,6 +94,18 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { routes::settings::admin::delete::handle_form(request) }, + (GET) (/scuttlebutt/blocks) => { + Response::html(routes::scuttlebutt::blocks::build_template()) + }, + + (GET) (/scuttlebutt/follows) => { + Response::html(routes::scuttlebutt::follows::build_template()) + }, + + (GET) (/scuttlebutt/friends) => { + Response::html(routes::scuttlebutt::friends::build_template()) + }, + (GET) (/scuttlebutt/peers) => { Response::html(routes::scuttlebutt::peers::build_template()) }, diff --git a/peach-web/src/routes/scuttlebutt/blocks.rs b/peach-web/src/routes/scuttlebutt/blocks.rs new file mode 100644 index 0000000..3d23b69 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/blocks.rs @@ -0,0 +1,21 @@ +use maud::PreEscaped; + +use crate::{templates, utils::sbot}; + +/// Scuttlebutt blocks list template builder. +pub fn build_template() -> PreEscaped { + // retrieve the list of blocked peers + match sbot::get_blocks_list() { + // populate the peers_list template with blocks and render it + Ok(blocks) => templates::peers_list::build_template(blocks, "Blocks"), + Err(e) => { + // render the sbot error template with the error message + let error_template = templates::error::build_template(e.to_string()); + // wrap the nav bars around the error template content + let body = templates::nav::build_template(error_template, "Blocks", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) + } + } +} diff --git a/peach-web/src/routes/scuttlebutt/follows.rs b/peach-web/src/routes/scuttlebutt/follows.rs new file mode 100644 index 0000000..da0bcd2 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/follows.rs @@ -0,0 +1,21 @@ +use maud::PreEscaped; + +use crate::{templates, utils::sbot}; + +/// Scuttlebutt follows list template builder. +pub fn build_template() -> PreEscaped { + // retrieve the list of follows + match sbot::get_follows_list() { + // populate the peers_list template with follows + Ok(follows) => templates::peers_list::build_template(follows, "Follows"), + Err(e) => { + // render the sbot error template with the error message + let error_template = templates::error::build_template(e.to_string()); + // wrap the nav bars around the error template content + let body = templates::nav::build_template(error_template, "Follows", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) + } + } +} diff --git a/peach-web/src/routes/scuttlebutt/friends.rs b/peach-web/src/routes/scuttlebutt/friends.rs new file mode 100644 index 0000000..4f32600 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/friends.rs @@ -0,0 +1,21 @@ +use maud::PreEscaped; + +use crate::{templates, utils::sbot}; + +/// Scuttlebutt friends list template builder. +pub fn build_template() -> PreEscaped { + // retrieve the list of friends + match sbot::get_friends_list() { + // populate the peers_list template with friends and render it + Ok(friends) => templates::peers_list::build_template(friends, "Friends"), + Err(e) => { + // render the sbot error template with the error message + let error_template = templates::error::build_template(e.to_string()); + // wrap the nav bars around the error template content + let body = templates::nav::build_template(error_template, "Friends", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) + } + } +} diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs index d9cebd1..018aef5 100644 --- a/peach-web/src/routes/scuttlebutt/mod.rs +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -1 +1,4 @@ +pub mod blocks; +pub mod follows; +pub mod friends; pub mod peers; diff --git a/peach-web/src/templates/mod.rs b/peach-web/src/templates/mod.rs index d92a585..8711a46 100644 --- a/peach-web/src/templates/mod.rs +++ b/peach-web/src/templates/mod.rs @@ -1,4 +1,6 @@ pub mod base; +pub mod error; pub mod flash; pub mod inactive; pub mod nav; +pub mod peers_list; diff --git a/peach-web/src/templates/peers_list.rs b/peach-web/src/templates/peers_list.rs new file mode 100644 index 0000000..315385d --- /dev/null +++ b/peach-web/src/templates/peers_list.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; + +use maud::{html, Markup, PreEscaped}; +use peach_lib::sbot::SbotStatus; + +use crate::templates; + +/// Render an unordered list of peers with one list element for each peer. +fn peers_template(peers: Vec>) -> Markup { + html! { + ul class="center list" { + @for peer in peers { + @let (name, name_alt) = match peer.get("name") { + Some(name) => ( + name.to_owned(), + format!("{}'s profile image", name) + ), + None => ( + // set a fall-back value for name in case the data is unavailable + "name unavailable".to_string(), + "Profile image".to_string() + ) + }; + @let profile_link = format!("/scuttlebutt/profile?public_key={}", peer["id"]); + li { + a class="list-item link" href=(profile_link) { + @if peer.get("blob_path").is_some() && peer["blob_exists"] == "true" { + @let blob_path = format!("/blob/{}", peer["blob_path"]); + img id="peerImage" class="icon list-icon" src=(blob_path) alt=(name_alt); + } @else { + // use a placeholder image if we don't have the blob + img id="peerImage" class="icon icon-active list-icon" src="/icons/user.svg" alt="Placeholder profile image"; + } + p id="peerName" class="font-normal list-text" { (name) }; + @let name_title = format!("{}'s public key", name); + label class="label-small label-ellipsis list-label font-gray" for="peerName" title=(name_title) { + (peer["id"]) + } + } + } + } + } + } +} + +/// Scuttlebutt peers list template builder. +/// +/// A list of peers. Currently used to render lists of friends, follows and +/// blocks. The title of the page is set according to the provided parameter. +pub fn build_template(peers: Vec>, title: &str) -> PreEscaped { + let peer_list_template = match SbotStatus::read() { + // only render the complete peers list if the sbot is active + Ok(status) if status.state == Some("active".to_string()) => { + html! { + div class="card center" { + @if !peers.is_empty() { + // render the peers list template + (peers_template(peers)) + } @else { + p { "No follows found" } + } + } + } + } + _ => { + // the sbot is not active; render a message instead of the menu + templates::inactive::build_template("Social lists and interactions are unavailable.") + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = + templates::nav::build_template(peer_list_template, title, Some("/scuttlebutt/peers")); + + // render the base template with the provided body + templates::base::build_template(body) +} -- 2.40.1 From a379de179d1c66442cd51fbe4516ae16fc6ef149 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 11:17:42 +0200 Subject: [PATCH 28/66] add sbot error template --- peach-web/src/templates/error.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 peach-web/src/templates/error.rs diff --git a/peach-web/src/templates/error.rs b/peach-web/src/templates/error.rs new file mode 100644 index 0000000..fef2196 --- /dev/null +++ b/peach-web/src/templates/error.rs @@ -0,0 +1,27 @@ +use maud::{html, Markup, PreEscaped}; + +/// Sbot error template builder. +/// +/// Display a message to the operator when an sbot command returns an error. +pub fn build_template(error_msg: String) -> Markup { + html! { + (PreEscaped("")) + div class="card center" { + div class="capsule capsule-container border-danger center-text" { + p class="card-text" style="font-size: var(--font-size-4);" { + "Sbot Error" + } + p class="card-text" { (error_msg) } + p class="card-text" { + "Visit the " + strong { + a href="/settings/scuttlebutt" class="link" { + "Scuttlebutt settings menu" + } + } + " to start the Sbot and then try again." + } + } + } + } +} -- 2.40.1 From 112cfca67b5faa464496649797c437761173ac5a Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 13:26:32 +0200 Subject: [PATCH 29/66] add url comment --- peach-web/src/routes/scuttlebutt/blocks.rs | 2 ++ peach-web/src/routes/scuttlebutt/follows.rs | 2 ++ peach-web/src/routes/scuttlebutt/friends.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/peach-web/src/routes/scuttlebutt/blocks.rs b/peach-web/src/routes/scuttlebutt/blocks.rs index 3d23b69..f22fbb4 100644 --- a/peach-web/src/routes/scuttlebutt/blocks.rs +++ b/peach-web/src/routes/scuttlebutt/blocks.rs @@ -2,6 +2,8 @@ use maud::PreEscaped; use crate::{templates, utils::sbot}; +// ROUTE: /scuttlebutt/blocks + /// Scuttlebutt blocks list template builder. pub fn build_template() -> PreEscaped { // retrieve the list of blocked peers diff --git a/peach-web/src/routes/scuttlebutt/follows.rs b/peach-web/src/routes/scuttlebutt/follows.rs index da0bcd2..258d85b 100644 --- a/peach-web/src/routes/scuttlebutt/follows.rs +++ b/peach-web/src/routes/scuttlebutt/follows.rs @@ -2,6 +2,8 @@ use maud::PreEscaped; use crate::{templates, utils::sbot}; +// ROUTE: /scuttlebutt/follows + /// Scuttlebutt follows list template builder. pub fn build_template() -> PreEscaped { // retrieve the list of follows diff --git a/peach-web/src/routes/scuttlebutt/friends.rs b/peach-web/src/routes/scuttlebutt/friends.rs index 4f32600..0279c62 100644 --- a/peach-web/src/routes/scuttlebutt/friends.rs +++ b/peach-web/src/routes/scuttlebutt/friends.rs @@ -2,6 +2,8 @@ use maud::PreEscaped; use crate::{templates, utils::sbot}; +// ROUTE: /scuttlebutt/friends + /// Scuttlebutt friends list template builder. pub fn build_template() -> PreEscaped { // retrieve the list of friends -- 2.40.1 From 34b4cbff32fb0209993aecb9bfdcca2ecb6b2d53 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 13:27:32 +0200 Subject: [PATCH 30/66] add search and invite templates and route handlers --- peach-web/src/router.rs | 18 +++ peach-web/src/routes/scuttlebutt/invites.rs | 94 +++++++++++ peach-web/src/routes/scuttlebutt/mod.rs | 2 + peach-web/src/routes/scuttlebutt/search.rs | 66 ++++++++ peach-web/src/utils/sbot.rs | 169 +++++++++++++------- 5 files changed, 293 insertions(+), 56 deletions(-) create mode 100644 peach-web/src/routes/scuttlebutt/invites.rs create mode 100644 peach-web/src/routes/scuttlebutt/search.rs diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 279225f..9284e1b 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -106,10 +106,28 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { Response::html(routes::scuttlebutt::friends::build_template()) }, + (GET) (/scuttlebutt/invites) => { + Response::html(routes::scuttlebutt::invites::build_template(request)) + .reset_flash() + }, + + (POST) (/scuttlebutt/invites) => { + routes::scuttlebutt::invites::handle_form(request) + }, + (GET) (/scuttlebutt/peers) => { Response::html(routes::scuttlebutt::peers::build_template()) }, + (GET) (/scuttlebutt/search) => { + Response::html(routes::scuttlebutt::search::build_template(request)) + .reset_flash() + }, + + (POST) (/scuttlebutt/search) => { + routes::scuttlebutt::search::handle_form(request) + }, + (GET) (/status/scuttlebutt) => { Response::html(routes::status::scuttlebutt::build_template()) }, diff --git a/peach-web/src/routes/scuttlebutt/invites.rs b/peach-web/src/routes/scuttlebutt/invites.rs new file mode 100644 index 0000000..098f04e --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/invites.rs @@ -0,0 +1,94 @@ +use maud::{html, Markup, PreEscaped}; +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + sbot, + }, +}; + +// ROUTE: /scuttlebutt/invites + +/// Render the invite form template. +fn invite_form_template( + flash_name: Option<&str>, + flash_msg: Option<&str>, + invite_code: Option<&str>, +) -> Markup { + html! { + (PreEscaped("")) + div class="card center" { + form id="invites" class="center" action="/scuttlebutt/invites" method="post" { + div class="center" style="width: 80%;" { + label for="inviteUses" class="label-small font-gray" title="Number of times the invite code can be reused" { "USES" } + input type="number" id="inviteUses" name="uses" min="1" max="150" size="3" value="1"; + @if let Some(code) = invite_code { + p class="card-text" style="margin-top: 1rem; user-select: all;" title="Invite code" { + (code) + } + } + } + (PreEscaped("")) + input id="createInvite" class="button button-primary center" style="margin-top: 1rem;" type="submit" title="Create a new invite code" value="Create"; + a id="cancel" class="button button-secondary center" href="/scuttlebutt/peers" title="Cancel" { "Cancel" } + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + // avoid displaying the invite code-containing flash msg + @if name != "code" { + (PreEscaped("")) + (templates::flash::build_template(&name, &msg)) + } + } + } + } +} + +/// Scuttlebutt invite template builder. +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + // if flash_name is "code" then flash_msg will be an invite code + let invite_code = if flash_name == Some("code") { + flash_msg + } else { + None + }; + + let invite_form_template = match SbotStatus::read() { + // only render the invite form template if the sbot is active + Ok(status) if status.state == Some("active".to_string()) => { + html! { (invite_form_template(flash_name, flash_msg, invite_code)) } + } + _ => { + // the sbot is not active; render a message instead of the invite form + templates::inactive::build_template("Invite creation is unavailable.") + } + }; + + let body = + templates::nav::build_template(invite_form_template, "Invites", Some("/scuttlebutt/peers")); + + templates::base::build_template(body) +} + +/// Parse the invite uses data and attempt to generate an invite code. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + // the number of times the invite code can be used + uses: u16, + })); + + let (flash_name, flash_msg) = match sbot::create_invite(data.uses) { + Ok(code) => ("flash_name=code".to_string(), format!("flash_msg={}", code)), + Err(e) => ("flash_name=error".to_string(), format!("flash_msg={}", e)), + }; + + Response::redirect_303("/scuttlebutt/invites").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs index 018aef5..41d4cf9 100644 --- a/peach-web/src/routes/scuttlebutt/mod.rs +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -1,4 +1,6 @@ pub mod blocks; pub mod follows; pub mod friends; +pub mod invites; pub mod peers; +pub mod search; diff --git a/peach-web/src/routes/scuttlebutt/search.rs b/peach-web/src/routes/scuttlebutt/search.rs new file mode 100644 index 0000000..b8055c0 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/search.rs @@ -0,0 +1,66 @@ +use maud::{html, PreEscaped}; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + sbot, + }, +}; + +// ROUTE: /scuttlebutt/search + +/// Scuttlebutt peer search template builder. +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + let search_template = html! { + (PreEscaped("")) + div class="card center" { + form id="sbotConfig" class="center" action="/scuttlebutt/search" method="post" { + div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Public key (ID) of a peer" { + label for="publicKey" class="label-small font-gray" { "PUBLIC KEY" } + input type="text" id="publicKey" name="public_key" placeholder="@xYz...=.ed25519" autofocus; + } + (PreEscaped("")) + input id="search" class="button button-primary center" type="submit" title="Search for peer" value="Search"; + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + } + }; + + let body = + templates::nav::build_template(search_template, "Search", Some("/scuttlebutt/peers")); + + templates::base::build_template(body) +} + +/// Parse the public key, verify that it's valid and then redirect to the +/// profile of the given key. +/// +/// If the public key is invalid, set an error flash message and redirect. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + public_key: String, + })); + + match sbot::validate_public_key(&data.public_key) { + Ok(_) => { + let url = format!("/scuttlebutt/profile?={}", &data.public_key); + Response::redirect_303(url) + } + Err(err) => { + let (flash_name, flash_msg) = + ("flash_name=error".to_string(), format!("flash_msg={}", err)); + Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg) + } + } +} diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index 5dcf05e..62d449a 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -46,65 +46,45 @@ pub async fn init_sbot_with_config( Ok(sbot_client) } -// FILEPATH FUNCTIONS -// return the path of the ssb-go directory -pub fn get_go_ssb_path() -> Result { - let go_ssb_path = match SbotConfig::read() { - Ok(conf) => conf.repo, - // return the default path if unable to read `config.toml` - Err(_) => { - // determine the home directory - let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; - // add the go-ssb subdirectory - home_path.push(".ssb-go"); - // convert the PathBuf to a String - home_path - .into_os_string() - .into_string() - .map_err(|_| PeachWebError::OsString)? - } +// SCUTTLEBUTT FUNCTIONS + +/// Ensure that the given public key is a valid ed25519 key. +/// +/// Return an error string if the key is invalid. +pub fn validate_public_key(public_key: &str) -> Result<(), String> { + // ensure the id starts with the correct sigil link + if !public_key.starts_with('@') { + return Err("Invalid key: expected '@' sigil as first character".to_string()); + } + + // find the dot index denoting the start of the algorithm definition tag + let dot_index = match public_key.rfind('.') { + Some(index) => index, + None => return Err("Invalid key: no dot index was found".to_string()), }; - Ok(go_ssb_path) + + // check hashing algorithm (must end with ".ed25519") + if !&public_key.ends_with(".ed25519") { + return Err("Invalid key: hashing algorithm must be ed25519".to_string()); + } + + // obtain the base64 portion (substring) of the public key + let base64_str = &public_key[1..dot_index]; + + // length of a base64 encoded ed25519 public key + if base64_str.len() != 44 { + return Err("Invalid key: base64 data length is incorrect".to_string()); + } + + Ok(()) } -// check whether a blob is in the blobstore -pub async fn blob_is_stored_locally(blob_path: &str) -> Result { - let go_ssb_path = get_go_ssb_path()?; - let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); - let blob_exists_locally = Path::new(&complete_path).exists(); - Ok(blob_exists_locally) -} - -/* -// take the path to a file, add it to the blobstore and return the blob id -pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { - // create temporary directory and path - let temp_dir = Directory::new("blob")?; - // we performed a `file.name().is_some()` check before calling `write_blob_to_store` - // so it should be safe to do a simple unwrap here - let filename = file.name().expect("retrieving filename from uploaded file"); - let temp_path = temp_dir.join(filename); - // write file to temporary path - file.persist_to(&temp_path).await?; - // open the file and read it into a buffer - let mut file = File::open(&temp_path)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; - // hash the bytes representing the file - let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; - // define the blobstore path and blob filename - let (blob_dir, blob_filename) = hex_hash.split_at(2); - let go_ssb_path = get_go_ssb_path()?; - let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); - // create the blobstore sub-directory - fs::create_dir_all(&blobstore_sub_dir)?; - // copy the file to the blobstore - let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); - fs::copy(temp_path, blob_path)?; - Ok(blob_id) -} -*/ - +/// Calculate the latest sequence number for the local profile. +/// +/// Retrieves a list of all messages authored by the local public key, +/// reverses the list and reads the sequence number of the most recently +/// authored message. This gives us the size of the database in terms of +/// the total number of locally-authored messages. pub fn latest_sequence_number() -> Result> { // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); @@ -126,6 +106,21 @@ pub fn latest_sequence_number() -> Result> { }) } +pub fn create_invite(uses: u16) -> Result> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + //TODO: debug!("Generating Scuttlebutt invite code"); + let invite_code = sbot_client.invite_create(uses).await?; + + Ok(invite_code) + }) +} + +/// Retrieve a list of peers blocked by the local public key. pub fn get_blocks_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); @@ -160,6 +155,7 @@ pub fn get_blocks_list() -> Result>, Box> }) } +/// Retrieve a list of peers followed by the local public key. pub fn get_follows_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); @@ -207,6 +203,7 @@ pub fn get_follows_list() -> Result>, Box }) } +/// Retrieve a list of peers friended by the local public key. pub fn get_friends_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); @@ -268,3 +265,63 @@ pub fn get_friends_list() -> Result>, Box Ok(peer_list) }) } + +// FILEPATH FUNCTIONS + +/// Return the path of the ssb-go directory. +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + Ok(go_ssb_path) +} + +/// Check whether a blob is in the blobstore. +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + Ok(blob_exists_locally) +} + +/* +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + // write file to temporary path + file.persist_to(&temp_path).await?; + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + Ok(blob_id) +} +*/ -- 2.40.1 From 602c6a90f19441260a69ecee9998efb0443d530e Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 15:17:16 +0200 Subject: [PATCH 31/66] use class splices to reduce code repetition --- peach-web/src/routes/home.rs | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/peach-web/src/routes/home.rs b/peach-web/src/routes/home.rs index 40e74a5..e3b6477 100644 --- a/peach-web/src/routes/home.rs +++ b/peach-web/src/routes/home.rs @@ -13,36 +13,20 @@ fn render_status_elements<'a>() -> (&'a str, &'a str, &'a str) { // status circle class color based on the go-sbot process state if let Ok(status) = sbot_status { if status.state == Some("active".to_string()) { - ( - "circle circle-large circle-success", - "^_^", - "circle circle-small border-circle-small border-success", - ) + ("circle-success", "^_^", "border-success") } else if status.state == Some("inactive".to_string()) { - ( - "circle circle-large circle-warning", - "z_z", - "circle circle-small border-circle-small border-warning", - ) + ("circle-warning", "z_z", "border-warning") } else { - ( - "circle circle-large circle-error", - "x_x", - "circle circle-small border-circle-small border-danger", - ) + ("circle-error", "x_x", "border-danger") } } else { - ( - "circle circle-large circle-error", - "x_x", - "circle circle-small border-circle-small border-danger", - ) + ("circle-error", "x_x", "border-danger") } } /// Home template builder. pub fn build_template() -> PreEscaped { - let (center_circle_class, center_circle_text, status_circle_class) = render_status_elements(); + let (circle_color, center_circle_text, circle_border) = render_status_elements(); // render the home template html let home_template = html! { @@ -71,7 +55,7 @@ pub fn build_template() -> PreEscaped { } (PreEscaped("")) a class="middle" { - div class=(center_circle_class) { + div class={ "circle circle-large" (circle_color) } { p style="font-size: 4rem; color: var(--near-black);" { (center_circle_text) } @@ -80,7 +64,7 @@ pub fn build_template() -> PreEscaped { (PreEscaped("")) (PreEscaped("")) a class="bottom-left" href="/status/scuttlebutt" title="Status" { - div class=(status_circle_class) { + div class={ "circle circle-small border-circle-small" (circle_border) } { img class="icon-medium" src="/icons/heart-pulse.svg"; } } -- 2.40.1 From 85231a20c7ac96d5f157b27d8d272e4d13817817 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 16:40:54 +0200 Subject: [PATCH 32/66] add profile template and route handler --- peach-web/src/router.rs | 10 ++ peach-web/src/routes/scuttlebutt/profile.rs | 180 ++++++++++++++++++++ peach-web/src/utils/sbot.rs | 126 ++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 peach-web/src/routes/scuttlebutt/profile.rs diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 9284e1b..d0bda4e 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -1,3 +1,4 @@ +use log::debug; use rouille::{router, Request, Response}; use crate::{routes, utils::flash::FlashResponse}; @@ -119,6 +120,15 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { Response::html(routes::scuttlebutt::peers::build_template()) }, + (GET) (/scuttlebutt/profile) => { + Response::html(routes::scuttlebutt::profile::build_template(request, None)) + }, + + (GET) (/scuttlebutt/profile/{ssb_id: String}) => { + debug!("ssb_id: {}", ssb_id); + Response::html(routes::scuttlebutt::profile::build_template(request, Some(ssb_id))) + }, + (GET) (/scuttlebutt/search) => { Response::html(routes::scuttlebutt::search::build_template(request)) .reset_flash() diff --git a/peach-web/src/routes/scuttlebutt/profile.rs b/peach-web/src/routes/scuttlebutt/profile.rs new file mode 100644 index 0000000..9ff1643 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/profile.rs @@ -0,0 +1,180 @@ +use maud::{html, Markup, PreEscaped}; +use peach_lib::sbot::SbotStatus; +use rouille::Request; + +use crate::{ + templates, + utils::{flash::FlashRequest, sbot, sbot::Profile}, +}; + +// ROUTE: /scuttlebutt/profile + +fn public_post_form_template() -> Markup { + html! { + (PreEscaped("")) + form id="postForm" class="center" action="/scuttlebutt/publish" method="post" { + (PreEscaped("")) + textarea id="publicPost" class="center input message-input" name="text" title="Compose Public Post" placeholder="Write a public post..." { } + input id="publishPost" class="button button-primary center" title="Publish" type="submit" value="Publish"; + } + } +} + +fn profile_info_box_template(profile: &Profile) -> Markup { + html! { + (PreEscaped("")) + div class="capsule capsule-profile border-ssb" title="Scuttlebutt account profile information" { + @if profile.is_local_profile { + (PreEscaped("")) + a class="nav-icon-right" href="/scuttlebutt/profile/update" title="Edit your profile" { + img id="editProfile" class="icon-small icon-active" src="/icons/pencil.svg" alt="Edit"; + } + } + // render the profile bio: picture, id, name, image & description + (profile_bio_template(&profile)) + } + } +} + +fn profile_bio_template(profile: &Profile) -> Markup { + html! { + (PreEscaped("")) + (PreEscaped("")) + // only try to render profile pic if we have the blob + @match &profile.blob_path { + Some(blob_path) if profile.blob_exists => { + img id="profilePicture" class="icon-large" src={ "/blob/" (blob_path) } title="Profile picture" alt="Profile picture"; + }, + _ => { + // use a placeholder image if we don't have the blob + img id="peerImage" class="icon icon-active list-icon" src="/icons/user.svg" alt="Placeholder profile image"; + } + } + (PreEscaped("")) + p id="profileName" class="card-text" title="Name" { + @if let Some(name) = &profile.name { + (name) + } @else { + "Name unavailable" + } + } + label class="label-small label-ellipsis font-gray" style="user-select: all;" for="profileName" title="Public Key" { + @if let Some(id) = &profile.id { + (id) + } @else { + "Public key unavailable" + } + } + p id="profileDescription" style="margin-top: 1rem" class="card-text" title="Description" { + @if let Some(description) = &profile.description { + (description) + } @else { + "Description unavailable" + } + } + + } +} + +fn social_interaction_buttons_template(profile: &Profile) -> Markup { + html! { + (PreEscaped("")) + div id="buttons" style="margin-top: 2rem;" { + @match (profile.following, &profile.id) { + (Some(false), Some(ssb_id)) => { + form id="followForm" class="center" action="/scuttlebutt/follow" method="post" { + input type="hidden" id="publicKey" name="public_key" value=(ssb_id); + input id="followPeer" class="button button-primary center" type="submit" title="Follow Peer" value="Follow"; + } + }, + (Some(true), Some(ssb_id)) => { + form id="unfollowForm" class="center" action="/scuttlebutt/unfollow" method="post" { + input type="hidden" id="publicKey" name="public_key" value=(ssb_id); + input id="unfollowPeer" class="button button-primary center" type="submit" title="Unfollow Peer" value="Unfollow"; + } + }, + _ => p { "Unable to determine follow state" } + } + @match (profile.blocking, &profile.id) { + (Some(false), Some(ssb_id)) => { + form id="blockForm" class="center" action="/scuttlebutt/block" method="post" { + input type="hidden" id="publicKey" name="public_key" value=(ssb_id); + input id="blockPeer" class="button button-primary center" type="submit" title="Block Peer" value="Block"; + } + }, + (Some(true), Some(ssb_id)) => { + form id="unblockForm" class="center" action="/scuttlebutt/unblock" method="post" { + input type="hidden" id="publicKey" name="public_key" value=(ssb_id); + input id="unblockPeer" class="button button-primary center" type="submit" title="Unblock Peer" value="Unblock"; + } + }, + _ => p { "Unable to determine block state" } + } + @if let Some(ssb_id) = &profile.id { + form class="center" { + a id="privateMessage" class="button button-primary center" href={ "/scuttlebutt/private?public_key=" (ssb_id) } title="Private Message" { + "Send Private Message" + } + } + } + } + } +} + +/// Scuttlebutt profile template builder. +/// +/// Render a Scuttlebutt profile, either for the local profile or for a peer +/// specified by a public key. If the public key query parameter is not +/// provided, the local profile is displayed (ie. the profile of the public key +/// associated with the local PeachCloud device). +pub fn build_template(request: &Request, ssb_id: Option) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + let profile_template = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + // TODO: validate ssb_id and return error template + + // retrieve the profile info + match sbot::get_profile_info(ssb_id) { + Ok(profile) => { + // render the profile template + html! { + (PreEscaped("")) + div class="card card-wide center" { + // render profile info box + (profile_info_box_template(&profile)) + @if profile.is_local_profile { + // render the public post form template + (public_post_form_template()) + } @else { + // render follow / unfollow, block / unblock and + // private message buttons + (social_interaction_buttons_template(&profile)) + } + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + } + Err(e) => { + // render the sbot error template with the error message + let error_template = templates::error::build_template(e.to_string()); + // wrap the nav bars around the error template content + let body = templates::nav::build_template(error_template, "Profile", Some("/")); + + // render the base template with the provided body + templates::base::build_template(body) + } + } + } + _ => templates::inactive::build_template("Profile is unavailable."), + }; + + let body = templates::nav::build_template(profile_template, "Profile", Some("/")); + + templates::base::build_template(body) +} diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index 62d449a..053ed39 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -120,6 +120,132 @@ pub fn create_invite(uses: u16) -> Result> { }) } +#[derive(Debug)] +pub struct Profile { + // is this the local profile or the profile of a peer? + pub is_local_profile: bool, + // an ssb_id which may or may not be the local public key + pub id: Option, + pub name: Option, + pub description: Option, + pub image: Option, + // the path to the blob defined in the `image` field (aka the profile picture) + pub blob_path: Option, + // whether or not the blob exists in the blobstore (ie. is saved on disk) + pub blob_exists: bool, + // relationship state (if the profile being viewed is not for the local public key) + pub following: Option, + pub blocking: Option, +} + +impl Profile { + pub fn default() -> Self { + Profile { + is_local_profile: true, + id: None, + name: None, + description: None, + image: None, + blob_path: None, + blob_exists: false, + following: None, + blocking: None, + } + } +} + +/// Retrieve the profile info for the given public key. +pub fn get_profile_info(ssb_id: Option) -> Result> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + let mut profile = Profile::default(); + + // if an ssb_id has been provided, we assume that the profile info + // being retrieved is for a peer (ie. not for our local profile) + let id = if ssb_id.is_some() { + // we are not dealing with the local profile + profile.is_local_profile = false; + + // we're safe to unwrap here because we know it's `Some(id)` + let peer_id = ssb_id.unwrap(); + + // determine relationship between peer and local id + let follow_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query follow state + profile.following = match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => Some(true), + Ok(following) if following == "false" => Some(false), + _ => None, + }; + + // TODO: i don't like that we have to instantiate the same query object + // twice. see if we can streamline this in golgi + let block_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query block state + profile.blocking = match sbot_client.friends_is_blocking(block_query).await { + Ok(blocking) if blocking == "true" => Some(true), + Ok(blocking) if blocking == "false" => Some(false), + _ => None, + }; + + peer_id + } else { + // if an ssb_id has not been provided, retrieve the local id using whoami + profile.is_local_profile = true; + + local_id + }; + + // retrieve the profile info for the given id + let info = sbot_client.get_profile_info(&id).await?; + // set each profile field accordingly + for (key, val) in info { + match key.as_str() { + "name" => profile.name = Some(val), + "description" => profile.description = Some(val), + "image" => profile.image = Some(val), + _ => (), + } + } + + // assign the ssb public key + // (could be for the local profile or a peer) + profile.id = Some(id); + + // determine the path to the blob defined by the value of `profile.image` + if let Some(ref blob_id) = profile.image { + profile.blob_path = match blobs::get_blob_path(&blob_id) { + Ok(path) => { + // if we get the path, check if the blob is in the blobstore. + // this allows us to default to a placeholder image in the template + if let Ok(exists) = blob_is_stored_locally(&path).await { + profile.blob_exists = exists + }; + + Some(path) + } + Err(_) => None, + } + } + + Ok(profile) + }) +} + /// Retrieve a list of peers blocked by the local public key. pub fn get_blocks_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters -- 2.40.1 From 3a7b499742c56ced418959e36a5199c982f5cf77 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 16:41:52 +0200 Subject: [PATCH 33/66] use splices for template rendering --- peach-web/src/routes/home.rs | 4 ++-- peach-web/src/routes/status/scuttlebutt.rs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/peach-web/src/routes/home.rs b/peach-web/src/routes/home.rs index e3b6477..eaaf060 100644 --- a/peach-web/src/routes/home.rs +++ b/peach-web/src/routes/home.rs @@ -55,7 +55,7 @@ pub fn build_template() -> PreEscaped { } (PreEscaped("")) a class="middle" { - div class={ "circle circle-large" (circle_color) } { + div class={ "circle circle-large " (circle_color) } { p style="font-size: 4rem; color: var(--near-black);" { (center_circle_text) } @@ -64,7 +64,7 @@ pub fn build_template() -> PreEscaped { (PreEscaped("")) (PreEscaped("")) a class="bottom-left" href="/status/scuttlebutt" title="Status" { - div class={ "circle circle-small border-circle-small" (circle_border) } { + div class={ "circle circle-small border-circle-small " (circle_border) } { img class="icon-medium" src="/icons/heart-pulse.svg"; } } diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 2a59029..5ebfd00 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -6,6 +6,9 @@ use crate::utils::sbot; // HTML RENDERING FOR ELEMENTS +// TODO: refactor this to make better use of splices +// https://maud.lambda.xyz/splices-toggles.html + fn downtime_element(downtime: &Option) -> Markup { match downtime { Some(time) => { -- 2.40.1 From e19fa0f99dd9651e8a6d8ebe2c4609262b43fd21 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 16:42:16 +0200 Subject: [PATCH 34/66] import profile module --- peach-web/src/routes/scuttlebutt/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs index 41d4cf9..a177b45 100644 --- a/peach-web/src/routes/scuttlebutt/mod.rs +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -3,4 +3,5 @@ pub mod follows; pub mod friends; pub mod invites; pub mod peers; +pub mod profile; pub mod search; -- 2.40.1 From 98121f4922601961af7fe0a08adfb0f24c48e15c Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 21 Mar 2022 16:43:22 +0200 Subject: [PATCH 35/66] use splices for rendering and url-encode the public key link --- peach-web/src/templates/peers_list.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/peach-web/src/templates/peers_list.rs b/peach-web/src/templates/peers_list.rs index 315385d..c9fea13 100644 --- a/peach-web/src/templates/peers_list.rs +++ b/peach-web/src/templates/peers_list.rs @@ -17,23 +17,21 @@ fn peers_template(peers: Vec>) -> Markup { ), None => ( // set a fall-back value for name in case the data is unavailable - "name unavailable".to_string(), + "Name unavailable".to_string(), "Profile image".to_string() ) }; - @let profile_link = format!("/scuttlebutt/profile?public_key={}", peer["id"]); + @let url_safe_peer_id = peer["id"].replace('/', "%2F"); li { - a class="list-item link" href=(profile_link) { + a class="list-item link" href={ "/scuttlebutt/profile/" (url_safe_peer_id) } { @if peer.get("blob_path").is_some() && peer["blob_exists"] == "true" { - @let blob_path = format!("/blob/{}", peer["blob_path"]); - img id="peerImage" class="icon list-icon" src=(blob_path) alt=(name_alt); + img id="peerImage" class="icon list-icon" src={ "/blob/" (peer["blob_path"]) } alt=(name_alt); } @else { // use a placeholder image if we don't have the blob img id="peerImage" class="icon icon-active list-icon" src="/icons/user.svg" alt="Placeholder profile image"; } - p id="peerName" class="font-normal list-text" { (name) }; - @let name_title = format!("{}'s public key", name); - label class="label-small label-ellipsis list-label font-gray" for="peerName" title=(name_title) { + p id="peerName" class="font-normal list-text " { (name) }; + label class="label-small label-ellipsis list-label font-gray" for="peerName" title={ (name) "'s public key" } { (peer["id"]) } } -- 2.40.1 From 3e918f66cf6b6b0361381d1851a3a36fbc2ad4ad Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 22 Mar 2022 16:11:13 +0200 Subject: [PATCH 36/66] fix error flash value --- peach-web/src/routes/scuttlebutt/profile.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/peach-web/src/routes/scuttlebutt/profile.rs b/peach-web/src/routes/scuttlebutt/profile.rs index 9ff1643..2fd11e9 100644 --- a/peach-web/src/routes/scuttlebutt/profile.rs +++ b/peach-web/src/routes/scuttlebutt/profile.rs @@ -31,7 +31,7 @@ fn profile_info_box_template(profile: &Profile) -> Markup { } } // render the profile bio: picture, id, name, image & description - (profile_bio_template(&profile)) + (profile_bio_template(profile)) } } } @@ -162,12 +162,7 @@ pub fn build_template(request: &Request, ssb_id: Option) -> PreEscaped { // render the sbot error template with the error message - let error_template = templates::error::build_template(e.to_string()); - // wrap the nav bars around the error template content - let body = templates::nav::build_template(error_template, "Profile", Some("/")); - - // render the base template with the provided body - templates::base::build_template(body) + templates::error::build_template(e.to_string()) } } } -- 2.40.1 From 084af1b48683c3d8e88dde46bfca3fff3c2516a6 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 22 Mar 2022 16:11:52 +0200 Subject: [PATCH 37/66] add profile update template and route handler --- peach-web/src/router.rs | 8 + .../src/routes/scuttlebutt/profile_update.rs | 169 ++++++++++++++++++ peach-web/src/utils/sbot.rs | 120 +++++++++++-- 3 files changed, 279 insertions(+), 18 deletions(-) create mode 100644 peach-web/src/routes/scuttlebutt/profile_update.rs diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index d0bda4e..74d0f44 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -124,6 +124,14 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { Response::html(routes::scuttlebutt::profile::build_template(request, None)) }, + (GET) (/scuttlebutt/profile/update) => { + Response::html(routes::scuttlebutt::profile_update::build_template(request)) + }, + + (POST) (/scuttlebutt/profile/update) => { + routes::scuttlebutt::profile_update::handle_form(request) + }, + (GET) (/scuttlebutt/profile/{ssb_id: String}) => { debug!("ssb_id: {}", ssb_id); Response::html(routes::scuttlebutt::profile::build_template(request, Some(ssb_id))) diff --git a/peach-web/src/routes/scuttlebutt/profile_update.rs b/peach-web/src/routes/scuttlebutt/profile_update.rs new file mode 100644 index 0000000..8620b07 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/profile_update.rs @@ -0,0 +1,169 @@ +use maud::{html, PreEscaped}; +use peach_lib::sbot::SbotStatus; +use rouille::{input::post::BufferedFile, post_input, try_or_400, Request, Response}; + +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + sbot, + sbot::Profile, + }, +}; + +// ROUTE: /scuttlebutt/profile/update + +fn parse_profile_info(profile: Profile) -> (String, String, String) { + let id = match profile.id { + Some(id) => id, + _ => "Public key unavailable".to_string(), + }; + + let name = match profile.name { + Some(name) => name, + _ => "Name unavailable".to_string(), + }; + + let description = match profile.description { + Some(description) => description, + _ => "Description unavailable".to_string(), + }; + + (id, name, description) +} + +/// Scuttlebutt profile update template builder. +/// +/// Serve a form for the purpose of updating the name, description and picture +/// for the local Scuttlebutt profile. +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + let profile_update_template = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + // retrieve the local profile info + match sbot::get_profile_info(None) { + Ok(profile) => { + let (id, name, description) = parse_profile_info(profile); + + // render the scuttlebutt profile update form + html! { + (PreEscaped("")) + div class="card card-wide center" { + form id="profileInfo" class="center" enctype="multipart/form-data" action="/scuttlebutt/profile/update" method="post" { + div style="display: flex; flex-direction: column" { + label for="name" class="label-small font-gray" { + "NAME" + } + input style="margin-bottom: 1rem;" type="text" id="name" name="new_name" placeholder="Choose a name for your profile..." value=(name); + label for="description" class="label-small font-gray" { + "DESCRIPTION" + } + textarea id="description" class="message-input" style="margin-bottom: 1rem;" name="new_description" placeholder="Write a description for your profile..." { + (description) + } + label for="image" class="label-small font-gray" { + "IMAGE" + } + input type="file" id="fileInput" class="font-normal" name="image"; + } + input type="hidden" name="id" value=(id); + input type="hidden" name="current_name" value=(name); + input type="hidden" name="current_description" value=(description); + div id="buttonDiv" style="margin-top: 2rem;" { + input id="updateProfile" class="button button-primary center" title="Publish" type="submit" value="Publish"; + a class="button button-secondary center" href="/scuttlebutt/profile" title="Cancel" { "Cancel" } + + } + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + } + } + Err(e) => { + // render the sbot error template with the error message + templates::error::build_template(e.to_string()) + } + } + } + _ => { + // the sbot is not active; render a message instead of the form + templates::inactive::build_template("Profile is unavailable.") + } + }; + + let body = templates::nav::build_template( + profile_update_template, + "Profile", + Some("/scuttlebutt/profile"), + ); + + templates::base::build_template(body) +} + +/// Update the name, description and picture for the local Scuttlebutt profile. +/// +/// Redirects to profile page of the PeachCloud local identity with a flash +/// message describing the outcome of the action (may be successful or +/// unsuccessful). +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + id: String, + current_name: String, + current_description: String, + new_name: Option, + new_description: Option, + image: Option, + })); + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + // we can't pass `data` into the function (due to macro creation) + // so we pass in each individual value instead + match sbot::update_profile_info( + data.current_name, + data.current_description, + data.new_name, + data.new_description, + data.image, + ) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Profile is unavailable.".to_string(), + ), + }; + + Response::redirect_303("/scuttlebutt/profile/update").add_flash(flash_name, flash_msg) +} + +/* + match sbot::validate_public_key(&data.public_key) { + Ok(_) => { + let url = format!("/scuttlebutt/profile?={}", &data.public_key); + Response::redirect_303(url) + } + Err(err) => { + let (flash_name, flash_msg) = + ("flash_name=error".to_string(), format!("flash_msg={}", err)); + Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg) + } + } +} +*/ diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index 053ed39..d50450b 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -4,6 +4,7 @@ use std::{ fs, fs::File, io, + io::prelude::*, path::Path, process::{Command, Output}, }; @@ -12,8 +13,8 @@ use async_std::task; use dirs; use futures::stream::TryStreamExt; use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; -use log::info; use peach_lib::sbot::SbotConfig; +use rouille::input::post::BufferedFile; use temporary::Directory; use crate::{error::PeachWebError, utils::sbot}; @@ -168,13 +169,10 @@ pub fn get_profile_info(ssb_id: Option) -> Result) -> Result { // if we get the path, check if the blob is in the blobstore. // this allows us to default to a placeholder image in the template @@ -246,6 +244,84 @@ pub fn get_profile_info(ssb_id: Option) -> Result, + new_description: Option, + image: Option, +) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + // track whether the name, description or image have been updated + let mut name_updated: bool = false; + let mut description_updated: bool = false; + let mut image_updated: bool = false; + + // check if a new_name value has been submitted in the form + if let Some(name) = new_name { + // only update the name if it has changed + if name != current_name { + // TODO: debug!("Publishing new Scuttlebutt profile name"); + if let Err(e) = sbot_client.publish_name(&name).await { + return Err(format!("Failed to update name: {}", e)); + } else { + name_updated = true + } + } + } + + if let Some(description) = new_description { + // only update the description if it has changed + if description != current_description { + //debug!("Publishing new Scuttlebutt profile description"); + if let Err(e) = sbot_client.publish_description(&description).await { + return Err(format!("Failed to update description: {}", e)); + } else { + description_updated = true + } + } + } + + // only update the image if a file was uploaded + if let Some(img) = image { + // only write the blob if it has a filename and data > 0 bytes + if img.filename.is_some() && !img.data.is_empty() { + match write_blob_to_store(img).await { + Ok(blob_id) => { + // if the file was successfully added to the blobstore, + // publish an about image message with the blob id + if let Err(e) = sbot_client.publish_image(&blob_id).await { + return Err(format!("Failed to update image: {}", e)); + } else { + image_updated = true + } + } + Err(e) => return Err(format!("Failed to add image to blobstore: {}", e)), + } + } else { + image_updated = false + } + } + + if name_updated || description_updated || image_updated { + Ok("Profile updated".to_string()) + } else { + // no updates were made but no errors were encountered either + Ok("Profile info unchanged".to_string()) + } + }) +} + /// Retrieve a list of peers blocked by the local public key. pub fn get_blocks_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters @@ -306,13 +382,13 @@ pub fn get_follows_list() -> Result>, Box // retrieve the profile image blob id for the given peer if let Some(blob_id) = peer_info.get("image") { // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + if let Ok(blob_path) = blobs::get_blob_path(blob_id) { // insert the image blob path of the peer into the info hashmap peer_info.insert("blob_path".to_string(), blob_path.to_string()); // check if the blob is in the blobstore // set a flag in the info hashmap match blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { + Ok(exists) if exists => { peer_info.insert("blob_exists".to_string(), "true".to_string()) } _ => peer_info.insert("blob_exists".to_string(), "false".to_string()), @@ -356,13 +432,13 @@ pub fn get_friends_list() -> Result>, Box // retrieve the profile image blob id for the given peer if let Some(blob_id) = peer_info.get("image") { // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + if let Ok(blob_path) = blobs::get_blob_path(blob_id) { // insert the image blob path of the peer into the info hashmap peer_info.insert("blob_path".to_string(), blob_path.to_string()); // check if the blob is in the blobstore // set a flag in the info hashmap match sbot::blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { + Ok(exists) if exists => { peer_info.insert("blob_exists".to_string(), "true".to_string()) } _ => peer_info.insert("blob_exists".to_string(), "false".to_string()), @@ -401,7 +477,7 @@ pub fn get_go_ssb_path() -> Result { // return the default path if unable to read `config.toml` Err(_) => { // determine the home directory - let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + let mut home_path = dirs::home_dir().ok_or(PeachWebError::HomeDir)?; // add the go-ssb subdirectory home_path.push(".ssb-go"); // convert the PathBuf to a String @@ -422,32 +498,40 @@ pub async fn blob_is_stored_locally(blob_path: &str) -> Result) -> Result { +pub async fn write_blob_to_store(image: BufferedFile) -> Result { + // we performed a `image.filename.is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = image + .filename + .expect("retrieving filename from uploaded file"); + // create temporary directory and path let temp_dir = Directory::new("blob")?; - // we performed a `file.name().is_some()` check before calling `write_blob_to_store` - // so it should be safe to do a simple unwrap here - let filename = file.name().expect("retrieving filename from uploaded file"); let temp_path = temp_dir.join(filename); + // write file to temporary path - file.persist_to(&temp_path).await?; + fs::write(&temp_path, &image.data)?; + // open the file and read it into a buffer let mut file = File::open(&temp_path)?; let mut buffer = Vec::new(); file.read_to_end(&mut buffer)?; + // hash the bytes representing the file let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + // define the blobstore path and blob filename let (blob_dir, blob_filename) = hex_hash.split_at(2); let go_ssb_path = get_go_ssb_path()?; let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + // create the blobstore sub-directory fs::create_dir_all(&blobstore_sub_dir)?; + // copy the file to the blobstore let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); fs::copy(temp_path, blob_path)?; + Ok(blob_id) } -*/ -- 2.40.1 From 703f35d8b158513317923b7e2255aa884939a216 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 22 Mar 2022 16:12:28 +0200 Subject: [PATCH 38/66] add profile_update module --- peach-web/src/routes/scuttlebutt/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs index a177b45..9b221a2 100644 --- a/peach-web/src/routes/scuttlebutt/mod.rs +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -4,4 +4,5 @@ pub mod friends; pub mod invites; pub mod peers; pub mod profile; +pub mod profile_update; pub mod search; -- 2.40.1 From 65f0ac76300bf274b48ad5a7a7af844ae5bd584e Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 22 Mar 2022 16:12:47 +0200 Subject: [PATCH 39/66] update flash message class width --- peach-web/static/css/peachcloud.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/peach-web/static/css/peachcloud.css b/peach-web/static/css/peachcloud.css index 850bb00..7e18374 100644 --- a/peach-web/static/css/peachcloud.css +++ b/peach-web/static/css/peachcloud.css @@ -648,9 +648,10 @@ html[data-theme='dark'] { .flash-message { font-family: var(--sans-serif); font-size: var(--font-size-6); - margin-left: 2rem; - margin-right: 2rem; + /*margin-left: 2rem;*/ + /*margin-right: 2rem;*/ margin-top: 1rem; + width: 14rem; } /* -- 2.40.1 From b20822a644b36c5bf32e4a0255818844e5eb6c85 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 22 Mar 2022 16:13:34 +0200 Subject: [PATCH 40/66] satisfy clippy --- peach-web/src/routes/scuttlebutt/invites.rs | 2 +- peach-web/src/routes/settings/admin/configure.rs | 2 +- peach-web/src/routes/settings/scuttlebutt/menu.rs | 2 +- peach-web/src/routes/status/scuttlebutt.rs | 15 ++++++++------- peach-web/src/utils/flash.rs | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/peach-web/src/routes/scuttlebutt/invites.rs b/peach-web/src/routes/scuttlebutt/invites.rs index 098f04e..b5372c8 100644 --- a/peach-web/src/routes/scuttlebutt/invites.rs +++ b/peach-web/src/routes/scuttlebutt/invites.rs @@ -40,7 +40,7 @@ fn invite_form_template( // avoid displaying the invite code-containing flash msg @if name != "code" { (PreEscaped("")) - (templates::flash::build_template(&name, &msg)) + (templates::flash::build_template(name, msg)) } } } diff --git a/peach-web/src/routes/settings/admin/configure.rs b/peach-web/src/routes/settings/admin/configure.rs index 9b37a1d..f984d36 100644 --- a/peach-web/src/routes/settings/admin/configure.rs +++ b/peach-web/src/routes/settings/admin/configure.rs @@ -55,7 +55,7 @@ pub fn build_template(request: &Request) -> PreEscaped { // render flash message if cookies were found in the request @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { (PreEscaped("")) - (templates::flash::build_template(name, &msg)) + (templates::flash::build_template(name, msg)) } } }; diff --git a/peach-web/src/routes/settings/scuttlebutt/menu.rs b/peach-web/src/routes/settings/scuttlebutt/menu.rs index 8acd000..fd6d989 100644 --- a/peach-web/src/routes/settings/scuttlebutt/menu.rs +++ b/peach-web/src/routes/settings/scuttlebutt/menu.rs @@ -45,7 +45,7 @@ pub fn build_template(request: &Request) -> PreEscaped { // render flash message if cookies were found in the request @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { (PreEscaped("")) - (templates::flash::build_template(name, &msg)) + (templates::flash::build_template(name, msg)) } } }; diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 5ebfd00..101c88d 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -52,14 +52,15 @@ fn database_element(state: &str) -> Markup { // retrieve the sequence number of the latest message in the sbot database let sequence_num = sbot::latest_sequence_number(); - if state == "active" && sequence_num.is_ok() { - let number = sequence_num.unwrap(); - html! { - label class="card-text" style="margin-right: 5px;" { (number) } - label class="label-small font-gray" { "MESSAGES IN LOCAL DATABASE" } + match (state, sequence_num) { + // if the state is "active" and latest_sequence_number() was successful + ("active", Ok(number)) => { + html! { + label class="card-text" style="margin-right: 5px;" { (number) } + label class="label-small font-gray" { "MESSAGES IN LOCAL DATABASE" } + } } - } else { - html! { label class="label-small font-gray" { "DATABASE UNAVAILABLE" } } + (_, _) => html! { label class="label-small font-gray" { "DATABASE UNAVAILABLE" } }, } } diff --git a/peach-web/src/utils/flash.rs b/peach-web/src/utils/flash.rs index 53c2501..0134ca1 100644 --- a/peach-web/src/utils/flash.rs +++ b/peach-web/src/utils/flash.rs @@ -9,11 +9,11 @@ pub trait FlashRequest { impl FlashRequest for Request { fn retrieve_flash(&self) -> (Option<&str>, Option<&str>) { // check for flash cookies - let flash_name = input::cookies(&self) + let flash_name = input::cookies(self) .find(|&(n, _)| n == "flash_name") // return the value of the cookie (key is already known) .map(|key_val| key_val.1); - let flash_msg = input::cookies(&self) + let flash_msg = input::cookies(self) .find(|&(n, _)| n == "flash_msg") .map(|key_val| key_val.1); -- 2.40.1 From 7d9bc2d7cd141a254399e72413576578be416d0e Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 22 Mar 2022 16:13:50 +0200 Subject: [PATCH 41/66] reduce code repetition with class splices --- peach-web/src/templates/flash.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/peach-web/src/templates/flash.rs b/peach-web/src/templates/flash.rs index bc597b2..ee0a3f2 100644 --- a/peach-web/src/templates/flash.rs +++ b/peach-web/src/templates/flash.rs @@ -4,16 +4,18 @@ use maud::{html, Markup}; /// /// Render a flash elements based on the given flash name and message. pub fn build_template(flash_name: &str, flash_msg: &str) -> Markup { - let flash_class = match flash_name { - "success" => "capsule center-text flash-message font-normal border-success", - "info" => "capsule center-text flash-message font-normal border-info", - "warning" => "capsule center-text flash-message font-normal border-warning", - "error" => "capsule center-text flash-message font-normal border-danger", + let common_classes = "capsule center center-text flash-message font-normal "; + + let border_class = match flash_name { + "success" => "border-success", + "info" => "border-info", + "warning" => "border-warning", + "error" => "border-danger", _ => "", }; html! { - div class=(flash_class) { + div class={ (common_classes) (border_class) } { (flash_msg) } } -- 2.40.1 From 77c1ccb1c76bb9957393db77163bc85e740e17a7 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Mar 2022 09:14:54 +0200 Subject: [PATCH 42/66] add form handler and helper for sbot config updates --- peach-web/src/router.rs | 13 +- .../routes/settings/scuttlebutt/configure.rs | 115 ++++++++++++++++-- peach-web/src/utils/sbot.rs | 29 +++++ 3 files changed, 146 insertions(+), 11 deletions(-) diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 74d0f44..2809698 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -1,4 +1,3 @@ -use log::debug; use rouille::{router, Request, Response}; use crate::{routes, utils::flash::FlashResponse}; @@ -75,7 +74,16 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { }, (GET) (/settings/scuttlebutt/configure) => { - Response::html(routes::settings::scuttlebutt::configure::build_template()) + Response::html(routes::settings::scuttlebutt::configure::build_template(request)) + .reset_flash() + }, + + (POST) (/settings/scuttlebutt/configure) => { + routes::settings::scuttlebutt::configure::handle_form(request, false) + }, + + (POST) (/settings/scuttlebutt/configure/restart) => { + routes::settings::scuttlebutt::configure::handle_form(request, true) }, (GET) (/settings/admin) => { @@ -133,7 +141,6 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { }, (GET) (/scuttlebutt/profile/{ssb_id: String}) => { - debug!("ssb_id: {}", ssb_id); Response::html(routes::scuttlebutt::profile::build_template(request, Some(ssb_id))) }, diff --git a/peach-web/src/routes/settings/scuttlebutt/configure.rs b/peach-web/src/routes/settings/scuttlebutt/configure.rs index c53c1c8..bc24403 100644 --- a/peach-web/src/routes/settings/scuttlebutt/configure.rs +++ b/peach-web/src/routes/settings/scuttlebutt/configure.rs @@ -1,9 +1,15 @@ +use log::{debug, warn}; use maud::{html, PreEscaped}; use peach_lib::sbot::{SbotConfig, SbotStatus}; +use rouille::{post_input, try_or_400, Request, Response}; -use crate::templates; - -// TODO: flash message implementation for rouille +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + sbot, + }, +}; /// Read the status and configuration of the sbot. /// Define fallback values if an error is returned from either read function. @@ -31,7 +37,8 @@ fn read_status_and_config() -> (String, SbotConfig, String, String) { let (ip, port) = match sbot_config.lis.find(':') { Some(index) => { let (ip, port) = sbot_config.lis.split_at(index); - (ip.to_string(), port.to_string()) + // remove the : from the port + (ip.to_string(), port.replace(':', "").to_string()) } // if no ':' separator is found, assume an ip has been configured (without port) None => (sbot_config.lis.to_string(), String::new()), @@ -41,7 +48,10 @@ fn read_status_and_config() -> (String, SbotConfig, String, String) { } /// Scuttlebutt settings menu template builder. -pub fn build_template() -> PreEscaped { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + let (run_on_startup, sbot_config, ip, port) = read_status_and_config(); let menu_template = html! { @@ -152,11 +162,14 @@ pub fn build_template() -> PreEscaped { input type="hidden" id="nounixsock" name="nounixsock" value=(sbot_config.nounixsock); (PreEscaped("")) input id="saveConfig" class="button button-primary center" style="margin-top: 2rem;" type="submit" title="Save configuration parameters to file" value="Save"; - input id="saveRestartConfig" class="button button-primary center" type="submit" title="Save configuration parameters to file and then (re)start the pub" value="Save & Restart" formaction="/settings/scuttlebutt/configure?restart=true"; + input id="saveRestartConfig" class="button button-primary center" type="submit" title="Save configuration parameters to file and then (re)start the pub" value="Save & Restart" formaction="/settings/scuttlebutt/configure/restart"; a id="restoreDefaults" class="button button-warning center" href="/settings/scuttlebutt/configure/default" title="Restore default configuration parameters and save them to file" { "Restore Defaults" } } - (PreEscaped("")) - // TODO: flash message + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } } }; @@ -168,3 +181,89 @@ pub fn build_template() -> PreEscaped { // render the base template with the provided body templates::base::build_template(body) } + +/// Parse the sbot configuration values and write to file. +pub fn handle_form(request: &Request, restart: bool) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + repo: String, + debugdir: String, + shscap: String, + hmac: String, + hops: u8, + lis_ip: String, + lis_port: String, + wslis: String, + debuglis: String, + localadv: bool, + localdiscov: bool, + enable_ebt: bool, + promisc: bool, + nounixsock: bool, + startup: bool, + repair: bool, + })); + + debug!("ip: {} port: {}", data.lis_ip, data.lis_port); + + // concat the ip and port for listen address + let lis = format!("{}:{}", data.lis_ip, data.lis_port); + + debug!("{}", lis); + + // instantiate `SbotConfig` from form data + let config = SbotConfig { + lis, + hops: data.hops, + repo: data.repo, + debugdir: data.debugdir, + shscap: data.shscap, + localadv: data.localadv, + localdiscov: data.localdiscov, + hmac: data.hmac, + wslis: data.wslis, + debuglis: data.debuglis, + enable_ebt: data.enable_ebt, + promisc: data.promisc, + nounixsock: data.nounixsock, + repair: data.repair, + }; + + match data.startup { + true => { + // TODO: rouille - log integration + //info!("Enabling go-sbot.service"); + if let Err(e) = sbot::systemctl_sbot_cmd("enable") { + warn!("Failed to enable go-sbot.service: {}", e) + } + } + false => { + // TODO: info!("Disabling go-sbot.service"); + if let Err(e) = sbot::systemctl_sbot_cmd("disable") { + warn!("Failed to disable go-sbot.service: {}", e) + } + } + }; + + // write config to file + let (name, msg) = match SbotConfig::write(config) { + Ok(_) => { + // if `restart` query parameter is `true`, attempt sbot process (re)start + if restart { + // returns a tuple of (name, msg) based on the outcome (success or error) + sbot::restart_sbot_process() + } else { + ("success".to_string(), "Updated configuration".to_string()) + } + } + Err(err) => ( + "error".to_string(), + format!("Failed to update configuration: {}", err), + ), + }; + + let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); + + Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index d50450b..0390ff7 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -30,6 +30,35 @@ pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result { .output() } +/// Executes a systemctl stop command followed by start command. +/// Returns a redirect with a flash message stating the output of the restart attempt. +pub fn restart_sbot_process() -> (String, String) { + // TODO: info!("Restarting go-sbot.service"); + match systemctl_sbot_cmd("stop") { + // if stop was successful, try to start the process + Ok(_) => match systemctl_sbot_cmd("start") { + Ok(_) => ( + "success".to_string(), + "Updated configuration and restarted the sbot process".to_string(), + ), + Err(err) => ( + "error".to_string(), + format!( + "Updated configuration but failed to start the sbot process: {}", + err + ), + ), + }, + Err(err) => ( + "error".to_string(), + format!( + "Updated configuration but failed to stop the sbot process: {}", + err + ), + ), + } +} + /// Initialise an sbot client with the given configuration parameters. pub async fn init_sbot_with_config( sbot_config: &Option, -- 2.40.1 From 41bd39d4223981b248dc0e4486559a543a155979 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Mar 2022 11:41:21 +0200 Subject: [PATCH 43/66] add themes, add public and private msg publishing --- peach-web/src/routes/authentication/change.rs | 10 +- peach-web/src/routes/authentication/login.rs | 10 +- peach-web/src/routes/authentication/reset.rs | 10 +- peach-web/src/routes/guide.rs | 7 +- peach-web/src/routes/home.rs | 7 +- peach-web/src/routes/scuttlebutt/blocks.rs | 10 +- peach-web/src/routes/scuttlebutt/follows.rs | 10 +- peach-web/src/routes/scuttlebutt/friends.rs | 10 +- peach-web/src/routes/scuttlebutt/invites.rs | 7 +- peach-web/src/routes/scuttlebutt/mod.rs | 2 + peach-web/src/routes/scuttlebutt/peers.rs | 7 +- peach-web/src/routes/scuttlebutt/private.rs | 131 ++++++++++++++++++ peach-web/src/routes/scuttlebutt/profile.rs | 19 +-- .../src/routes/scuttlebutt/profile_update.rs | 6 +- peach-web/src/routes/scuttlebutt/publish.rs | 41 ++++++ peach-web/src/routes/scuttlebutt/search.rs | 7 +- .../src/routes/settings/admin/configure.rs | 10 +- peach-web/src/routes/settings/admin/menu.rs | 7 +- peach-web/src/routes/settings/menu.rs | 7 +- peach-web/src/routes/settings/mod.rs | 2 +- .../routes/settings/scuttlebutt/configure.rs | 9 +- .../src/routes/settings/scuttlebutt/menu.rs | 10 +- peach-web/src/routes/settings/theme.rs | 18 +-- peach-web/src/routes/status/scuttlebutt.rs | 11 +- peach-web/src/templates/base.rs | 4 +- peach-web/src/templates/mod.rs | 1 + peach-web/src/templates/nav.rs | 4 +- peach-web/src/templates/not_found.rs | 34 +++++ peach-web/src/templates/peers_list.rs | 7 +- peach-web/src/utils/sbot.rs | 53 +++++++ peach-web/src/utils/theme.rs | 109 --------------- 31 files changed, 410 insertions(+), 170 deletions(-) create mode 100644 peach-web/src/routes/scuttlebutt/private.rs create mode 100644 peach-web/src/routes/scuttlebutt/publish.rs create mode 100644 peach-web/src/templates/not_found.rs diff --git a/peach-web/src/routes/authentication/change.rs b/peach-web/src/routes/authentication/change.rs index c33abad..50e7b69 100644 --- a/peach-web/src/routes/authentication/change.rs +++ b/peach-web/src/routes/authentication/change.rs @@ -6,7 +6,10 @@ use rouille::{post_input, try_or_400, Request, Response}; use crate::{ error::PeachWebError, templates, - utils::flash::{FlashRequest, FlashResponse}, + utils::{ + flash::{FlashRequest, FlashResponse}, + theme, + }, }; // HELPER AND ROUTES FOR /auth/change (GET and POST) @@ -48,8 +51,11 @@ pub fn build_template(request: &Request) -> PreEscaped { let body = templates::nav::build_template(form_template, "Change Password", Some("/settings/admin")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } /// Verify, validate and set a new password, overwriting the current password. diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs index aa643c9..0ad8b9d 100644 --- a/peach-web/src/routes/authentication/login.rs +++ b/peach-web/src/routes/authentication/login.rs @@ -5,7 +5,10 @@ use rouille::{post_input, try_or_400, Request, Response}; use crate::{ templates, - utils::flash::{FlashRequest, FlashResponse}, + utils::{ + flash::{FlashRequest, FlashResponse}, + theme, + }, }; // HELPER AND ROUTES FOR /auth/login (GET and POST) @@ -42,8 +45,11 @@ pub fn build_template(request: &Request) -> PreEscaped { // parameters are template, title and back url let body = templates::nav::build_template(form_template, "Login", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } /// Parse and verify the submitted password. If verification succeeds, set the diff --git a/peach-web/src/routes/authentication/reset.rs b/peach-web/src/routes/authentication/reset.rs index 6d19afd..3464ae0 100644 --- a/peach-web/src/routes/authentication/reset.rs +++ b/peach-web/src/routes/authentication/reset.rs @@ -6,7 +6,10 @@ use rouille::{post_input, try_or_400, Request, Response}; use crate::{ error::PeachWebError, templates, - utils::flash::{FlashRequest, FlashResponse}, + utils::{ + flash::{FlashRequest, FlashResponse}, + theme, + }, }; // HELPER AND ROUTES FOR /auth/reset (GET and POST) @@ -48,8 +51,11 @@ pub fn build_template(request: &Request) -> PreEscaped { let body = templates::nav::build_template(form_template, "Reset Password", Some("/settings/admin")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } /// Verify, validate and set a new password, overwriting the current password. diff --git a/peach-web/src/routes/guide.rs b/peach-web/src/routes/guide.rs index 2898d0f..5babd7c 100644 --- a/peach-web/src/routes/guide.rs +++ b/peach-web/src/routes/guide.rs @@ -1,6 +1,6 @@ use maud::{html, PreEscaped}; -use crate::templates; +use crate::{templates, utils::theme}; /// Guide template builder. pub fn build_template() -> PreEscaped { @@ -98,6 +98,9 @@ pub fn build_template() -> PreEscaped { // title is "" and back button link is `None` because this is the homepage let body = templates::nav::build_template(guide_template, "Guide", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/home.rs b/peach-web/src/routes/home.rs index eaaf060..4b085be 100644 --- a/peach-web/src/routes/home.rs +++ b/peach-web/src/routes/home.rs @@ -1,7 +1,7 @@ use maud::{html, PreEscaped}; use peach_lib::sbot::SbotStatus; -use crate::templates; +use crate::{templates, utils::theme}; /// Read the state of the go-sbot process and define status-related /// elements accordingly. @@ -97,6 +97,9 @@ pub fn build_template() -> PreEscaped { // title is "" and back button link is `None` because this is the homepage let body = templates::nav::build_template(home_template, "", None); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/scuttlebutt/blocks.rs b/peach-web/src/routes/scuttlebutt/blocks.rs index f22fbb4..c7cd069 100644 --- a/peach-web/src/routes/scuttlebutt/blocks.rs +++ b/peach-web/src/routes/scuttlebutt/blocks.rs @@ -1,6 +1,9 @@ use maud::PreEscaped; -use crate::{templates, utils::sbot}; +use crate::{ + templates, + utils::{sbot, theme}, +}; // ROUTE: /scuttlebutt/blocks @@ -16,8 +19,11 @@ pub fn build_template() -> PreEscaped { // wrap the nav bars around the error template content let body = templates::nav::build_template(error_template, "Blocks", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } } } diff --git a/peach-web/src/routes/scuttlebutt/follows.rs b/peach-web/src/routes/scuttlebutt/follows.rs index 258d85b..517e1c3 100644 --- a/peach-web/src/routes/scuttlebutt/follows.rs +++ b/peach-web/src/routes/scuttlebutt/follows.rs @@ -1,6 +1,9 @@ use maud::PreEscaped; -use crate::{templates, utils::sbot}; +use crate::{ + templates, + utils::{sbot, theme}, +}; // ROUTE: /scuttlebutt/follows @@ -16,8 +19,11 @@ pub fn build_template() -> PreEscaped { // wrap the nav bars around the error template content let body = templates::nav::build_template(error_template, "Follows", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } } } diff --git a/peach-web/src/routes/scuttlebutt/friends.rs b/peach-web/src/routes/scuttlebutt/friends.rs index 0279c62..761d661 100644 --- a/peach-web/src/routes/scuttlebutt/friends.rs +++ b/peach-web/src/routes/scuttlebutt/friends.rs @@ -1,6 +1,9 @@ use maud::PreEscaped; -use crate::{templates, utils::sbot}; +use crate::{ + templates, + utils::{sbot, theme}, +}; // ROUTE: /scuttlebutt/friends @@ -16,8 +19,11 @@ pub fn build_template() -> PreEscaped { // wrap the nav bars around the error template content let body = templates::nav::build_template(error_template, "Friends", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } } } diff --git a/peach-web/src/routes/scuttlebutt/invites.rs b/peach-web/src/routes/scuttlebutt/invites.rs index b5372c8..f2cc5d1 100644 --- a/peach-web/src/routes/scuttlebutt/invites.rs +++ b/peach-web/src/routes/scuttlebutt/invites.rs @@ -6,7 +6,7 @@ use crate::{ templates, utils::{ flash::{FlashRequest, FlashResponse}, - sbot, + sbot, theme, }, }; @@ -73,7 +73,10 @@ pub fn build_template(request: &Request) -> PreEscaped { let body = templates::nav::build_template(invite_form_template, "Invites", Some("/scuttlebutt/peers")); - templates::base::build_template(body) + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) } /// Parse the invite uses data and attempt to generate an invite code. diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs index 9b221a2..6f77fd3 100644 --- a/peach-web/src/routes/scuttlebutt/mod.rs +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -3,6 +3,8 @@ pub mod follows; pub mod friends; pub mod invites; pub mod peers; +pub mod private; pub mod profile; pub mod profile_update; +pub mod publish; pub mod search; diff --git a/peach-web/src/routes/scuttlebutt/peers.rs b/peach-web/src/routes/scuttlebutt/peers.rs index 9c004db..39bb64a 100644 --- a/peach-web/src/routes/scuttlebutt/peers.rs +++ b/peach-web/src/routes/scuttlebutt/peers.rs @@ -1,7 +1,7 @@ use maud::{html, PreEscaped}; use peach_lib::sbot::SbotStatus; -use crate::templates; +use crate::{templates, utils::theme}; /// Scuttlebutt peer menu template builder. /// @@ -37,6 +37,9 @@ pub fn build_template() -> PreEscaped { // parameters are template, title and back url let body = templates::nav::build_template(menu_template, "Scuttlebutt Peers", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/scuttlebutt/private.rs b/peach-web/src/routes/scuttlebutt/private.rs new file mode 100644 index 0000000..737bbe8 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/private.rs @@ -0,0 +1,131 @@ +use maud::{html, Markup, PreEscaped}; +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::{ + templates, + utils::{ + flash::{FlashRequest, FlashResponse}, + sbot, theme, + }, +}; + +// ROUTE: /scuttlebutt/private + +fn public_key_input_template(ssb_id: &Option) -> Markup { + match ssb_id { + Some(id) => { + html! { input type="text" id="publicKey" name="recipient" placeholder="@xYz...=.ed25519" value=(id); } + } + // render the input with autofocus if no ssb_id has been provided + None => { + html! { input type="text" id="publicKey" name="recipient" placeholder="@xYz...=.ed25519" autofocus; } + } + } +} + +fn private_message_textarea_template(ssb_id: &Option) -> Markup { + match ssb_id { + Some(_) => { + html! { textarea id="privatePost" class="center input message-input" name="text" title="Compose a private message" placeholder="Write a private message..." autofocus { "" } } + } + // render the textarea with autofocus if an ssb_id has been provided + None => { + html! { textarea id="privatePost" class="center input message-input" name="text" title="Compose a private message" placeholder="Write a private message..." { "" } } + } + } +} + +/// Scuttlebutt private message template builder. +/// +/// Render a form for publishing a provate message. The recipient input field +/// is populated with the provided ssb_id. If no recipient is provided, the +/// template autofocuses on the recipient input field. +pub fn build_template(request: &Request, ssb_id: Option) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + let profile_template = match SbotStatus::read() { + // only render the private message elements if the sbot is active + Ok(status) if status.state == Some("active".to_string()) => { + // retrieve the local public key (set to blank if an error is returned) + let local_id = match sbot::get_local_id() { + Ok(id) => id, + Err(_) => "".to_string(), + }; + + html! { + (PreEscaped("")) + div class="card card-wide center" { + form id="sbotConfig" class="center" action="/scuttlebutt/private" method="post" { + div class="center" style="display: flex; flex-direction: column; margin-bottom: 1rem;" title="Public key (ID) of the peer being written to" { + label for="publicKey" class="label-small font-gray" { + "PUBLIC KEY" + } + (public_key_input_template(&ssb_id)) + } + (PreEscaped("")) + (private_message_textarea_template(&ssb_id)) + (PreEscaped("")) + input type="hidden" id="localId" name="id" value=(local_id); + (PreEscaped("")) + input id="publish" class="button button-primary center" type="submit" style="margin-top: 1rem;" title="Publish private message to peer" value="Publish"; + } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } + } + } + } + _ => templates::inactive::build_template("Private messaging is unavailable."), + }; + + let body = templates::nav::build_template(profile_template, "Profile", Some("/")); + + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) +} + +/// Publish a private message. +/// +/// Parse the public key and private message text from the submitted form +/// and publish the message. Set a flash message communicating the outcome +/// of the publishing attempt and redirect to the private message page. +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + id: String, + text: String, + recipient: String + })); + + // now we need to add the local id to the recipients vector, + // otherwise the local id will not be able to read the message. + let recipients = vec![data.id, data.recipient]; + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + match sbot::publish_private_msg(data.text, recipients) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Private messaging is unavailable.".to_string(), + ), + }; + + Response::redirect_303("/scuttlebutt/private").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/scuttlebutt/profile.rs b/peach-web/src/routes/scuttlebutt/profile.rs index 2fd11e9..2d2f048 100644 --- a/peach-web/src/routes/scuttlebutt/profile.rs +++ b/peach-web/src/routes/scuttlebutt/profile.rs @@ -4,7 +4,7 @@ use rouille::Request; use crate::{ templates, - utils::{flash::FlashRequest, sbot, sbot::Profile}, + utils::{flash::FlashRequest, sbot, sbot::Profile, theme}, }; // ROUTE: /scuttlebutt/profile @@ -112,7 +112,7 @@ fn social_interaction_buttons_template(profile: &Profile) -> Markup { } @if let Some(ssb_id) = &profile.id { form class="center" { - a id="privateMessage" class="button button-primary center" href={ "/scuttlebutt/private?public_key=" (ssb_id) } title="Private Message" { + a id="privateMessage" class="button button-primary center" href={ "/scuttlebutt/private/" (ssb_id) } title="Private Message" { "Send Private Message" } } @@ -152,11 +152,11 @@ pub fn build_template(request: &Request, ssb_id: Option) -> PreEscaped")) - (templates::flash::build_template(name, msg)) + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } } } } @@ -171,5 +171,8 @@ pub fn build_template(request: &Request, ssb_id: Option) -> PreEscaped PreEscaped { Some("/scuttlebutt/profile"), ); - templates::base::build_template(body) + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) } /// Update the name, description and picture for the local Scuttlebutt profile. diff --git a/peach-web/src/routes/scuttlebutt/publish.rs b/peach-web/src/routes/scuttlebutt/publish.rs new file mode 100644 index 0000000..3d1b0a9 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/publish.rs @@ -0,0 +1,41 @@ +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::utils::{flash::FlashResponse, sbot}; + +// ROUTE: /scuttlebutt/publish + +/// Publish a public Scuttlebutt post. +/// +/// Parse the post text from the submitted form and publish the message. +/// Redirect to the profile page of the PeachCloud local identity with a flash +/// message describing the outcome of the action (may be successful or +/// unsuccessful). +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + text: String, + })); + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + match sbot::publish_public_post(data.text) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Public posting is unavailable.".to_string(), + ), + }; + + Response::redirect_303("/scuttlebutt/profile").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/scuttlebutt/search.rs b/peach-web/src/routes/scuttlebutt/search.rs index b8055c0..eb71acb 100644 --- a/peach-web/src/routes/scuttlebutt/search.rs +++ b/peach-web/src/routes/scuttlebutt/search.rs @@ -5,7 +5,7 @@ use crate::{ templates, utils::{ flash::{FlashRequest, FlashResponse}, - sbot, + sbot, theme, }, }; @@ -38,7 +38,10 @@ pub fn build_template(request: &Request) -> PreEscaped { let body = templates::nav::build_template(search_template, "Search", Some("/scuttlebutt/peers")); - templates::base::build_template(body) + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + + templates::base::build_template(body, theme) } /// Parse the public key, verify that it's valid and then redirect to the diff --git a/peach-web/src/routes/settings/admin/configure.rs b/peach-web/src/routes/settings/admin/configure.rs index f984d36..fdb8621 100644 --- a/peach-web/src/routes/settings/admin/configure.rs +++ b/peach-web/src/routes/settings/admin/configure.rs @@ -2,7 +2,10 @@ use maud::{html, PreEscaped}; use peach_lib::config_manager; use rouille::Request; -use crate::{templates, utils::flash::FlashRequest}; +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; /// Administrator settings menu template builder. pub fn build_template(request: &Request) -> PreEscaped { @@ -68,6 +71,9 @@ pub fn build_template(request: &Request) -> PreEscaped { Some("/settings/admin"), ); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/settings/admin/menu.rs b/peach-web/src/routes/settings/admin/menu.rs index 036fea0..49470d5 100644 --- a/peach-web/src/routes/settings/admin/menu.rs +++ b/peach-web/src/routes/settings/admin/menu.rs @@ -1,6 +1,6 @@ use maud::{html, PreEscaped}; -use crate::templates; +use crate::{templates, utils::theme}; /// Administrator settings menu template builder. pub fn build_template() -> PreEscaped { @@ -22,6 +22,9 @@ pub fn build_template() -> PreEscaped { let body = templates::nav::build_template(menu_template, "Administrator Settings", Some("/settings")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/settings/menu.rs b/peach-web/src/routes/settings/menu.rs index dddfd94..c49218d 100644 --- a/peach-web/src/routes/settings/menu.rs +++ b/peach-web/src/routes/settings/menu.rs @@ -1,6 +1,6 @@ use maud::{html, PreEscaped}; -use crate::{templates, CONFIG}; +use crate::{templates, utils::theme, CONFIG}; // ROUTE: /settings @@ -25,6 +25,9 @@ pub fn build_template() -> PreEscaped { // parameters are template, title and back url let body = templates::nav::build_template(menu_template, "Settings", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/settings/mod.rs b/peach-web/src/routes/settings/mod.rs index 2088ee3..d910549 100644 --- a/peach-web/src/routes/settings/mod.rs +++ b/peach-web/src/routes/settings/mod.rs @@ -3,4 +3,4 @@ pub mod admin; pub mod menu; //pub mod network; pub mod scuttlebutt; -//pub mod theme; +pub mod theme; diff --git a/peach-web/src/routes/settings/scuttlebutt/configure.rs b/peach-web/src/routes/settings/scuttlebutt/configure.rs index bc24403..d546a73 100644 --- a/peach-web/src/routes/settings/scuttlebutt/configure.rs +++ b/peach-web/src/routes/settings/scuttlebutt/configure.rs @@ -7,7 +7,7 @@ use crate::{ templates, utils::{ flash::{FlashRequest, FlashResponse}, - sbot, + sbot, theme, }, }; @@ -38,7 +38,7 @@ fn read_status_and_config() -> (String, SbotConfig, String, String) { Some(index) => { let (ip, port) = sbot_config.lis.split_at(index); // remove the : from the port - (ip.to_string(), port.replace(':', "").to_string()) + (ip.to_string(), port.replace(':', "")) } // if no ':' separator is found, assume an ip has been configured (without port) None => (sbot_config.lis.to_string(), String::new()), @@ -178,8 +178,11 @@ pub fn build_template(request: &Request) -> PreEscaped { let body = templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } /// Parse the sbot configuration values and write to file. diff --git a/peach-web/src/routes/settings/scuttlebutt/menu.rs b/peach-web/src/routes/settings/scuttlebutt/menu.rs index fd6d989..66997fd 100644 --- a/peach-web/src/routes/settings/scuttlebutt/menu.rs +++ b/peach-web/src/routes/settings/scuttlebutt/menu.rs @@ -2,7 +2,10 @@ use maud::{html, PreEscaped}; use peach_lib::sbot::SbotStatus; use rouille::Request; -use crate::{templates, utils::flash::FlashRequest}; +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; /// Read the status of the go-sbot service and render buttons accordingly. fn render_process_buttons() -> PreEscaped { @@ -55,6 +58,9 @@ pub fn build_template(request: &Request) -> PreEscaped { let body = templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/routes/settings/theme.rs b/peach-web/src/routes/settings/theme.rs index 3b28e2a..438b463 100644 --- a/peach-web/src/routes/settings/theme.rs +++ b/peach-web/src/routes/settings/theme.rs @@ -1,16 +1,16 @@ -use rocket::{get, response::Redirect}; +use rouille::Response; -use crate::routes::authentication::Authenticated; -use crate::{utils, utils::Theme}; +use crate::utils::{theme, theme::Theme}; + +// ROUTE: /settings/theme/{theme} /// Set the user-interface theme according to the query parameter value. -#[get("/theme?")] -pub fn set_theme(_auth: Authenticated, theme: &str) -> Redirect { - match theme { - "light" => utils::set_theme(Theme::Light), - "dark" => utils::set_theme(Theme::Dark), +pub fn set_theme(theme: String) -> Response { + match theme.as_str() { + "light" => theme::set_theme(Theme::Light), + "dark" => theme::set_theme(Theme::Dark), _ => (), } - Redirect::to("/") + Response::redirect_303("/") } diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 101c88d..083703f 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -1,8 +1,10 @@ use maud::{html, Markup, PreEscaped}; use peach_lib::sbot::{SbotConfig, SbotStatus}; -use crate::templates; -use crate::utils::sbot; +use crate::{ + templates, + utils::{sbot, theme}, +}; // HTML RENDERING FOR ELEMENTS @@ -274,6 +276,9 @@ pub fn build_template() -> PreEscaped { // parameters are template, title and back url let body = templates::nav::build_template(status_template, "Settings", Some("/")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/templates/base.rs b/peach-web/src/templates/base.rs index 20019a0..db8304e 100644 --- a/peach-web/src/templates/base.rs +++ b/peach-web/src/templates/base.rs @@ -3,10 +3,10 @@ use maud::{html, PreEscaped, DOCTYPE}; /// Base template builder. /// /// Takes an HTML body as input and splices it into the base template. -pub fn build_template(body: PreEscaped) -> PreEscaped { +pub fn build_template(body: PreEscaped, theme: String) -> PreEscaped { html! { (DOCTYPE) - html lang="en" data-theme="light"; + html lang="en" data-theme=(theme); head { meta charset="utf-8"; meta name="description" content="PeachCloud web interface"; diff --git a/peach-web/src/templates/mod.rs b/peach-web/src/templates/mod.rs index 8711a46..77c1d32 100644 --- a/peach-web/src/templates/mod.rs +++ b/peach-web/src/templates/mod.rs @@ -3,4 +3,5 @@ pub mod error; pub mod flash; pub mod inactive; pub mod nav; +pub mod not_found; pub mod peers_list; diff --git a/peach-web/src/templates/nav.rs b/peach-web/src/templates/nav.rs index 98136c4..6650eff 100644 --- a/peach-web/src/templates/nav.rs +++ b/peach-web/src/templates/nav.rs @@ -19,7 +19,7 @@ pub fn build_template( "dark" => ( "/icons/hermies_hex_light.svg", html! { - a class="nav-item" href="/theme?theme=light" { + a class="nav-item" href="/settings/theme/light" { img class="icon-medium nav-icon-right icon-active" title="Toggle theme" src="/icons/sun.png" alt="Sun"; } }, @@ -28,7 +28,7 @@ pub fn build_template( _ => ( "/icons/hermies_hex.svg", html! { - a class="nav-item" href="/theme?theme=dark" { + a class="nav-item" href="/settings/theme/dark" { img class="icon-medium nav-icon-right icon-active" title="Toggle theme" src="/icons/moon.png" alt="Moon"; } }, diff --git a/peach-web/src/templates/not_found.rs b/peach-web/src/templates/not_found.rs new file mode 100644 index 0000000..3526a94 --- /dev/null +++ b/peach-web/src/templates/not_found.rs @@ -0,0 +1,34 @@ +use maud::{html, PreEscaped}; + +use crate::{templates, utils::theme}; + +// 404 ROUTE NOT FOUND CATCHER + +/// 404 template builder. +pub fn build_template() -> PreEscaped { + let not_found_template = html! { + div class="card center" { + div class="capsule-container" { + div class="capsule info-border" { + p { + "No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct." + } + p { + "Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home." + } + } + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = + templates::nav::build_template(not_found_template, "404: Route Not Found", Some("/")); + + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + + // render the base template with the provided body + templates::base::build_template(body, theme) +} diff --git a/peach-web/src/templates/peers_list.rs b/peach-web/src/templates/peers_list.rs index c9fea13..01e40d2 100644 --- a/peach-web/src/templates/peers_list.rs +++ b/peach-web/src/templates/peers_list.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use maud::{html, Markup, PreEscaped}; use peach_lib::sbot::SbotStatus; -use crate::templates; +use crate::{templates, utils::theme}; /// Render an unordered list of peers with one list element for each peer. fn peers_template(peers: Vec>) -> Markup { @@ -71,6 +71,9 @@ pub fn build_template(peers: Vec>, title: &str) -> PreEs let body = templates::nav::build_template(peer_list_template, title, Some("/scuttlebutt/peers")); + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + // render the base template with the provided body - templates::base::build_template(body) + templates::base::build_template(body, theme) } diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index 0390ff7..0852662 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -497,6 +497,59 @@ pub fn get_friends_list() -> Result>, Box }) } +/// Retrieve the local public key (id). +pub fn get_local_id() -> Result> { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + Ok(local_id) + }) +} + +/// Publish a public post. +pub fn publish_public_post(text: String) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + // TODO: debug!("Publishing new Scuttlebutt public post"); + match sbot_client.publish_post(&text).await { + Ok(_) => Ok("Published post".to_string()), + Err(e) => Err(format!("Failed to publish post: {}", e)), + } + }) +} + +/// Publish a private message. +pub fn publish_private_msg(text: String, recipients: Vec) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + // TODO: debug!("Publishing a new Scuttlebutt private message"); + match sbot_client + .publish_private(text.to_string(), recipients) + .await + { + Ok(_) => Ok("Published private message".to_string()), + Err(e) => Err(format!("Failed to publish private message: {}", e)), + } + }) +} + // FILEPATH FUNCTIONS /// Return the path of the ssb-go directory. diff --git a/peach-web/src/utils/theme.rs b/peach-web/src/utils/theme.rs index 9f99492..e65fe2f 100644 --- a/peach-web/src/utils/theme.rs +++ b/peach-web/src/utils/theme.rs @@ -23,112 +23,3 @@ pub fn set_theme(theme: Theme) { let mut writable_theme = THEME.write().unwrap(); *writable_theme = theme; } - -// get_cookie - -// set_cookie - -/* -pub mod monitor; - -use std::io::prelude::*; -use std::{fs, fs::File, path::Path}; - -use dirs; -use golgi::blobs; -use log::info; -use peach_lib::sbot::SbotConfig; -use rocket::{ - fs::TempFile, - response::{Redirect, Responder}, - serde::Serialize, -}; -use rocket_dyn_templates::Template; -use temporary::Directory; - -use crate::{error::PeachWebError, THEME}; - -// FILEPATH FUNCTIONS - -// return the path of the ssb-go directory -pub fn get_go_ssb_path() -> Result { - let go_ssb_path = match SbotConfig::read() { - Ok(conf) => conf.repo, - // return the default path if unable to read `config.toml` - Err(_) => { - // determine the home directory - let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; - // add the go-ssb subdirectory - home_path.push(".ssb-go"); - // convert the PathBuf to a String - home_path - .into_os_string() - .into_string() - .map_err(|_| PeachWebError::OsString)? - } - }; - - Ok(go_ssb_path) -} - -// check whether a blob is in the blobstore -pub async fn blob_is_stored_locally(blob_path: &str) -> Result { - let go_ssb_path = get_go_ssb_path()?; - let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); - let blob_exists_locally = Path::new(&complete_path).exists(); - - Ok(blob_exists_locally) -} - -// take the path to a file, add it to the blobstore and return the blob id -pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { - // create temporary directory and path - let temp_dir = Directory::new("blob")?; - // we performed a `file.name().is_some()` check before calling `write_blob_to_store` - // so it should be safe to do a simple unwrap here - let filename = file.name().expect("retrieving filename from uploaded file"); - let temp_path = temp_dir.join(filename); - - // write file to temporary path - file.persist_to(&temp_path).await?; - - // open the file and read it into a buffer - let mut file = File::open(&temp_path)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; - - // hash the bytes representing the file - let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; - - // define the blobstore path and blob filename - let (blob_dir, blob_filename) = hex_hash.split_at(2); - let go_ssb_path = get_go_ssb_path()?; - let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); - - // create the blobstore sub-directory - fs::create_dir_all(&blobstore_sub_dir)?; - - // copy the file to the blobstore - let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); - fs::copy(temp_path, blob_path)?; - - Ok(blob_id) -} - -// HELPER FUNCTIONS - -#[derive(Debug, Serialize)] -pub struct FlashContext { - pub flash_name: Option, - pub flash_msg: Option, -} - -/// A helper enum which allows routes to either return a Template or a Redirect -/// from: https://github.com/SergioBenitez/Rocket/issues/253#issuecomment-532356066 -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Responder)] -pub enum TemplateOrRedirect { - Template(Template), - Redirect(Redirect), -} -*/ -- 2.40.1 From 3a05396afb41bd1da81bde19c3eea495a4d8ab75 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Mar 2022 11:41:47 +0200 Subject: [PATCH 44/66] mount blobstore and add theme support for all routes --- peach-web/src/main.rs | 34 ++++- peach-web/src/router.rs | 291 +++++++++++++--------------------------- 2 files changed, 123 insertions(+), 202 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index f5cb47b..ce85fbf 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -32,7 +32,7 @@ use log::info; // crate-local dependencies use config::Config; -use utils::theme::Theme; +use utils::{sbot, theme::Theme}; pub type BoxError = Box; @@ -50,6 +50,7 @@ pub struct RocketConfig { } */ +/// The path of the application configuration parameters. static CONFIG_PATH: &str = "./config"; lazy_static! { @@ -116,12 +117,35 @@ fn main() { // static file server // matches on assets in the `static` directory - let response = rouille::match_assets(request, "static"); - if response.is_success() { - return response; + let static_response = rouille::match_assets(request, "static"); + if static_response.is_success() { + return static_response; + } + + // set the `.ssb-go` path in order to mount the blob fileserver + let ssb_path = sbot::get_go_ssb_path().expect("define ssb-go dir path"); + let blobstore = format!("{}/blobs/sha256", ssb_path); + + // blobstore file server + // removes the /blob url prefix and serves blobs from blobstore + // matches on assets in the `static` directory + if let Some(request) = request.remove_prefix("/blob") { + return rouille::match_assets(&request, &blobstore); } - // TODO: mount the blobstore fileserver (see old code in src/router.rs) router::mount_peachpub_routes(request) }); } + +// TODO: write a test for each route +// look at `init_test_rocket()` from old code +// https://docs.rs/rouille/latest/rouille/struct.Request.html#method.fake_http +/* +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} +*/ diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 2809698..3237431 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -1,6 +1,9 @@ use rouille::{router, Request, Response}; -use crate::{routes, utils::flash::FlashResponse}; +use crate::{routes, templates, utils::flash::FlashResponse}; + +// TODO: add mount_peachcloud_routes() +// https://github.com/tomaka/rouille/issues/232#issuecomment-919225104 /// Define the PeachPub router. /// @@ -52,10 +55,95 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { Response::html(routes::guide::build_template()) }, + (GET) (/scuttlebutt/blocks) => { + Response::html(routes::scuttlebutt::blocks::build_template()) + }, + + (GET) (/scuttlebutt/follows) => { + Response::html(routes::scuttlebutt::follows::build_template()) + }, + + (GET) (/scuttlebutt/friends) => { + Response::html(routes::scuttlebutt::friends::build_template()) + }, + + (GET) (/scuttlebutt/invites) => { + Response::html(routes::scuttlebutt::invites::build_template(request)) + .reset_flash() + }, + + (POST) (/scuttlebutt/invites) => { + routes::scuttlebutt::invites::handle_form(request) + }, + + (GET) (/scuttlebutt/peers) => { + Response::html(routes::scuttlebutt::peers::build_template()) + }, + + (GET) (/scuttlebutt/private) => { + Response::html(routes::scuttlebutt::private::build_template(request, None)) + }, + + (POST) (/scuttlebutt/private) => { + routes::scuttlebutt::private::handle_form(request) + }, + + (GET) (/scuttlebutt/private/{ssb_id: String}) => { + Response::html(routes::scuttlebutt::private::build_template(request, Some(ssb_id))) + }, + + (GET) (/scuttlebutt/profile) => { + Response::html(routes::scuttlebutt::profile::build_template(request, None)) + .reset_flash() + }, + + (GET) (/scuttlebutt/profile/update) => { + Response::html(routes::scuttlebutt::profile_update::build_template(request)) + .reset_flash() + }, + + (POST) (/scuttlebutt/profile/update) => { + routes::scuttlebutt::profile_update::handle_form(request) + }, + + (GET) (/scuttlebutt/profile/{ssb_id: String}) => { + Response::html(routes::scuttlebutt::profile::build_template(request, Some(ssb_id))) + }, + + (POST) (/scuttlebutt/publish) => { + routes::scuttlebutt::publish::handle_form(request) + }, + + (GET) (/scuttlebutt/search) => { + Response::html(routes::scuttlebutt::search::build_template(request)) + .reset_flash() + }, + + (POST) (/scuttlebutt/search) => { + routes::scuttlebutt::search::handle_form(request) + }, + (GET) (/settings) => { Response::html(routes::settings::menu::build_template()) }, + (GET) (/settings/admin) => { + Response::html(routes::settings::admin::menu::build_template()) + }, + + (POST) (/settings/admin/add) => { + routes::settings::admin::add::handle_form(request) + }, + + (GET) (/settings/admin/configure) => { + Response::html(routes::settings::admin::configure::build_template(request)) + .reset_flash() + }, + + (POST) (/settings/admin/delete) => { + routes::settings::admin::delete::handle_form(request) + }, + (GET) (/settings/scuttlebutt) => { Response::html(routes::settings::scuttlebutt::menu::build_template(request)) .reset_flash() @@ -86,207 +174,16 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { routes::settings::scuttlebutt::configure::handle_form(request, true) }, - (GET) (/settings/admin) => { - Response::html(routes::settings::admin::menu::build_template()) - }, - - (POST) (/settings/admin/add) => { - routes::settings::admin::add::handle_form(request) - }, - - (GET) (/settings/admin/configure) => { - Response::html(routes::settings::admin::configure::build_template(request)) - .reset_flash() - }, - - (POST) (/settings/admin/delete) => { - routes::settings::admin::delete::handle_form(request) - }, - - (GET) (/scuttlebutt/blocks) => { - Response::html(routes::scuttlebutt::blocks::build_template()) - }, - - (GET) (/scuttlebutt/follows) => { - Response::html(routes::scuttlebutt::follows::build_template()) - }, - - (GET) (/scuttlebutt/friends) => { - Response::html(routes::scuttlebutt::friends::build_template()) - }, - - (GET) (/scuttlebutt/invites) => { - Response::html(routes::scuttlebutt::invites::build_template(request)) - .reset_flash() - }, - - (POST) (/scuttlebutt/invites) => { - routes::scuttlebutt::invites::handle_form(request) - }, - - (GET) (/scuttlebutt/peers) => { - Response::html(routes::scuttlebutt::peers::build_template()) - }, - - (GET) (/scuttlebutt/profile) => { - Response::html(routes::scuttlebutt::profile::build_template(request, None)) - }, - - (GET) (/scuttlebutt/profile/update) => { - Response::html(routes::scuttlebutt::profile_update::build_template(request)) - }, - - (POST) (/scuttlebutt/profile/update) => { - routes::scuttlebutt::profile_update::handle_form(request) - }, - - (GET) (/scuttlebutt/profile/{ssb_id: String}) => { - Response::html(routes::scuttlebutt::profile::build_template(request, Some(ssb_id))) - }, - - (GET) (/scuttlebutt/search) => { - Response::html(routes::scuttlebutt::search::build_template(request)) - .reset_flash() - }, - - (POST) (/scuttlebutt/search) => { - routes::scuttlebutt::search::handle_form(request) + (GET) (/settings/theme/{theme: String}) => { + routes::settings::theme::set_theme(theme) }, (GET) (/status/scuttlebutt) => { Response::html(routes::status::scuttlebutt::build_template()) }, - // The code block is called if none of the other blocks matches the request. - // We return an empty response with a 404 status code. - _ => Response::empty_404() + // render the not_found template and set a 404 status code if none of + // the other blocks matches the request + _ => Response::html(templates::not_found::build_template()).with_status_code(404) ) } - -/* -use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; -use rocket_dyn_templates::Template; - -use crate::{ - routes::{ - authentication::*, - catchers::*, - index::*, - scuttlebutt::*, - settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*}, - status::{device::*, network::*, scuttlebutt::*}, - }, - utils, -}; - -/// Create a Rocket instance and mount PeachPub routes, fileserver and -/// catchers. This gives us everything we need to run PeachPub and excludes -/// settings and status routes related to networking and the device (memory, -/// hard disk, CPU etc.). -pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { - // set the `.ssb-go` path in order to mount the blob fileserver - let ssb_path = utils::get_go_ssb_path().expect("define ssb-go dir path"); - let blobstore = format!("{}/blobs/sha256", ssb_path); - - rocket - .mount( - "/", - routes![ - guide, - home, - login, - login_post, - logout, - settings_menu, - set_theme, - ], - ) - .mount( - "/settings/admin", - routes![ - admin_menu, - configure_admin, - add_admin_post, - delete_admin_post, - change_password, - change_password_post, - reset_password, - reset_password_post, - forgot_password_page, - send_password_reset_post, - ], - ) - .mount( - "/settings/scuttlebutt", - routes![ - ssb_settings_menu, - configure_sbot, - configure_sbot_default, - configure_sbot_post, - restart_sbot, - start_sbot, - stop_sbot - ], - ) - .mount( - "/scuttlebutt", - routes![ - invites, - create_invite, - peers, - search, - search_post, - friends, - follows, - blocks, - profile, - update_profile, - update_profile_post, - private, - private_post, - follow, - unfollow, - block, - unblock, - publish, - ], - ) - .mount("/status", routes![scuttlebutt_status]) - .mount("/", FileServer::from("static")) - .mount("/blob", FileServer::from(blobstore).rank(-1)) - .register("/", catchers![not_found, internal_error, forbidden]) - .attach(Template::fairing()) -} - -/// Create a Rocket instance with PeachPub routes, fileserver and catchers by -/// calling `mount_peachpub_routes()` and then mount all additional routes -/// required to run a complete PeachCloud build. -pub fn mount_peachcloud_routes(rocket: Rocket) -> Rocket { - mount_peachpub_routes(rocket) - .mount("/", routes![reboot_cmd, shutdown_cmd, power_menu,]) - .mount( - "/settings/network", - routes![ - add_credentials, - connect_wifi, - configure_dns, - configure_dns_post, - disconnect_wifi, - deploy_ap, - deploy_client, - forget_wifi, - network_home, - add_ssid, - add_wifi, - network_detail, - wifi_list, - wifi_password, - wifi_set_password, - wifi_usage, - wifi_usage_alerts, - wifi_usage_reset, - ], - ) - .mount("/status", routes![device_status, network_status]) -} -*/ -- 2.40.1 From 5d37c1291363c07485b00e3707919e6fc15f7f8a Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Mar 2022 14:56:31 +0200 Subject: [PATCH 45/66] implement authentication with separate public and private routers --- peach-web/src/main.rs | 101 ++++++++++++------ .../src/{router.rs => private_router.rs} | 30 ++---- peach-web/src/public_router.rs | 73 +++++++++++++ peach-web/src/routes/authentication/forgot.rs | 43 ++++++++ peach-web/src/routes/authentication/login.rs | 18 ++-- peach-web/src/routes/authentication/logout.rs | 25 +++-- peach-web/src/routes/authentication/mod.rs | 1 + 7 files changed, 218 insertions(+), 73 deletions(-) rename peach-web/src/{router.rs => private_router.rs} (88%) create mode 100644 peach-web/src/public_router.rs create mode 100644 peach-web/src/routes/authentication/forgot.rs diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index ce85fbf..5704998 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -15,14 +15,18 @@ //mod context; mod config; pub mod error; -mod router; +mod private_router; +mod public_router; mod routes; //#[cfg(test)] //mod tests; mod templates; pub mod utils; -use std::sync::RwLock; +use std::{ + collections::HashMap, + sync::{Mutex, RwLock}, +}; use lazy_static::lazy_static; //use log::{debug, error, info}; @@ -32,7 +36,7 @@ use log::info; // crate-local dependencies use config::Config; -use utils::{sbot, theme::Theme}; +use utils::theme::Theme; pub type BoxError = Box; @@ -88,6 +92,13 @@ pub fn init_rocket() -> Rocket { } */ +/// Session data for each authenticated client. +#[derive(Debug, Clone)] +pub struct SessionData { + _login: String, +} + +// TODO: parse these values from config file (or env var) const HOSTNAME_AND_PORT: &str = "localhost:8000"; /// Launch the peach-web server. @@ -109,43 +120,67 @@ fn main() { } */ + // store the session data for each session and a hashmap that associates + // each session id with the data + // note: we are storing this data in memory. all sessions are erased when + // the program is restarted. + let sessions_storage: Mutex> = Mutex::new(HashMap::new()); + info!("Launching web server..."); - // the `start_server` starts listening forever on the given address. + // the `start_server` starts listening forever on the given address rouille::start_server(HOSTNAME_AND_PORT, move |request| { info!("Now listening on {}", HOSTNAME_AND_PORT); - // static file server - // matches on assets in the `static` directory - let static_response = rouille::match_assets(request, "static"); - if static_response.is_success() { - return static_response; - } + // We call `session::session` in order to assign a unique identifier + // to each client. This identifier is tracked through a cookie that + // is automatically appended to the response. + // + // The parameters of the function are the name of the cookie + // ("SID") and the duration of the session in seconds (one hour). + rouille::session::session(request, "SID", 3600, |session| { + // If the client already has an identifier from a previous request, + // we try to load the existing session data. If we successfully + // load data from `sessions_storage`, we make a copy of the data + // in order to avoid locking the session for too long. + // + // We thus obtain a `Option`. + let mut session_data = if session.client_has_sid() { + sessions_storage.lock().unwrap().get(session.id()).cloned() + } else { + None + }; - // set the `.ssb-go` path in order to mount the blob fileserver - let ssb_path = sbot::get_go_ssb_path().expect("define ssb-go dir path"); - let blobstore = format!("{}/blobs/sha256", ssb_path); + // Pass the request to the public router. + // + // The public router includes authentication-related routes which + // do not require the user to be authenticated (ie. login and reset + // password). + // + // If the user is already authenticated, their request will be + // passed to the private router by public_router::handle_route(). + // + // We pass a mutable reference to the `Option` so that + // the function is free to modify it. + let response = public_router::handle_route(request, &mut session_data); - // blobstore file server - // removes the /blob url prefix and serves blobs from blobstore - // matches on assets in the `static` directory - if let Some(request) = request.remove_prefix("/blob") { - return rouille::match_assets(&request, &blobstore); - } + // Since the function call to `handle_route` can modify the session + // data, we have to store it back in the `sessions_storage` when + // necessary. + if let Some(data) = session_data { + sessions_storage + .lock() + .unwrap() + .insert(session.id().to_owned(), data); + } else if session.client_has_sid() { + // If `handle_route` erased the content of the `Option`, we + // remove the session from the storage. This is only done + // if the client already has an identifier, otherwise calling + // `session.id()` will assign one. + sessions_storage.lock().unwrap().remove(session.id()); + } - router::mount_peachpub_routes(request) + response + }) }); } - -// TODO: write a test for each route -// look at `init_test_rocket()` from old code -// https://docs.rs/rouille/latest/rouille/struct.Request.html#method.fake_http -/* -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} -*/ diff --git a/peach-web/src/router.rs b/peach-web/src/private_router.rs similarity index 88% rename from peach-web/src/router.rs rename to peach-web/src/private_router.rs index 3237431..6b72a26 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/private_router.rs @@ -1,6 +1,6 @@ use rouille::{router, Request, Response}; -use crate::{routes, templates, utils::flash::FlashResponse}; +use crate::{routes, templates, utils::flash::FlashResponse, SessionData}; // TODO: add mount_peachcloud_routes() // https://github.com/tomaka/rouille/issues/232#issuecomment-919225104 @@ -10,9 +10,15 @@ use crate::{routes, templates, utils::flash::FlashResponse}; /// Takes an incoming request and matches on the defined routes, /// returning either a template or a redirect. /// +/// All of these routes require the user to be authenticated. See the +/// `public_router` for publically-accessible, authentication-related routes. +/// /// Excludes settings and status routes related to networking and the device /// (memory, hard disk, CPU etc.). -pub fn mount_peachpub_routes(request: &Request) -> Response { +pub fn mount_peachpub_routes( + request: &Request, + session_data: &mut Option, +) -> Response { router!(request, (GET) (/) => { Response::html(routes::home::build_template()) @@ -29,26 +35,8 @@ pub fn mount_peachpub_routes(request: &Request) -> Response { routes::authentication::change::handle_form(request) }, - (GET) (/auth/login) => { - Response::html(routes::authentication::login::build_template(request)) - .reset_flash() - }, - - (POST) (/auth/login) => { - routes::authentication::login::handle_form(request) - }, - (GET) (/auth/logout) => { - routes::authentication::logout::deauthenticate() - }, - - (GET) (/auth/reset) => { - Response::html(routes::authentication::reset::build_template(request)) - .reset_flash() - }, - - (POST) (/auth/reset) => { - routes::authentication::reset::handle_form(request) + routes::authentication::logout::deauthenticate(session_data) }, (GET) (/guide) => { diff --git a/peach-web/src/public_router.rs b/peach-web/src/public_router.rs new file mode 100644 index 0000000..8d0cc69 --- /dev/null +++ b/peach-web/src/public_router.rs @@ -0,0 +1,73 @@ +use rouille::{router, Request, Response}; + +use crate::{ + private_router, routes, + utils::{flash::FlashResponse, sbot}, + SessionData, +}; + +/// Receive an incoming request, mount the fileservers for static assets and +/// define the publically-accessible routes. +/// +/// If the request is for a private route (ie. a route requiring successful +/// authentication to view), check the authentication status of the user +/// by querying the `session_data`. If the user is authenticated, pass their +/// request to the private router. Otherwise, redirect them to the login page. +pub fn handle_route(request: &Request, session_data: &mut Option) -> Response { + // static file server + // matches on assets in the `static` directory + let static_response = rouille::match_assets(request, "static"); + if static_response.is_success() { + return static_response; + } + + // set the `.ssb-go` path in order to mount the blob fileserver + let ssb_path = sbot::get_go_ssb_path().expect("define ssb-go dir path"); + let blobstore = format!("{}/blobs/sha256", ssb_path); + + // blobstore file server + // removes the /blob url prefix and serves blobs from blobstore + // matches on assets in the `static` directory + if let Some(request) = request.remove_prefix("/blob") { + return rouille::match_assets(&request, &blobstore); + } + + // handle the routes which are always accessible (ie. whether logged-in + // or not) + router!(request, + (GET) (/auth/forgot) => { + Response::html(routes::authentication::forgot::build_template()) + }, + + (GET) (/auth/login) => { + Response::html(routes::authentication::login::build_template(request)) + .reset_flash() + }, + + (POST) (/auth/login) => { + routes::authentication::login::handle_form(request, session_data) + }, + + (GET) (/auth/reset) => { + Response::html(routes::authentication::reset::build_template(request)) + .reset_flash() + }, + + (POST) (/auth/reset) => { + routes::authentication::reset::handle_form(request) + }, + + _ => { + // now that we handled all the routes that are accessible in all + // circumstances, we check that the user is logged in before proceeding + if let Some(_session) = session_data.as_ref() { + // logged in: + // mount the routes which require authentication to view + private_router::mount_peachpub_routes(request, session_data) + } else { + // not logged in: + Response::redirect_303("/auth/login") + } + } + ) +} diff --git a/peach-web/src/routes/authentication/forgot.rs b/peach-web/src/routes/authentication/forgot.rs new file mode 100644 index 0000000..f9c256f --- /dev/null +++ b/peach-web/src/routes/authentication/forgot.rs @@ -0,0 +1,43 @@ +use maud::{html, PreEscaped}; + +use crate::{templates, utils::theme}; + +// ROUTE: /auth/forgot + +/// Forgot password template builder. +pub fn build_template() -> PreEscaped { + let form_template = html! { + (PreEscaped("")) + div class="card center" { + div class="capsule capsule-container border-info" { + p class="card-text" { + "Click the 'Send Temporary Password' button to send a new temporary password which can be used to change your device password." + } + p class="card-text" style="margin-top: 1rem;" { + "The temporary password will be sent in an SSB private message to the admin of this device." + } + p class="card-text" style="margin-top: 1rem;" { + "Once you have the temporary password, click the 'Set New Password' button to reach the password reset page." + } + } + form id="sendPasswordReset" action="/auth/send_password_reset" method="post" { + div id="buttonDiv" { + input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Temporary Password" title="Send temporary password to Scuttlebutt admin"; + a href="/auth/reset_password" class="button button-primary center" title="Set a new password using the temporary password" { + "Set New Password" + } + } + } + } + }; + + // wrap the nav bars around the settings menu template content + // parameters are template, title and back url + let body = templates::nav::build_template(form_template, "Send Password Reset", Some("/")); + + // query the current theme so we can pass it into the base template builder + let theme = theme::get_theme(); + + // render the base template with the provided body + templates::base::build_template(body, theme) +} diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs index 0ad8b9d..3ef76c2 100644 --- a/peach-web/src/routes/authentication/login.rs +++ b/peach-web/src/routes/authentication/login.rs @@ -9,6 +9,7 @@ use crate::{ flash::{FlashRequest, FlashResponse}, theme, }, + SessionData, }; // HELPER AND ROUTES FOR /auth/login (GET and POST) @@ -29,7 +30,7 @@ pub fn build_template(request: &Request) -> PreEscaped { (PreEscaped("")) input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login"; div class="center-text" style="margin-top: 1rem;" { - a href="/settings/admin/forgot_password" class="label-small link font-gray" { "Forgot Password?" } + a href="/auth/forgot" class="label-small link font-gray" { "Forgot Password?" } } } } @@ -55,7 +56,7 @@ pub fn build_template(request: &Request) -> PreEscaped { /// Parse and verify the submitted password. If verification succeeds, set the /// auth session cookie and redirect to the home page. If not, set a flash /// message and redirect to the login page. -pub fn handle_form(request: &Request) -> Response { +pub fn handle_form(request: &Request, session_data: &mut Option) -> Response { // query the request body for form data // return a 400 error if the admin_id field is missing let data = try_or_400!(post_input!(request, { password: String })); @@ -63,12 +64,11 @@ pub fn handle_form(request: &Request) -> Response { match password_utils::verify_password(&data.password) { Ok(_) => { info!("Successful login attempt"); - // if successful login, add a cookie indicating the user is authenticated - // and redirect to home page - // NOTE: since we currently have just one user, the value of the cookie - // is just admin (this is arbitrary). - // If we had multiple users, we could put the user_id here. - //cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); + // if password verification is successful, write to `session_data` + // to authenticate the user + *session_data = Some(SessionData { + _login: data.password, + }); Response::redirect_303("/") } @@ -77,7 +77,7 @@ pub fn handle_form(request: &Request) -> Response { let err_msg = format!("Invalid password: {}", err); let (flash_name, flash_msg) = ( "flash_name=error".to_string(), - format!("flash_msg=Failed to save new password: {}", err_msg), + format!("flash_msg={}", err_msg), ); // if unsuccessful login, render /login page again diff --git a/peach-web/src/routes/authentication/logout.rs b/peach-web/src/routes/authentication/logout.rs index d3bb81b..21cc514 100644 --- a/peach-web/src/routes/authentication/logout.rs +++ b/peach-web/src/routes/authentication/logout.rs @@ -1,18 +1,23 @@ use log::info; use rouille::Response; -// HELPER AND ROUTES FOR /auth/logout (GET) +use crate::{utils::flash::FlashResponse, SessionData}; -/// Deauthenticate the logged-in user by removing the auth cookie. +// ROUTE: /auth/logout (GET) + +/// Deauthenticate the logged-in user by erasing the session data. /// Redirect to the login page. -pub fn deauthenticate() -> Response { - // logout authenticated user +pub fn deauthenticate(session_data: &mut Option) -> Response { info!("Attempting deauthentication of user."); - // TODO: remove auth cookie - //cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY)); - // redirect to the login page - // TODO: add flash message - //Flash::success(Redirect::to("/login"), "Logged out") - Response::redirect_303("/auth/login".to_string()) + // erase the content of `session_data` to deauthenticate the user + *session_data = None; + + let (flash_name, flash_msg) = ( + "flash_name=success".to_string(), + "flash_msg=Logged out".to_string(), + ); + + // set the flash cookie headers and redirect to the login page + Response::redirect_303("/auth/login".to_string()).add_flash(flash_name, flash_msg) } diff --git a/peach-web/src/routes/authentication/mod.rs b/peach-web/src/routes/authentication/mod.rs index f0b9e67..b848d99 100644 --- a/peach-web/src/routes/authentication/mod.rs +++ b/peach-web/src/routes/authentication/mod.rs @@ -1,4 +1,5 @@ pub mod change; +pub mod forgot; pub mod login; pub mod logout; pub mod reset; -- 2.40.1 From b78fafe84d0a6b70b17aa4b97c39987c3b8287ce Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Mar 2022 14:56:44 +0200 Subject: [PATCH 46/66] update dependencies --- Cargo.lock | 22 ++++++++++++++++++++-- peach-web/Cargo.toml | 16 ++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b684a3..53aabea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,6 +1123,24 @@ dependencies = [ "sha2", ] +[[package]] +name = "golgi" +version = "0.1.1" +source = "git+https://git.coopcloud.tech/golgi-ssb/golgi.git#77dd75bcd4649b7487069a61e2a8069b49f60a1d" +dependencies = [ + "async-std", + "async-stream 0.3.2", + "base64 0.13.0", + "futures 0.3.21", + "hex", + "kuska-handshake", + "kuska-sodiumoxide", + "kuska-ssb", + "serde 1.0.136", + "serde_json", + "sha2", +] + [[package]] name = "gpio-cdev" version = "0.2.0" @@ -2424,7 +2442,7 @@ dependencies = [ "chrono", "dirs 4.0.0", "fslock", - "golgi", + "golgi 0.1.1", "jsonrpc-client-core", "jsonrpc-client-http", "jsonrpc-core 8.0.1", @@ -2516,7 +2534,7 @@ dependencies = [ "dirs 4.0.0", "env_logger 0.8.4", "futures 0.3.21", - "golgi", + "golgi 0.1.1 (git+https://git.coopcloud.tech/golgi-ssb/golgi.git)", "lazy_static", "log 0.4.14", "maud", diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index a4aa206..5902770 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -36,17 +36,17 @@ maintenance = { status = "actively-developed" } [dependencies] async-std = "1.10" -base64 = "0.13.0" -dirs = "4.0.0" +base64 = "0.13" +dirs = "4.0" env_logger = "0.8" futures = "0.3" -golgi = { path = "/home/glyph/Projects/playground/rust/golgi" } -lazy_static = "1.4.0" +golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" } +lazy_static = "1.4" log = "0.4" -maud = "0.23.0" +maud = "0.23" peach-lib = { path = "../peach-lib" } peach-network = { path = "../peach-network" } peach-stats = { path = "../peach-stats" } -rouille = { version = "3.5.0", default-features = false } -temporary = "0.6.4" -xdg = "2.2.0" +rouille = { version = "3.5", default-features = false } +temporary = "0.6" +xdg = "2.2" -- 2.40.1 From 4a94f14dc5a67214f2b352b23b2ec4ab63cd4180 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Mar 2022 14:59:33 +0200 Subject: [PATCH 47/66] update golgi dependency to use git path --- peach-lib/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/peach-lib/Cargo.toml b/peach-lib/Cargo.toml index fc51192..9b08559 100644 --- a/peach-lib/Cargo.toml +++ b/peach-lib/Cargo.toml @@ -9,8 +9,7 @@ async-std = "1.10.0" chrono = "0.4.19" dirs = "4.0" fslock="0.1.6" -#golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi" } -golgi = { path = "../../../playground/rust/golgi" } +golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi" } jsonrpc-client-core = "0.5" jsonrpc-client-http = "0.5" jsonrpc-core = "8.0.1" -- 2.40.1 From deaedc44283a3c6c0ada5132cd432a1b8ac6fb63 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:05:26 +0200 Subject: [PATCH 48/66] add temporary password reset routes --- peach-lib/src/password_utils.rs | 4 +- peach-web/src/public_router.rs | 98 ++++++++++++------- peach-web/src/routes/authentication/forgot.rs | 25 +++-- peach-web/src/routes/authentication/login.rs | 8 +- peach-web/src/routes/authentication/mod.rs | 1 + peach-web/src/routes/authentication/reset.rs | 1 - .../src/routes/authentication/temporary.rs | 42 ++++++++ 7 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 peach-web/src/routes/authentication/temporary.rs diff --git a/peach-lib/src/password_utils.rs b/peach-lib/src/password_utils.rs index d0aff4d..a806648 100644 --- a/peach-lib/src/password_utils.rs +++ b/peach-lib/src/password_utils.rs @@ -86,7 +86,7 @@ pub fn send_password_reset() -> Result<(), PeachError> { "Your new temporary password is: {} If you are on the same WiFi network as your PeachCloud device you can reset your password \ -using this link: http://peach.local/reset_password", +using this link: http://peach.local/auth/reset", temporary_password ); // if there is an external domain, then include remote link in message @@ -95,7 +95,7 @@ using this link: http://peach.local/reset_password", Some(domain) => { format!( "\n\nOr if you are on a different WiFi network, you can reset your password \ - using the the following link: {}/reset_password", + using the the following link: {}/auth/reset", domain ) } diff --git a/peach-web/src/public_router.rs b/peach-web/src/public_router.rs index 8d0cc69..fd433f3 100644 --- a/peach-web/src/public_router.rs +++ b/peach-web/src/public_router.rs @@ -1,3 +1,4 @@ +use log::{error, info}; use rouille::{router, Request, Response}; use crate::{ @@ -6,8 +7,11 @@ use crate::{ SessionData, }; -/// Receive an incoming request, mount the fileservers for static assets and -/// define the publically-accessible routes. +/// Request handler. +/// +/// Mount the fileservers for static assets and define the +/// publically-accessible routes (including per-route handlers). Includes +/// logging of all incoming requests. /// /// If the request is for a private route (ie. a route requiring successful /// authentication to view), check the authentication status of the user @@ -32,42 +36,68 @@ pub fn handle_route(request: &Request, session_data: &mut Option) - return rouille::match_assets(&request, &blobstore); } - // handle the routes which are always accessible (ie. whether logged-in - // or not) - router!(request, - (GET) (/auth/forgot) => { - Response::html(routes::authentication::forgot::build_template()) - }, + // get the current time (for logging purposes) + let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.6f"); - (GET) (/auth/login) => { - Response::html(routes::authentication::login::build_template(request)) - .reset_flash() - }, + // define the success logger for incoming requests + let log_ok = |req: &Request, _resp: &Response, _elap: std::time::Duration| { + info!("{} {} {}", now, req.method(), req.raw_url()); + }; - (POST) (/auth/login) => { - routes::authentication::login::handle_form(request, session_data) - }, + // define the error logger for incoming requests + let log_err = |req: &Request, _elap: std::time::Duration| { + error!( + "{} Handler panicked: {} {}", + now, + req.method(), + req.raw_url() + ); + }; - (GET) (/auth/reset) => { - Response::html(routes::authentication::reset::build_template(request)) - .reset_flash() - }, + // instantiate request logging + rouille::log_custom(request, log_ok, log_err, || { + // handle the routes which are always accessible (ie. whether logged-in + // or not) + router!(request, + (GET) (/auth/forgot) => { + Response::html(routes::authentication::forgot::build_template(request)) + .reset_flash() + }, - (POST) (/auth/reset) => { - routes::authentication::reset::handle_form(request) - }, + (GET) (/auth/login) => { + Response::html(routes::authentication::login::build_template(request)) + .reset_flash() + }, - _ => { - // now that we handled all the routes that are accessible in all - // circumstances, we check that the user is logged in before proceeding - if let Some(_session) = session_data.as_ref() { - // logged in: - // mount the routes which require authentication to view - private_router::mount_peachpub_routes(request, session_data) - } else { - // not logged in: - Response::redirect_303("/auth/login") + (POST) (/auth/login) => { + routes::authentication::login::handle_form(request, session_data) + }, + + (GET) (/auth/reset) => { + Response::html(routes::authentication::reset::build_template(request)) + .reset_flash() + }, + + (POST) (/auth/reset) => { + routes::authentication::reset::handle_form(request) + }, + + (POST) (/auth/temporary) => { + routes::authentication::temporary::handle_form() + }, + + _ => { + // now that we handled all the routes that are accessible in all + // circumstances, we check that the user is logged in before proceeding + if let Some(_session) = session_data.as_ref() { + // logged in: + // mount the routes which require authentication to view + private_router::mount_peachpub_routes(request, session_data) + } else { + // not logged in: + Response::redirect_303("/auth/login") + } } - } - ) + ) + }) } diff --git a/peach-web/src/routes/authentication/forgot.rs b/peach-web/src/routes/authentication/forgot.rs index f9c256f..d61f90d 100644 --- a/peach-web/src/routes/authentication/forgot.rs +++ b/peach-web/src/routes/authentication/forgot.rs @@ -1,12 +1,19 @@ use maud::{html, PreEscaped}; +use rouille::Request; -use crate::{templates, utils::theme}; +use crate::{ + templates, + utils::{flash::FlashRequest, theme}, +}; // ROUTE: /auth/forgot /// Forgot password template builder. -pub fn build_template() -> PreEscaped { - let form_template = html! { +pub fn build_template(request: &Request) -> PreEscaped { + // check for flash cookies; will be (None, None) if no flash cookies are found + let (flash_name, flash_msg) = request.retrieve_flash(); + + let password_reset_template = html! { (PreEscaped("")) div class="card center" { div class="capsule capsule-container border-info" { @@ -20,20 +27,26 @@ pub fn build_template() -> PreEscaped { "Once you have the temporary password, click the 'Set New Password' button to reach the password reset page." } } - form id="sendPasswordReset" action="/auth/send_password_reset" method="post" { + form id="sendPasswordReset" action="/auth/temporary" method="post" { div id="buttonDiv" { - input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Temporary Password" title="Send temporary password to Scuttlebutt admin"; + input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Temporary Password" title="Send temporary password to Scuttlebutt admin(s)"; a href="/auth/reset_password" class="button button-primary center" title="Set a new password using the temporary password" { "Set New Password" } } } + // render flash message if cookies were found in the request + @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { + (PreEscaped("")) + (templates::flash::build_template(name, msg)) + } } }; // wrap the nav bars around the settings menu template content // parameters are template, title and back url - let body = templates::nav::build_template(form_template, "Send Password Reset", Some("/")); + let body = + templates::nav::build_template(password_reset_template, "Send Password Reset", Some("/")); // query the current theme so we can pass it into the base template builder let theme = theme::get_theme(); diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs index 3ef76c2..f914e30 100644 --- a/peach-web/src/routes/authentication/login.rs +++ b/peach-web/src/routes/authentication/login.rs @@ -1,4 +1,4 @@ -use log::info; +use log::debug; use maud::{html, PreEscaped}; use peach_lib::password_utils; use rouille::{post_input, try_or_400, Request, Response}; @@ -63,17 +63,17 @@ pub fn handle_form(request: &Request, session_data: &mut Option) -> match password_utils::verify_password(&data.password) { Ok(_) => { - info!("Successful login attempt"); + debug!("Successful login attempt"); // if password verification is successful, write to `session_data` // to authenticate the user *session_data = Some(SessionData { - _login: data.password, + _login: "success".to_string(), }); Response::redirect_303("/") } Err(err) => { - info!("Unsuccessful login attempt"); + debug!("Unsuccessful login attempt"); let err_msg = format!("Invalid password: {}", err); let (flash_name, flash_msg) = ( "flash_name=error".to_string(), diff --git a/peach-web/src/routes/authentication/mod.rs b/peach-web/src/routes/authentication/mod.rs index b848d99..291d221 100644 --- a/peach-web/src/routes/authentication/mod.rs +++ b/peach-web/src/routes/authentication/mod.rs @@ -3,3 +3,4 @@ pub mod forgot; pub mod login; pub mod logout; pub mod reset; +pub mod temporary; diff --git a/peach-web/src/routes/authentication/reset.rs b/peach-web/src/routes/authentication/reset.rs index 3464ae0..e352490 100644 --- a/peach-web/src/routes/authentication/reset.rs +++ b/peach-web/src/routes/authentication/reset.rs @@ -100,7 +100,6 @@ pub fn handle_form(request: &Request) -> Response { &data.new_password2, ) { Ok(_) => ( - // = "flash_name=success".to_string(), "flash_msg=New password has been saved. Return home to login".to_string(), ), diff --git a/peach-web/src/routes/authentication/temporary.rs b/peach-web/src/routes/authentication/temporary.rs new file mode 100644 index 0000000..9bf17fb --- /dev/null +++ b/peach-web/src/routes/authentication/temporary.rs @@ -0,0 +1,42 @@ +use log::debug; +use peach_lib::password_utils; +use rouille::Response; + +use crate::utils::flash::FlashResponse; + +// ROUTE: /auth/temporary (POST) + +/// Send a temporary password as a Scuttlebutt private message to the admin(s). +/// +/// This route is used by a user who is not logged in and is specifically for +/// users who have forgotten their password. A successful request results +/// in a Scuttlebutt private message being sent to the account of the device +/// admin. +/// +/// Redirects to the Send Password Reset page a flash message describing the +/// outcome of the action (may be successful or unsuccessful). +pub fn handle_form() -> Response { + // save submitted admin id to file + let (flash_name, flash_msg) = match password_utils::send_password_reset() { + Ok(_) => { + debug!("Sent temporary password to device admin(s)"); + ( + "flash_name=success".to_string(), + "flash_msg=A temporary password has been sent to the admin(s) of this device" + .to_string(), + ) + } + Err(err) => { + debug!( + "Received an error while trying to send temporary password to device admin(s): {}", + err + ); + ( + "error".to_string(), + format!("Failed to send temporary password: {}", err), + ) + } + }; + + Response::redirect_303("/auth/forgot").add_flash(flash_name, flash_msg) +} -- 2.40.1 From 1bdacf36326e3ea1a70fe636fb6b5e4079773560 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:05:53 +0200 Subject: [PATCH 49/66] add application configuration to replace Rocket.toml --- peach-web/src/config.rs | 67 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/peach-web/src/config.rs b/peach-web/src/config.rs index 5ea7b93..b1c2e95 100644 --- a/peach-web/src/config.rs +++ b/peach-web/src/config.rs @@ -1,54 +1,53 @@ -// Kind thanks to Alex Wennerberg (https://alexwennerberg.com/) for writing -// this code and offered it under the 0BSD (BDS 0-Clause) license. +//! Define the configuration parameters for the web application. +//! +//! Sets default values and updates them if the corresponding environment +//! variables have been set. -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; +use std::env; + +// environment variable keys to check for +const ENV_VARS: [&str; 4] = ["STANDALONE_MODE", "DISABLE_AUTH", "ADDR", "PORT"]; -// Ini-like key=value configuration with global config only (no subsections). -#[derive(Debug, PartialEq)] pub struct Config { - pub disable_auth: bool, pub standalone_mode: bool, + pub disable_auth: bool, + pub addr: String, + pub port: String, } impl Default for Config { fn default() -> Self { Self { - disable_auth: false, standalone_mode: true, + disable_auth: false, + addr: "127.0.0.1".to_string(), + port: "8000".to_string(), } } } impl Config { - pub fn match_kv(&mut self, key: &str, value: &str) { - match key { - "disable_auth" => self.disable_auth = value.parse().unwrap(), - "standalone_mode" => self.standalone_mode = value.parse().unwrap(), - _ => {} - } - } -} + pub fn new() -> Config { + // define default config values + let mut config = Config::default(); -impl Config { - pub fn from_file>(path: P) -> Result { - let file = File::open(path)?; - let mut conf = Config::default(); - - for l in io::BufReader::new(file).lines() { - let line = l?; - if line.is_empty() { - continue; - } - if let Some(i) = line.find('=') { - let key = &line[..i]; - let value = &line[i + 1..]; - conf.match_kv(key, value); - } else { - // panic!("Invalid config") + // check for the environment variables in our config + for key in ENV_VARS { + // if a variable (key) has been set, check the value + if let Ok(val) = env::var(key) { + // if the value is of the correct type, update the config value + match key { + "STANDALONE_MODE" if val.as_str() == "true" => config.standalone_mode = true, + "STANDALONE_MODE" if val.as_str() == "false" => config.standalone_mode = false, + "DISABLE_AUTH" if val.as_str() == "true" => config.disable_auth = true, + "DISABLE_AUTH" if val.as_str() == "false" => config.disable_auth = false, + "ADDR" => config.addr = val, + "PORT" => config.port = val, + _ => (), + } } } - Ok(conf) + + config } } -- 2.40.1 From 928afb35d359d5a060fb49baef17e777e358287c Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:07:43 +0200 Subject: [PATCH 50/66] update application configuration --- peach-web/src/main.rs | 126 +++++++++++------------------------------- 1 file changed, 32 insertions(+), 94 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 5704998..d5d4555 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -12,14 +12,11 @@ //! micro-web-framework), Maud (an HTML template engine for Rust), HTML and //! CSS. -//mod context; mod config; pub mod error; mod private_router; mod public_router; mod routes; -//#[cfg(test)] -//mod tests; mod templates; pub mod utils; @@ -29,96 +26,45 @@ use std::{ }; use lazy_static::lazy_static; -//use log::{debug, error, info}; -use log::info; -//use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG}; -//use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket}; +use log::{debug, info}; +use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG}; // crate-local dependencies use config::Config; use utils::theme::Theme; -pub type BoxError = Box; - -/* -/// Application configuration parameters. -/// These values are extracted from Rocket's default configuration provider: -/// `Config::figment()`. As such, the values are drawn from `Rocket.toml` or -/// the TOML file path in the `ROCKET_CONFIG` environment variable. The TOML -/// file parameters are automatically overruled by any `ROCKET_` variables -/// which might be set. -#[derive(Debug, Deserialize)] -pub struct RocketConfig { - disable_auth: bool, - standalone_mode: bool, -} -*/ - -/// The path of the application configuration parameters. -static CONFIG_PATH: &str = "./config"; - +// load the application configuration and create the theme switcher lazy_static! { - static ref CONFIG: Config = Config::from_file(CONFIG_PATH).expect("failed to read config file"); + static ref CONFIG: Config = Config::new(); static ref THEME: RwLock = RwLock::new(Theme::Light); } -//static WLAN_IFACE: &str = "wlan0"; -//static AP_IFACE: &str = "ap0"; - -/* -pub fn init_rocket() -> Rocket { - info!("Initializing Rocket"); - // build a basic rocket instance - let rocket = rocket::build(); - - // return the default provider figment used by `rocket::build()` - let figment = rocket.figment(); - - // deserialize configuration parameters into our `RocketConfig` struct (defined above) - // since we're in the intialisation phase, panic if the extraction fails - let config: RocketConfig = figment.extract().expect("configuration extraction failed"); - - debug!("{:?}", config); - - info!("Mounting Rocket routes"); - let mounted_rocket = if config.standalone_mode { - router::mount_peachpub_routes(rocket) - } else { - router::mount_peachcloud_routes(rocket) - }; - - info!("Attaching application configuration to managed state"); - mounted_rocket.attach(AdHoc::config::()) -} -*/ - /// Session data for each authenticated client. #[derive(Debug, Clone)] pub struct SessionData { _login: String, } -// TODO: parse these values from config file (or env var) -const HOSTNAME_AND_PORT: &str = "localhost:8000"; - /// Launch the peach-web server. fn main() { // initialize logger env_logger::init(); - /* // check if /var/lib/peachcloud/config.yml exists if !std::path::Path::new(PEACH_CONFIG).exists() { - info!("PeachCloud configuration file not found; loading default values"); + debug!("PeachCloud configuration file not found; loading default values"); // since we're in the intialisation phase, panic if the loading fails let config = config_manager::load_peach_config().expect("peachcloud configuration loading failed"); - info!("Saving default PeachCloud configuration values to file"); + debug!("Saving default PeachCloud configuration values to file"); // this ensures a config file is created if it does not already exist config_manager::save_peach_config(config).expect("peachcloud configuration saving failed"); } - */ + + // set ip address / hostname and port for the webserver + // defaults to "127.0.0.1:8000" + let addr_and_port = format!("{}:{}", CONFIG.addr, CONFIG.port); // store the session data for each session and a hashmap that associates // each session id with the data @@ -126,57 +72,49 @@ fn main() { // the program is restarted. let sessions_storage: Mutex> = Mutex::new(HashMap::new()); - info!("Launching web server..."); + info!("Launching web server on {}", addr_and_port); // the `start_server` starts listening forever on the given address - rouille::start_server(HOSTNAME_AND_PORT, move |request| { - info!("Now listening on {}", HOSTNAME_AND_PORT); - - // We call `session::session` in order to assign a unique identifier - // to each client. This identifier is tracked through a cookie that - // is automatically appended to the response. - // - // The parameters of the function are the name of the cookie - // ("SID") and the duration of the session in seconds (one hour). + rouille::start_server(addr_and_port, move |request| { + // assign a unique id to each client (appends a cookie to the response + // with a name of "SID" and a duration of one hour (3600 seconds) rouille::session::session(request, "SID", 3600, |session| { - // If the client already has an identifier from a previous request, - // we try to load the existing session data. If we successfully - // load data from `sessions_storage`, we make a copy of the data - // in order to avoid locking the session for too long. - // - // We thus obtain a `Option`. + // if the client already has an identifier from a previous request, + // try to load the existing session data. if successful, make a + // copy of the data in order to avoid locking the session for too + // long let mut session_data = if session.client_has_sid() { sessions_storage.lock().unwrap().get(session.id()).cloned() } else { None }; - // Pass the request to the public router. + // pass the request to the public router // - // The public router includes authentication-related routes which + // the public router includes authentication-related routes which // do not require the user to be authenticated (ie. login and reset - // password). + // password) // - // If the user is already authenticated, their request will be - // passed to the private router by public_router::handle_route(). + // if the user is already authenticated, their request will be + // passed to the private router by public_router::handle_route() // - // We pass a mutable reference to the `Option` so that - // the function is free to modify it. + // we pass a mutable reference to the `Option` so that + // the function is free to modify it let response = public_router::handle_route(request, &mut session_data); - // Since the function call to `handle_route` can modify the session - // data, we have to store it back in the `sessions_storage` when - // necessary. + // since the function call to `handle_route` can modify the session + // data, we have to store it back in the `sessions_storage` after + // the request has been handled if let Some(data) = session_data { sessions_storage .lock() .unwrap() .insert(session.id().to_owned(), data); } else if session.client_has_sid() { - // If `handle_route` erased the content of the `Option`, we - // remove the session from the storage. This is only done - // if the client already has an identifier, otherwise calling - // `session.id()` will assign one. + // if the content of the `Option` was erased (ie. due to + // deauthentication on logout), remove the session from the + // storage. this is only done if the client already has an + // identifier, otherwise calling `session.id()` will assign one sessions_storage.lock().unwrap().remove(session.id()); } -- 2.40.1 From 50afb61955a983412af5c4e97c6456b399d3c060 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:09:08 +0200 Subject: [PATCH 51/66] update dependencies and bump minor version --- Cargo.lock | 26 ++++---------------------- peach-web/Cargo.toml | 10 +++++----- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53aabea..79940c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,23 +1106,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "golgi" -version = "0.1.1" -dependencies = [ - "async-std", - "async-stream 0.3.2", - "base64 0.13.0", - "futures 0.3.21", - "hex", - "kuska-handshake", - "kuska-sodiumoxide", - "kuska-ssb", - "serde 1.0.136", - "serde_json", - "sha2", -] - [[package]] name = "golgi" version = "0.1.1" @@ -2442,7 +2425,7 @@ dependencies = [ "chrono", "dirs 4.0.0", "fslock", - "golgi 0.1.1", + "golgi", "jsonrpc-client-core", "jsonrpc-client-http", "jsonrpc-core 8.0.1", @@ -2527,20 +2510,19 @@ dependencies = [ [[package]] name = "peach-web" -version = "0.5.0" +version = "0.6.0" dependencies = [ "async-std", "base64 0.13.0", + "chrono", "dirs 4.0.0", "env_logger 0.8.4", "futures 0.3.21", - "golgi 0.1.1 (git+https://git.coopcloud.tech/golgi-ssb/golgi.git)", + "golgi", "lazy_static", "log 0.4.14", "maud", "peach-lib", - "peach-network", - "peach-stats", "rouille", "temporary", "xdg", diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 5902770..0b4f9a7 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "peach-web" -version = "0.5.0" +version = "0.6.0" authors = ["Andrew Reid "] edition = "2018" description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins." @@ -21,8 +21,6 @@ maintainer-scripts="debian" systemd-units = { unit-name = "peach-web" } assets = [ ["target/release/peach-web", "/usr/bin/", "755"], - ["Rocket.toml", "/usr/share/peach-web/Rocket.toml", "644"], - ["templates/**/*", "/usr/share/peach-web/templates/", "644"], ["static/*", "/usr/share/peach-web/static/", "644"], ["static/css/*", "/usr/share/peach-web/static/css/", "644"], ["static/icons/*", "/usr/share/peach-web/static/icons/", "644"], @@ -37,6 +35,7 @@ maintenance = { status = "actively-developed" } [dependencies] async-std = "1.10" base64 = "0.13" +chrono = "0.4" dirs = "4.0" env_logger = "0.8" futures = "0.3" @@ -45,8 +44,9 @@ lazy_static = "1.4" log = "0.4" maud = "0.23" peach-lib = { path = "../peach-lib" } -peach-network = { path = "../peach-network" } -peach-stats = { path = "../peach-stats" } +# these will be reintroduced when the full peachcloud mode is added +#peach-network = { path = "../peach-network" } +#peach-stats = { path = "../peach-stats" } rouille = { version = "3.5", default-features = false } temporary = "0.6" xdg = "2.2" -- 2.40.1 From 5fc0094146006f12efb3bf18478d786d9f8e3f7c Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:11:26 +0200 Subject: [PATCH 52/66] remove tera templates --- peach-web/templates/base.html.tera | 16 -- .../catchers/internal_error.html.tera | 9 - .../templates/catchers/not_found.html.tera | 11 -- peach-web/templates/guide.html.tera | 33 ---- peach-web/templates/home.html.tera | 68 -------- peach-web/templates/login.html.tera | 20 --- peach-web/templates/nav.html.tera | 41 ----- peach-web/templates/power.html.tera | 15 -- .../templates/scuttlebutt/inactive.html.tera | 11 -- .../templates/scuttlebutt/invites.html.tera | 20 --- .../templates/scuttlebutt/peers.html.tera | 21 --- .../scuttlebutt/peers_list.html.tera | 30 ---- .../templates/scuttlebutt/private.html.tera | 23 --- .../templates/scuttlebutt/profile.html.tera | 75 --------- .../templates/scuttlebutt/search.html.tera | 16 -- .../scuttlebutt/update_profile.html.tera | 27 --- .../settings/admin/change_password.html.tera | 24 --- .../settings/admin/configure_admin.html.tera | 32 ---- .../settings/admin/forgot_password.html.tera | 19 --- .../templates/settings/admin/menu.html.tera | 12 -- .../settings/admin/reset_password.html.tera | 23 --- peach-web/templates/settings/menu.html.tera | 14 -- .../settings/network/add_ap.html.tera | 20 --- .../settings/network/ap_details.html.tera | 75 --------- .../settings/network/configure_dns.html.tera | 60 ------- .../network/data_usage_limits.html.tera | 55 ------ .../settings/network/list_aps.html.tera | 38 ----- .../templates/settings/network/menu.html.tera | 23 --- .../settings/network/modify_ap.html.tera | 20 --- .../scuttlebutt/configure_sbot.html.tera | 87 ---------- .../settings/scuttlebutt/menu.html.tera | 18 -- .../snippets/flash_message.html.tera | 14 -- peach-web/templates/status/device.html.tera | 137 --------------- peach-web/templates/status/network.html.tera | 158 ------------------ .../templates/status/scuttlebutt.html.tera | 95 ----------- 35 files changed, 1360 deletions(-) delete mode 100644 peach-web/templates/base.html.tera delete mode 100644 peach-web/templates/catchers/internal_error.html.tera delete mode 100644 peach-web/templates/catchers/not_found.html.tera delete mode 100644 peach-web/templates/guide.html.tera delete mode 100644 peach-web/templates/home.html.tera delete mode 100644 peach-web/templates/login.html.tera delete mode 100644 peach-web/templates/nav.html.tera delete mode 100644 peach-web/templates/power.html.tera delete mode 100644 peach-web/templates/scuttlebutt/inactive.html.tera delete mode 100644 peach-web/templates/scuttlebutt/invites.html.tera delete mode 100644 peach-web/templates/scuttlebutt/peers.html.tera delete mode 100644 peach-web/templates/scuttlebutt/peers_list.html.tera delete mode 100644 peach-web/templates/scuttlebutt/private.html.tera delete mode 100644 peach-web/templates/scuttlebutt/profile.html.tera delete mode 100644 peach-web/templates/scuttlebutt/search.html.tera delete mode 100644 peach-web/templates/scuttlebutt/update_profile.html.tera delete mode 100644 peach-web/templates/settings/admin/change_password.html.tera delete mode 100644 peach-web/templates/settings/admin/configure_admin.html.tera delete mode 100644 peach-web/templates/settings/admin/forgot_password.html.tera delete mode 100644 peach-web/templates/settings/admin/menu.html.tera delete mode 100644 peach-web/templates/settings/admin/reset_password.html.tera delete mode 100644 peach-web/templates/settings/menu.html.tera delete mode 100644 peach-web/templates/settings/network/add_ap.html.tera delete mode 100644 peach-web/templates/settings/network/ap_details.html.tera delete mode 100644 peach-web/templates/settings/network/configure_dns.html.tera delete mode 100644 peach-web/templates/settings/network/data_usage_limits.html.tera delete mode 100644 peach-web/templates/settings/network/list_aps.html.tera delete mode 100644 peach-web/templates/settings/network/menu.html.tera delete mode 100644 peach-web/templates/settings/network/modify_ap.html.tera delete mode 100644 peach-web/templates/settings/scuttlebutt/configure_sbot.html.tera delete mode 100644 peach-web/templates/settings/scuttlebutt/menu.html.tera delete mode 100644 peach-web/templates/snippets/flash_message.html.tera delete mode 100644 peach-web/templates/status/device.html.tera delete mode 100644 peach-web/templates/status/network.html.tera delete mode 100644 peach-web/templates/status/scuttlebutt.html.tera diff --git a/peach-web/templates/base.html.tera b/peach-web/templates/base.html.tera deleted file mode 100644 index ff9eb59..0000000 --- a/peach-web/templates/base.html.tera +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - PeachCloud - - - - - - - - {% block nav %}{% endblock nav %} - - diff --git a/peach-web/templates/catchers/internal_error.html.tera b/peach-web/templates/catchers/internal_error.html.tera deleted file mode 100644 index d2c4f15..0000000 --- a/peach-web/templates/catchers/internal_error.html.tera +++ /dev/null @@ -1,9 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -{%- endblock card -%} diff --git a/peach-web/templates/catchers/not_found.html.tera b/peach-web/templates/catchers/not_found.html.tera deleted file mode 100644 index 4a1f28b..0000000 --- a/peach-web/templates/catchers/not_found.html.tera +++ /dev/null @@ -1,11 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} -
-
-
-

No PeachCloud resource exists for this URL. Please ensure that the URL in the address bar is correct.

-

Click the back arrow in the top-left or the PeachCloud logo at the bottom of your screen to return Home.

-
-
-
-{%- endblock card -%} diff --git a/peach-web/templates/guide.html.tera b/peach-web/templates/guide.html.tera deleted file mode 100644 index 5481b1a..0000000 --- a/peach-web/templates/guide.html.tera +++ /dev/null @@ -1,33 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- -
- Getting started -

The Scuttlebutt server (sbot) will be inactive when you first run PeachCloud. This is to allow configuration parameters to be set before it is activated for the first time. Navigate to the Sbot Configuration page to configure your system. The default configuration will be fine for most usecases.

-

Once the configuration is set, navigate to the Scuttlebutt settings menu to start the sbot. If the server starts successfully, you will see a green smiley face on the home page. If the face is orange and sleeping, that means the sbot is still inactive (ie. the process is not running). If the face is red and dead, that means the sbot failed to start - indicated an error. For now, the best way to gain insight into the problem is to check the systemd log. Open a terminal and enter: systemctl --user status go-sbot.service. The log output may give some clues about the source of the error.

-
- -
- Submit a bug report -

Bug reports can be submitted by filing an issue on the peach-workspace git repo. Before filing a report, first check to see if an issue already exists for the bug you've encountered. If not, you're invited to submit a new report; the template will guide you through several questions.

-
- -
- Share feedback & request support -

You're invited to share your thoughts and experiences of PeachCloud in the #peachcloud channel on Scuttlebutt. The channel is also a good place to ask for help.

-

Alternatively, we have a Matrix channel for discussion about PeachCloud and you can also reach out to @glyph via email.

-
- -
- Contribute to PeachCloud -

PeachCloud is free, open-source software and relies on donations and grants to fund develop. Donations can be made on our Open Collective page.

-

Programmers, designers, artists and writers are also welcome to contribute to the project. Please visit the main PeachCloud git repository to find out more details or contact the team via Scuttlebutt, Matrix or email.

-
-
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/home.html.tera b/peach-web/templates/home.html.tera deleted file mode 100644 index 27ff7de..0000000 --- a/peach-web/templates/home.html.tera +++ /dev/null @@ -1,68 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - - -{%- endblock card %} diff --git a/peach-web/templates/login.html.tera b/peach-web/templates/login.html.tera deleted file mode 100644 index 6235a96..0000000 --- a/peach-web/templates/login.html.tera +++ /dev/null @@ -1,20 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - - - -
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/nav.html.tera b/peach-web/templates/nav.html.tera deleted file mode 100644 index 7e31734..0000000 --- a/peach-web/templates/nav.html.tera +++ /dev/null @@ -1,41 +0,0 @@ -{%- extends "base" -%} -{%- block nav -%} - - - -
- {%- block card -%}{%- endblock card %} -
- - -{%- endblock nav -%} diff --git a/peach-web/templates/power.html.tera b/peach-web/templates/power.html.tera deleted file mode 100644 index 3d91b5f..0000000 --- a/peach-web/templates/power.html.tera +++ /dev/null @@ -1,15 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - - - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/inactive.html.tera b/peach-web/templates/scuttlebutt/inactive.html.tera deleted file mode 100644 index beeb692..0000000 --- a/peach-web/templates/scuttlebutt/inactive.html.tera +++ /dev/null @@ -1,11 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-

Sbot Inactive

-

{{ unavailable_msg }}

-

Visit the Scuttlebutt settings menu to start the Sbot and then try again.

-
-
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/invites.html.tera b/peach-web/templates/scuttlebutt/invites.html.tera deleted file mode 100644 index 3a0e960..0000000 --- a/peach-web/templates/scuttlebutt/invites.html.tera +++ /dev/null @@ -1,20 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - {% if invite_code %} -

{{ invite_code }}

- {% endif %} -
- - - Cancel -
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera deleted file mode 100644 index 5409bf7..0000000 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ /dev/null @@ -1,21 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- {# only render the peer menu elements if the sbot is active #} - {%- if sbot_state == "active" %} -
- - -
- {%- endif %} - - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers_list.html.tera b/peach-web/templates/scuttlebutt/peers_list.html.tera deleted file mode 100644 index 0599b8c..0000000 --- a/peach-web/templates/scuttlebutt/peers_list.html.tera +++ /dev/null @@ -1,30 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} -
- {%- if peers %} - - {%- else %} -

No follows found

- {%- endif %} -
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/private.html.tera b/peach-web/templates/scuttlebutt/private.html.tera deleted file mode 100644 index fcb7924..0000000 --- a/peach-web/templates/scuttlebutt/private.html.tera +++ /dev/null @@ -1,23 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- {# only render the private message elements if the sbot is active #} - {%- if sbot_status and sbot_status.state == "active" %} -
-
- - -
- - - - - - -
- {%- endif %} - - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera deleted file mode 100644 index 8507de3..0000000 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ /dev/null @@ -1,75 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- {# only render the profile info elements if the sbot is active #} - {%- if sbot_status and sbot_status.state == "active" %} - -
- {% if is_local_profile %} - - - Edit - - {% endif %} - - - {# only try to render profile pic if we have the blob #} - {%- if blob_path and blob_exists == true %} - Profile picture - {% else %} - {# render a placeholder profile picture (icon) #} - Profile picture - {% endif %} - -

{{ name }}

- -

{{ description }}

-
- {% if is_local_profile %} - -
- - - -
- {% else %} - - -
- {% if following == false %} -
- - -
- {% elif following == true %} -
- - -
- {% else %} -

Unable to determine follow state

- {% endif %} - {% if blocking == false %} -
- - -
- {% elif blocking == true %} -
- - -
- {% else %} -

Unable to determine block state

- {% endif %} -
- Send Private Message -
-
- {%- endif %} - {%- endif %} - - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/search.html.tera b/peach-web/templates/scuttlebutt/search.html.tera deleted file mode 100644 index f4582dc..0000000 --- a/peach-web/templates/scuttlebutt/search.html.tera +++ /dev/null @@ -1,16 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - -
- - - - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/update_profile.html.tera b/peach-web/templates/scuttlebutt/update_profile.html.tera deleted file mode 100644 index 3637c67..0000000 --- a/peach-web/templates/scuttlebutt/update_profile.html.tera +++ /dev/null @@ -1,27 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - {# ASSIGN VARIABLES #} - {# ---------------- #} - -
-
-
- - - - - - -
- - - -
- - Cancel -
-
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/change_password.html.tera b/peach-web/templates/settings/admin/change_password.html.tera deleted file mode 100644 index 3308593..0000000 --- a/peach-web/templates/settings/admin/change_password.html.tera +++ /dev/null @@ -1,24 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - - - - - - - - - Cancel -
-
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/configure_admin.html.tera b/peach-web/templates/settings/admin/configure_admin.html.tera deleted file mode 100644 index fa1843a..0000000 --- a/peach-web/templates/settings/admin/configure_admin.html.tera +++ /dev/null @@ -1,32 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
Administrators are identified and added by their Scuttlebutt public keys. These accounts will be sent private messages on Scuttlebutt when a password reset is requested.
- {% if not ssb_admin_ids %} -
- There are no currently configured admins. -
- {% else %} - {% for admin in ssb_admin_ids %} -
-
- -

{{ admin }}

- -
-
- {% endfor %} - {% endif %} -
-
- - -
- - - - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/forgot_password.html.tera b/peach-web/templates/settings/admin/forgot_password.html.tera deleted file mode 100644 index 9f1e662..0000000 --- a/peach-web/templates/settings/admin/forgot_password.html.tera +++ /dev/null @@ -1,19 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-

Click the 'Send Temporary Password' button to send a new temporary password which can be used to change your device password.

-

The temporary password will be sent in an SSB private message to the admin of this device.

-

Once you have the temporary password, click the 'Set New Password' button to reach the password reset page.

-
-
- -
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/menu.html.tera b/peach-web/templates/settings/admin/menu.html.tera deleted file mode 100644 index dbd62b5..0000000 --- a/peach-web/templates/settings/admin/menu.html.tera +++ /dev/null @@ -1,12 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - - -{%- endblock card -%} diff --git a/peach-web/templates/settings/admin/reset_password.html.tera b/peach-web/templates/settings/admin/reset_password.html.tera deleted file mode 100644 index 7fb4bd9..0000000 --- a/peach-web/templates/settings/admin/reset_password.html.tera +++ /dev/null @@ -1,23 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - - - - - - - - -
-
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings/menu.html.tera b/peach-web/templates/settings/menu.html.tera deleted file mode 100644 index 4ac241e..0000000 --- a/peach-web/templates/settings/menu.html.tera +++ /dev/null @@ -1,14 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- -
- {% if standalone_mode == false %} - Network - {% endif %} - Scuttlebutt - Administration -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/network/add_ap.html.tera b/peach-web/templates/settings/network/add_ap.html.tera deleted file mode 100644 index 21aa1e1..0000000 --- a/peach-web/templates/settings/network/add_ap.html.tera +++ /dev/null @@ -1,20 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - -
- - Cancel -
-
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/network/ap_details.html.tera b/peach-web/templates/settings/network/ap_details.html.tera deleted file mode 100644 index b707042..0000000 --- a/peach-web/templates/settings/network/ap_details.html.tera +++ /dev/null @@ -1,75 +0,0 @@ -{%- extends "nav" -%} -{%- block card -%} - {%- if wlan_networks -%} - {%- for ssid, ap in wlan_networks -%} - {# select only the access point we are interested in #} - {%- if ssid == selected %} - -
- -
- - -
- WiFi icon - -
- - -
- -

{{ ssid }}

- -

{% if ap.detail %}{% if ap.detail.protocol != "" %}{{ ap.detail.protocol }}{% else %}None{% endif %}{% else %}Unknown{% endif %}

- -

{% if ap.signal %}{{ ap.signal }}%{% else %}Unknown{% endif %}

-
-
- -
-
- {%- if wlan_ssid == selected -%} -
- - - -
- {%- endif -%} - {%- if saved_aps -%} - {# Loop through the list of AP's with saved credentials #} - {%- for ap in saved_aps -%} - {# If the selected access point appears in the list, #} - {# display the Modify and Forget buttons. #} - {%- if ap.ssid == selected -%} - {# Set 'in_list' to true to allow correct Add button display #} - {% set_global in_list = true %} - {%- if wlan_ssid != selected and ap.state == "Available" -%} -
- - - -
- {%- endif -%} - Modify -
- - - -
- {%- endif -%} - {%- endfor -%} - {%- endif -%} - {%- if in_list == false -%} - {# Display the Add button if AP creds not already in saved networks list #} - Add - {%- endif -%} - Cancel -
- - {% include "snippets/flash_message" %} -
-
- {%- endif -%} - {%- endfor -%} - {%- endif -%} -{%- endblock card -%} diff --git a/peach-web/templates/settings/network/configure_dns.html.tera b/peach-web/templates/settings/network/configure_dns.html.tera deleted file mode 100644 index 40f1dad..0000000 --- a/peach-web/templates/settings/network/configure_dns.html.tera +++ /dev/null @@ -1,60 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- {% if enable_dyndns %} - -
-
- {% if is_dyndns_online %} - - {% else %} - - {% endif %} -
-
- {% endif %} -
-
- - -
-
-
- - - -
-
-
- - -
-
-
- -
- - - - {% if flash_msg and flash_name == "success" %} - -
{{ flash_msg }}.
- {%- elif flash_msg and flash_name == "info" %} - -
{{ flash_msg }}.
- {%- elif flash_msg and flash_name == "error" %} - -
{{ flash_msg }}.
- {%- endif -%} -
- -{%- endblock card -%} diff --git a/peach-web/templates/settings/network/data_usage_limits.html.tera b/peach-web/templates/settings/network/data_usage_limits.html.tera deleted file mode 100644 index b36baf2..0000000 --- a/peach-web/templates/settings/network/data_usage_limits.html.tera +++ /dev/null @@ -1,55 +0,0 @@ -{%- extends "nav" -%} -{%- block card -%} - {# ASSIGN VARIABLES #} - {# ---------------- #} - {%- if data_total -%} - {% set data_usage_total = data_total.total / 1024 / 1024 | round -%} - {%- else -%} - {% set data_usage_total = "x" -%} - {% endif -%} - -
-
-
- - -
- -
-
-
- Warning -
-
- - - -
-
- - -
-
- Cutoff -
-
- - - -
-
- - -
-
-
- - Reset - Cancel -
- - {% include "snippets/flash_message" %} -
-{%- endblock card %} diff --git a/peach-web/templates/settings/network/list_aps.html.tera b/peach-web/templates/settings/network/list_aps.html.tera deleted file mode 100644 index 852af77..0000000 --- a/peach-web/templates/settings/network/list_aps.html.tera +++ /dev/null @@ -1,38 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} -
-
- -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/network/menu.html.tera b/peach-web/templates/settings/network/menu.html.tera deleted file mode 100644 index 8c3b9d0..0000000 --- a/peach-web/templates/settings/network/menu.html.tera +++ /dev/null @@ -1,23 +0,0 @@ -{%- extends "nav" -%} - -{%- block card %} - -
- -
- Add WiFi Network - Configure DNS - - {%- if ap_state == "up" %} - Enable WiFi - {%- else %} - Deploy Access Point - {%- endif -%} - List WiFi Networks - View Data Usage - View Network Status -
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings/network/modify_ap.html.tera b/peach-web/templates/settings/network/modify_ap.html.tera deleted file mode 100644 index f945283..0000000 --- a/peach-web/templates/settings/network/modify_ap.html.tera +++ /dev/null @@ -1,20 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
-
- - - - -
- - Cancel -
-
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/settings/scuttlebutt/configure_sbot.html.tera b/peach-web/templates/settings/scuttlebutt/configure_sbot.html.tera deleted file mode 100644 index 3b74880..0000000 --- a/peach-web/templates/settings/scuttlebutt/configure_sbot.html.tera +++ /dev/null @@ -1,87 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - {# ASSIGN VARIABLES #} - {# ---------------- #} - {%- if sbot_config.hops -%} - {% set hops = sbot_config.hops -%} - {%- else -%} - {% set hops = "X" -%} - {%- endif -%} - {%- if sbot_config.lis -%} - {%- set listen_addr = sbot_config.lis | split(pat=":") -%} - {%- else -%} - {%- set listen_addr = ["", ""] -%} - {%- endif -%} - -
-
-
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- -
- -
- -
- - -
- - - - - - - - - - - - Restore Defaults -
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/settings/scuttlebutt/menu.html.tera b/peach-web/templates/settings/scuttlebutt/menu.html.tera deleted file mode 100644 index 93fb2d3..0000000 --- a/peach-web/templates/settings/scuttlebutt/menu.html.tera +++ /dev/null @@ -1,18 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
- -
- Configure Sbot - {% if sbot_status.state == "active" %} - Stop Sbot - Restart Sbot - {% else %} - Start Sbot - {% endif %} -
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/snippets/flash_message.html.tera b/peach-web/templates/snippets/flash_message.html.tera deleted file mode 100644 index ca56bfd..0000000 --- a/peach-web/templates/snippets/flash_message.html.tera +++ /dev/null @@ -1,14 +0,0 @@ - -{% if flash_msg and flash_name == "success" %} - -
{{ flash_msg }}.
-{%- elif flash_msg and flash_name == "info" %} - -
{{ flash_msg }}.
-{%- elif flash_msg and flash_name == "warning" %} - -
{{ flash_msg }}.
-{%- elif flash_msg and flash_name == "error" %} - -
{{ flash_msg }}.
-{%- endif -%} diff --git a/peach-web/templates/status/device.html.tera b/peach-web/templates/status/device.html.tera deleted file mode 100644 index ead9e93..0000000 --- a/peach-web/templates/status/device.html.tera +++ /dev/null @@ -1,137 +0,0 @@ -{%- extends "nav" -%} -{%- block card -%} - {# ASSIGN VARIABLES #} - {# ---------------- #} - {%- if mem_stats -%} - {% set mem_usage_percent = mem_stats.used / mem_stats.total * 100 | round -%} - {% set mem_used = mem_stats.used / 1024 | round -%} - {% set mem_free = mem_stats.free / 1024 | round -%} - {% set mem_total = mem_stats.total / 1024 | round -%} - {% endif -%} - {% if cpu_stat_percent -%} - {% set cpu_usage_percent = cpu_stat_percent.nice + cpu_stat_percent.system + cpu_stat_percent.user | round -%} - {%- endif -%} - {%- if disk_stats -%} - {%- for disk in disk_stats -%} - {%- set_global disk_usage_percent = disk.used_percentage -%} - {# Calculate free disk space in megabytes #} - {%- set_global disk_free = disk.one_k_blocks_free / 1024 | round -%} - {%- endfor -%} - {%- endif -%} - -
-
-
- {# Display microservice status for network, oled & stats #} - - -
- Network -
- - -
-
-
- -
- Display -
- - -
-
- -
- Stats -
- - -
-
- {# Display status for dynsdns, config & sbot #} - -
- Dyndns -
- - -
-
- -
- Config -
- - -
-
- - -
- Sbot -
- - -
-
-
- -
- {# Display CPU usage meter #} - {%- if cpu_stat_percent -%} -
- CPU - {{ cpu_usage_percent }}% -
- -
- CPU Usage -
-
- {%- else -%} -

CPU usage data unavailable

- {% endif -%} - {# Display memory usage meter #} - {%- if mem_stats %} -
- Memory - {{ mem_usage_percent }}% ({{ mem_free }} MB free) -
- -
- Memory Usage -
-
- {%- else -%} -

Memory usage data unavailable

- {% endif -%} - {# Display disk usage meter #} - {%- if disk_stats %} -
- Disk - {{ disk_usage_percent }}% ({% if disk_free > 1024 %}{{ disk_free / 1024 | round }} GB{% else %}{{ disk_free }} MB{% endif %} free) -
- -
- Disk Usage -
-
- {%- else -%} -

Disk usage data unavailable

- {%- endif %} - {# Display system uptime in minutes #} - {%- if uptime and uptime < 60 %} -

Uptime: {{ uptime }} minutes

- {# Display system uptime in hours & minutes #} - {%- elif uptime and uptime > 60 -%} -

Uptime: {{ uptime / 60 | round(method="floor") }} hours, {{ uptime % 60 }} minutes

- {%- else -%} -

Uptime data unavailable

- {%- endif %} -
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card %} diff --git a/peach-web/templates/status/network.html.tera b/peach-web/templates/status/network.html.tera deleted file mode 100644 index 432a777..0000000 --- a/peach-web/templates/status/network.html.tera +++ /dev/null @@ -1,158 +0,0 @@ -{%- extends "nav" -%} - -{%- block card %} - - {%- if ap_state == "up" %} - -
- -
- -
- - - Configure - - - -
- WiFi router - -
- - -
- -

Access Point

- -

peach

- -

{{ ap_ip }}

-
-
- -
- -
-
- Digital devices -
- -
- -
-
- Download -
- {%- if ap_traffic -%} - - - {%- else -%} - - - {%- endif -%} -
- -
-
- Upload -
- {%- if ap_traffic -%} - - - {%- else -%} - - - {%- endif -%} -
- -
-
-
-
- {%- else %} - -
- - {%- if wlan_state == "up" %} -
- -
- - Configure - - - - -
- WiFi online - - {%- else %} -
-
- - Configure - -
- WiFi offline - - {%- endif %} -
-
- - - -

WiFi Client

- -

{{ wlan_ssid }}

- -

{{ wlan_ip }}

-
-
- -
- - -
-
- Signal -
- -
- -
-
- Download -
- {%- if wlan_traffic %} - - - - {%- else %} - - - - {%- endif %} -
- -
-
- Upload -
- {%- if wlan_traffic %} - - - - {%- else %} - - - - {%- endif %} -
- -
-
-
-
- {%- endif -%} -{%- endblock card -%} diff --git a/peach-web/templates/status/scuttlebutt.html.tera b/peach-web/templates/status/scuttlebutt.html.tera deleted file mode 100644 index 7e1e4d5..0000000 --- a/peach-web/templates/status/scuttlebutt.html.tera +++ /dev/null @@ -1,95 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - {# ASSIGN VARIABLES #} - {# ---------------- #} - {%- if sbot_status.memory -%} - {% set mem = sbot_status.memory / 1024 / 1024 | round | int -%} - {%- else -%} - {% set mem = "0" -%} - {%- endif -%} - {%- if sbot_status.blobstore -%} - {% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%} - {%- else -%} - {% set blobs = "0" -%} - {%- endif -%} - -
- -
- -
- - - Configure - - - -
- Hermies - -
- - -
- -

1.1.0-alpha

- {% if sbot_status.state == "active" %} - -

{{ sbot_status.uptime }}

- {# render downtime element if downtime is `Some(time)` #} - {# downtime will be `None` if service is stopped and disabled #} - {%- elif sbot_status.downtime -%} - -

{{ sbot_status.downtime }}

- {%- endif -%} - - {% if sbot_status.boot_state == "enabled" %} -

Enabled

- {% else %} -

Disabled

- {%- endif -%} -
-
-
-
-
-
- {% if sbot_status.state == "active" %} - - - {% else %} - - {% endif %} -
-
-
-
- -
-
- -
- -
- -
-
- -
- - -
- -
-
- -
- - -
- -
-
-
-
-{%- endblock card -%} -- 2.40.1 From 07147f8a4fcfa9c037c1bc06e7b53fc10fcf72d4 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:13:25 +0200 Subject: [PATCH 53/66] remove rocket config, context and tests --- peach-web/Rocket.toml | 11 - peach-web/config | 2 - peach-web/rouille_refactor | 76 ---- peach-web/src/context/dns.rs | 38 -- peach-web/src/context/mod.rs | 3 - peach-web/src/context/network.rs | 394 ------------------ peach-web/src/context/scuttlebutt.rs | 570 --------------------------- peach-web/src/tests.rs | 562 -------------------------- 8 files changed, 1656 deletions(-) delete mode 100644 peach-web/Rocket.toml delete mode 100644 peach-web/config delete mode 100644 peach-web/rouille_refactor delete mode 100644 peach-web/src/context/dns.rs delete mode 100644 peach-web/src/context/mod.rs delete mode 100644 peach-web/src/context/network.rs delete mode 100644 peach-web/src/context/scuttlebutt.rs delete mode 100644 peach-web/src/tests.rs diff --git a/peach-web/Rocket.toml b/peach-web/Rocket.toml deleted file mode 100644 index c1f285e..0000000 --- a/peach-web/Rocket.toml +++ /dev/null @@ -1,11 +0,0 @@ -[default] -secret_key = "VYVUDivXvu8g6llxeJd9F92pMfocml5xl/Jjv5Sk4yw=" -disable_auth = false -standalone_mode = true - -[debug] -template_dir = "templates/" -disable_auth = true - -[release] -template_dir = "templates/" diff --git a/peach-web/config b/peach-web/config deleted file mode 100644 index aa7ad74..0000000 --- a/peach-web/config +++ /dev/null @@ -1,2 +0,0 @@ -disable_auth=false -standalone_mode=true diff --git a/peach-web/rouille_refactor b/peach-web/rouille_refactor deleted file mode 100644 index f8ad8d7..0000000 --- a/peach-web/rouille_refactor +++ /dev/null @@ -1,76 +0,0 @@ - - -go slow and steady. - -optimise for few dependencies and short compilation times. - -we do not need to be super fast or feature-rich. - -[ architecture ] - - - use the one-file-per-route patten - -[ rouille-specific ] - - - logging - - https://docs.rs/rouille/latest/rouille/fn.log_custom.html - x flash message - - https://docs.rs/rouille/latest/rouille/input/fn.cookies.html - - https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#creating_cookies - - https://docs.rs/rouille/latest/rouille/struct.Response.html#method.with_additional_header - - https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie - - file upload - - https://docs.rs/rouille/latest/rouille/input/post/index.html#handling-file-uploads - - auth - - https://github.com/tomaka/rouille/blob/master/examples/login-session.rs - - https://docs.rs/rouille/latest/rouille/struct.Response.html#method.basic_http_auth_login_required - - -[ tasks ] - - - write the settings route(s) - - scuttlebutt - - peers - x menu - - peers list - - invites - - profile - - private - x menu - x guide - x status - x scuttlebutt - x scuttlebutt menu - - configure_sbot - x template - x sbot_config data - - might need some thought...render elements or input data - - admin - x menu - x configure - x add - x delete - - auth - x change password - x form - x post - x reset password - x form - x post - x login - x form - x post - x logout - x get - -[ flash messages ] - - - for now, use simple redirects in the handlers - - then add flash messages later - - - write getter, setter and unsetter for flash messages - - from rocket docs - - A “removal” cookie is a cookie that has the same name as the original cookie but has an empty value, a max-age of 0, and an expiration date far in the past. - - use Response::with_additional_header() method to set cookie - - https://docs.rs/rouille/latest/rouille/struct.Response.html#method.with_additional_header - - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie diff --git a/peach-web/src/context/dns.rs b/peach-web/src/context/dns.rs deleted file mode 100644 index 7a86646..0000000 --- a/peach-web/src/context/dns.rs +++ /dev/null @@ -1,38 +0,0 @@ -use peach_lib::{config_manager, dyndns_client}; -use rocket::serde::Serialize; - -#[derive(Debug, Serialize)] -pub struct ConfigureDNSContext { - pub external_domain: String, - pub dyndns_subdomain: String, - pub enable_dyndns: bool, - pub is_dyndns_online: bool, - pub back: Option, - pub title: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub theme: Option, -} - -impl ConfigureDNSContext { - pub fn build() -> ConfigureDNSContext { - // TODO: replace `unwrap` with resilient error handling - let peach_config = config_manager::load_peach_config().unwrap(); - let dyndns_fulldomain = peach_config.dyn_domain; - let is_dyndns_online = dyndns_client::is_dns_updater_online().unwrap(); - let dyndns_subdomain = - dyndns_client::get_dyndns_subdomain(&dyndns_fulldomain).unwrap_or(dyndns_fulldomain); - - ConfigureDNSContext { - external_domain: peach_config.external_domain, - dyndns_subdomain, - enable_dyndns: peach_config.dyn_enabled, - is_dyndns_online, - back: None, - title: None, - flash_name: None, - flash_msg: None, - theme: None, - } - } -} diff --git a/peach-web/src/context/mod.rs b/peach-web/src/context/mod.rs deleted file mode 100644 index 65581c1..0000000 --- a/peach-web/src/context/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//pub mod dns; -//pub mod network; -//pub mod scuttlebutt; diff --git a/peach-web/src/context/network.rs b/peach-web/src/context/network.rs deleted file mode 100644 index 9e3b1b4..0000000 --- a/peach-web/src/context/network.rs +++ /dev/null @@ -1,394 +0,0 @@ -//! Data retrieval for the purpose of serving routes and hydrating -//! network-related HTML templates. - -use std::collections::HashMap; - -use rocket::{form::FromForm, serde::Serialize, UriDisplayQuery}; - -use peach_network::{ - network, - network::{Scan, Status, Traffic}, -}; - -use crate::{ - utils::{ - monitor, - monitor::{Alert, Data, Threshold}, - }, - AP_IFACE, WLAN_IFACE, -}; - -#[derive(Debug, Serialize)] -pub struct AccessPoint { - pub detail: Option, - pub signal: Option, - pub state: String, -} - -pub fn ap_state() -> String { - match network::state(&*AP_IFACE) { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - } -} - -#[derive(Debug, FromForm, UriDisplayQuery)] -pub struct Ssid { - pub ssid: String, -} - -#[derive(Debug, FromForm)] -pub struct WiFi { - pub ssid: String, - pub pass: String, -} - -fn convert_traffic(traffic: Traffic) -> Option { - // modify traffic values & assign measurement unit - // based on received and transmitted values - let (rx, rx_unit) = if traffic.received > 1_047_527_424 { - // convert to GB - (traffic.received / 1_073_741_824, "GB".to_string()) - } else if traffic.received > 0 { - // otherwise, convert it to MB - ((traffic.received / 1024) / 1024, "MB".to_string()) - } else { - (0, "MB".to_string()) - }; - - let (tx, tx_unit) = if traffic.transmitted > 1_047_527_424 { - // convert to GB - (traffic.transmitted / 1_073_741_824, "GB".to_string()) - } else if traffic.transmitted > 0 { - ((traffic.transmitted / 1024) / 1024, "MB".to_string()) - } else { - (0, "MB".to_string()) - }; - - Some(IfaceTraffic { - rx, - rx_unit, - tx, - tx_unit, - }) -} - -#[derive(Debug, Serialize)] -pub struct IfaceTraffic { - pub rx: u64, - pub rx_unit: String, - pub tx: u64, - pub tx_unit: String, -} - -#[derive(Debug, Serialize)] -pub struct NetworkAlertContext { - pub alert: Alert, - pub back: Option, - pub data_total: Option, // combined stored and current wifi traffic in bytes - pub flash_name: Option, - pub flash_msg: Option, - pub threshold: Threshold, - pub title: Option, - pub traffic: Option, // current wifi traffic in bytes (since boot) -} - -impl NetworkAlertContext { - pub fn build() -> NetworkAlertContext { - let alert = monitor::get_alerts().unwrap(); - // stored wifi data values as bytes - let stored_traffic = monitor::get_data().unwrap(); - let threshold = monitor::get_thresholds().unwrap(); - - let (traffic, data_total) = match network::traffic(&*WLAN_IFACE) { - // convert bytes to mb or gb and add appropriate units - Ok(Some(t)) => { - let current_traffic = t.received + t.transmitted; - let traffic = convert_traffic(t); - let total = stored_traffic.total + current_traffic; - let data_total = Data { total }; - (traffic, Some(data_total)) - } - _ => (None, None), - }; - - NetworkAlertContext { - alert, - back: None, - data_total, - flash_name: None, - flash_msg: None, - threshold, - title: None, - traffic, - } - } -} - -#[derive(Debug, Serialize)] -pub struct NetworkDetailContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub selected: Option, - pub title: Option, - pub saved_aps: Vec, - pub wlan_ip: String, - pub wlan_networks: HashMap, - pub wlan_rssi: Option, - pub wlan_ssid: String, - pub wlan_state: String, - pub wlan_status: Option, - pub wlan_traffic: Option, -} - -impl NetworkDetailContext { - pub fn build() -> NetworkDetailContext { - let wlan_ip = match network::ip(&*WLAN_IFACE) { - Ok(Some(ip)) => ip, - _ => "x.x.x.x".to_string(), - }; - - // list of networks saved in wpa_supplicant.conf - let wlan_list = match network::saved_networks() { - Ok(Some(ssids)) => ssids, - _ => Vec::new(), - }; - - // list of networks saved in wpa_supplicant.conf - let saved_aps = wlan_list.clone(); - - let wlan_rssi = match network::rssi_percent(&*WLAN_IFACE) { - Ok(rssi) => rssi, - Err(_) => None, - }; - - // list of networks currently in range (online & accessible) - let wlan_scan = match network::available_networks(&*WLAN_IFACE) { - Ok(Some(networks)) => networks, - _ => Vec::new(), - }; - - let wlan_ssid = match network::ssid(&*WLAN_IFACE) { - Ok(Some(ssid)) => ssid, - _ => "Not connected".to_string(), - }; - - let wlan_state = match network::state(&*WLAN_IFACE) { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - }; - - let wlan_status = match network::status(&*WLAN_IFACE) { - Ok(status) => status, - // interface unavailable - _ => None, - }; - - let wlan_traffic = match network::traffic(&*WLAN_IFACE) { - // convert bytes to mb or gb and add appropriate units - Ok(Some(traffic)) => convert_traffic(traffic), - _ => None, - }; - - // create a hashmap to combine wlan_list & wlan_scan without repetition - let mut wlan_networks = HashMap::new(); - - for ap in wlan_scan { - let ssid = ap.ssid.clone(); - let rssi = ap.signal_level.clone(); - // parse the string to a signed integer (for math) - let rssi_parsed = rssi.parse::().unwrap(); - // perform rssi (dBm) to quality (%) conversion - let quality_percent = 2 * (rssi_parsed + 100); - let ap_detail = AccessPoint { - detail: Some(ap), - state: "Available".to_string(), - signal: Some(quality_percent), - }; - wlan_networks.insert(ssid, ap_detail); - } - - for network in wlan_list { - // avoid repetition by checking that ssid is not already in list - if !wlan_networks.contains_key(&network) { - let ssid = network.clone(); - let net_detail = AccessPoint { - detail: None, - state: "Not in range".to_string(), - signal: None, - }; - wlan_networks.insert(ssid, net_detail); - } - } - - NetworkDetailContext { - back: None, - flash_name: None, - flash_msg: None, - selected: None, - title: None, - saved_aps, - wlan_ip, - wlan_networks, - wlan_rssi, - wlan_ssid, - wlan_state, - wlan_status, - wlan_traffic, - } - } -} - -#[derive(Debug, Serialize)] -pub struct NetworkListContext { - pub ap_state: String, - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub wlan_networks: HashMap, - pub wlan_ssid: String, -} - -impl NetworkListContext { - pub fn build() -> NetworkListContext { - // list of networks saved in wpa_supplicant.conf - let wlan_list = match network::saved_networks() { - Ok(Some(ssids)) => ssids, - _ => Vec::new(), - }; - - // list of networks currently in range (online & accessible) - let wlan_scan = match network::available_networks(&*WLAN_IFACE) { - Ok(Some(networks)) => networks, - _ => Vec::new(), - }; - - let wlan_ssid = match network::ssid(&*WLAN_IFACE) { - Ok(Some(ssid)) => ssid, - _ => "Not connected".to_string(), - }; - - // create a hashmap to combine wlan_list & wlan_scan without repetition - let mut wlan_networks = HashMap::new(); - for ap in wlan_scan { - wlan_networks.insert(ap.ssid, "Available".to_string()); - } - for network in wlan_list { - // insert ssid (with state) only if it doesn't already exist - wlan_networks - .entry(network) - .or_insert_with(|| "Not in range".to_string()); - } - - let ap_state = match network::state(&*AP_IFACE) { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - }; - - NetworkListContext { - ap_state, - back: None, - flash_msg: None, - flash_name: None, - title: None, - wlan_networks, - wlan_ssid, - } - } -} - -#[derive(Debug, Serialize)] -pub struct NetworkStatusContext { - pub ap_ip: String, - pub ap_ssid: String, - pub ap_state: String, - pub ap_traffic: Option, - pub wlan_ip: String, - pub wlan_rssi: Option, - pub wlan_ssid: String, - pub wlan_state: String, - pub wlan_status: Option, - pub wlan_traffic: Option, - pub flash_name: Option, - pub flash_msg: Option, - // passing in the ssid of a chosen access point - pub selected: Option, - pub title: Option, - pub back: Option, -} - -impl NetworkStatusContext { - pub fn build() -> Self { - let ap_ip = match network::ip(&*AP_IFACE) { - Ok(Some(ip)) => ip, - _ => "x.x.x.x".to_string(), - }; - - let ap_ssid = match network::ssid(&*AP_IFACE) { - Ok(Some(ssid)) => ssid, - _ => "Not currently activated".to_string(), - }; - - let ap_state = match network::state(&*AP_IFACE) { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - }; - - let ap_traffic = match network::traffic(&*AP_IFACE) { - // convert bytes to mb or gb and add appropriate units - Ok(Some(traffic)) => convert_traffic(traffic), - _ => None, - }; - - let wlan_ip = match network::ip(&*WLAN_IFACE) { - Ok(Some(ip)) => ip, - _ => "x.x.x.x".to_string(), - }; - - let wlan_rssi = match network::rssi_percent(&*WLAN_IFACE) { - Ok(rssi) => rssi, - _ => None, - }; - - let wlan_ssid = match network::ssid(&*WLAN_IFACE) { - Ok(Some(ssid)) => ssid, - _ => "Not connected".to_string(), - }; - - let wlan_state = match network::state(&*WLAN_IFACE) { - Ok(Some(state)) => state, - _ => "Interface unavailable".to_string(), - }; - - let wlan_status = match network::status(&*WLAN_IFACE) { - Ok(status) => status, - _ => None, - }; - - let wlan_traffic = match network::traffic(&*WLAN_IFACE) { - // convert bytes to mb or gb and add appropriate units - Ok(Some(traffic)) => convert_traffic(traffic), - _ => None, - }; - - NetworkStatusContext { - ap_ip, - ap_ssid, - ap_state, - ap_traffic, - wlan_ip, - wlan_rssi, - wlan_ssid, - wlan_state, - wlan_status, - wlan_traffic, - flash_name: None, - flash_msg: None, - selected: None, - title: None, - back: None, - } - } -} diff --git a/peach-web/src/context/scuttlebutt.rs b/peach-web/src/context/scuttlebutt.rs deleted file mode 100644 index 9f5ac77..0000000 --- a/peach-web/src/context/scuttlebutt.rs +++ /dev/null @@ -1,570 +0,0 @@ -use std::collections::HashMap; - -use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; -use peach_lib::sbot::{SbotConfig, SbotStatus}; -use rocket::{futures::TryStreamExt, serde::Serialize}; - -use crate::{error::PeachWebError, utils}; - -// HELPER FUNCTIONS - -pub async fn init_sbot_with_config( - sbot_config: &Option, -) -> Result { - // initialise sbot connection with ip:port and shscap from config file - let sbot_client = match sbot_config { - // TODO: panics if we pass `Some(conf.shscap)` as second arg - Some(conf) => { - let ip_port = conf.lis.clone(); - Sbot::init(Some(ip_port), None).await? - } - None => Sbot::init(None, None).await?, - }; - - Ok(sbot_client) -} - -// CONTEXT STRUCTS AND BUILDERS - -#[derive(Debug, Serialize)] -pub struct StatusContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - // latest sequence number for the local log - pub latest_seq: Option, -} - -impl StatusContext { - pub fn default() -> Self { - StatusContext { - back: Some("/".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Scuttlebutt Status".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - latest_seq: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - // retrieve the local id - let id = sbot_client.whoami().await?; - - let history_stream = sbot_client.create_history_stream(id).await?; - let mut msgs: Vec = history_stream.try_collect().await?; - - // reverse the list of messages so we can easily reference the latest one - msgs.reverse(); - - // assign the sequence number of the latest msg - context.latest_seq = Some(msgs[0].sequence); - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, status data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_config = sbot_config; - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -// peers who are blocked by the local account -#[derive(Debug, Serialize)] -pub struct BlocksContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - pub peers: Option>>, -} - -impl BlocksContext { - pub fn default() -> Self { - BlocksContext { - back: Some("/scuttlebutt/peers".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Blocks".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - peers: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let blocks = sbot_client.get_blocks().await?; - - // we'll use this to store the profile info for each peer who follows us - let mut peer_info = Vec::new(); - - if !blocks.is_empty() { - for peer in blocks.iter() { - // trim whitespace (including newline characters) and - // remove the inverted-commas around the id - let key = peer.trim().replace('"', ""); - // retrieve the profile info for the given peer - let mut info = sbot_client.get_profile_info(&key).await?; - // insert the public key of the peer into the info hashmap - info.insert("id".to_string(), key.to_string()); - // we do not even attempt to find the blob for a blocked peer, - // since it may be vulgar to cause distress to the local peer. - info.insert("blob_exists".to_string(), "false".to_string()); - // push profile info to peer_list vec - peer_info.push(info) - } - - context.peers = Some(peer_info) - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -// peers who are followed by the local account -#[derive(Debug, Serialize)] -pub struct FollowsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - pub peers: Option>>, -} - -impl FollowsContext { - pub fn default() -> Self { - FollowsContext { - back: Some("/scuttlebutt/peers".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Follows".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - peers: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let follows = sbot_client.get_follows().await?; - - // we'll use this to store the profile info for each peer who follows us - let mut peer_info = Vec::new(); - - if !follows.is_empty() { - for peer in follows.iter() { - // trim whitespace (including newline characters) and - // remove the inverted-commas around the id - let key = peer.trim().replace('"', ""); - // retrieve the profile info for the given peer - let mut info = sbot_client.get_profile_info(&key).await?; - // insert the public key of the peer into the info hashmap - info.insert("id".to_string(), key.to_string()); - // retrieve the profile image blob id for the given peer - if let Some(blob_id) = info.get("image") { - // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { - // insert the image blob path of the peer into the info hashmap - info.insert("blob_path".to_string(), blob_path.to_string()); - // check if the blob is in the blobstore - // set a flag in the info hashmap - match utils::blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { - info.insert("blob_exists".to_string(), "true".to_string()) - } - _ => info.insert("blob_exists".to_string(), "false".to_string()), - }; - } - } - // push profile info to peer_list vec - peer_info.push(info) - } - - context.peers = Some(peer_info) - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -// peers who follow and are followed by the local account (friends) -#[derive(Debug, Serialize)] -pub struct FriendsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - pub peers: Option>>, -} - -impl FriendsContext { - pub fn default() -> Self { - FriendsContext { - back: Some("/scuttlebutt/peers".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Friends".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - peers: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let local_id = sbot_client.whoami().await?; - - let follows = sbot_client.get_follows().await?; - - // we'll use this to store the profile info for each peer who follows us - let mut peer_info = Vec::new(); - - if !follows.is_empty() { - for peer in follows.iter() { - // trim whitespace (including newline characters) and - // remove the inverted-commas around the id - let peer_id = peer.trim().replace('"', ""); - // retrieve the profile info for the given peer - let mut info = sbot_client.get_profile_info(&peer_id).await?; - // insert the public key of the peer into the info hashmap - info.insert("id".to_string(), peer_id.to_string()); - // retrieve the profile image blob id for the given peer - if let Some(blob_id) = info.get("image") { - // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { - // insert the image blob path of the peer into the info hashmap - info.insert("blob_path".to_string(), blob_path.to_string()); - // check if the blob is in the blobstore - // set a flag in the info hashmap - match utils::blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { - info.insert("blob_exists".to_string(), "true".to_string()) - } - _ => info.insert("blob_exists".to_string(), "false".to_string()), - }; - } - } - - // check if the peer follows us (making us friends) - let follow_query = RelationshipQuery { - source: peer_id.to_string(), - dest: local_id.clone(), - }; - - // query follow state - match sbot_client.friends_is_following(follow_query).await { - Ok(following) if following == "true" => { - // only push profile info to peer_list vec if they follow us - peer_info.push(info) - } - _ => (), - }; - } - - context.peers = Some(peer_info) - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -#[derive(Debug, Serialize)] -pub struct ProfileContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - // is this the local profile or the profile of a peer? - pub is_local_profile: bool, - // an ssb_id which may or may not be the local public key - pub id: Option, - pub name: Option, - pub description: Option, - pub image: Option, - // the path to the blob defined in the `image` field (aka the profile picture) - pub blob_path: Option, - // whether or not the blob exists in the blobstore (ie. is saved on disk) - pub blob_exists: bool, - // relationship state (if the profile being viewed is not for the local public key) - pub following: Option, - pub blocking: Option, -} - -impl ProfileContext { - pub fn default() -> Self { - ProfileContext { - back: Some("/".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Profile".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - is_local_profile: true, - id: None, - name: None, - description: None, - image: None, - blob_path: None, - blob_exists: false, - following: None, - blocking: None, - } - } - - pub async fn build(ssb_id: Option) -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let local_id = sbot_client.whoami().await?; - - // if an ssb_id has been provided to the context builder, we assume that - // the profile info being retrieved is for a peer (ie. not for our local - // profile) - let id = if ssb_id.is_some() { - // we are not dealing with the local profile - context.is_local_profile = false; - - // we're safe to unwrap here because we know it's `Some(id)` - let peer_id = ssb_id.unwrap(); - - // determine relationship between peer and local id - let follow_query = RelationshipQuery { - source: local_id.clone(), - dest: peer_id.clone(), - }; - - // query follow state - context.following = match sbot_client.friends_is_following(follow_query).await { - Ok(following) if following == "true" => Some(true), - Ok(following) if following == "false" => Some(false), - _ => None, - }; - - // TODO: i don't like that we have to instantiate the same query object - // twice. see if we can streamline this in golgi - let block_query = RelationshipQuery { - source: local_id.clone(), - dest: peer_id.clone(), - }; - - // query block state - context.blocking = match sbot_client.friends_is_blocking(block_query).await { - Ok(blocking) if blocking == "true" => Some(true), - Ok(blocking) if blocking == "false" => Some(false), - _ => None, - }; - - peer_id - } else { - // if an ssb_id has not been provided, retrieve the local id using whoami - context.is_local_profile = true; - - local_id - }; - - // retrieve the profile info for the given id - let info = sbot_client.get_profile_info(&id).await?; - // set each context field accordingly - for (key, val) in info { - match key.as_str() { - "name" => context.name = Some(val), - "description" => context.description = Some(val), - "image" => context.image = Some(val), - _ => (), - } - } - - // assign the ssb public key to the context - // (could be for the local profile or a peer) - context.id = Some(id); - - // determine the path to the blob defined by the value of `context.image` - if let Some(ref blob_id) = context.image { - context.blob_path = match blobs::get_blob_path(&blob_id) { - Ok(path) => { - // if we get the path, check if the blob is in the blobstore. - // this allows us to default to a placeholder image in the template - if let Ok(exists) = utils::blob_is_stored_locally(&path).await { - context.blob_exists = exists - }; - - Some(path) - } - Err(_) => None, - } - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -#[derive(Debug, Serialize)] -pub struct PrivateContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - // local peer id (whoami) - pub id: Option, - // id of the peer being messaged - pub recipient_id: Option, -} - -impl PrivateContext { - pub fn default() -> Self { - PrivateContext { - back: Some("/".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Private Messages".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - id: None, - recipient_id: None, - } - } - - pub async fn build(recipient_id: Option) -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - context.recipient_id = recipient_id; - - let local_id = sbot_client.whoami().await?; - context.id = Some(local_id); - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} diff --git a/peach-web/src/tests.rs b/peach-web/src/tests.rs deleted file mode 100644 index 3e6f7c6..0000000 --- a/peach-web/src/tests.rs +++ /dev/null @@ -1,562 +0,0 @@ -use std::fs::File; -use std::io::Read; - -use rocket::http::{ContentType, Status}; -use rocket::local::blocking::Client; -use rocket::{Build, Config, Rocket}; - -use super::init_rocket; - -// define authentication mode -const DISABLE_AUTH: bool = true; - -/// Wrapper around `init_rocket()` to simplify the process of invoking the application with the desired authentication status. This is particularly useful for testing purposes. -fn init_test_rocket(disable_auth: bool) -> Rocket { - // set authentication based on provided `disable_auth` value - Config::figment().merge(("disable_auth", disable_auth)); - - init_rocket() -} - -// helper function to test correct retrieval and content of a file -fn test_query_file(path: &str, file: T, status: Status) -where - T: Into>, -{ - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).unwrap(); - let response = client.get(path).dispatch(); - assert_eq!(response.status(), status); - - let body_data = response.into_bytes(); - if let Some(filename) = file.into() { - let expected_data = read_file_content(filename); - assert!(body_data.map_or(false, |s| s == expected_data)); - } -} - -// helper function to return the content of a file, given a path -fn read_file_content(path: &str) -> Vec { - let mut fp = File::open(&path).expect(&format!("Can't open {}", path)); - let mut file_content = vec![]; - - fp.read_to_end(&mut file_content) - .expect(&format!("Reading {} failed.", path)); - file_content -} - -// WEB PAGE ROUTES - -#[test] -fn index_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("/peers")); - assert!(body.contains("/profile")); - assert!(body.contains("/private")); - assert!(body.contains("/status")); - assert!(body.contains("/help")); - assert!(body.contains("/settings")); -} - -#[test] -fn help_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/help").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Help")); -} - -#[test] -fn login_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/login").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Login")); -} - -#[test] -fn logout_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/logout").dispatch(); - // check for 303 status (redirect to "/login") - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.content_type(), None); -} - -#[test] -fn power_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/power").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Shutdown Device")); -} - -/* - -NOTE: these tests are comment-out for the moment, due to the fact that they invoke system commands (resulting in a `sudo` password request during test execution). see if we can find a way to test the results without triggering the shutdown or restart. - -#[test] -fn reboot() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/power/reboot").dispatch(); - // check for redirect - assert_eq!(response.status(), Status::SeeOther); -} - -#[test] -fn shutdown() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/power/shutdown").dispatch(); - // check for redirect - assert_eq!(response.status(), Status::SeeOther); -} -*/ - -// SCUTTLEBUTT ROUTES - -#[test] -fn block() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/scuttlebutt/block") - .header(ContentType::Form) - .body("key=HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") - .dispatch(); - assert_eq!(response.status(), Status::SeeOther); -} - -#[test] -fn blocks_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/blocks").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Blocks")); -} - -#[test] -fn follow() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/scuttlebutt/follow") - .header(ContentType::Form) - .body("key=@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") - .dispatch(); - // ensure we redirect (303) - assert_eq!(response.status(), Status::SeeOther); -} - -#[test] -fn follows_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/follows").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Follows")); -} - -#[test] -fn followers_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/followers").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Followers")); -} - -#[test] -fn friends_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/friends").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Friends")); -} - -#[test] -fn peers_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/peers").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Scuttlebutt Peers")); -} - -#[test] -fn private_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/private").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Private Messages")); -} - -#[test] -fn profile_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/scuttlebutt/profile").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Profile")); -} - -#[test] -fn publish_post() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/scuttlebutt/publish") - .header(ContentType::Form) - .body("text='golden ripples in the meshwork'") - .dispatch(); - assert_eq!(response.status(), Status::SeeOther); -} - -#[test] -fn unfollow() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/scuttlebutt/unfollow") - .header(ContentType::Form) - .body("key=@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") - .dispatch(); - assert_eq!(response.status(), Status::SeeOther); -} - -// ADMIN SETTINGS ROUTES - -#[test] -fn admin_settings_menu_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/admin").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Administrator Settings")); - assert!(body.contains("Change Password")); - assert!(body.contains("Configure Admin")); -} - -#[test] -fn add_admin_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/admin/add").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Add Admin")); - assert!(body.contains("SSB ID")); - assert!(body.contains("Add")); - assert!(body.contains("Cancel")); -} - -#[test] -fn add_admin() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/settings/admin/add") - .header(ContentType::Form) - .body("ssb_id=@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") - .dispatch(); - // check for redirect - assert_eq!(response.status(), Status::SeeOther); -} - -#[test] -fn change_password_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/admin/change_password").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Change Password")); - assert!(body.contains("Current password")); - assert!(body.contains("New password")); - assert!(body.contains("New password duplicate")); - assert!(body.contains("Save")); -} - -#[test] -fn configure_admin_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/admin/configure").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Configure Admin")); - assert!(body.contains("Current Admins")); - assert!(body.contains("Add Admin")); -} - -#[test] -fn forgot_password_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/admin/forgot_password").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Send Password Reset")); -} - -// NETWORK SETTINGS ROUTES - -#[test] -fn network_settings_menu_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Network Configuration")); -} - -/* - -NOTE: these tests are commented-out for the moment, due to the fact that they -invoke system commands (resulting in a `sudo` password request during -test execution). see if we can find a way to test the results without -triggering the `systemctl` call. - -#[test] -fn deploy_ap() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/ap/activate").dispatch(); - // check for 303 status (redirect) - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.content_type(), None); -} - -#[test] -fn deploy_client() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/wifi/activate").dispatch(); - // check for 303 status (redirect) - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.content_type(), None); -} -*/ - -#[test] -fn dns_settings_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/dns").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Configure DNS")); - assert!(body.contains("External Domain (optional)")); - assert!(body.contains("Enable Dynamic DNS")); - assert!(body.contains("Dynamic DNS Domain")); - assert!(body.contains("Save")); -} - -#[test] -fn list_aps_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/wifi").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("WiFi Networks")); - assert!(body.contains("No saved or available networks found.")); -} - -// TODO: needs further testing once template has been refactored -#[test] -fn ap_details_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/wifi?ssid=Home").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - //let body = response.into_string().unwrap(); - //assert!(body.contains("Network not found")); -} - -#[test] -fn add_ap_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/wifi/add").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Add WiFi Network")); - assert!(body.contains("SSID")); - assert!(body.contains("Password")); - assert!(body.contains("Add")); - assert!(body.contains("Cancel")); -} - -#[test] -fn add_ap_ssid_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .get("/settings/network/wifi/add?ssid=Home") - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Add WiFi Network")); - assert!(body.contains("Home")); - assert!(body.contains("Password")); - assert!(body.contains("Add")); - assert!(body.contains("Cancel")); -} - -#[test] -fn add_credentials() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/settings/network/wifi/add") - .header(ContentType::Form) - .body("ssid=Home&pass=Password") - .dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); -} - -#[test] -fn forget_wifi() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/settings/network/wifi/forget") - .header(ContentType::Form) - .body("ssid=Home") - .dispatch(); - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.content_type(), None); -} - -#[test] -fn modify_password() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client - .post("/settings/network/wifi/modify") - .header(ContentType::Form) - .body("ssid=Home&pass=Password") - .dispatch(); - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.content_type(), None); -} - -#[test] -fn data_usage_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/network/wifi/usage").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Network Data Usage")); - assert!(body.contains("WARNING THRESHOLD")); - assert!(body.contains("Update")); - assert!(body.contains("Cancel")); -} - -// SCUTTLEBUTT SETTINGS HTML ROUTES - -#[test] -fn scuttlebutt_settings_menu_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/settings/scuttlebutt").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Scuttlebutt Settings")); - assert!(body.contains("Configure Sbot")); -} - -// STATUS HTML ROUTES - -#[test] -fn status_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/status").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Device Status")); - assert!(body.contains("Networking")); - assert!(body.contains("Display")); - assert!(body.contains("Statistics")); -} - -#[test] -fn network_status_html() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).expect("valid rocket instance"); - let response = client.get("/status/network").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.content_type(), Some(ContentType::HTML)); - let body = response.into_string().unwrap(); - assert!(body.contains("Network Status")); - assert!(body.contains("Mode")); - assert!(body.contains("SSID")); - assert!(body.contains("IP")); - assert!(body.contains("DOWNLOAD")); - assert!(body.contains("UPLOAD")); -} -// FILE TESTS - -#[test] -fn nested_file() { - test_query_file( - "/images/placeholder.txt", - "static/images/placeholder.txt", - Status::Ok, - ); - test_query_file( - "/images/placeholder.txt?v=1", - "static/images/placeholder.txt", - Status::Ok, - ); - test_query_file( - "/images/placeholder.txt?v=1&a=b", - "static/images/placeholder.txt", - Status::Ok, - ); -} - -#[test] -fn icon_file() { - test_query_file( - "/icons/peach-icon.png", - "static/icons/peach-icon.png", - Status::Ok, - ); -} - -#[test] -fn invalid_path() { - test_query_file("/thou_shalt_not_exist", None, Status::NotFound); - test_query_file("/thou_shalt_not_exist", None, Status::NotFound); - test_query_file("/thou/shalt/not/exist?a=b&c=d", None, Status::NotFound); -} - -#[test] -fn invalid_get_request() { - let client = Client::tracked(init_test_rocket(DISABLE_AUTH)).unwrap(); - - // try to get a path that doesn't exist - let res = client - .get("/message/99") - .header(ContentType::JSON) - .dispatch(); - assert_eq!(res.status(), Status::NotFound); - - let body = res.into_string().unwrap(); - assert!(body.contains("404: Page Not Found")); - assert!(body.contains("No PeachCloud resource exists for this URL.")); -} -- 2.40.1 From d9019d6a4b9edf7fe1fecce0a9907ec6cfaebef5 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:19:39 +0200 Subject: [PATCH 54/66] update git ignore and remove outdated code --- peach-web/.gitignore | 5 +- peach-web/src/routes/authentication.rs_old | 333 ------------------ peach-web/src/routes/catchers.rs | 60 ---- peach-web/src/routes/index.rs | 51 --- peach-web/src/routes/settings/admin.rs_old | 124 ------- .../src/routes/status/scuttlebutt.rs_old | 37 -- peach-web/src/utils/monitor.rs | 198 ----------- 7 files changed, 1 insertion(+), 807 deletions(-) delete mode 100644 peach-web/src/routes/authentication.rs_old delete mode 100644 peach-web/src/routes/catchers.rs delete mode 100644 peach-web/src/routes/index.rs delete mode 100644 peach-web/src/routes/settings/admin.rs_old delete mode 100644 peach-web/src/routes/status/scuttlebutt.rs_old delete mode 100644 peach-web/src/utils/monitor.rs diff --git a/peach-web/.gitignore b/peach-web/.gitignore index 8f0bd8f..c3e9867 100644 --- a/peach-web/.gitignore +++ b/peach-web/.gitignore @@ -1,8 +1,5 @@ *.bak static/icons/optimized/* -api_docs.md -js_docs.md -hashmap_notes -notes target **/*.rs.bk +leftovers diff --git a/peach-web/src/routes/authentication.rs_old b/peach-web/src/routes/authentication.rs_old deleted file mode 100644 index 3ff8f2b..0000000 --- a/peach-web/src/routes/authentication.rs_old +++ /dev/null @@ -1,333 +0,0 @@ -use log::info; -use rocket::{ - form::{Form, FromForm}, - get, - http::{Cookie, CookieJar, Status}, - post, - request::{self, FlashMessage, FromRequest, Request}, - response::{Flash, Redirect}, -}; -use rocket_dyn_templates::{tera::Context, Template}; - -use peach_lib::{error::PeachError, password_utils}; - -use crate::error::PeachWebError; -use crate::utils; -use crate::utils::TemplateOrRedirect; -use crate::RocketConfig; - -// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES - -pub const AUTH_COOKIE_KEY: &str = "peachweb_auth"; -pub const ADMIN_USERNAME: &str = "admin"; - -/// Note: Currently we use an empty struct for the Authenticated request guard -/// because there is only one user to be authenticated, and no data needs to be stored here. -/// In a multi-user authentication scheme, we would store the user_id in this struct, -/// and retrieve the correct user via the user_id stored in the cookie. -pub struct Authenticated; - -#[derive(Debug)] -pub enum LoginError { - UserNotLoggedIn, -} - -/// Request guard which returns an empty Authenticated struct from the request -/// if and only if the user has a cookie which proves they are authenticated with peach-web. -/// -/// Note that cookies.get_private uses encryption, which means that this private cookie -/// cannot be inspected, tampered with, or manufactured by clients. -#[rocket::async_trait] -impl<'r> FromRequest<'r> for Authenticated { - type Error = LoginError; - - async fn from_request(req: &'r Request<'_>) -> request::Outcome { - // retrieve auth state from managed state (returns `Option`). - // this value is read from the Rocket.toml config file on start-up - let authentication_is_disabled: bool = *req - .rocket() - .state::() - .map(|config| (&config.disable_auth)) - .unwrap_or(&false); - - if authentication_is_disabled { - let auth = Authenticated {}; - request::Outcome::Success(auth) - } else { - let authenticated = req - .cookies() - .get_private(AUTH_COOKIE_KEY) - .and_then(|cookie| cookie.value().parse().ok()) - .map(|_value: String| Authenticated {}); - match authenticated { - Some(auth) => request::Outcome::Success(auth), - None => request::Outcome::Failure((Status::Forbidden, LoginError::UserNotLoggedIn)), - } - } - } -} - -// HELPERS AND ROUTES FOR /login - -#[get("/login")] -pub fn login(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Login".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("login", &context.into_json()) -} - -#[derive(Debug, FromForm)] -pub struct LoginForm { - pub password: String, -} - -/// Takes in a LoginForm and returns Ok(()) if the password is correct. -/// -/// Note: there is currently only one user, therefore we don't need a username. -pub fn verify_login_form(login_form: LoginForm) -> Result<(), PeachError> { - password_utils::verify_password(&login_form.password) -} - -#[post("/login", data = "")] -pub fn login_post(login_form: Form, cookies: &CookieJar<'_>) -> TemplateOrRedirect { - match verify_login_form(login_form.into_inner()) { - Ok(_) => { - // if successful login, add a cookie indicating the user is authenticated - // and redirect to home page - // NOTE: since we currently have just one user, the value of the cookie - // is just admin (this is arbitrary). - // If we had multiple users, we could put the user_id here. - cookies.add_private(Cookie::new(AUTH_COOKIE_KEY, ADMIN_USERNAME)); - - TemplateOrRedirect::Redirect(Redirect::to("/")) - } - Err(e) => { - let err_msg = format!("Invalid password: {}", e); - // if unsuccessful login, render /login page again - let mut context = Context::new(); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Login".to_string())); - context.insert("flash_name", &("error".to_string())); - context.insert("flash_msg", &(err_msg)); - - TemplateOrRedirect::Template(Template::render("login", &context.into_json())) - } - } -} - -// HELPERS AND ROUTES FOR /logout - -#[get("/logout")] -pub fn logout(cookies: &CookieJar<'_>) -> Flash { - // logout authenticated user - info!("Attempting deauthentication of user."); - cookies.remove_private(Cookie::named(AUTH_COOKIE_KEY)); - Flash::success(Redirect::to("/login"), "Logged out") -} - -// HELPERS AND ROUTES FOR /reset_password - -#[derive(Debug, FromForm)] -pub struct ResetPasswordForm { - pub temporary_password: String, - pub new_password1: String, - pub new_password2: String, -} - -/// Verify, validate and save the submitted password. This function is publicly exposed for users who have forgotten their password. -pub fn save_reset_password_form(password_form: ResetPasswordForm) -> Result<(), PeachWebError> { - info!( - "reset password!: {} {} {}", - password_form.temporary_password, password_form.new_password1, password_form.new_password2 - ); - password_utils::verify_temporary_password(&password_form.temporary_password)?; - // if the previous line did not throw an error, then the secret_link is correct - password_utils::validate_new_passwords( - &password_form.new_password1, - &password_form.new_password2, - )?; - // if the previous line did not throw an error, then the new password is valid - password_utils::set_new_password(&password_form.new_password1)?; - Ok(()) -} - -/// Password reset request handler. This route is used by a user who is not logged in -/// and is specifically for users who have forgotten their password. -#[get("/reset_password")] -pub fn reset_password(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Reset Password".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/reset_password", &context.into_json()) -} - -/// Password reset form request handler. This route is used by a user who is not logged in -/// and is specifically for users who have forgotten their password. -#[post("/reset_password", data = "")] -pub fn reset_password_post(reset_password_form: Form) -> Template { - let mut context = Context::new(); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Reset Password".to_string())); - - let (flash_name, flash_msg) = match save_reset_password_form(reset_password_form.into_inner()) { - Ok(_) => ( - "success".to_string(), - "New password has been saved. Return home to login".to_string(), - ), - Err(err) => ( - "error".to_string(), - format!("Failed to reset password: {}", err), - ), - }; - - context.insert("flash_name", &Some(flash_name)); - context.insert("flash_msg", &Some(flash_msg)); - - Template::render("settings/admin/reset_password", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /send_password_reset - -/// Page for users who have forgotten their password. -/// This route is used by a user who is not logged in -/// to initiate the sending of a new password reset. -#[get("/forgot_password")] -pub fn forgot_password_page(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Send Password Reset".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/forgot_password", &context.into_json()) -} - -/// Send password reset request handler. This route is used by a user who is not logged in -/// and is specifically for users who have forgotten their password. A successful request results -/// in a Scuttlebutt private message being sent to the account of the device admin. -#[post("/send_password_reset")] -pub fn send_password_reset_post() -> Template { - info!("++ send password reset post"); - let mut context = Context::new(); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Send Password Reset".to_string())); - - let (flash_name, flash_msg) = match password_utils::send_password_reset() { - Ok(_) => ( - "success".to_string(), - "A password reset link has been sent to the admin of this device".to_string(), - ), - Err(err) => ( - "error".to_string(), - format!("Failed to send password reset link: {}", err), - ), - }; - - context.insert("flash_name", &Some(flash_name)); - context.insert("flash_msg", &Some(flash_msg)); - - Template::render("settings/admin/forgot_password", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /settings/change_password - -#[derive(Debug, FromForm)] -pub struct PasswordForm { - pub current_password: String, - pub new_password1: String, - pub new_password2: String, -} - -/// Password save form request handler. This function is for use by a user who is already logged in to change their password. -pub fn save_password_form(password_form: PasswordForm) -> Result<(), PeachWebError> { - info!( - "change password!: {} {} {}", - password_form.current_password, password_form.new_password1, password_form.new_password2 - ); - password_utils::verify_password(&password_form.current_password)?; - // if the previous line did not throw an error, then the old password is correct - password_utils::validate_new_passwords( - &password_form.new_password1, - &password_form.new_password2, - )?; - // if the previous line did not throw an error, then the new password is valid - password_utils::set_new_password(&password_form.new_password1)?; - Ok(()) -} - -/// Change password request handler. This is used by a user who is already logged in. -#[get("/change_password")] -pub fn change_password(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings/admin".to_string())); - context.insert("title", &Some("Change Password".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/change_password", &context.into_json()) -} - -/// Change password form request handler. This route is used by a user who is already logged in. -#[post("/change_password", data = "")] -pub fn change_password_post(password_form: Form, _auth: Authenticated) -> Template { - let mut context = Context::new(); - context.insert("back", &Some("/settings/admin".to_string())); - context.insert("title", &Some("Change Password".to_string())); - - let (flash_name, flash_msg) = match save_password_form(password_form.into_inner()) { - Ok(_) => ( - "success".to_string(), - "New password has been saved".to_string(), - ), - Err(err) => ( - "error".to_string(), - format!("Failed to save new password: {}", err), - ), - }; - - context.insert("flash_name", &Some(flash_name)); - context.insert("flash_msg", &Some(flash_msg)); - - Template::render("settings/admin/change_password", &context.into_json()) -} diff --git a/peach-web/src/routes/catchers.rs b/peach-web/src/routes/catchers.rs deleted file mode 100644 index 0c3523e..0000000 --- a/peach-web/src/routes/catchers.rs +++ /dev/null @@ -1,60 +0,0 @@ -use log::debug; -use rocket::catch; -use rocket::response::Redirect; -use rocket_dyn_templates::Template; -use serde::Serialize; - -// HELPERS AND ROUTES FOR 404 ERROR - -#[derive(Debug, Serialize)] -pub struct ErrorContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, -} - -impl ErrorContext { - pub fn build() -> ErrorContext { - ErrorContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - } - } -} - -#[catch(404)] -pub fn not_found() -> Template { - debug!("404 Page Not Found"); - let mut context = ErrorContext::build(); - context.back = Some("/".to_string()); - context.title = Some("404: Page Not Found".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some("No resource found for given URL".to_string()); - - Template::render("catchers/not_found", context) -} - -// HELPERS AND ROUTES FOR 500 ERROR - -#[catch(500)] -pub fn internal_error() -> Template { - debug!("500 Internal Server Error"); - let mut context = ErrorContext::build(); - context.back = Some("/".to_string()); - context.title = Some("500: Internal Server Error".to_string()); - context.flash_name = Some("error".to_string()); - context.flash_msg = Some("Internal server error".to_string()); - - Template::render("catchers/internal_error", context) -} - -// HELPERS AND ROUTES FOR 403 FORBIDDEN - -#[catch(403)] -pub fn forbidden() -> Redirect { - debug!("403 Forbidden"); - Redirect::to("/login") -} diff --git a/peach-web/src/routes/index.rs b/peach-web/src/routes/index.rs deleted file mode 100644 index 45eb21c..0000000 --- a/peach-web/src/routes/index.rs +++ /dev/null @@ -1,51 +0,0 @@ -use peach_lib::sbot::SbotStatus; -use rocket::{get, request::FlashMessage, State}; -use rocket_dyn_templates::{tera::Context, Template}; - -use crate::routes::authentication::Authenticated; -use crate::utils; -use crate::RocketConfig; - -// HELPERS AND ROUTES FOR / (HOME PAGE) - -#[get("/")] -pub fn home(_auth: Authenticated, config: &State) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read().ok(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("sbot_status", &sbot_status); - context.insert("flash_name", &None::<()>); - context.insert("flash_msg", &None::<()>); - context.insert("title", &None::<()>); - - // pass in mode from managed state so we can define appropriate urls in template - context.insert("standalone_mode", &config.standalone_mode); - - Template::render("home", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /guide - -#[get("/guide")] -pub fn guide(flash: Option) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/".to_string())); - context.insert("title", &Some("Guide".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("guide", &context.into_json()) -} diff --git a/peach-web/src/routes/settings/admin.rs_old b/peach-web/src/routes/settings/admin.rs_old deleted file mode 100644 index c5d437a..0000000 --- a/peach-web/src/routes/settings/admin.rs_old +++ /dev/null @@ -1,124 +0,0 @@ -/* -use rocket::{ - form::{Form, FromForm}, - get, post, - request::FlashMessage, - response::{Flash, Redirect}, - uri, -}; -use rocket_dyn_templates::{tera::Context, Template}; - -use peach_lib::config_manager; - -use crate::error::PeachWebError; -use crate::routes::authentication::Authenticated; -use crate::utils; - -// HELPERS AND ROUTES FOR /settings/admin - -/// Administrator settings menu. -#[get("/")] -pub fn admin_menu(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings".to_string())); - context.insert("title", &Some("Administrator Settings".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - Template::render("settings/admin/menu", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /settings/admin/configure - -/// View and delete currently configured admin. -#[get("/configure")] -pub fn configure_admin(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("back", &Some("/settings/admin".to_string())); - context.insert("title", &Some("Configure Admin".to_string())); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - context.insert("flash_name", &Some(flash.kind().to_string())); - context.insert("flash_msg", &Some(flash.message().to_string())); - }; - - // load the peach configuration vector - match config_manager::load_peach_config() { - Ok(config) => { - // retrieve the vector of ssb admin ids - let ssb_admin_ids = config.ssb_admin_ids; - context.insert("ssb_admin_ids", &ssb_admin_ids); - } - // if load fails, overwrite the flash_name and flash_msg - Err(e) => { - context.insert("flash_name", &Some("error".to_string())); - context.insert( - "flash_msg", - &Some(format!("Failed to load Peach config: {}", e)), - ); - } - } - - Template::render("settings/admin/configure_admin", &context.into_json()) -} - -// HELPERS AND ROUTES FOR /settings/admin/add - -#[derive(Debug, FromForm)] -pub struct AddAdminForm { - pub ssb_id: String, -} - -pub fn save_add_admin_form(admin_form: AddAdminForm) -> Result<(), PeachWebError> { - let _result = config_manager::add_ssb_admin_id(&admin_form.ssb_id)?; - - // if the previous line didn't throw an error then it was a success - Ok(()) -} - -#[post("/add", data = "")] -pub fn add_admin_post(add_admin_form: Form, _auth: Authenticated) -> Flash { - let result = save_add_admin_form(add_admin_form.into_inner()); - let url = uri!("/settings/admin/configure"); - match result { - Ok(_) => Flash::success(Redirect::to(url), "Added SSB administrator"), - Err(e) => Flash::error(Redirect::to(url), format!("Failed to add new admin: {}", e)), - } -} - -// HELPERS AND ROUTES FOR /settings/admin/delete - -#[derive(Debug, FromForm)] -pub struct DeleteAdminForm { - pub ssb_id: String, -} - -#[post("/delete", data = "")] -pub fn delete_admin_post( - delete_admin_form: Form, - _auth: Authenticated, -) -> Flash { - let result = config_manager::delete_ssb_admin_id(&delete_admin_form.ssb_id); - let url = uri!("/settings/admin", configure_admin); - match result { - Ok(_) => Flash::success(Redirect::to(url), "Removed SSB administrator"), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to remove admin id: {}", e), - ), - } -} -*/ diff --git a/peach-web/src/routes/status/scuttlebutt.rs_old b/peach-web/src/routes/status/scuttlebutt.rs_old deleted file mode 100644 index 34aa61d..0000000 --- a/peach-web/src/routes/status/scuttlebutt.rs_old +++ /dev/null @@ -1,37 +0,0 @@ -use rocket::{get, State}; -use rocket_dyn_templates::Template; - -use crate::routes::authentication::Authenticated; -use crate::{context::scuttlebutt::StatusContext, RocketConfig}; - -// HELPERS AND ROUTES FOR /status/scuttlebutt - -#[get("/scuttlebutt")] -pub async fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { - let context = StatusContext::build().await; - - let back = if config.standalone_mode { - // return to home page - Some("/".to_string()) - } else { - // return to status menu - Some("/status".to_string()) - }; - - match context { - Ok(mut context) => { - // define back arrow url based on mode - context.back = back; - - Template::render("status/scuttlebutt", &context) - } - Err(_) => { - let mut context = StatusContext::default(); - - // define back arrow url based on mode - context.back = back; - - Template::render("status/scuttlebutt", &context) - } - } -} diff --git a/peach-web/src/utils/monitor.rs b/peach-web/src/utils/monitor.rs deleted file mode 100644 index fd896cf..0000000 --- a/peach-web/src/utils/monitor.rs +++ /dev/null @@ -1,198 +0,0 @@ -// Monitor data transmission totals, set thresholds and check alert flags - -use std::convert::TryInto; - -use nest::{Error, Store, Value}; -use rocket::form::FromForm; -use rocket::serde::{Deserialize, Serialize}; -use serde_json::json; - -/// Network traffic data total -#[derive(Debug, Serialize)] -pub struct Data { - pub total: u64, // total traffic in bytes -} - -impl Data { - /// Retrieve network traffic data values from the store - fn get(store: &Store) -> Data { - // retrieve previous network traffic statistics - let data_stored = match store.get(&["net", "traffic", "total"]) { - Ok(total) => total, - // return 0 if no value exists - Err(_) => Value::Uint(u64::MIN), - }; - - let mut data = Vec::new(); - // retrieve u64 from Value type - if let Value::Uint(total) = data_stored { - data.push(total); - }; - - Data { total: data[0] } - } -} - -/// Network traffic notification thresholds and flags (user-defined) -#[derive(Debug, Deserialize, Serialize, FromForm)] -pub struct Threshold { - warn: u64, // traffic warning threshold - cut: u64, // traffic cutoff threshold - warn_flag: bool, // traffic warning notification flag - cut_flag: bool, // traffic cutoff notification flag -} - -impl Threshold { - /// Retrieve notification thresholds and flags from the store - fn get(store: &Store) -> Threshold { - let mut threshold = Vec::new(); - - let warn_val = store - .get(&["net", "notify", "warn"]) - .unwrap_or(Value::Uint(0)); - if let Value::Uint(val) = warn_val { - threshold.push(val); - }; - - let cut_val = store - .get(&["net", "notify", "cut"]) - .unwrap_or(Value::Uint(0)); - if let Value::Uint(val) = cut_val { - threshold.push(val); - }; - - let mut flag = Vec::new(); - - let warn_flag = store - .get(&["net", "notify", "warn_flag"]) - .unwrap_or(Value::Bool(false)); - if let Value::Bool(state) = warn_flag { - flag.push(state); - } - - let cut_flag = store - .get(&["net", "notify", "cut_flag"]) - .unwrap_or(Value::Bool(false)); - if let Value::Bool(state) = cut_flag { - flag.push(state); - } - - Threshold { - warn: threshold[0], - cut: threshold[1], - warn_flag: flag[0], - cut_flag: flag[1], - } - } - - /// Store notification flags from user data - fn set(self, store: &Store) { - store - .set(&["net", "notify", "warn"], &Value::Uint(self.warn)) - .unwrap(); - store - .set(&["net", "notify", "cut"], &Value::Uint(self.cut)) - .unwrap(); - store - .set( - &["net", "notify", "warn_flag"], - &Value::Bool(self.warn_flag), - ) - .unwrap(); - store - .set(&["net", "notify", "cut_flag"], &Value::Bool(self.cut_flag)) - .unwrap(); - } -} - -/// Warning and cutoff network traffic alert flags (programatically-defined) -#[derive(Debug, Serialize)] -pub struct Alert { - warn: bool, - cut: bool, -} - -impl Alert { - /// Retrieve latest alert flags from the store - fn get(store: &Store) -> Alert { - let mut alert = Vec::new(); - - let warn_flag = store - .get(&["net", "alert", "warn"]) - .unwrap_or(Value::Bool(false)); - if let Value::Bool(flag) = warn_flag { - alert.push(flag); - } - - let cut_flag = store - .get(&["net", "alert", "cut"]) - .unwrap_or(Value::Bool(false)); - if let Value::Bool(flag) = cut_flag { - alert.push(flag); - } - - Alert { - warn: alert[0], - cut: alert[1], - } - } -} - -fn create_store() -> std::result::Result { - // define the path - let path = xdg::BaseDirectories::new() - .unwrap() - .create_data_directory("peachcloud") - .unwrap(); - - // define the schema - let schema = json!({ - "net": { - "traffic": "json", - "alert": "json", - "notify": "json", - } - }) - .try_into()?; - - // create the data store - let store = Store::new(path, schema); - - Ok(store) -} - -pub fn get_alerts() -> std::result::Result { - let store = create_store()?; - let alerts = Alert::get(&store); - - Ok(alerts) -} - -pub fn get_data() -> std::result::Result { - let store = create_store()?; - let data = Data::get(&store); - - Ok(data) -} - -pub fn get_thresholds() -> std::result::Result { - let store = create_store()?; - let thresholds = Threshold::get(&store); - - Ok(thresholds) -} - -// set stored traffic total to 0 -pub fn reset_data() -> std::result::Result<(), Error> { - let store = create_store()?; - store.set(&["net", "traffic", "total"], &Value::Uint(0))?; - - Ok(()) -} - -pub fn update_store(threshold: Threshold) -> std::result::Result<(), Error> { - let store = create_store()?; - Threshold::set(threshold, &store); - - Ok(()) -} -- 2.40.1 From 979ec4eb6499ce6a7c036b162c8391af8c41ff42 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:27:06 +0200 Subject: [PATCH 55/66] reintroduce logging statements --- .../src/routes/settings/scuttlebutt/configure.rs | 9 ++------- peach-web/src/utils/sbot.rs | 14 ++++++++------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/peach-web/src/routes/settings/scuttlebutt/configure.rs b/peach-web/src/routes/settings/scuttlebutt/configure.rs index d546a73..1a03187 100644 --- a/peach-web/src/routes/settings/scuttlebutt/configure.rs +++ b/peach-web/src/routes/settings/scuttlebutt/configure.rs @@ -208,13 +208,9 @@ pub fn handle_form(request: &Request, restart: bool) -> Response { repair: bool, })); - debug!("ip: {} port: {}", data.lis_ip, data.lis_port); - // concat the ip and port for listen address let lis = format!("{}:{}", data.lis_ip, data.lis_port); - debug!("{}", lis); - // instantiate `SbotConfig` from form data let config = SbotConfig { lis, @@ -235,14 +231,13 @@ pub fn handle_form(request: &Request, restart: bool) -> Response { match data.startup { true => { - // TODO: rouille - log integration - //info!("Enabling go-sbot.service"); + debug!("Enabling go-sbot.service"); if let Err(e) = sbot::systemctl_sbot_cmd("enable") { warn!("Failed to enable go-sbot.service: {}", e) } } false => { - // TODO: info!("Disabling go-sbot.service"); + debug!("Disabling go-sbot.service"); if let Err(e) = sbot::systemctl_sbot_cmd("disable") { warn!("Failed to disable go-sbot.service: {}", e) } diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index 0852662..3a0bb74 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -13,6 +13,7 @@ use async_std::task; use dirs; use futures::stream::TryStreamExt; use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; +use log::debug; use peach_lib::sbot::SbotConfig; use rouille::input::post::BufferedFile; use temporary::Directory; @@ -33,7 +34,7 @@ pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result { /// Executes a systemctl stop command followed by start command. /// Returns a redirect with a flash message stating the output of the restart attempt. pub fn restart_sbot_process() -> (String, String) { - // TODO: info!("Restarting go-sbot.service"); + debug!("Restarting go-sbot.service"); match systemctl_sbot_cmd("stop") { // if stop was successful, try to start the process Ok(_) => match systemctl_sbot_cmd("start") { @@ -63,6 +64,7 @@ pub fn restart_sbot_process() -> (String, String) { pub async fn init_sbot_with_config( sbot_config: &Option, ) -> Result { + debug!("Initialising an sbot client with configuration parameters"); // initialise sbot connection with ip:port and shscap from config file let sbot_client = match sbot_config { // TODO: panics if we pass `Some(conf.shscap)` as second arg @@ -143,7 +145,7 @@ pub fn create_invite(uses: u16) -> Result> { task::block_on(async { let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - //TODO: debug!("Generating Scuttlebutt invite code"); + debug!("Generating Scuttlebutt invite code"); let invite_code = sbot_client.invite_create(uses).await?; Ok(invite_code) @@ -300,7 +302,7 @@ pub fn update_profile_info( if let Some(name) = new_name { // only update the name if it has changed if name != current_name { - // TODO: debug!("Publishing new Scuttlebutt profile name"); + debug!("Publishing a new Scuttlebutt profile name"); if let Err(e) = sbot_client.publish_name(&name).await { return Err(format!("Failed to update name: {}", e)); } else { @@ -312,7 +314,7 @@ pub fn update_profile_info( if let Some(description) = new_description { // only update the description if it has changed if description != current_description { - //debug!("Publishing new Scuttlebutt profile description"); + debug!("Publishing a new Scuttlebutt profile description"); if let Err(e) = sbot_client.publish_description(&description).await { return Err(format!("Failed to update description: {}", e)); } else { @@ -521,7 +523,7 @@ pub fn publish_public_post(text: String) -> Result { .await .map_err(|e| e.to_string())?; - // TODO: debug!("Publishing new Scuttlebutt public post"); + debug!("Publishing a new Scuttlebutt public post"); match sbot_client.publish_post(&text).await { Ok(_) => Ok("Published post".to_string()), Err(e) => Err(format!("Failed to publish post: {}", e)), @@ -539,7 +541,7 @@ pub fn publish_private_msg(text: String, recipients: Vec) -> Result Date: Thu, 24 Mar 2022 09:43:25 +0200 Subject: [PATCH 56/66] authenticate session if disable_auth env var is true --- peach-web/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index d5d4555..80966e2 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -79,11 +79,16 @@ fn main() { // assign a unique id to each client (appends a cookie to the response // with a name of "SID" and a duration of one hour (3600 seconds) rouille::session::session(request, "SID", 3600, |session| { + // if the "DISABLE_AUTH" env var is true, authenticate the session + let mut session_data = if CONFIG.disable_auth { + Some(SessionData { + _login: "success".to_string(), + }) // if the client already has an identifier from a previous request, // try to load the existing session data. if successful, make a // copy of the data in order to avoid locking the session for too // long - let mut session_data = if session.client_has_sid() { + } else if session.client_has_sid() { sessions_storage.lock().unwrap().get(session.id()).cloned() } else { None -- 2.40.1 From 952951515b616774664ae416b77a943574cf4f18 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 09:43:31 +0200 Subject: [PATCH 57/66] update readme --- peach-web/README.md | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/peach-web/README.md b/peach-web/README.md index 986cc7a..860b124 100644 --- a/peach-web/README.md +++ b/peach-web/README.md @@ -1,6 +1,6 @@ # peach-web -![Generic badge](https://img.shields.io/badge/version-0.5.0-.svg) +![Generic badge](https://img.shields.io/badge/version-0.6.0-.svg) ## Web Interface for PeachCloud @@ -17,7 +17,7 @@ The web interface is primarily designed as a means of managing a Scuttlebutt pub Additional features are focused on administration of the device itself. This includes networking functionality and device statistics. -The peach-web stack currently consists of [Rocket](https://rocket.rs/) (Rust web framework), [Tera](http://tera.netlify.com/) (Rust template engine), HTML and CSS. Scuttlebutt functionality is provided by [golgi](http://golgi.mycelial.technology). +The peach-web stack currently consists of [Rouille](https://crates.io/crates/rouille) (Rust web framework), [Maud](https://maud.lambda.xyz/) (Rust template engine), HTML and CSS. Scuttlebutt functionality is provided by [golgi](http://golgi.mycelial.technology). _Note: This is a work-in-progress._ @@ -32,39 +32,21 @@ Move into the repo and compile: `cd peach-workspace/peach-web` `cargo build --release` -Run the tests: - -`ROCKET_DISABLE_AUTH=true ROCKET_STANDALONE_MODE=false cargo test` - -Move back to the `peach-workspace` directory: - -`cd ..` - Run the binary: -`./target/release/peach-web` +`../target/release/peach-web` ## Environment -### Deployment Profile - -The web application deployment profile can be configured with the `ROCKET_ENV` environment variable: - -`export ROCKET_ENV=stage` - -Default configuration parameters are defined in `Rocket.toml`. This file defines a set of default parameters, some of which are overwritten when running in `debug` mode (ie. `cargo run` or `cargo build`) or `release` mode (ie. `cargo run --release` or `cargo build --release`). - -Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information. - ### Configuration Mode -The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is enabled by default (as defined in `Rocket.toml`) but can be overwritten using the `ROCKET_STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode. +The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is enabled by default (as defined in `Rocket.toml`) but can be overwritten using the `STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode. ### Authentication -Authentication is disabled in `debug` mode and enabled by default when running the application in `release` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`: +Authentication is enabled by default when running the application. It can be disabled by setting the `DISABLE_AUTH` environment variable to `true`: -`export ROCKET_DISABLE_AUTH=true` +`export DISABLE_AUTH=true` ### Logging @@ -106,10 +88,6 @@ Remove configuration files (not removed with `apt-get remove`): `sudo apt-get purge peach-web` -## Design - -`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call `peach-` libraries and serve HTML and assets. Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered. - ## Configuration Configuration variables are stored in /var/lib/peachcloud/config.yml. @@ -124,6 +102,10 @@ If the config dyn_use_custom_server=true, then a value must also be set for dyn_ This value is the URL of the instance of peach-dyndns-server that requests will be sent to for domain registration. Using a custom value can here can be useful for testing. +## Design + +`peach-web` has been designed with simplicity and resource minimalism in mind. Both the dependencies used by the project, as well as the code itself, reflect these design priorities. The Rouille micro-web-framework and Maud templating engine have been used to present a web interface for interacting with the device. HTML is rendered server-side and request handlers call `peach-` libraries and serve HTML and assets. The optimised binary for `peach-web` can be compiled on a RPi 3 B+ in approximately 30 minutes. + ## Licensing AGPL-3.0 -- 2.40.1 From 0bfad25d3d06f03cef559477f94a2234c96f5117 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 13:41:49 +0200 Subject: [PATCH 58/66] add follow, unfollow, block and unblock routes and sbot helper functions --- peach-web/src/private_router.rs | 16 ++++ peach-web/src/routes/scuttlebutt/block.rs | 42 ++++++++++ peach-web/src/routes/scuttlebutt/follow.rs | 42 ++++++++++ peach-web/src/routes/scuttlebutt/mod.rs | 4 + peach-web/src/routes/scuttlebutt/search.rs | 2 +- peach-web/src/routes/scuttlebutt/unblock.rs | 42 ++++++++++ peach-web/src/routes/scuttlebutt/unfollow.rs | 42 ++++++++++ peach-web/src/routes/status/scuttlebutt.rs | 2 +- peach-web/src/utils/sbot.rs | 85 +++++++++++++++++++- 9 files changed, 271 insertions(+), 6 deletions(-) create mode 100644 peach-web/src/routes/scuttlebutt/block.rs create mode 100644 peach-web/src/routes/scuttlebutt/follow.rs create mode 100644 peach-web/src/routes/scuttlebutt/unblock.rs create mode 100644 peach-web/src/routes/scuttlebutt/unfollow.rs diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 6b72a26..975eb75 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -43,10 +43,18 @@ pub fn mount_peachpub_routes( Response::html(routes::guide::build_template()) }, + (POST) (/scuttlebutt/block) => { + routes::scuttlebutt::block::handle_form(request) + }, + (GET) (/scuttlebutt/blocks) => { Response::html(routes::scuttlebutt::blocks::build_template()) }, + (POST) (/scuttlebutt/follow) => { + routes::scuttlebutt::follow::handle_form(request) + }, + (GET) (/scuttlebutt/follows) => { Response::html(routes::scuttlebutt::follows::build_template()) }, @@ -111,6 +119,14 @@ pub fn mount_peachpub_routes( routes::scuttlebutt::search::handle_form(request) }, + (POST) (/scuttlebutt/unblock) => { + routes::scuttlebutt::unblock::handle_form(request) + }, + + (POST) (/scuttlebutt/unfollow) => { + routes::scuttlebutt::unfollow::handle_form(request) + }, + (GET) (/settings) => { Response::html(routes::settings::menu::build_template()) }, diff --git a/peach-web/src/routes/scuttlebutt/block.rs b/peach-web/src/routes/scuttlebutt/block.rs new file mode 100644 index 0000000..176133c --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/block.rs @@ -0,0 +1,42 @@ +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::utils::{flash::FlashResponse, sbot}; + +// ROUTE: /scuttlebutt/block + +/// Block a Scuttlebutt profile specified by the given public key. +/// +/// Parse the public key from the submitted form and publish a contact message. +/// Redirect to the appropriate profile page with a flash message describing +/// the outcome of the action (may be successful or unsuccessful). +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + public_key: String, + })); + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + match sbot::block_peer(&data.public_key) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Social interactions are unavailable.".to_string(), + ), + }; + + let url = format!("/scuttlebutt/profile/{}", data.public_key); + + Response::redirect_303(url).add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/scuttlebutt/follow.rs b/peach-web/src/routes/scuttlebutt/follow.rs new file mode 100644 index 0000000..a73d47b --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/follow.rs @@ -0,0 +1,42 @@ +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::utils::{flash::FlashResponse, sbot}; + +// ROUTE: /scuttlebutt/follow + +/// Follow a Scuttlebutt profile specified by the given public key. +/// +/// Parse the public key from the submitted form and publish a contact message. +/// Redirect to the appropriate profile page with a flash message describing +/// the outcome of the action (may be successful or unsuccessful). +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + public_key: String, + })); + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + match sbot::follow_peer(&data.public_key) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Social interactions are unavailable.".to_string(), + ), + }; + + let url = format!("/scuttlebutt/profile/{}", data.public_key); + + Response::redirect_303(url).add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/scuttlebutt/mod.rs b/peach-web/src/routes/scuttlebutt/mod.rs index 6f77fd3..bea4722 100644 --- a/peach-web/src/routes/scuttlebutt/mod.rs +++ b/peach-web/src/routes/scuttlebutt/mod.rs @@ -1,4 +1,6 @@ +pub mod block; pub mod blocks; +pub mod follow; pub mod follows; pub mod friends; pub mod invites; @@ -8,3 +10,5 @@ pub mod profile; pub mod profile_update; pub mod publish; pub mod search; +pub mod unblock; +pub mod unfollow; diff --git a/peach-web/src/routes/scuttlebutt/search.rs b/peach-web/src/routes/scuttlebutt/search.rs index eb71acb..01bebf5 100644 --- a/peach-web/src/routes/scuttlebutt/search.rs +++ b/peach-web/src/routes/scuttlebutt/search.rs @@ -57,7 +57,7 @@ pub fn handle_form(request: &Request) -> Response { match sbot::validate_public_key(&data.public_key) { Ok(_) => { - let url = format!("/scuttlebutt/profile?={}", &data.public_key); + let url = format!("/scuttlebutt/profile/{}", &data.public_key); Response::redirect_303(url) } Err(err) => { diff --git a/peach-web/src/routes/scuttlebutt/unblock.rs b/peach-web/src/routes/scuttlebutt/unblock.rs new file mode 100644 index 0000000..7ebf7f2 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/unblock.rs @@ -0,0 +1,42 @@ +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::utils::{flash::FlashResponse, sbot}; + +// ROUTE: /scuttlebutt/unblock + +/// Unblock a Scuttlebutt profile specified by the given public key. +/// +/// Parse the public key from the submitted form and publish a contact message. +/// Redirect to the appropriate profile page with a flash message describing +/// the outcome of the action (may be successful or unsuccessful). +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + public_key: String, + })); + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + match sbot::unblock_peer(&data.public_key) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Social interactions are unavailable.".to_string(), + ), + }; + + let url = format!("/scuttlebutt/profile/{}", data.public_key); + + Response::redirect_303(url).add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/scuttlebutt/unfollow.rs b/peach-web/src/routes/scuttlebutt/unfollow.rs new file mode 100644 index 0000000..a043f06 --- /dev/null +++ b/peach-web/src/routes/scuttlebutt/unfollow.rs @@ -0,0 +1,42 @@ +use peach_lib::sbot::SbotStatus; +use rouille::{post_input, try_or_400, Request, Response}; + +use crate::utils::{flash::FlashResponse, sbot}; + +// ROUTE: /scuttlebutt/unfollow + +/// Unfollow a Scuttlebutt profile specified by the given public key. +/// +/// Parse the public key from the submitted form and publish a contact message. +/// Redirect to the appropriate profile page with a flash message describing +/// the outcome of the action (may be successful or unsuccessful). +pub fn handle_form(request: &Request) -> Response { + // query the request body for form data + // return a 400 error if the admin_id field is missing + let data = try_or_400!(post_input!(request, { + public_key: String, + })); + + let (flash_name, flash_msg) = match SbotStatus::read() { + Ok(status) if status.state == Some("active".to_string()) => { + match sbot::unfollow_peer(&data.public_key) { + Ok(success_msg) => ( + "flash_name=success".to_string(), + format!("flash_msg={}", success_msg), + ), + Err(error_msg) => ( + "flash_name=error".to_string(), + format!("flash_msg={}", error_msg), + ), + } + } + _ => ( + "flash_name=warning".to_string(), + "Social interactions are unavailable.".to_string(), + ), + }; + + let url = format!("/scuttlebutt/profile/{}", data.public_key); + + Response::redirect_303(url).add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 083703f..e25aa57 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -75,7 +75,7 @@ fn memory_element(memory: Option) -> Markup { memory_rounded.to_string(), "icon icon-active", "label-medium font-normal", - "label-small font-normal", + "label-small font-gray", ) } _ => ( diff --git a/peach-web/src/utils/sbot.rs b/peach-web/src/utils/sbot.rs index 3a0bb74..b170b7b 100644 --- a/peach-web/src/utils/sbot.rs +++ b/peach-web/src/utils/sbot.rs @@ -130,11 +130,16 @@ pub fn latest_sequence_number() -> Result> { let history_stream = sbot_client.create_history_stream(id).await?; let mut msgs: Vec = history_stream.try_collect().await?; - // reverse the list of messages so we can easily reference the latest one - msgs.reverse(); + // there will be zero messages when the sbot is run for the first time + if msgs.is_empty() { + Ok(0) + } else { + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); - // return the sequence number of the latest msg - Ok(msgs[0].sequence) + // return the sequence number of the latest msg + Ok(msgs[0].sequence) + } }) } @@ -353,6 +358,78 @@ pub fn update_profile_info( }) } +/// Follow a peer. +pub fn follow_peer(public_key: &str) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + debug!("Following a Scuttlebutt peer"); + match sbot_client.follow(public_key).await { + Ok(_) => Ok("Followed peer".to_string()), + Err(e) => Err(format!("Failed to follow peer: {}", e)), + } + }) +} + +/// Unfollow a peer. +pub fn unfollow_peer(public_key: &str) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + debug!("Unfollowing a Scuttlebutt peer"); + match sbot_client.unfollow(public_key).await { + Ok(_) => Ok("Unfollowed peer".to_string()), + Err(e) => Err(format!("Failed to unfollow peer: {}", e)), + } + }) +} + +/// Block a peer. +pub fn block_peer(public_key: &str) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + debug!("Blocking a Scuttlebutt peer"); + match sbot_client.block(public_key).await { + Ok(_) => Ok("Blocked peer".to_string()), + Err(e) => Err(format!("Failed to block peer: {}", e)), + } + }) +} + +/// Unblock a peer. +pub fn unblock_peer(public_key: &str) -> Result { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + task::block_on(async { + let mut sbot_client = init_sbot_with_config(&sbot_config) + .await + .map_err(|e| e.to_string())?; + + debug!("Unblocking a Scuttlebutt peer"); + match sbot_client.unblock(public_key).await { + Ok(_) => Ok("Unblocked peer".to_string()), + Err(e) => Err(format!("Failed to unblock peer: {}", e)), + } + }) +} + /// Retrieve a list of peers blocked by the local public key. pub fn get_blocks_list() -> Result>, Box> { // retrieve latest go-sbot configuration parameters -- 2.40.1 From 25e3a145fc04742e0d3e1f56d47b304c20221ca4 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 24 Mar 2022 13:42:02 +0200 Subject: [PATCH 59/66] fix alt attribute for logout icon --- peach-web/src/templates/nav.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/templates/nav.rs b/peach-web/src/templates/nav.rs index 6650eff..b6e2a7c 100644 --- a/peach-web/src/templates/nav.rs +++ b/peach-web/src/templates/nav.rs @@ -43,7 +43,7 @@ pub fn build_template( } h1 class="nav-title" { (title) } a class="nav-item" id="logoutButton" href="/auth/logout" title="Logout" { - img class="icon-medium nav-icon-right icon-active" src="/icons/enter.svg" alt="Enter"; + img class="icon-medium nav-icon-right icon-active" src="/icons/enter.svg" alt="Logout"; } } (PreEscaped("")) -- 2.40.1 From 30aff5d7ac23415b7ec7e2ef64f58a23d03cefc2 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 08:43:15 +0200 Subject: [PATCH 60/66] add clarity to empty peers list message --- peach-web/src/templates/peers_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/templates/peers_list.rs b/peach-web/src/templates/peers_list.rs index 01e40d2..60ce677 100644 --- a/peach-web/src/templates/peers_list.rs +++ b/peach-web/src/templates/peers_list.rs @@ -55,7 +55,7 @@ pub fn build_template(peers: Vec>, title: &str) -> PreEs // render the peers list template (peers_template(peers)) } @else { - p { "No follows found" } + p class="font-normal" { "No peers found" } } } } -- 2.40.1 From 2ae9cb5c480d8b517e4cc33389ba8245ee34e969 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 08:43:32 +0200 Subject: [PATCH 61/66] remove ellipsis label class width --- peach-web/static/css/peachcloud.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/static/css/peachcloud.css b/peach-web/static/css/peachcloud.css index 7e18374..a1f712e 100644 --- a/peach-web/static/css/peachcloud.css +++ b/peach-web/static/css/peachcloud.css @@ -795,7 +795,7 @@ form { .label-ellipsis { overflow: hidden; text-overflow: ellipsis; - width: 10rem; + /*width: 10rem;*/ } .input-label { -- 2.40.1 From b1c5c701e51e891465e57543d3ff613a9e74b289 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 08:45:10 +0200 Subject: [PATCH 62/66] reduce specificity of peach-lib dependencies --- peach-lib/Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/peach-lib/Cargo.toml b/peach-lib/Cargo.toml index 9b08559..9d53e7f 100644 --- a/peach-lib/Cargo.toml +++ b/peach-lib/Cargo.toml @@ -5,19 +5,19 @@ authors = ["Andrew Reid "] edition = "2018" [dependencies] -async-std = "1.10.0" -chrono = "0.4.19" +async-std = "1.10" +chrono = "0.4" dirs = "4.0" -fslock="0.1.6" +fslock="0.1" golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi" } jsonrpc-client-core = "0.5" jsonrpc-client-http = "0.5" -jsonrpc-core = "8.0.1" +jsonrpc-core = "8.0" log = "0.4" -nanorand = "0.6.1" +nanorand = "0.6" regex = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.8" -toml = "0.5.8" -sha3 = "0.10.0" +toml = "0.5" +sha3 = "0.10" -- 2.40.1 From 367f0307b6dfd43f6c4525d1aac2759ce4c7686b Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 09:19:28 +0200 Subject: [PATCH 63/66] autofocus password input --- peach-web/src/routes/authentication/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/routes/authentication/login.rs b/peach-web/src/routes/authentication/login.rs index f914e30..ae423b3 100644 --- a/peach-web/src/routes/authentication/login.rs +++ b/peach-web/src/routes/authentication/login.rs @@ -26,7 +26,7 @@ pub fn build_template(request: &Request) -> PreEscaped { div style="display: flex; flex-direction: column; margin-bottom: 1rem;" { (PreEscaped("")) label for="password" class="center label-small font-gray" style="width: 80%;" { "PASSWORD" } - input id="password" name="password" class="center input" type="password" title="Password for given username"; + input id="password" name="password" class="center input" type="password" title="Password for given username" autofocus; (PreEscaped("")) input id="loginUser" class="button button-primary center" title="Login" type="submit" value="Login"; div class="center-text" style="margin-top: 1rem;" { -- 2.40.1 From aefa1525fb9c632a06654427f66012a15a4ad621 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 09:19:43 +0200 Subject: [PATCH 64/66] clarify missing name and description --- peach-web/src/routes/scuttlebutt/profile.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peach-web/src/routes/scuttlebutt/profile.rs b/peach-web/src/routes/scuttlebutt/profile.rs index 2d2f048..feecdb7 100644 --- a/peach-web/src/routes/scuttlebutt/profile.rs +++ b/peach-web/src/routes/scuttlebutt/profile.rs @@ -55,7 +55,7 @@ fn profile_bio_template(profile: &Profile) -> Markup { @if let Some(name) = &profile.name { (name) } @else { - "Name unavailable" + i { "Name is unavailable or has not been set" } } } label class="label-small label-ellipsis font-gray" style="user-select: all;" for="profileName" title="Public Key" { @@ -69,7 +69,7 @@ fn profile_bio_template(profile: &Profile) -> Markup { @if let Some(description) = &profile.description { (description) } @else { - "Description unavailable" + i { "Description is unavailable or has not been set" } } } -- 2.40.1 From d6695b291d43f43b4fccf4e9e4358d15b8e07da4 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 09:26:44 +0200 Subject: [PATCH 65/66] tweak no-peers element --- peach-web/src/templates/peers_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/src/templates/peers_list.rs b/peach-web/src/templates/peers_list.rs index 60ce677..d3230f6 100644 --- a/peach-web/src/templates/peers_list.rs +++ b/peach-web/src/templates/peers_list.rs @@ -55,7 +55,7 @@ pub fn build_template(peers: Vec>, title: &str) -> PreEs // render the peers list template (peers_template(peers)) } @else { - p class="font-normal" { "No peers found" } + p class="center-text font-normal" { "None found" } } } } -- 2.40.1 From f29659669c0514f423802c5bfc4fd9e95c78ecc9 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Mar 2022 09:39:06 +0200 Subject: [PATCH 66/66] add route to handle resetting default sbot config --- peach-web/src/private_router.rs | 4 ++++ .../routes/settings/scuttlebutt/default.rs | 21 +++++++++++++++++++ .../src/routes/settings/scuttlebutt/mod.rs | 1 + 3 files changed, 26 insertions(+) create mode 100644 peach-web/src/routes/settings/scuttlebutt/default.rs diff --git a/peach-web/src/private_router.rs b/peach-web/src/private_router.rs index 975eb75..e48f337 100644 --- a/peach-web/src/private_router.rs +++ b/peach-web/src/private_router.rs @@ -178,6 +178,10 @@ pub fn mount_peachpub_routes( routes::settings::scuttlebutt::configure::handle_form(request, true) }, + (GET) (/settings/scuttlebutt/configure/default) => { + routes::settings::scuttlebutt::default::write_config() + }, + (GET) (/settings/theme/{theme: String}) => { routes::settings::theme::set_theme(theme) }, diff --git a/peach-web/src/routes/settings/scuttlebutt/default.rs b/peach-web/src/routes/settings/scuttlebutt/default.rs new file mode 100644 index 0000000..daf8d94 --- /dev/null +++ b/peach-web/src/routes/settings/scuttlebutt/default.rs @@ -0,0 +1,21 @@ +use peach_lib::sbot::SbotConfig; +use rouille::Response; + +use crate::utils::flash::FlashResponse; + +/// Set default configuration parameters for the go-sbot and save them to file. +pub fn write_config() -> Response { + let default_config = SbotConfig::default(); + // write default config to file + let (name, msg) = match SbotConfig::write(default_config) { + Ok(_) => ("success", "Restored default configuration".to_string()), + Err(e) => ( + "error", + format!("Failed to restore default configuration: {}", e), + ), + }; + + let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); + + Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg) +} diff --git a/peach-web/src/routes/settings/scuttlebutt/mod.rs b/peach-web/src/routes/settings/scuttlebutt/mod.rs index e969135..f4d9faf 100644 --- a/peach-web/src/routes/settings/scuttlebutt/mod.rs +++ b/peach-web/src/routes/settings/scuttlebutt/mod.rs @@ -1,4 +1,5 @@ pub mod configure; +pub mod default; pub mod menu; pub mod restart; pub mod start; -- 2.40.1