From a1474dab4368552b6c2118d96b91055f1d77c657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 16 Jan 2026 20:22:11 +0100 Subject: [PATCH 1/2] Split up files and add templating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- Cargo.lock | 494 ++++++++++++++++++++- nixie-server/Cargo.toml | 3 + nixie-server/src/main.rs | 182 +++----- nixie-server/src/users/login.rs | 43 ++ nixie-server/src/users/mod.rs | 53 +++ nixie-server/src/users/register.rs | 41 ++ nixie-server/templates/base.tera.html | 36 ++ nixie-server/templates/protected.tera.html | 9 + 8 files changed, 738 insertions(+), 123 deletions(-) create mode 100644 nixie-server/src/users/login.rs create mode 100644 nixie-server/src/users/mod.rs create mode 100644 nixie-server/src/users/register.rs create mode 100644 nixie-server/templates/base.tera.html create mode 100644 nixie-server/templates/protected.tera.html diff --git a/Cargo.lock b/Cargo.lock index 9a7496b..6f64c45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -227,6 +236,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -254,6 +269,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -294,6 +319,39 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "4.5.54" @@ -366,6 +424,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -390,6 +454,25 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -448,6 +531,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "digest" version = "0.10.7" @@ -541,6 +630,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.7" @@ -579,6 +677,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.31" @@ -702,6 +809,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.10.0", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -812,6 +943,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "1.8.1" @@ -849,6 +989,30 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -951,6 +1115,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -961,6 +1141,26 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.10.0", + "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]] name = "is_terminal_polyfill" version = "1.70.2" @@ -992,6 +1192,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1019,7 +1239,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", "redox_syscall 0.7.0", ] @@ -1113,6 +1333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] @@ -1162,17 +1383,57 @@ dependencies = [ "axum", "axum-login", "displaydoc", + "notify-debouncer-full", "password-auth", "serde", "sqlx", + "tera", "thiserror 2.0.17", "tokio", + "tower-livereload", "tower-sessions", "tower-sessions-sqlx-store", "tracing", "tracing-subscriber", ] +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.10.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1275,6 +1536,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "password-auth" version = "1.0.0" @@ -1313,6 +1583,49 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.8.3" @@ -1325,6 +1638,44 @@ dependencies = [ "serde", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1442,7 +1793,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -1451,7 +1802,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ - "bitflags", + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1671,12 +2034,28 @@ dependencies = [ "rand_core", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -1809,7 +2188,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.10.0", "byteorder", "bytes", "crc", @@ -1852,7 +2231,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.10.0", "byteorder", "crc", "dotenvy", @@ -1964,6 +2343,28 @@ dependencies = [ "syn", ] +[[package]] +name = "tera" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unicode-segmentation", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2071,9 +2472,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -2146,6 +2547,20 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +[[package]] +name = "tower-livereload" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62a893f6b4fbc35dc32eacd6b5cf61dd49abac153ac7b3892c5725b350912e25" +dependencies = [ + "bytes", + "http", + "http-body", + "pin-project-lite", + "tokio", + "tower", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -2285,6 +2700,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -2312,6 +2733,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "url" version = "2.5.8" @@ -2446,12 +2873,65 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/nixie-server/Cargo.toml b/nixie-server/Cargo.toml index 5ad50b8..9be0ebb 100644 --- a/nixie-server/Cargo.toml +++ b/nixie-server/Cargo.toml @@ -20,3 +20,6 @@ displaydoc.workspace = true password-auth = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } +tera = "1.20.1" +notify-debouncer-full = "0.6.0" +tower-livereload = "0.10.2" diff --git a/nixie-server/src/main.rs b/nixie-server/src/main.rs index 11a227d..f23cd45 100644 --- a/nixie-server/src/main.rs +++ b/nixie-server/src/main.rs @@ -1,54 +1,39 @@ -use axum::Form; +use std::sync::LazyLock; +use std::time::Duration; + use axum::Router; -use axum::extract::State; -use axum::http::StatusCode; use axum::response::Html; -use axum::response::IntoResponse; -use axum::response::Redirect; use axum::routing::get; -use axum::routing::post; use axum_login::AuthManagerLayerBuilder; -use axum_login::AuthUser; use axum_login::AuthnBackend; use axum_login::login_required; use displaydoc::Display; -use password_auth::generate_hash; +use notify_debouncer_full::DebouncedEvent; +use notify_debouncer_full::notify::EventKind; use password_auth::verify_password; use serde::Deserialize; -use serde::Serialize; use sqlx::SqlitePool; -use sqlx::prelude::FromRow; +use tera::Context; +use tera::Tera; use thiserror::Error; +use tokio::sync::RwLock; use tokio::task; use tokio::task::AbortHandle; +use tower_livereload::LiveReloadLayer; use tower_sessions::ExpiredDeletion; use tower_sessions::SessionManagerLayer; use tower_sessions_sqlx_store::SqliteStore; +use tracing::error; use tracing_subscriber::EnvFilter; +pub mod users; + #[tokio::main] async fn main() -> anyhow::Result<()> { run().await } -#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] -struct User { - id: i64, - username: String, - password: String, -} - -impl AuthUser for User { - type Id = i64; - - fn id(&self) -> Self::Id { - self.id - } - - fn session_auth_hash(&self) -> &[u8] { - self.password.as_bytes() - } -} +type AuthSession = axum_login::AuthSession; #[derive(Clone)] struct Backend { @@ -56,7 +41,7 @@ struct Backend { } #[derive(Debug, Clone, Deserialize)] -struct Credentials { +struct UserCredentials { username: String, password: String, } @@ -71,8 +56,8 @@ pub enum AuthBackendError { } impl AuthnBackend for Backend { - type User = User; - type Credentials = Credentials; + type User = users::User; + type Credentials = UserCredentials; type Error = AuthBackendError; async fn authenticate( @@ -85,7 +70,7 @@ impl AuthnBackend for Backend { .await?; task::spawn_blocking(move || { - Ok(user.filter(|user| verify_password(&creds.password, &user.password).is_ok())) + Ok(user.filter(|user| verify_password(&creds.password, user.password()).is_ok())) }) .await? } @@ -104,10 +89,13 @@ impl AuthnBackend for Backend { } #[derive(Debug, Clone)] -pub(crate) struct AppState { +pub struct AppState { pub db: SqlitePool, } +pub static TERA: LazyLock> = + LazyLock::new(|| Tera::new("templates/**.tera.html").unwrap().into()); + async fn run() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) @@ -131,21 +119,54 @@ async fn run() -> anyhow::Result<()> { let backend = Backend { db: db.clone() }; let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build(); + let livereload = LiveReloadLayer::new(); + let reloader = livereload.reloader(); + let app = Router::new() .route("/protected", get(show_protected)) .route_layer(login_required!(Backend, login_url = "/login")) - .route("/login", get(show_login)) - .route("/login", post(do_login)) - .route("/register", get(show_register)) - .route("/register", post(do_register)) + .merge(users::routes()) .layer(auth_layer) + .layer(livereload) .with_state(AppState { db }); + let mut debouncer = notify_debouncer_full::new_debouncer( + Duration::from_millis(50), + None, + move |event: Result, Vec>| { + match event { + Ok(events) => { + if events + .iter() + .any(|ev| !matches!(ev.event.kind, EventKind::Access(_))) + { + match TERA.blocking_write().full_reload() { + Ok(()) => { + reloader.reload(); + } + Err(error) => { + error!("Could not reload tera templates: {error}"); + } + } + } + } + Err(_e) => {} + } + }, + )?; + + debouncer.watch( + "templates/", + notify_debouncer_full::notify::RecursiveMode::Recursive, + )?; + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app.into_make_service()) .with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle())) .await?; + debouncer.stop(); + Ok(()) } @@ -171,85 +192,14 @@ async fn shutdown_signal(handle: AbortHandle) { handle.abort(); } +pub async fn render_template(name: &str, context: &Context) -> tera::Result { + TERA.read().await.render(name, context) +} + async fn show_protected() -> Html { - format!( - r##" - - - - Yay, you did it! - - - "## + Html( + render_template("protected.tera.html", &Context::default()) + .await + .unwrap(), ) - .into() -} - -async fn show_login() -> Html { - format!( - r##" - - - -
- - - -
- - - "## - ) - .into() -} - -async fn show_register() -> Html { - format!( - r##" - - - -
- - - -
- - - "## - ) - .into() -} - -type AuthSession = axum_login::AuthSession; - -async fn do_register( - app_state: State, - Form(creds): Form, -) -> Result> { - sqlx::query("INSERT INTO users (username, password) VALUES (?, ?)") - .bind(creds.username) - .bind(generate_hash(creds.password)) - .execute(&app_state.db) - .await - .map_err(|err| Html(err.to_string()))?; - - Ok(Redirect::to("/login").into_response()) -} - -async fn do_login( - mut auth_session: AuthSession, - Form(creds): Form, -) -> impl IntoResponse { - let user = match auth_session.authenticate(creds.clone()).await { - Ok(Some(user)) => user, - Ok(None) => return StatusCode::UNAUTHORIZED.into_response(), - Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(), - }; - - if auth_session.login(&user).await.is_err() { - return StatusCode::INTERNAL_SERVER_ERROR.into_response(); - } - - Redirect::to("/protected").into_response() } diff --git a/nixie-server/src/users/login.rs b/nixie-server/src/users/login.rs new file mode 100644 index 0000000..4dfb6d9 --- /dev/null +++ b/nixie-server/src/users/login.rs @@ -0,0 +1,43 @@ +use axum::Form; +use axum::http::StatusCode; +use axum::response::Html; +use axum::response::IntoResponse; +use axum::response::Redirect; + +use crate::AuthSession; +use crate::UserCredentials; + +pub async fn show_login() -> Html { + format!( + r##" + + + +
+ + + +
+ + + "## + ) + .into() +} + +pub async fn do_login( + mut auth_session: AuthSession, + Form(creds): Form, +) -> impl IntoResponse { + let user = match auth_session.authenticate(creds.clone()).await { + Ok(Some(user)) => user, + Ok(None) => return StatusCode::UNAUTHORIZED.into_response(), + Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(), + }; + + if auth_session.login(&user).await.is_err() { + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + + Redirect::to("/protected").into_response() +} diff --git a/nixie-server/src/users/mod.rs b/nixie-server/src/users/mod.rs new file mode 100644 index 0000000..18e913b --- /dev/null +++ b/nixie-server/src/users/mod.rs @@ -0,0 +1,53 @@ +use axum::Router; +use axum::routing::get; +use axum::routing::post; +use axum_login::AuthUser; +use serde::Deserialize; +use serde::Serialize; +use sqlx::FromRow; + +use crate::AppState; + +mod login; +mod register; + +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] +pub struct User { + id: i64, + username: String, + password: String, +} + +impl User { + pub fn id(&self) -> i64 { + self.id + } + + pub fn username(&self) -> &str { + &self.username + } + + pub fn password(&self) -> &str { + &self.password + } +} + +impl AuthUser for User { + type Id = i64; + + fn id(&self) -> Self::Id { + self.id + } + + fn session_auth_hash(&self) -> &[u8] { + self.password.as_bytes() + } +} + +pub fn routes() -> Router { + Router::new() + .route("/login", get(login::show_login)) + .route("/login", post(login::do_login)) + .route("/register", get(register::show_register)) + .route("/register", post(register::do_register)) +} diff --git a/nixie-server/src/users/register.rs b/nixie-server/src/users/register.rs new file mode 100644 index 0000000..030f6ab --- /dev/null +++ b/nixie-server/src/users/register.rs @@ -0,0 +1,41 @@ +use axum::Form; +use axum::extract::State; +use axum::response::Html; +use axum::response::IntoResponse; +use axum::response::Redirect; +use password_auth::generate_hash; + +use crate::AppState; +use crate::UserCredentials; + +pub async fn show_register() -> Html { + format!( + r##" + + + +
+ + + +
+ + + "## + ) + .into() +} + +pub async fn do_register( + app_state: State, + Form(creds): Form, +) -> Result> { + sqlx::query("INSERT INTO users (username, password) VALUES (?, ?)") + .bind(creds.username) + .bind(generate_hash(creds.password)) + .execute(&app_state.db) + .await + .map_err(|err| Html(err.to_string()))?; + + Ok(Redirect::to("/login").into_response()) +} diff --git a/nixie-server/templates/base.tera.html b/nixie-server/templates/base.tera.html new file mode 100644 index 0000000..edfecd0 --- /dev/null +++ b/nixie-server/templates/base.tera.html @@ -0,0 +1,36 @@ + + + + + + + + {% block head %} + {% block title %}{% endblock title%} - Nixie CI + {% endblock head%} + + + + +
+
+ {% block content %}{% endblock content %} +
+
+
+
+ {% block footer %} + © Copyright 2026 by Hemera + {% endblock footer %} +
+
+ + + diff --git a/nixie-server/templates/protected.tera.html b/nixie-server/templates/protected.tera.html new file mode 100644 index 0000000..5ed964b --- /dev/null +++ b/nixie-server/templates/protected.tera.html @@ -0,0 +1,9 @@ +{% extends "base.tera.html" %} + +{% block title %} +Protected Page +{% endblock title %} + +{% block content %} + This is super secret content! +{% endblock content %} From 79305724f2ef7600d7e19375bcd94ecc0318717a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 18 Jan 2026 13:18:35 +0100 Subject: [PATCH 2/2] Style login page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- nixie-server/src/main.rs | 40 +++++++++++++++----- nixie-server/src/users/login.rs | 21 +++------- nixie-server/src/users/register.rs | 21 +++------- nixie-server/templates/base.tera.html | 18 +++++---- nixie-server/templates/inputs.tera.html | 6 +++ nixie-server/templates/users/login.tera.html | 28 ++++++++++++++ 6 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 nixie-server/templates/inputs.tera.html create mode 100644 nixie-server/templates/users/login.tera.html diff --git a/nixie-server/src/main.rs b/nixie-server/src/main.rs index f23cd45..e2a7493 100644 --- a/nixie-server/src/main.rs +++ b/nixie-server/src/main.rs @@ -2,7 +2,9 @@ use std::sync::LazyLock; use std::time::Duration; use axum::Router; +use axum::http::StatusCode; use axum::response::Html; +use axum::response::IntoResponse; use axum::routing::get; use axum_login::AuthManagerLayerBuilder; use axum_login::AuthnBackend; @@ -16,7 +18,6 @@ use sqlx::SqlitePool; use tera::Context; use tera::Tera; use thiserror::Error; -use tokio::sync::RwLock; use tokio::task; use tokio::task::AbortHandle; use tower_livereload::LiveReloadLayer; @@ -28,6 +29,29 @@ use tracing_subscriber::EnvFilter; pub mod users; +pub type WebResult = Result; + +#[derive(Debug, Error, Display)] +pub enum AppError { + /// An error occurred while templating + Tera(#[from] tera::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> axum::response::Response { + let mut error_context = Context::new(); + error_context.insert("error", &self.to_string()); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Html( + render_template("internal_error.tera.html", &error_context) + .unwrap_or_else(|_| "ERROR RENDERING ERROR! FATAL".to_string()), + ), + ) + .into_response() + } +} + #[tokio::main] async fn main() -> anyhow::Result<()> { run().await @@ -93,7 +117,7 @@ pub struct AppState { pub db: SqlitePool, } -pub static TERA: LazyLock> = +pub static TERA: LazyLock> = LazyLock::new(|| Tera::new("templates/**.tera.html").unwrap().into()); async fn run() -> anyhow::Result<()> { @@ -140,7 +164,7 @@ async fn run() -> anyhow::Result<()> { .iter() .any(|ev| !matches!(ev.event.kind, EventKind::Access(_))) { - match TERA.blocking_write().full_reload() { + match TERA.write().unwrap().full_reload() { Ok(()) => { reloader.reload(); } @@ -192,14 +216,10 @@ async fn shutdown_signal(handle: AbortHandle) { handle.abort(); } -pub async fn render_template(name: &str, context: &Context) -> tera::Result { - TERA.read().await.render(name, context) +pub fn render_template(name: &str, context: &Context) -> tera::Result { + TERA.read().unwrap().render(name, context) } async fn show_protected() -> Html { - Html( - render_template("protected.tera.html", &Context::default()) - .await - .unwrap(), - ) + Html(render_template("protected.tera.html", &Context::default()).unwrap()) } diff --git a/nixie-server/src/users/login.rs b/nixie-server/src/users/login.rs index 4dfb6d9..9e45694 100644 --- a/nixie-server/src/users/login.rs +++ b/nixie-server/src/users/login.rs @@ -3,26 +3,15 @@ use axum::http::StatusCode; use axum::response::Html; use axum::response::IntoResponse; use axum::response::Redirect; +use tera::Context; use crate::AuthSession; use crate::UserCredentials; +use crate::WebResult; +use crate::render_template; -pub async fn show_login() -> Html { - format!( - r##" - - - -
- - - -
- - - "## - ) - .into() +pub async fn show_login() -> WebResult> { + Ok(render_template("users/login.tera.html", &Context::default()).map(Html)?) } pub async fn do_login( diff --git a/nixie-server/src/users/register.rs b/nixie-server/src/users/register.rs index 030f6ab..82e7fbb 100644 --- a/nixie-server/src/users/register.rs +++ b/nixie-server/src/users/register.rs @@ -4,26 +4,15 @@ use axum::response::Html; use axum::response::IntoResponse; use axum::response::Redirect; use password_auth::generate_hash; +use tera::Context; use crate::AppState; use crate::UserCredentials; +use crate::WebResult; +use crate::render_template; -pub async fn show_register() -> Html { - format!( - r##" - - - -
- - - -
- - - "## - ) - .into() +pub async fn show_register() -> WebResult> { + Ok(render_template("users/register.tera.html", &Context::default()).map(Html)?) } pub async fn do_register( diff --git a/nixie-server/templates/base.tera.html b/nixie-server/templates/base.tera.html index edfecd0..c0b50f0 100644 --- a/nixie-server/templates/base.tera.html +++ b/nixie-server/templates/base.tera.html @@ -11,20 +11,24 @@ -