diff --git a/.editorconfig b/.editorconfig index 2c5ac236..c38915a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,15 @@ root = true +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + [*.rs] indent_style = tab indent_size = 4 [*.html] indent_style = tab -indent_size = 4 +indent_size = 2 diff --git a/.gitignore b/.gitignore index 1505f973..c26ea541 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ static/dist/ node_modules/ keys/*.pem .sass-cache/ +.diesel_lock diff --git a/.hooks/pre-commit b/.hooks/pre-commit index e52d51f3..d5427914 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -1,25 +1,8 @@ #!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" +set -eu cargo fmt -- --check + +if cargo --list | grep -q "sort-derives"; then + cargo sort-derives --check +fi diff --git a/Cargo.lock b/Cargo.lock index c9c64fc8..dd2e4b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,18 +37,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -58,12 +46,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -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" @@ -74,61 +56,61 @@ dependencies = [ ] [[package]] -name = "ar_archive_writer" -version = "0.2.0" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" -dependencies = [ - "object", -] +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "askama" -version = "0.12.1" -source = "git+https://github.com/djc/askama.git?rev=40bb338#40bb3382b0c98e97f484c81659207b6b950c5cb0" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608" dependencies = [ - "askama_derive", - "askama_escape", - "humansize", - "num-traits", + "askama_macros", + "itoa", "percent-encoding", + "serde", + "serde_json", ] [[package]] name = "askama_derive" -version = "0.12.2" -source = "git+https://github.com/djc/askama.git?rev=40bb338#40bb3382b0c98e97f484c81659207b6b950c5cb0" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c" dependencies = [ "askama_parser", "basic-toml", - "mime", - "mime_guess", + "memchr", "proc-macro2", "quote", + "rustc-hash", "serde", + "serde_derive", "syn", ] [[package]] -name = "askama_escape" -version = "0.10.3" -source = "git+https://github.com/djc/askama.git?rev=40bb338#40bb3382b0c98e97f484c81659207b6b950c5cb0" - -[[package]] -name = "askama_parser" -version = "0.1.1" -source = "git+https://github.com/djc/askama.git?rev=40bb338#40bb3382b0c98e97f484c81659207b6b950c5cb0" +name = "askama_macros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f" dependencies = [ - "nom 7.1.3", + "askama_derive", ] [[package]] -name = "askama_rocket" -version = "0.12.0" -source = "git+https://github.com/djc/askama.git?rev=40bb338#40bb3382b0c98e97f484c81659207b6b950c5cb0" +name = "askama_parser" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868" dependencies = [ - "askama", - "rocket", + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow 1.0.1", ] [[package]] @@ -238,9 +220,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64urlsafedata" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "215ee31f8a88f588c349ce2d20108b2ed96089b96b9c2b03775dc35dd72938e8" +checksum = "42f7f6be94fa637132933fd0a68b9140bcb60e3d46164cb68e82a2bb8d102b3a" dependencies = [ "base64 0.21.7", "pastey", @@ -284,9 +266,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -319,15 +301,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -337,15 +319,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.44" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "shlex", @@ -359,26 +341,16 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "chumsky" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" -dependencies = [ - "hashbrown 0.14.5", - "stacker", + "windows-link", ] [[package]] @@ -402,11 +374,11 @@ dependencies = [ [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -428,9 +400,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -559,9 +531,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der-parser" @@ -579,9 +551,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -621,9 +593,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.3.4" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c415189028b232660655e4893e8bc25ca7aee8e96888db66d9edb400535456a" +checksum = "f4ae09a41a4b89f94ec1e053623da8340d996bc32c6517d325a9daad9b239358" dependencies = [ "bitflags", "byteorder", @@ -649,9 +621,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.4" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9daac6489a36e42570da165a10c424f3edcefdff70c5fd55e1847c23f3dd7562" +checksum = "47618bf0fac06bb670c036e48404c26a865e6a71af4114dfd97dfe89936e404e" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -780,9 +752,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "figment" @@ -800,9 +772,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -810,6 +782,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -836,9 +814,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -850,9 +828,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -860,33 +838,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -895,7 +873,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -924,9 +901,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -943,10 +920,23 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + [[package]] name = "ghash" version = "0.5.1" @@ -995,19 +985,18 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "ahash", - "allocator-api2", + "foldhash", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1063,13 +1052,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" dependencies = [ "cfg-if", "libc", - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -1085,12 +1074,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1117,15 +1105,6 @@ 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 = "0.14.32" @@ -1152,9 +1131,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1176,12 +1155,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1189,9 +1169,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1202,9 +1182,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1216,15 +1196,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1236,15 +1216,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1255,6 +1235,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1284,12 +1270,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1322,15 +1308,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "once_cell", "wasm-bindgen", @@ -1357,14 +1343,19 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lettre" -version = "0.11.19" +version = "0.11.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" +checksum = "dabda5859ee7c06b995b9d1165aa52c39110e079ef609db97178d86aeb051fa7" dependencies = [ "base64 0.22.1", - "chumsky", "email-encoding", "email_address", "fastrand", @@ -1377,34 +1368,28 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "socket2 0.6.1", + "socket2 0.6.3", "tokio", "url", ] [[package]] name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "libm" -version = "0.2.15" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -1417,9 +1402,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "loom" @@ -1467,9 +1452,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "migrations_internals" @@ -1478,7 +1463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" dependencies = [ "serde", - "toml 0.9.8", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -1498,16 +1483,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1516,9 +1491,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1534,7 +1509,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http 1.4.0", "httparse", "memchr", "mime", @@ -1546,9 +1521,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -1601,9 +1576,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -1642,15 +1617,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - [[package]] name = "oid-registry" version = "0.7.1" @@ -1662,9 +1628,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -1674,9 +1640,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ "bitflags", "cfg-if", @@ -1700,15 +1666,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -1736,7 +1702,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1786,15 +1752,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -1816,9 +1776,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -1849,6 +1809,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1873,9 +1843,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1893,16 +1863,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "psm" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" -dependencies = [ - "ar_archive_writer", - "cc", -] - [[package]] name = "pwhash" version = "1.0.0" @@ -1920,18 +1880,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" [[package]] name = "r-efi" @@ -1939,6 +1899,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "r2d2" version = "0.8.10" @@ -1968,7 +1934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1988,7 +1954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1997,14 +1963,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -2040,9 +2006,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2052,9 +2018,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2063,9 +2029,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -2075,7 +2041,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2188,6 +2154,12 @@ dependencies = [ "quote", ] +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -2199,9 +2171,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -2218,15 +2190,15 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -2254,9 +2226,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation", @@ -2267,14 +2239,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -2317,15 +2295,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2339,9 +2317,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -2412,30 +2390,31 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] [[package]] name = "simple_logger" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291bee647ce7310b0ea721bfd7e0525517b4468eb7c7e15eb8bd774343179702" +checksum = "c7038d0e96661bf9ce647e1a6f6ef6d6f3663f66d9bf741abf14ba4876071c17" dependencies = [ "colored", "log", @@ -2445,9 +2424,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2467,12 +2446,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2496,19 +2475,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "stacker" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "state" version = "0.6.0" @@ -2532,9 +2498,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2554,12 +2520,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2576,11 +2542,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2596,9 +2562,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2616,9 +2582,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -2626,22 +2592,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2649,9 +2615,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -2659,25 +2625,25 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -2686,9 +2652,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -2697,9 +2663,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2722,15 +2688,15 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2744,9 +2710,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -2762,16 +2728,16 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.1", ] [[package]] @@ -2788,9 +2754,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2799,9 +2765,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -2810,9 +2776,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -2831,9 +2797,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -2878,12 +2844,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - [[package]] name = "unicode-id" version = "0.3.6" @@ -2892,9 +2852,9 @@ checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -2926,14 +2886,15 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2950,13 +2911,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.18.1" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] @@ -3031,18 +2992,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -3053,9 +3023,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3063,9 +3033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -3076,18 +3046,52 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "webauthn-attestation-ca" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77a2892ec44032e6c48dad9aad1b05fada09c346ada11d8d32db119b4b4f205" +checksum = "fafcf13f7dc1fb292ed4aea22cdd3757c285d7559e9748950ee390249da4da6b" dependencies = [ "base64urlsafedata", "openssl", @@ -3099,9 +3103,9 @@ dependencies = [ [[package]] name = "webauthn-rs" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7c3a2f9c8bddd524e47bbd427bcf3a28aa074de55d74470b42a91a41937b8e" +checksum = "1b24d082d3360258fefb6ffe56123beef7d6868c765c779f97b7a2fcf06727f8" dependencies = [ "base64urlsafedata", "serde", @@ -3113,9 +3117,9 @@ dependencies = [ [[package]] name = "webauthn-rs-core" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f1d80f3146382529fe70a3ab5d0feb2413a015204ed7843f9377cd39357fc4" +checksum = "15784340a24c170ce60567282fb956a0938742dbfbf9eff5df793a686a009b8b" dependencies = [ "base64 0.21.7", "base64urlsafedata", @@ -3124,8 +3128,8 @@ dependencies = [ "nom 7.1.3", "openssl", "openssl-sys", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.2", + "rand_chacha 0.9.0", "serde", "serde_cbor_2", "serde_json", @@ -3140,9 +3144,9 @@ dependencies = [ [[package]] name = "webauthn-rs-proto" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e786894f89facb9aaf1c5f6559670236723c98382e045521c76f3d5ca5047bd" +checksum = "16a1fb2580ce73baa42d3011a24de2ceab0d428de1879ece06e02e8c416e497c" dependencies = [ "base64 0.21.7", "base64urlsafedata", @@ -3168,7 +3172,7 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", + "windows-link", "windows-result", "windows-strings", ] @@ -3195,12 +3199,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -3213,7 +3211,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3222,7 +3220,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3234,31 +3232,13 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3285,30 +3265,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3321,12 +3284,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3339,12 +3296,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3357,24 +3308,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3387,12 +3326,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3405,12 +3338,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3423,12 +3350,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3442,31 +3363,116 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.7.13" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x509-parser" @@ -3496,9 +3502,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3507,9 +3513,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -3522,7 +3528,6 @@ name = "zauth" version = "2.0.2" dependencies = [ "askama", - "askama_rocket", "base64 0.22.1", "bincode", "chrono", @@ -3547,7 +3552,7 @@ dependencies = [ "serde_urlencoded", "simple_logger", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml 0.8.23", "urlencoding", "validator", @@ -3557,18 +3562,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -3577,18 +3582,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -3598,9 +3603,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -3609,9 +3614,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -3620,11 +3625,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 896bebef..99c4b7c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,45 +5,44 @@ edition = "2024" authors = ["Rien Maertens "] [dependencies] -askama = { git = "https://github.com/djc/askama.git", rev = "40bb338", features = ["with-rocket", "mime", "mime_guess"] } -askama_rocket = { git = "https://github.com/djc/askama.git", rev = "40bb338" } +askama = "0.15.6" base64 = "0.22" bincode = {version = "2.0", features = ["serde"]} pwhash = "1.0" chrono = { version = "0.4", features = ["serde"] } -lazy_static = "1.1" +lazy_static = "1.5" lettre = { version = "0.11", features = ["builder", "smtp-transport"] } log = "0.4" urlencoding = "2.1" toml = "0.8" rand = "0.9" -regex = "1.6" -rocket = { version = "0.5.0-rc.3", features = [ "json", "secrets" ] } -rocket_sync_db_pools = { version = "0.1.0-rc.3", features = [ "diesel_postgres_pool" ] } +regex = "1.12" +rocket = { version = "0.5.1", features = [ "json", "secrets" ] } +rocket_sync_db_pools = { version = "0.1.0", features = [ "diesel_postgres_pool" ] } serde = "1.0" serde_json = "1.0" serde_derive = "1.0" serde_urlencoded = "0.7" -simple_logger = "5.0" -diesel = { version = "2.3.4", features = ["postgres", "r2d2", "chrono"] } +simple_logger = "5.2" +diesel = { version = "2.3.7", features = ["postgres", "r2d2", "chrono"] } diesel-derive-enum = { version = "3.0.0-beta.1", features = ["postgres"] } diesel_migrations = "2.3.1" -tempfile = "3.1" +tempfile = "3.27" parking_lot = { version = "0.12" } thiserror = "2.0" validator = { version = "0.20", features = [ "derive" ] } -jsonwebtoken = "9.1" +jsonwebtoken = "9.3" openssl = "0.10" -webauthn-rs = { version = "0.5.0", features = [ +webauthn-rs = { version = "0.5.4", features = [ "conditional-ui" ]} -webauthn-rs-proto = "0.5.1" +webauthn-rs-proto = "0.5.4" markdown = "1.0.0" [dev-dependencies] -webauthn-rs = { version = "0.5.0", features = [ +webauthn-rs = { version = "0.5.4", features = [ "conditional-ui", "danger-allow-state-serialisation" ]} diff --git a/README.md b/README.md index c95adcb5..f040548d 100644 --- a/README.md +++ b/README.md @@ -81,12 +81,13 @@ You can also test the OAuth2 flow manually by running the flask application in ### Creating a migration -To make a change to the database scheme, we use diesel migrations +To make a change to the database schema, we use diesel migrations. -1. To create a migration, run `diesel migration generate ` -2. Fill in the generated `up.sql` and `down.sql` -3. Re-generate `src/models/schema.rs` by running `diesel print-schema` - > Caution: at the moment, the `users` schema cannot be generated correctly automatically. +1. To create a migration, run `diesel migration generate ` +2. Fill in the generated `up.sql` and `down.sql`. +3. Run the new migration with `diesel migration run` +4. Check that the migration can be undone and redone with `diesel migration redo` +5. By runnning a migration, the `src/models/schema.rs` file will have been regenerated, so make sure `cargo test` still succeeds and add the changes to git. ### Using Nix diff --git a/diesel.toml b/diesel.toml index 9bb0fcf4..53c9d5ff 100644 --- a/diesel.toml +++ b/diesel.toml @@ -2,3 +2,11 @@ # see diesel.rs/guides/configuring-diesel-cli [print_schema] +file = "src/models/schema.rs" +import_types = [ + "diesel::sql_types::*", + "crate::models::schema_custom_sql_types::*", +] +except_custom_type_definitions = [ + "UserState", +] diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 00000000..bc094bd9 --- /dev/null +++ b/manifest.scm @@ -0,0 +1,30 @@ +;; Example usage: +;; guix shell -mmanifest.scm -CFN -Sbin/cc=bin/gcc -ETERM --share=$HOME/.cargo -- sh -c 'LD_LIBRARY_PATH=$LIBRARY_PATH cargo build' + +(use-modules (guix channels) + (guix inferior) + (guix profiles) + (guix ui) + (srfi srfi-11) + (srfi srfi-26)) + +(define (pkgs channels specs) + (let* ((inferior (inferior-for-channels channels)) + (lookup (cut lookup-inferior-packages inferior <> <>))) + (map (lambda (spec) + (let-values (((name version output) + (package-specification->name+version+output spec))) + (list (car (lookup name version)) + output))) + specs))) + +(packages->manifest + (pkgs (list (channel + (inherit %default-guix-channel) + (commit "dd080e7fda2be54e2bcec3814473f90b326cb256"))) + (list "bash" "coreutils" + "gcc-toolchain" "pkg-config" "postgresql" "openssl" + "rust" "rust:cargo" "rust:rust-src" "rust:tools" + "node" + "python" "python-flask" "python-requests" + "nss-certs" "man-db"))) diff --git a/migrations/2025-08-06-201354_alter_user_client_session_to_nullable/down.sql b/migrations/2025-08-06-201354_alter_user_client_session_to_nullable/down.sql index 0efbcde8..98ae9432 100644 --- a/migrations/2025-08-06-201354_alter_user_client_session_to_nullable/down.sql +++ b/migrations/2025-08-06-201354_alter_user_client_session_to_nullable/down.sql @@ -1,4 +1,4 @@ -- This file should undo anything in `up.sql` ALTER TABLE sessions - ALTER COLUMN user_id ADD NOT NULL; + ALTER COLUMN user_id SET NOT NULL; diff --git a/migrations/2025-08-06-220900_add_clients_roles/down.sql b/migrations/2025-08-06-220900_add_clients_roles/down.sql index 23176d37..fafd441d 100644 --- a/migrations/2025-08-06-220900_add_clients_roles/down.sql +++ b/migrations/2025-08-06-220900_add_clients_roles/down.sql @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -DROP TABLE users_roles; +DROP TABLE clients_roles; diff --git a/migrations/2025-10-06-210914_drop_mail_author_fkey/down.sql b/migrations/2025-10-06-210914_drop_mail_author_fkey/down.sql index 3eead2e3..c95ed231 100644 --- a/migrations/2025-10-06-210914_drop_mail_author_fkey/down.sql +++ b/migrations/2025-10-06-210914_drop_mail_author_fkey/down.sql @@ -1,4 +1,4 @@ -- This file should undo anything in `up.sql` ALTER TABLE mails -ADD CONSTRAINT mails_author_fkey; -FOREIGN KEY (author) REFERENCES users(username) +ADD CONSTRAINT mails_author_fkey +FOREIGN KEY (author) REFERENCES users(username); diff --git a/migrations/2026-04-17-164002-0000_rename_users_roles_to_users_assigned_roles/down.sql b/migrations/2026-04-17-164002-0000_rename_users_roles_to_users_assigned_roles/down.sql new file mode 100644 index 00000000..b07fb6f5 --- /dev/null +++ b/migrations/2026-04-17-164002-0000_rename_users_roles_to_users_assigned_roles/down.sql @@ -0,0 +1 @@ +ALTER TABLE users_assigned_roles RENAME TO users_roles; diff --git a/migrations/2026-04-17-164002-0000_rename_users_roles_to_users_assigned_roles/up.sql b/migrations/2026-04-17-164002-0000_rename_users_roles_to_users_assigned_roles/up.sql new file mode 100644 index 00000000..9598fc38 --- /dev/null +++ b/migrations/2026-04-17-164002-0000_rename_users_roles_to_users_assigned_roles/up.sql @@ -0,0 +1 @@ +ALTER TABLE users_roles RENAME TO users_assigned_roles; diff --git a/migrations/2026-04-17-164434-0000_rename_clients_roles_to_clients_assigned_roles/down.sql b/migrations/2026-04-17-164434-0000_rename_clients_roles_to_clients_assigned_roles/down.sql new file mode 100644 index 00000000..f235058f --- /dev/null +++ b/migrations/2026-04-17-164434-0000_rename_clients_roles_to_clients_assigned_roles/down.sql @@ -0,0 +1 @@ +ALTER TABLE clients_assigned_roles RENAME TO clients_roles; diff --git a/migrations/2026-04-17-164434-0000_rename_clients_roles_to_clients_assigned_roles/up.sql b/migrations/2026-04-17-164434-0000_rename_clients_roles_to_clients_assigned_roles/up.sql new file mode 100644 index 00000000..bac199ea --- /dev/null +++ b/migrations/2026-04-17-164434-0000_rename_clients_roles_to_clients_assigned_roles/up.sql @@ -0,0 +1 @@ +ALTER TABLE clients_roles RENAME TO clients_assigned_roles; diff --git a/migrations/2026-04-17-175409-0000_add_visibility_to_roles/down.sql b/migrations/2026-04-17-175409-0000_add_visibility_to_roles/down.sql new file mode 100644 index 00000000..a161f17f --- /dev/null +++ b/migrations/2026-04-17-175409-0000_add_visibility_to_roles/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE roles DROP COLUMN visibility; +DROP TYPE role_visibility; diff --git a/migrations/2026-04-17-175409-0000_add_visibility_to_roles/up.sql b/migrations/2026-04-17-175409-0000_add_visibility_to_roles/up.sql new file mode 100644 index 00000000..f56ef2db --- /dev/null +++ b/migrations/2026-04-17-175409-0000_add_visibility_to_roles/up.sql @@ -0,0 +1,2 @@ +CREATE TYPE role_visibility AS ENUM ('global', 'limited'); +ALTER TABLE roles ADD COLUMN visibility role_visibility DEFAULT 'global' NOT NULL; diff --git a/migrations/2026-04-17-185854-0000_create_roles_limited_to_clients/down.sql b/migrations/2026-04-17-185854-0000_create_roles_limited_to_clients/down.sql new file mode 100644 index 00000000..d0b8d6bf --- /dev/null +++ b/migrations/2026-04-17-185854-0000_create_roles_limited_to_clients/down.sql @@ -0,0 +1 @@ +DROP TABLE roles_limited_to_clients; diff --git a/migrations/2026-04-17-185854-0000_create_roles_limited_to_clients/up.sql b/migrations/2026-04-17-185854-0000_create_roles_limited_to_clients/up.sql new file mode 100644 index 00000000..9d10f696 --- /dev/null +++ b/migrations/2026-04-17-185854-0000_create_roles_limited_to_clients/up.sql @@ -0,0 +1,5 @@ +CREATE TABLE roles_limited_to_clients ( + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + client_id INTEGER REFERENCES clients(id) ON DELETE CASCADE, + PRIMARY KEY (role_id, client_id) +); diff --git a/migrations/2026-04-21-143710-0000_remove_client_id_from_roles/down.sql b/migrations/2026-04-21-143710-0000_remove_client_id_from_roles/down.sql new file mode 100644 index 00000000..ffb3c66f --- /dev/null +++ b/migrations/2026-04-21-143710-0000_remove_client_id_from_roles/down.sql @@ -0,0 +1,9 @@ +ALTER TABLE roles ADD COLUMN client_id INTEGER REFERENCES clients(id) DEFAULT NULL; + +UPDATE roles SET client_id = ( + SELECT client_id FROM roles_limited_to_clients + WHERE roles_limited_to_clients.role_id = roles.id +); +TRUNCATE TABLE roles_limited_to_clients; + +UPDATE roles SET visibility = 'global'; diff --git a/migrations/2026-04-21-143710-0000_remove_client_id_from_roles/up.sql b/migrations/2026-04-21-143710-0000_remove_client_id_from_roles/up.sql new file mode 100644 index 00000000..bf71eda6 --- /dev/null +++ b/migrations/2026-04-21-143710-0000_remove_client_id_from_roles/up.sql @@ -0,0 +1,12 @@ +UPDATE roles SET visibility = 'global' + WHERE client_id IS NULL; +UPDATE roles SET visibility = 'limited' + WHERE client_id IS NOT NULL; + +TRUNCATE TABLE roles_limited_to_clients; +INSERT INTO roles_limited_to_clients (role_id, client_id) ( + SELECT id, client_id FROM roles + WHERE client_id IS NOT NULL +); + +ALTER TABLE roles DROP COLUMN client_id; diff --git a/package-lock.json b/package-lock.json index fb0bcf71..46fd1426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,12 @@ { "name": "zauth", - "version": "1.0.0", + "version": "0.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "zauth", - "version": "1.0.0", - "license": "MIT", + "version": "0.0.0", "devDependencies": { "bulma": "^0.9.4", "sass": "^1.58.3" diff --git a/package.json b/package.json index 6b670511..7b2c42f9 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,13 @@ { "name": "zauth", - "version": "1.0.0", - "description": "The name is open for discussion.", - "author": "Zeus WPI ", - "homepage": "https://github.com/ZeusWPI/zauth#readme", + "private": true, + "version": "0.0.0", + "description": "This npm package.json is only used for building the css.", + "type": "module", "scripts": { "build": "sass static/scss/* static/dist/css/application.css --style compressed", "watch": "npm run build -- --watch" }, - "repository": { - "type": "git", - "url": "git+https://github.com/ZeusWPI/zauth.git" - }, - "license": "MIT", "devDependencies": { "sass": "^1.58.3", "bulma": "^0.9.4" diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 00000000..d8ac93be --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.93.1 diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 292fe499..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "stable" diff --git a/src/config.rs b/src/config.rs index 778815d9..5f97ac63 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use lettre::message::Mailbox; use rocket::http::uri::Absolute; use rocket::serde::Deserialize; -#[derive(Debug, Deserialize, Clone)] +#[derive(Clone, Debug, Deserialize)] #[serde(crate = "rocket::serde")] pub struct Config { pub admin_email: String, diff --git a/src/controllers/clients_controller.rs b/src/controllers/clients_controller.rs index 99f4e51f..16642ded 100644 --- a/src/controllers/clients_controller.rs +++ b/src/controllers/clients_controller.rs @@ -1,10 +1,10 @@ use rocket::form::Form; use rocket::http::Status; +use rocket::response::content::RawHtml; use rocket::response::status; use rocket::response::status::Custom; use rocket::response::{Redirect, Responder}; use rocket::serde::json::Json; -use std::fmt::Debug; use crate::DbConn; use crate::ephemeral::from_api::Api; @@ -22,7 +22,7 @@ use crate::views::accepter::Accepter; // struct does not play nice with any other libraries. (So it can't be // deserialized by serde.) -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct JsonClientChange { pub name: Option, pub needs_grant: Option, @@ -30,7 +30,7 @@ pub struct JsonClientChange { pub redirect_uri_list: Option, } -#[derive(FromForm, Debug)] +#[derive(Debug, FromForm)] pub struct FormClientChange { pub name: Option, pub needs_grant: Vec, @@ -62,43 +62,51 @@ impl std::convert::From for ClientChange { #[get("/clients")] pub async fn list_clients<'r>( - db: DbConn, + // from headers session: AdminSession, + // injected + db: DbConn, ) -> Result> { let clients = Client::all(&db).await?; Ok(Accepter { - html: template! { - "clients/index.html"; + html: RawHtml(template!("clients/index.html", { clients: Vec = clients.clone(), current_user: User = session.admin, - }, + })), json: Json(clients), }) } #[get("/clients//edit")] pub async fn update_client_page<'r>( + // from url id: i32, + // from headers session: AdminSession, + // injected db: DbConn, ) -> Result> { let client = Client::find(id, &db).await?; let roles = Role::all(&db).await?; - Ok(template! { "clients/edit_client.html"; + Ok(RawHtml(template!("clients/edit_client.html", { current_user: User = session.admin, client: Client = client.clone(), client_roles: Vec = client.roles(&db).await?, roles: Vec = roles - }) + }))) } #[put("/clients/", data = "")] pub async fn update_client<'r>( + // from url id: i32, + // from body change: SplitApi, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let mut client = Client::find(id, &db).await?; @@ -112,8 +120,11 @@ pub async fn update_client<'r>( #[delete("/clients/")] pub async fn delete_client<'r>( + // from url id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let client = Client::find(id, &db).await?; @@ -126,9 +137,12 @@ pub async fn delete_client<'r>( #[post("/clients", data = "")] pub async fn create_client<'r>( + // from body client: Api, - db: DbConn, + // from headers _admin: AdminSession, + // injected + db: DbConn, ) -> Result> { let client = Client::create(client.into_inner(), &db).await?; Ok(Accepter { @@ -139,20 +153,26 @@ pub async fn create_client<'r>( #[get("/clients//generate_secret")] pub async fn get_generate_secret<'r>( + // from url id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let client = Client::find(id, &db).await?; - Ok(template! { "clients/confirm_generate_secret.html"; + Ok(RawHtml(template!("clients/confirm_generate_secret.html", { client: Client = client, - }) + }))) } #[post("/clients//generate_secret")] pub async fn post_generate_secret<'r>( + // from url id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let client = Client::find(id, &db).await?; @@ -190,7 +210,9 @@ impl ClientInfo { #[get("/current_client")] pub async fn current_client( + // from headers session: ClientSession, + // injected db: DbConn, ) -> Result> { Ok(Json(ClientInfo::new(session.client, &db).await?)) @@ -198,10 +220,14 @@ pub async fn current_client( #[post("/clients//roles", data = "")] pub async fn add_role<'r>( + // from url id: i32, + // from body role_id: Form, - db: DbConn, + // from headers _session: AdminSession, + // injected + db: DbConn, ) -> Result> { let role = Role::find(*role_id, &db).await?; let client = Client::find(id, &db).await?; @@ -214,9 +240,12 @@ pub async fn add_role<'r>( #[delete("/clients//roles/")] pub async fn delete_role<'r>( - role_id: i32, + // from url id: i32, + role_id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let role = Role::find(role_id, &db).await?; diff --git a/src/controllers/mailing_list_controller.rs b/src/controllers/mailing_list_controller.rs index 1f5598ce..6185659a 100644 --- a/src/controllers/mailing_list_controller.rs +++ b/src/controllers/mailing_list_controller.rs @@ -1,7 +1,10 @@ +use askama::Template; use lettre::message::{Mailbox, header}; +use rocket::State; use rocket::form::validate::Contains; +use rocket::response::content::RawHtml; use rocket::response::{Redirect, Responder}; -use std::fmt::Debug; +use rocket::serde::json::Json; use crate::DbConn; use crate::config::Config; @@ -14,24 +17,22 @@ use crate::mailer::Mailer; use crate::models::mail::*; use crate::models::user::User; use crate::views::accepter::Accepter; -use askama::Template; -use rocket::State; -use rocket::serde::json::Json; /// Show an overview of all mails, sorted by send date #[get("/mails")] pub async fn list_mails<'r>( + // from headers session: UserSession, + // injected db: DbConn, ) -> Result> { let mails = Mail::all(&db).await?; Ok(Accepter { - html: template! { - "maillist/index.html"; + html: RawHtml(template!("maillist/index.html", { current_user: User = session.user, mails: Vec = mails.clone(), - }, + })), json: Json(mails), }) } @@ -39,11 +40,14 @@ pub async fn list_mails<'r>( /// Send a new mail and archive it #[post("/mails", data = "", rank = 2)] pub async fn send_mail_as_user<'r>( - _session: AdminSession, + // from body new_mail: Api, - db: DbConn, + // from headers + _session: AdminSession, + // injected conf: &'r State, mailer: &'r State, + db: DbConn, ) -> Result> { send_mail(new_mail, db, conf, mailer).await } @@ -51,11 +55,14 @@ pub async fn send_mail_as_user<'r>( /// Send a new mail as a client and archive it #[post("/mails", data = "", rank = 1)] pub async fn send_mail_as_client<'r>( - client_session: ClientSession, + // from body new_mail: Api, - db: DbConn, + // from headers + client_session: ClientSession, + // injected conf: &'r State, mailer: &'r State, + db: DbConn, ) -> Result> { if !client_session .client @@ -83,14 +90,6 @@ impl From for header::ContentType { } } -// Separate template struct to disable HTML escaping -#[derive(Template)] -#[template(path = "mails/mailinglist_mail.html", escape = "none")] -struct MailingListTemplate { - body: String, - unsubscribe_url: String, -} - async fn send_mail<'r>( new_mail: Api, db: DbConn, @@ -110,19 +109,29 @@ async fn send_mail<'r>( uri!(conf.base_url(), show_confirm_unsubscribe(token)); let text = match &mail.content_type { - &ContentType::Plain => template!( - "mails/mailinglist_mail.txt"; + &ContentType::Plain => template!("mails/mailinglist_mail.txt", { body: String = body.clone(), unsubscribe_url: String = unsubscribe_url.to_string(), - ) - .render(), - &ContentType::Markdown => MailingListTemplate { - body: body.clone(), - unsubscribe_url: unsubscribe_url.to_string(), - } - .render(), - } - .map_err(InternalError::from)?; + })?, + &ContentType::Markdown => { + #[derive(Template)] + #[template( + path = "mails/mailinglist_mail.html", + escape = "none" + )] + struct MailingListTemplate { + body: String, + unsubscribe_url: String, + } + MailingListTemplate { + body: body.clone(), + unsubscribe_url: unsubscribe_url.to_string(), + } + .render() + .map_err(InternalError::from) + .map_err(ZauthError::from)? + }, + }; mailer.create_for_mailinglist( receiver, @@ -141,30 +150,32 @@ async fn send_mail<'r>( /// Show the new_mail page #[get("/mails/new")] pub async fn show_create_mail_page<'r>( + // from headers session: AdminSession, ) -> Result> { - Ok(template! { - "maillist/new_mail.html"; + Ok(RawHtml(template!("maillist/new_mail.html", { current_user: User = session.admin, - }) + }))) } /// Show a specific mail #[get("/mails/")] pub async fn show_mail<'r>( + // from url + id: i32, + // from headers session: UserSession, + // injected db: DbConn, - id: i32, ) -> Result> { let mail = Mail::get_by_id(id, &db).await?; Ok(Accepter { - html: template! { - "maillist/show_mail.html"; + html: RawHtml(template!("maillist/show_mail.html", { current_user: User = session.user, mail: Mail = mail.clone(), rendered_body: Option = mail.render_body().ok(), - }, + })), json: Json(mail), }) } diff --git a/src/controllers/oauth_controller.rs b/src/controllers/oauth_controller.rs index 8e994767..039167b6 100644 --- a/src/controllers/oauth_controller.rs +++ b/src/controllers/oauth_controller.rs @@ -4,29 +4,56 @@ use jsonwebtoken::jwk::JwkSet; use rocket::State; use rocket::form::Form; use rocket::http::{Cookie, CookieJar}; +use rocket::response::content::RawHtml; use rocket::response::{Redirect, Responder}; use rocket::serde::json::Json; -use std::fmt::Debug; use crate::DbConn; use crate::config::Config; use crate::ephemeral::session::UserSession; -use crate::errors::Either::{Left, Right}; +use crate::ephemeral::session::ensure_logged_in_and_redirect; +use crate::errors::Either; +use crate::errors::OAuthError::InvalidCookie; use crate::errors::*; use crate::http_authentication::BasicAuthentication; use crate::jwt::JWTBuilder; use crate::models::client::*; use crate::models::session::*; use crate::models::user::*; +use crate::token_store::TokenStore; use crate::util::split_scopes; -use crate::ephemeral::session::ensure_logged_in_and_redirect; -use crate::errors::OAuthError::InvalidCookie; -use crate::token_store::TokenStore; +/// See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata +#[get("/.well-known/openid-configuration")] +pub async fn get_well_known_openid_configuration<'r>( + // injected + config: &State, +) -> impl Responder<'r, 'static> { + // FIXME: Should only be serialized once on boot since config is immutable. + #[derive(Serialize)] + pub struct OpenidConfiguration { + pub issuer: String, + pub authorization_endpoint: String, + pub token_endpoint: String, + pub jwks_uri: String, + pub response_types_supported: Vec, + pub grant_types_supported: Vec, + pub userinfo_endpoint: String, + } + Json(OpenidConfiguration { + issuer: config.base_url.clone(), + authorization_endpoint: config.base_url.clone() + "/oauth/authorize", + token_endpoint: config.base_url.clone() + "/oauth/token", + jwks_uri: config.base_url.clone() + "/oauth/jwks", + response_types_supported: Vec::from(["code".to_string()]), + grant_types_supported: Vec::from(["authorization_code".to_string()]), + userinfo_endpoint: config.base_url.clone() + "/current_user", + }) +} const OAUTH_COOKIE: &str = "ZAUTH_OAUTH"; -#[derive(Serialize, Deserialize, Debug, FromForm, UriDisplayQuery)] +#[derive(Debug, Deserialize, FromForm, Serialize, UriDisplayQuery)] pub struct AuthState { pub client_id: i32, pub client_name: String, @@ -91,7 +118,7 @@ impl AuthState { } } -#[derive(Debug, FromForm, Serialize, Deserialize)] +#[derive(Debug, Deserialize, FromForm, Serialize)] pub struct AuthorizationRequest { pub response_type: String, pub client_id: String, @@ -102,8 +129,11 @@ pub struct AuthorizationRequest { #[get("/oauth/authorize?")] pub async fn authorize<'r>( - cookies: &CookieJar<'_>, + // from url req: AuthorizationRequest, + // from headers + cookies: &CookieJar<'_>, + // injected db: DbConn, ) -> Result + use<'r>> { if !req.response_type.eq("code") { @@ -118,11 +148,10 @@ pub async fn authorize<'r>( let client_description = client.description.clone(); let state = AuthState::from_req(client, req); cookies.add_private(state.into_cookie()?); - Ok(template! { - "oauth/authorize.html"; + Ok(RawHtml(template!("oauth/authorize.html", { authorize_post_url: String = uri!(do_authorize).to_string(), client_description: String = client_description, - }) + }))) } else { Err(AuthenticationError::Unauthorized(format!( "client with id {} is not authorized to use redirect_uri '{}'", @@ -139,15 +168,17 @@ pub async fn authorize<'r>( } } -#[derive(FromForm, Debug)] +#[derive(Debug, FromForm)] pub struct AuthorizeFormData { authorized: bool, } #[post("/oauth/authorize", data = "
")] pub async fn do_authorize( - cookies: &CookieJar<'_>, + // from body form: Form, + // from headers + cookies: &CookieJar<'_>, ) -> Result { let state = AuthState::from_cookies(cookies)?; if form.into_inner().authorized { @@ -157,7 +188,7 @@ pub async fn do_authorize( } } -#[derive(FromForm, Debug)] +#[derive(Debug, FromForm)] pub struct GrantFormData { grant: bool, } @@ -173,8 +204,10 @@ pub struct UserToken { #[get("/oauth/grant")] pub async fn grant_get<'r>( - session: UserSession, + // from headers cookies: &CookieJar<'_>, + session: UserSession, + // injected token_store: &State>, db: DbConn, ) -> Result< @@ -187,12 +220,11 @@ pub async fn grant_get<'r>( match Client::find(state.client_id, &db).await { Ok(client) => { if client.needs_grant { - Ok(Left(template! { - "oauth/grant.html"; + Ok(Either::Left(RawHtml(template!("oauth/grant.html", { client_description: String = client.description.clone(), - })) + })))) } else { - Ok(Right( + Ok(Either::Right( authorization_granted( state, session.user, @@ -208,9 +240,12 @@ pub async fn grant_get<'r>( #[post("/oauth/grant", data = "")] pub async fn grant_post<'r>( - session: UserSession, - cookies: &CookieJar<'_>, + // from body form: Form, + // from headers + cookies: &CookieJar<'_>, + session: UserSession, + // injected token_store: &State>, ) -> Result + use<'r>> { let data = form.into_inner(); @@ -255,7 +290,7 @@ fn authorization_denied(state: AuthState) -> Redirect { )) } -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] pub struct TokenSuccess { access_token: String, token_type: String, @@ -264,7 +299,7 @@ pub struct TokenSuccess { expires_in: i64, } -#[derive(FromForm, Debug)] +#[derive(Debug, FromForm)] pub struct TokenFormData { grant_type: String, code: Option, @@ -431,11 +466,14 @@ pub async fn client_credentials_grant( #[post("/oauth/token", data = "")] pub async fn token( - auth: Option, + // from body form: Form, + // from headers + auth: Option, + // injected config: &State, - token_state: &State>, jwt_builder: &State, + token_state: &State>, db: DbConn, ) -> Result> { let data = form.into_inner(); @@ -460,6 +498,9 @@ pub async fn token( } #[get("/oauth/jwks")] -pub async fn jwks(jwt_builder: &State) -> Json { +pub async fn jwks( + // injected + jwt_builder: &State, +) -> Json { Json(jwt_builder.jwks.clone()) } diff --git a/src/controllers/pages_controller.rs b/src/controllers/pages_controller.rs index 77233269..9b69f3a5 100644 --- a/src/controllers/pages_controller.rs +++ b/src/controllers/pages_controller.rs @@ -1,16 +1,17 @@ +use rocket::response::content::RawHtml; use rocket::response::{Redirect, Responder}; use crate::controllers::users_controller::rocket_uri_macro_show_user; - use crate::ephemeral::session::UserSession; use crate::errors::Either; #[get("/")] pub fn home_page<'r>( + // from headers session: Option, ) -> Either> { match session { - None => Either::Right(template! {"pages/home.html"}), + None => Either::Right(RawHtml(template!("pages/home.html"))), Some(session) => { Either::Left(Redirect::to(uri!(show_user(session.user.username)))) }, diff --git a/src/controllers/roles_controller.rs b/src/controllers/roles_controller.rs index 85cd2193..ade618d2 100644 --- a/src/controllers/roles_controller.rs +++ b/src/controllers/roles_controller.rs @@ -1,50 +1,57 @@ +use std::vec::Vec; + use diesel::result::DatabaseErrorKind; use rocket::form::Form; use rocket::http::Status; +use rocket::response::content::RawHtml; use rocket::response::status::Custom; use rocket::response::{Redirect, Responder, status}; use rocket::serde::json::Json; -use std::fmt::Debug; use crate::DbConn; use crate::ephemeral::from_api::Api; use crate::ephemeral::session::AdminSession; use crate::errors::{Either, InternalError, Result, ZauthError}; use crate::models::client::Client; -use crate::models::role::{NewRole, Role}; +use crate::models::role::{NewRole, Role, RoleVisibility}; use crate::models::user::User; use crate::views::accepter::Accepter; #[get("/roles?")] pub async fn list_roles<'r>( + // from url error: Option, - db: DbConn, + // from headers session: AdminSession, + // injected + db: DbConn, ) -> Result> { - let roles = Role::all(&db).await?; - let clients = Client::all(&db).await?; + let roles: Vec = Role::all(&db).await?; Ok(Accepter { - html: template! { - "roles/index.html"; - roles: Vec = roles.clone(), - clients: Vec = clients, - error: Option = error, + html: RawHtml(template!("roles/index.html", { current_user: User = session.admin, - }, + error: Option = error, + roles: Vec = roles.clone(), + })), json: Json(roles), }) } #[post("/roles", data = "")] pub async fn create_role<'r, 'a>( + // from body role: Api, - db: DbConn, + // from headers _admin: AdminSession, + // injected + db: DbConn, ) -> Result< Either, impl Responder<'r, 'static> + use<'r>>, > { - let role = Role::create(role.into_inner(), &db).await; + let new_role: NewRole = role.into_inner(); + let new_role_name = new_role.name.clone(); + let role = Role::create(new_role, &db).await; match role { Ok(role) => Ok(Either::Left(Accepter { html: Redirect::to(uri!(list_roles(None::))), @@ -55,68 +62,175 @@ pub async fn create_role<'r, 'a>( DatabaseErrorKind::UniqueViolation, _, ), - ))) => Ok(Either::Right(Accepter { - html: Redirect::to(uri!(list_roles(Some( - "role name already exists" - )))), - json: "role name already exists", + ))) => Ok(Either::Right({ + let error_msg = + format!("Role with name “{}” already exists", new_role_name); + Accepter { + html: Redirect::to(uri!(list_roles(Some(error_msg.clone())))), + json: error_msg, + } })), Err(err) => Err(err), } } -#[get("/roles/?&")] +#[get("/roles/?&")] pub async fn show_role_page<'r>( + // from url id: i32, error: Option, - info: Option, + success: Option, + // from headers session: AdminSession, + // injected db: DbConn, ) -> Result> { let role = Role::find(id, &db).await?; - let users = role.clone().users(&db).await?; - let clients = role.clone().clients(&db).await?; - let client = if let Some(id) = role.client_id { - Some(Client::find(id, &db).await?) - } else { - None - }; + let limited_to_clients: Vec = + role.clone().limited_to_clients(&db).await?; + let limited_to_client_ids: Vec = + limited_to_clients.iter().map(|client| client.id).collect(); + + let users: Vec = role.clone().users(&db).await?; + let user_ids: Vec = users.iter().map(|user| user.id).collect(); - Ok(template! { "roles/show_role.html"; + let clients: Vec = role.clone().clients(&db).await?; + let client_ids: Vec = clients.iter().map(|client| client.id).collect(); + + let all_users: Vec = User::all(&db).await?; + let all_clients: Vec = Client::all(&db).await?; + + Ok(RawHtml(template!("roles/show_role.html", { current_user: User = session.admin, + + error: Option = error, + success: Option = success, + role: Role = role, - client: Option = client, + limited_to_clients: Vec = limited_to_clients, + limited_to_client_ids: Vec = limited_to_client_ids, users: Vec = users, - clients: Vec = clients, - error: Option = error, - info: Option = info + user_ids: Vec = user_ids, + clients: Vec = clients, + client_ids: Vec = client_ids, + + all_clients: Vec = all_clients, + all_users: Vec = all_users, + }))) +} + +#[post("/roles//description", data = "")] +pub async fn update_description<'r>( + // from url + role_id: i32, + // from body + description: Form, + // from headers + _session: AdminSession, + // injected + db: DbConn, +) -> Result> { + let mut role: Role = Role::find(role_id, &db).await?; + role.description = description.clone(); + role.update(&db).await?; + Ok(Accepter { + html: Redirect::to(uri!(show_role_page( + role_id, + None::, + Some("Successfully changed description") + ))), + json: Custom(Status::Ok, ()), }) } -#[delete("/roles/")] -pub async fn delete_role<'r>( - id: i32, +#[post("/roles//visibility", data = "")] +pub async fn update_visibility<'r>( + // from url + role_id: i32, + // from body + visibility: Form, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { - let role = Role::find(id, &db).await?; - role.delete(&db).await?; + let mut role: Role = Role::find(role_id, &db).await?; + role.visibility = visibility.into_inner(); + let role: Role = role.update(&db).await?; Ok(Accepter { - html: Redirect::to(uri!(list_roles(None::))), - json: Custom(Status::NoContent, ()), + html: Redirect::to(uri!(show_role_page( + role_id, + None::, + Some(format!( + "Successfully changed visibility to “{}”", + role.visibility, + )), + ))), + json: Custom(Status::Ok, ()), }) } -#[post("/roles//users", data = "")] -pub async fn add_user<'r>( - username: Form, +#[post("/roles//limited_to_clients", data = "")] +pub async fn add_limited_to_client<'r>( + // from url role_id: i32, + // from body + client_id: Form, + // from headers + _session: AdminSession, + // injected db: DbConn, +) -> Result> { + let role = Role::find(role_id, &db).await?; + let client_result = Client::find(client_id.clone(), &db).await; + Ok(match client_result { + Ok(client) => { + role.add_client_to_limited_to(client.id, &db).await?; + Accepter { + html: Redirect::to(uri!(show_role_page( + role.id, + None::, + Some(format!( + "Successfully added client “{}” to the “limited to” list", + client.name, + )), + ))), + json: Custom(Status::Ok, ()), + } + }, + Err(ZauthError::NotFound(_)) => Accepter { + html: Redirect::to(uri!(show_role_page( + role.id, + Some("Client not found"), + None::, + ))), + json: Custom(Status::NotFound, ()), + }, + _ => Accepter { + html: Redirect::to(uri!(show_role_page( + role.id, + Some("An internal server error occured"), + None::, + ))), + json: Custom(Status::InternalServerError, ()), + }, + }) +} + +#[post("/roles//users", data = "")] +pub async fn add_user<'r>( + // from url + role_id: i32, + // from body + user_id: Form, + // from headers _session: AdminSession, + // injected + db: DbConn, ) -> Result> { let role = Role::find(role_id, &db).await?; - let user_result = User::find_by_username(username.clone(), &db).await; + let user_result = User::find(user_id.into_inner(), &db).await; Ok(match user_result { Ok(user) => { role.add_user(user.id, &db).await?; @@ -124,7 +238,10 @@ pub async fn add_user<'r>( html: Redirect::to(uri!(show_role_page( role.id, None::, - Some("user added") + Some(format!( + "Successfully assigned this role to user “{}”", + user.username, + )), ))), json: Custom(Status::Ok, ()), } @@ -132,31 +249,35 @@ pub async fn add_user<'r>( Err(ZauthError::NotFound(_)) => Accepter { html: Redirect::to(uri!(show_role_page( role.id, - Some("user not found"), - None:: + Some("User not found"), + None::, ))), json: Custom(Status::NotFound, ()), }, _ => Accepter { html: Redirect::to(uri!(show_role_page( role.id, - Some("error occured"), - None:: + Some("An internal server error occured"), + None::, ))), json: Custom(Status::InternalServerError, ()), }, }) } -#[post("/roles//clients", data = "")] +#[post("/roles//clients", data = "")] pub async fn add_client<'r>( - client_name: Form, + // from url role_id: i32, - db: DbConn, + // from body + client_id: Form, + // from headers _session: AdminSession, + // injected + db: DbConn, ) -> Result> { let role = Role::find(role_id, &db).await?; - let client_result = Client::find_by_name(client_name.clone(), &db).await; + let client_result = Client::find(client_id.clone(), &db).await; Ok(match client_result { Ok(client) => { role.add_client(client.id, &db).await?; @@ -164,7 +285,10 @@ pub async fn add_client<'r>( html: Redirect::to(uri!(show_role_page( role.id, None::, - Some("client added") + Some(format!( + "Successfully assigned this role to client “{}”", + client.name, + )), ))), json: Custom(Status::Ok, ()), } @@ -172,36 +296,69 @@ pub async fn add_client<'r>( Err(ZauthError::NotFound(_)) => Accepter { html: Redirect::to(uri!(show_role_page( role.id, - Some("client not found"), - None:: + Some("Client not found"), + None::, ))), json: Custom(Status::NotFound, ()), }, _ => Accepter { html: Redirect::to(uri!(show_role_page( role.id, - Some("error occured"), - None:: + Some("An internal server error occured"), + None::, ))), json: Custom(Status::InternalServerError, ()), }, }) } +#[delete("/roles//limited_to_clients/")] +pub async fn delete_limited_to_client<'r>( + // from url + role_id: i32, + client_id: i32, + // from headers + _session: AdminSession, + // injected + db: DbConn, +) -> Result> { + let role = Role::find(role_id, &db).await?; + let client = Client::find(client_id, &db).await?; + role.remove_limited_to_client(client_id, &db).await?; + Ok(Accepter { + html: Redirect::to(uri!(show_role_page( + role_id, + None::, + Some(format!( + "Successfully removed client “{}” from the “limited to” list", + client.name, + )) + ))), + json: Custom(Status::Ok, ()), + }) +} + #[delete("/roles//users/")] pub async fn delete_user<'r>( + // from url role_id: i32, user_id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let role = Role::find(role_id, &db).await?; + let user = User::find(user_id, &db).await?; role.remove_user(user_id, &db).await?; Ok(Accepter { html: Redirect::to(uri!(show_role_page( role_id, None::, - Some("user deleted") + Some(format!( + "Successfully removed this role from user “{}”", + user.username, + )) ))), json: Custom(Status::Ok, ()), }) @@ -209,19 +366,43 @@ pub async fn delete_user<'r>( #[delete("/roles//clients/")] pub async fn delete_client<'r>( + // from url role_id: i32, client_id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let role = Role::find(role_id, &db).await?; + let client = Client::find(client_id, &db).await?; role.remove_client(client_id, &db).await?; Ok(Accepter { html: Redirect::to(uri!(show_role_page( role_id, None::, - Some("client deleted") + Some(format!( + "Successfully removed this role from client “{}”", + client.name, + )) ))), json: Custom(Status::Ok, ()), }) } + +#[delete("/roles/")] +pub async fn delete_role<'r>( + // from url + id: i32, + // from headers + _session: AdminSession, + // injected + db: DbConn, +) -> Result> { + let role = Role::find(id, &db).await?; + role.delete(&db).await?; + Ok(Accepter { + html: Redirect::to(uri!(list_roles(None::))), + json: Custom(Status::NoContent, ()), + }) +} diff --git a/src/controllers/sessions_controller.rs b/src/controllers/sessions_controller.rs index b263b83f..fbb6abe6 100644 --- a/src/controllers/sessions_controller.rs +++ b/src/controllers/sessions_controller.rs @@ -1,5 +1,7 @@ use rocket::State; use rocket::form::Form; +use rocket::http::CookieJar; +use rocket::response::content::RawHtml; use rocket::response::{Redirect, Responder}; use crate::Config; @@ -11,31 +13,32 @@ use crate::ephemeral::session::{ use crate::errors::{Either, Result, ZauthError}; use crate::models::session::Session; use crate::models::user::User; -use rocket::http::CookieJar; #[get("/login")] pub fn new_session<'r>( - session: Option, + // from headers cookies: &CookieJar, + session: Option, ) -> Either + use<'r>> { match session { - None => Either::Right(template! { - "session/login.html"; - error: Option = None - }), + None => Either::Right(RawHtml(template!("session/login.html", { + error: Option = None, + }))), _ => Either::Left(stored_redirect_or(cookies, uri!(home_page))), } } #[get("/logout")] -pub fn delete_session<'r>(session: UserSession) -> impl Responder<'r, 'static> { - template! { - "session/logout.html"; - current_user: User = session.user - } +pub fn delete_session<'r>( + // from headers + session: UserSession, +) -> impl Responder<'r, 'static> { + RawHtml(template!("session/logout.html", { + current_user: User = session.user, + })) } -#[derive(FromForm, Debug)] +#[derive(Debug, FromForm)] pub struct LoginFormData { username: String, password: String, @@ -43,18 +46,20 @@ pub struct LoginFormData { #[post("/login", data = "")] pub async fn create_session<'r>( + // from body form: Form, + // from headers cookies: &'r CookieJar<'_>, + // injected config: &'r State, db: DbConn, ) -> Result + use<'r>>> { let form = form.into_inner(); match User::find_and_authenticate(form.username, form.password, &db).await { Err(ZauthError::LoginError(login_error)) => { - Ok(Either::Right(template! { - "session/login.html"; + Ok(Either::Right(RawHtml(template!("session/login.html", { error: Option = Some(login_error.to_string()), - })) + })))) }, Ok(user) => { let session = @@ -70,8 +75,10 @@ pub async fn create_session<'r>( #[post("/logout")] pub async fn destroy_session<'r>( - session: UserSession, + // from headers cookies: &'r CookieJar<'_>, + session: UserSession, + // injected db: DbConn, ) -> Result { session.destroy(cookies, &db).await?; diff --git a/src/controllers/users_controller.rs b/src/controllers/users_controller.rs index a26bae02..8b2efd1d 100644 --- a/src/controllers/users_controller.rs +++ b/src/controllers/users_controller.rs @@ -1,29 +1,28 @@ +use chrono::{Duration, Utc}; use lettre::message::header; +use rocket::State; +use rocket::form::Form; use rocket::http::Status; use rocket::http::uri::Absolute; +use rocket::response::content::RawHtml; use rocket::response::status::Custom; use rocket::response::{Redirect, Responder}; -use std::fmt::Debug; +use rocket::serde::json::Json; use validator::ValidationErrors; +use crate::DbConn; use crate::config::{AdminEmail, Config}; use crate::controllers::sessions_controller::rocket_uri_macro_new_session; use crate::ephemeral::from_api::Api; use crate::ephemeral::session::{AdminSession, UserClientSession, UserSession}; use crate::errors::Either::{self, Left, Right}; -use crate::errors::{InternalError, OneOf, Result, ZauthError}; +use crate::errors::{OneOf, Result, ZauthError}; use crate::mailer::Mailer; use crate::models::client::Client; use crate::models::role::Role; use crate::models::user::*; -use crate::util::split_scopes; +use crate::util; use crate::views::accepter::Accepter; -use crate::{DbConn, util}; -use askama::Template; -use chrono::{Duration, Utc}; -use rocket::State; -use rocket::form::Form; -use rocket::serde::json::Json; #[derive(Serialize)] pub struct UserInfo { @@ -47,7 +46,7 @@ impl UserInfo { db: &DbConn, config: &Config, ) -> Result { - let scopes = split_scopes(&scope); + let scopes = util::split_scopes(&scope); let roles = if let Some(client) = &client { if scopes.contains(&"roles".into()) { @@ -94,9 +93,11 @@ impl UserInfo { #[get("/current_user", rank = 1)] pub async fn current_user_as_client( + // from headers session: UserClientSession, - db: DbConn, + // injected config: &State, + db: DbConn, ) -> Result> { Ok(Json( UserInfo::new( @@ -112,9 +113,11 @@ pub async fn current_user_as_client( #[get("/current_user", rank = 2)] pub async fn current_user( + // from headers session: UserSession, - db: DbConn, + // injected config: &State, + db: DbConn, ) -> Result> { Ok(Json( UserInfo::new(session.user, None, None, &db, config).await?, @@ -123,9 +126,12 @@ pub async fn current_user( #[get("/users/")] pub async fn show_user<'r>( + // from url + username: String, + // from headers session: UserSession, + // injected db: DbConn, - username: String, ) -> Result> { // Cloning the username is necessary because it's used later let user = User::find_by_username(username.clone(), &db).await?; @@ -138,13 +144,13 @@ pub async fn show_user<'r>( // Check whether the current session is allowed to view this user if session.user.admin || session.user.username == username { Ok(Accepter { - html: template!("users/show.html"; - user: User = user.clone(), - current_user: User = session.user, - user_roles: Vec = user_roles, - roles: Vec = roles, - errors: Option = None - ), + html: RawHtml(template!("users/show.html", { + user: User = user.clone(), + current_user: User = session.user, + user_roles: Vec = user_roles, + roles: Vec = roles, + errors: Option = None, + })), json: Json(user), }) } else { @@ -157,8 +163,10 @@ pub async fn show_user<'r>( #[get("/users//keys", rank = 1)] pub async fn show_ssh_key<'r>( - db: DbConn, + // from url username: String, + // injected + db: DbConn, ) -> Result> { let user = User::find_by_username(username, &db).await?; let mut keys = vec![]; @@ -176,48 +184,55 @@ pub async fn show_ssh_key<'r>( } } Ok(Accepter { - html: template!("users/keys.html"; keys: String = keys.join("\n")), + html: RawHtml(template!("users/keys.html", { + keys: String = keys.join("\n"), + })), json: Json(keys), }) } #[get("/users")] pub async fn list_users<'r>( + // from headers session: AdminSession, - db: DbConn, + // injected conf: &'r State, + db: DbConn, ) -> Result> { let users = User::all(&db).await?; let full = User::pending_count(&db).await? >= conf.maximum_pending_users; let users_pending_for_approval: Vec = User::find_by_pending(&db).await?; Ok(Accepter { - html: template! { - "users/index.html"; + html: RawHtml(template!("users/index.html", { users: Vec = users.clone(), current_user: User = session.admin, registrations_full: bool = full, users_pending_for_approval: Vec = users_pending_for_approval.clone(), - }, + })), json: Json(users), }) } #[get("/users/new")] pub fn create_user_page<'r>( + // from headers session: AdminSession, ) -> Result> { - Ok(template! { "users/new_user.html"; + Ok(RawHtml(template!("users/new_user.html", { current_user: User = session.admin, - }) + }))) } #[post("/users", data = "")] pub async fn create_user<'r>( - _session: AdminSession, + // from body user: Api, - db: DbConn, + // from headers + _session: AdminSession, + // injected config: &State, + db: DbConn, ) -> Result + use<'r>> { let user = User::create(user.into_inner(), config.bcrypt_cost, &db).await?; // Cloning the username is necessary because it's used later @@ -229,12 +244,12 @@ pub async fn create_user<'r>( #[get("/register")] pub async fn register_page<'r>( - db: DbConn, + // injected conf: &'r State, + db: DbConn, ) -> Result> { let full = User::pending_count(&db).await? >= conf.maximum_pending_users; - Ok(template! { - "users/registration_form.html"; + Ok(RawHtml(template!("users/registration_form.html", { registrations_full: bool = full, errors: Option = None, user: NewUser = NewUser { @@ -245,15 +260,17 @@ pub async fn register_page<'r>( ssh_key: None, not_a_robot: false, } - }) + }))) } #[post("/register", data = "")] pub async fn register<'r>( + // from body user: Api, - db: DbConn, + // injected conf: &'r State, mailer: &'r State, + db: DbConn, ) -> Result, impl Responder<'r, 'static>>> { let new_user = user.into_inner(); let pending = User::create_pending(new_user.clone(), conf, &db).await; @@ -271,20 +288,17 @@ pub async fn register<'r>( mailer.try_create( &user, String::from("[Zauth] Confirm your email"), - template!( - "mails/confirm_user_registration.txt"; - name: String = user.full_name.to_string(), - confirm_url: String = confirm_url.to_string(), - ) - .render() - .map_err(InternalError::from)?, + template!("mails/confirm_user_registration.txt", { + name: String = user.full_name.to_string(), + confirm_url: String = confirm_url.to_string(), + })?, header::ContentType::TEXT_PLAIN, )?; Ok(Left(Accepter { html: Custom( Status::Created, - template!("users/registration_success.html"), + RawHtml(template!("users/registration_success.html")), ), json: Custom(Status::Created, Json(user)), })) @@ -292,12 +306,11 @@ pub async fn register<'r>( Err(ZauthError::ValidationError(errors)) => Ok(Right(Accepter { html: Custom( Status::UnprocessableEntity, - template! { - "users/registration_form.html"; + RawHtml(template!("users/registration_form.html", { registrations_full: bool = full, user: NewUser = new_user, errors: Option = Some(errors.clone()), - }, + })), ), json: Custom(Status::UnprocessableEntity, Json(errors)), })), @@ -307,9 +320,13 @@ pub async fn register<'r>( #[put("/users/", data = "")] pub async fn update_user<'r, 'o: 'r>( + // from url username: String, + // from body change: Api, + // from headers session: UserSession, + // injected db: DbConn, ) -> Result> { let mut user = User::find_by_username(username, &db).await?; @@ -326,14 +343,13 @@ pub async fn update_user<'r, 'o: 'r>( let roles = user.clone().roles(&db).await?; Ok(OneOf::Two(Custom( Status::UnprocessableEntity, - template! { - "users/show.html"; + RawHtml(template!("users/show.html", { user: User = user, current_user: User = session.user, - user_roles: Vec = roles, + user_roles: Vec = roles, roles: Vec = vec![], errors: Option = Some(errors.clone()), - }, + })), ))) }, Err(other) => Err(other), @@ -345,9 +361,13 @@ pub async fn update_user<'r, 'o: 'r>( #[post("/users//admin", data = "")] pub async fn set_admin<'r>( + // from url username: String, + // from body value: Api, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let mut user = User::find_by_username(username, &db).await?; @@ -361,9 +381,13 @@ pub async fn set_admin<'r>( #[post("/users//change_state", data = "")] pub async fn change_state<'r>( + // from url username: String, + // from body value: Api, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let mut user = User::find_by_username(username, &db).await?; @@ -377,10 +401,13 @@ pub async fn change_state<'r>( #[post("/users//approve")] pub async fn set_approved<'r>( + // from url username: String, + // from headers _session: AdminSession, - mailer: &'r State, + // injected conf: &'r State, + mailer: &'r State, db: DbConn, ) -> Result> { let user = User::find_by_username(username, &db).await?; @@ -392,13 +419,10 @@ pub async fn set_approved<'r>( .create( &user, String::from("[Zauth] Your account has been approved"), - template!( - "mails/user_approved.txt"; - name: String = user.full_name.to_string(), - login_url: String = login_url.to_string(), - ) - .render() - .map_err(InternalError::from)?, + template!("mails/user_approved.txt", { + name: String = user.full_name.to_string(), + login_url: String = login_url.to_string(), + })?, header::ContentType::TEXT_PLAIN, ) .await?; @@ -411,8 +435,11 @@ pub async fn set_approved<'r>( #[post("/users//reject")] pub async fn reject<'r>( + // from url username: String, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let user = User::find_by_username(username, &db).await?; @@ -433,20 +460,22 @@ pub async fn reject<'r>( #[get("/users/forgot_password")] pub fn forgot_password_get<'r>() -> impl Responder<'r, 'static> { - template! { "users/forgot_password.html" } + RawHtml(template!("users/forgot_password.html")) } -#[derive(Debug, FromForm, Deserialize)] +#[derive(Debug, Deserialize, FromForm)] pub struct ResetPassword { for_email: String, } #[post("/users/forgot_password", data = "")] pub async fn forgot_password_post<'r>( + // from body value: Form, + // injected conf: &State, - db: DbConn, mailer: &State, + db: DbConn, ) -> Result + use<'r>> { let for_email = value.into_inner().for_email; @@ -468,31 +497,27 @@ pub async fn forgot_password_post<'r>( mailer.try_create( &user, String::from("[Zauth] You've requested a password reset"), - template!( - "mails/password_reset_token.txt"; + template!("mails/password_reset_token.txt", { name: String = user.username.to_string(), reset_url: String = reset_url.to_string(), - ) - .render() - .map_err(InternalError::from)?, + })?, header::ContentType::TEXT_PLAIN, )? }; - Ok(template! { - "users/reset_link_sent.html"; - email: String = for_email - }) + Ok(RawHtml(template!("users/reset_link_sent.html", { + email: String = for_email, + }))) } #[get("/users/unsubscribe/")] pub fn show_confirm_unsubscribe<'r>( + // from url token: String, ) -> impl Responder<'r, 'static> { - template! { - "users/confirm_unsubscribe_form.html"; + RawHtml(template!("users/confirm_unsubscribe_form.html", { token: String = token, - } + })) } #[derive(Debug, FromForm)] @@ -502,7 +527,9 @@ pub struct UnsubscribeForm { #[post("/users/unsubscribe", data = "")] pub async fn unsubscribe_user<'r>( + // from body form: Form, + // injected db: DbConn, ) -> Result, impl Responder<'r, 'static>>> { let user = @@ -511,7 +538,7 @@ pub async fn unsubscribe_user<'r>( if user.is_none() { return Ok(Either::Left(Custom( Status::Unauthorized, - template!("users/unsubscribe_invalid.html"), + RawHtml(template!("users/unsubscribe_invalid.html")), ))); } @@ -521,16 +548,18 @@ pub async fn unsubscribe_user<'r>( user.subscribed_to_mailing_list = false; user.update(&db).await?; - Ok(Either::Right(template!("users/unsubscribed.html"))) + Ok(Either::Right(RawHtml(template!("users/unsubscribed.html")))) } #[get("/users/reset_password/")] -pub fn reset_password_get<'r>(token: String) -> impl Responder<'r, 'static> { - template! { - "users/reset_password_form.html"; +pub fn reset_password_get<'r>( + // from url + token: String, +) -> impl Responder<'r, 'static> { + RawHtml(template!("users/reset_password_form.html", { token: String = token, errors: Option = None, - } + })) } #[derive(Debug, FromForm)] @@ -541,10 +570,12 @@ pub struct PasswordReset { #[post("/users/reset_password", data = "")] pub async fn reset_password_post<'r, 'o: 'r>( + // from body form: Form, - db: DbConn, + // injected conf: &'r State, mailer: &'r State, + db: DbConn, ) -> Result> { let form = form.into_inner(); if let Some(user) = @@ -556,47 +587,46 @@ pub async fn reset_password_post<'r, 'o: 'r>( let changed = user.change_password(change, conf, &db).await; match changed { Ok(user) => { - let body = template!( - "mails/password_reset_success.txt"; - name: String = user.username.to_string(), - ) - .render() - .map_err(InternalError::from)?; mailer .create( &user, String::from("[Zauth] Your password has been reset"), - body, + template!("mails/password_reset_success.txt", { + name: String = user.username.to_string(), + })?, header::ContentType::TEXT_PLAIN, ) .await?; - Ok(OneOf::One( - template! { "users/reset_password_success.html" }, - )) + Ok(OneOf::One(RawHtml(template!( + "users/reset_password_success.html" + )))) }, Err(ZauthError::ValidationError(errors)) => Ok(OneOf::Two(Custom( Status::UnprocessableEntity, - template! { - "users/reset_password_form.html"; + RawHtml(template!("users/reset_password_form.html", { token: String = form.token, errors: Option = Some(errors.clone()), - }, + })), ))), Err(other) => Err(other), } } else { - let template = template! { "users/reset_password_invalid.html" }; - Ok(OneOf::Three(Custom(Status::Forbidden, template))) + Ok(OneOf::Three(Custom( + Status::Forbidden, + RawHtml(template!("users/reset_password_invalid.html")), + ))) } } #[get("/users/confirm/")] -pub fn confirm_email_get<'r>(token: String) -> impl Responder<'r, 'static> { - template! { - "users/confirm_email_form.html"; +pub fn confirm_email_get<'r>( + // from url + token: String, +) -> impl Responder<'r, 'static> { + RawHtml(template!("users/confirm_email_form.html", { token: String = token, - } + })) } #[derive(Debug, FromForm)] @@ -606,10 +636,12 @@ pub struct EmailConfirmation { #[post("/users/confirm", data = "")] pub async fn confirm_email_post<'r>( + // from body form: Form, - mailer: &State, + // injected admin_email: &State, conf: &'r State, + mailer: &State, db: DbConn, ) -> Result< Either< @@ -626,33 +658,35 @@ pub async fn confirm_email_post<'r>( mailer.try_create( admin_email.0.clone(), String::from("[Zauth] New user registration"), - template!( - "mails/new_user_registration.txt"; - name: String = user.username.to_string(), - user_list_url: String = user_list_url.to_string(), - ) - .render() - .map_err(InternalError::from)?, + template!("mails/new_user_registration.txt", { + name: String = user.username.to_string(), + user_list_url: String = user_list_url.to_string(), + })?, header::ContentType::TEXT_PLAIN, )?; - Ok(Either::Left(template! { - "users/confirm_email_success.html"; - user: User = user, - })) + Ok(Either::Left(RawHtml( + template!("users/confirm_email_success.html", { + user: User = user, + }), + ))) } else { - Ok(Either::Right( - template! {"users/confirm_email_invalid.html"}, - )) + Ok(Either::Right(RawHtml(template!( + "users/confirm_email_invalid.html" + )))) } } #[post("/users//roles", data = "")] pub async fn add_role<'r>( + // from url username: String, + // from body role_id: Form, - db: DbConn, + // from headers _session: AdminSession, + // injected + db: DbConn, ) -> Result> { let role = Role::find(*role_id, &db).await?; let user_result = User::find_by_username(username.clone(), &db).await?; @@ -665,9 +699,12 @@ pub async fn add_role<'r>( #[delete("/users//roles/")] pub async fn delete_role<'r>( - role_id: i32, + // from url username: String, + role_id: i32, + // from headers _session: AdminSession, + // injected db: DbConn, ) -> Result> { let role = Role::find(role_id, &db).await?; diff --git a/src/controllers/webauthn_controller.rs b/src/controllers/webauthn_controller.rs index 629cd69e..077e9a67 100644 --- a/src/controllers/webauthn_controller.rs +++ b/src/controllers/webauthn_controller.rs @@ -1,6 +1,7 @@ use chrono::{DateTime, Local}; use rocket::form::Form; use rocket::http::{CookieJar, Status}; +use rocket::response::content::RawHtml; use rocket::response::status::Custom; use rocket::response::{Redirect, Responder}; use rocket::{State, serde::json::Json}; @@ -27,9 +28,12 @@ use crate::webauthn::WebAuthnStore; #[post("/webauthn/start_register", format = "json", data = "")] pub async fn start_register( + // from body + residential: Json, + // from headers session: UserSession, + // injected webauthn_store: &State, - residential: Json, db: DbConn, ) -> Result> { let authenticator_criteria = AuthenticatorSelectionCriteria { @@ -76,9 +80,12 @@ pub struct PassKeyRegistration { #[post("/webauthn/finish_register", format = "json", data = "")] pub async fn finish_register<'r>( + // from body + reg: Json, + // from headers session: UserSession, + // injected webauthn_store: &State, - reg: Json, db: DbConn, ) -> Result + use<'r>>> { let reg_state = @@ -105,18 +112,21 @@ pub async fn finish_register<'r>( PassKey::create(passkey, &db).await?; Ok(Either::Left(Redirect::to(uri!(list_passkeys)))) }, - Err(e) => Ok(Either::Right(template! { - "passkeys/new_passkey.html"; - current_user: User = session.user, - errors: Option = Some(e.to_string()), - })), + Err(e) => Ok(Either::Right(RawHtml( + template!("passkeys/new_passkey.html", { + current_user: User = session.user, + errors: Option = Some(e.to_string()), + }), + ))), } } #[post("/webauthn/start_auth", format = "json", data = "")] pub async fn start_authentication( - webauthn_store: &State, + // from body username: Json>, + // injected + webauthn_store: &State, db: DbConn, ) -> Result, RequestChallengeResponse)>> { let now = Local::now(); @@ -236,10 +246,13 @@ async fn authenticate( #[post("/webauthn/finish_auth", data = "")] pub async fn finish_authentication<'r>( - webauthn_store: &State, + // from body auth: Form, + // from headers cookies: &'r CookieJar<'_>, + // injected config: &'r State, + webauthn_store: &State, db: DbConn, ) -> Result + use<'r>>> { let id = serde_json::from_str(&auth.id) @@ -258,10 +271,9 @@ pub async fn finish_authentication<'r>( Ok(Either::Left(stored_redirect_or(cookies, uri!(home_page)))) }, Err(ZauthError::LoginError(login_error)) => { - Ok(Either::Right(template! { - "session/login.html"; + Ok(Either::Right(RawHtml(template!("session/login.html", { error: Option = Some(login_error.to_string()), - })) + })))) }, Err(e) => Err(e), } @@ -269,34 +281,39 @@ pub async fn finish_authentication<'r>( #[get("/passkeys")] pub async fn list_passkeys<'r>( - db: DbConn, + // from headers session: UserSession, + // injected + db: DbConn, ) -> Result> { let passkeys = PassKey::find_by_user_id(session.user.id, &db).await?; Ok(Accepter { - html: template! { - "passkeys/index.html"; + html: RawHtml(template!("passkeys/index.html", { passkeys: Vec = passkeys.clone(), - current_user: User = session.user - }, + current_user: User = session.user, + })), json: Json(passkeys), }) } #[get("/passkeys/new")] pub async fn new_passkey<'r>( + // from headers session: UserSession, ) -> Result> { - Ok(template! { "passkeys/new_passkey.html"; + Ok(RawHtml(template!("passkeys/new_passkey.html", { current_user: User = session.user, errors: Option = None, - }) + }))) } #[delete("/passkeys/")] pub async fn delete_passkey<'r>( + // from url id: i32, + // from headers session: UserSession, + // injected db: DbConn, ) -> Result> { let passkey = PassKey::find(id, &db).await?; diff --git a/src/db_seed.rs b/src/db_seed.rs index 18f12020..ad3150e8 100644 --- a/src/db_seed.rs +++ b/src/db_seed.rs @@ -1,3 +1,5 @@ +use diesel::RunQueryDsl; + use crate::DbConn; use crate::errors::{Result, ZauthError}; use crate::models::client::{Client, NewClient}; @@ -5,8 +7,6 @@ use crate::models::schema::clients; use crate::models::schema::users; use crate::models::user::{NewUser, User}; use crate::util::random_token; -use diesel::RunQueryDsl; -use std::default::Default; #[derive(Default)] pub struct Seeder { diff --git a/src/ephemeral/from_api.rs b/src/ephemeral/from_api.rs index 14f17456..088ab13b 100644 --- a/src/ephemeral/from_api.rs +++ b/src/ephemeral/from_api.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use rocket::Request; use rocket::data::{Data, FromData, Outcome}; use rocket::form::Form; @@ -5,8 +7,6 @@ use rocket::http::ContentType; use rocket::http::Status; use rocket::serde::json::Json; -use std::marker::PhantomData; - #[derive(Debug)] pub struct Api { inner: T, diff --git a/src/ephemeral/session.rs b/src/ephemeral/session.rs index ac1e2a49..66fa1bea 100644 --- a/src/ephemeral/session.rs +++ b/src/ephemeral/session.rs @@ -1,7 +1,10 @@ +use std::str::FromStr; + +use rocket::http::uri::Origin; use rocket::http::{Cookie, CookieJar, Status}; use rocket::outcome::try_outcome; use rocket::request::{FromRequest, Outcome, Request}; -use std::str::FromStr; +use rocket::response::Redirect; use crate::DbConn; use crate::controllers::sessions_controller::rocket_uri_macro_new_session; @@ -9,8 +12,6 @@ use crate::errors::Result; use crate::models::client::Client; use crate::models::session::Session; use crate::models::user::User; -use rocket::http::uri::Origin; -use rocket::response::Redirect; const REDIRECT_COOKIE: &str = "ZAUTH_REDIRECT"; const SESSION_COOKIE: &str = "ZAUTH_SESSION"; @@ -35,7 +36,7 @@ pub fn stored_redirect_or(cookies: &CookieJar, fallback: Origin) -> Redirect { Redirect::to(location.to_string()) } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct SessionCookie { session_id: i32, } diff --git a/src/errors.rs b/src/errors.rs index b98b0667..5e693cca 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,20 +1,22 @@ -use rocket::Request; -use rocket::http::Status; -use rocket::response::{self, Responder, Response}; -use thiserror::Error; +use std::convert::Infallible; +use askama; use diesel::result::Error::NotFound; use lettre::Message; use log::warn; +use rocket::Request; +use rocket::http::Status; +use rocket::response::content::RawHtml; +use rocket::response::{self, Responder, Response}; use rocket::serde::json::Json; use rocket::tokio::sync::mpsc::error::{SendError, TrySendError}; -use std::convert::Infallible; +use thiserror::Error; use validator::ValidationErrors; use webauthn_rs::prelude::WebauthnError; use crate::views::accepter::Accepter; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum ZauthError { #[error("Internal server error {0:?}")] Internal(#[from] InternalError), @@ -109,7 +111,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ZauthError { #[catch(401)] pub fn unauthorized<'r>() -> impl Responder<'r, 'static> { Accepter { - html: template!("errors/401.html"), + html: RawHtml(template!("errors/401.html")), json: Json(JsonError { error: "unauthorized", status: 401, @@ -121,7 +123,7 @@ pub fn unauthorized<'r>() -> impl Responder<'r, 'static> { #[catch(404)] pub fn not_found<'r>() -> impl Responder<'r, 'static> { Accepter { - html: template!("errors/404.html"), + html: RawHtml(template!("errors/404.html")), json: Json(JsonError { error: "not found", status: 404, @@ -139,9 +141,9 @@ pub fn unprocessable_with_message<'r>( message: Option, ) -> impl Responder<'r, 'static> { Accepter { - html: template!("errors/422.html"; - message: Option = message.clone() - ), + html: RawHtml(template!("errors/422.html", { + message: Option = message.clone(), + })), json: Json(JsonError { error: "unprocessable", status: 422, @@ -159,7 +161,9 @@ fn internal_server_error_with_message<'r>( message: String, ) -> impl Responder<'r, 'static> { Accepter { - html: template!("errors/500.html"; error: String = message.clone()), + html: RawHtml(template!("errors/500.html", { + error: String = message.clone(), + })), json: Json(JsonError { error: "internal server error", status: 500, @@ -177,7 +181,9 @@ fn not_implemented_with_message<'r>( message: String, ) -> impl Responder<'r, 'static> { Accepter { - html: template!("errors/501.html"; error: String = message.clone()), + html: RawHtml(template!("errors/501.html", { + error: String = message.clone(), + })), json: Json(JsonError { error: "not implemented", status: 501, @@ -197,7 +203,7 @@ impl From for ZauthError { pub type Result = std::result::Result; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum InternalError { #[error("Hash error")] HashError(#[from] pwhash::error::Error), @@ -227,7 +233,7 @@ pub enum InternalError { pub type InternalResult = std::result::Result; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum LoginError { #[error("Username or password incorrect")] UsernamePasswordError, @@ -246,7 +252,7 @@ pub enum LoginError { PasskeyDiscoverableError, } -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum AuthenticationError { #[error("Not authorized '{0}'")] Unauthorized(String), @@ -257,7 +263,7 @@ pub enum AuthenticationError { } pub type AuthResult = std::result::Result; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum LaunchError { #[error("Incorrect config value type for key '{0}'")] BadConfigValueType(String), @@ -267,7 +273,7 @@ pub enum LaunchError { SMTPError(#[from] lettre::transport::smtp::Error), } -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum OAuthError { #[error( "The cookie used for storing OAuth information is invalid or has \ diff --git a/src/http_authentication.rs b/src/http_authentication.rs index 1a507527..8bb53970 100644 --- a/src/http_authentication.rs +++ b/src/http_authentication.rs @@ -1,10 +1,10 @@ +use std::str::FromStr; + use base64::Engine; use base64::prelude::BASE64_STANDARD; use rocket::http::Status; -use rocket::request::{self, FromRequest, Request}; - use rocket::outcome::Outcome; -use std::str::FromStr; +use rocket::request::{self, FromRequest, Request}; #[derive(Debug)] pub struct BasicAuthentication { diff --git a/src/jwt.rs b/src/jwt.rs index a0142b4e..e9ae96ca 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -1,7 +1,6 @@ -use crate::config::Config; -use crate::errors::{InternalError, LaunchError, Result}; -use crate::models::client::Client; -use crate::models::user::User; +use std::fs::File; +use std::io::Read; + use base64::engine::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use chrono::Utc; @@ -12,8 +11,11 @@ use jsonwebtoken::{EncodingKey, Header, encode}; use openssl::bn::{BigNum, BigNumContext}; use openssl::ec::EcKey; use serde::Serialize; -use std::fs::File; -use std::io::Read; + +use crate::config::Config; +use crate::errors::{InternalError, LaunchError, Result}; +use crate::models::client::Client; +use crate::models::user::User; pub struct JWTBuilder { pub key: EncodingKey, @@ -21,7 +23,7 @@ pub struct JWTBuilder { pub jwks: JwkSet, } -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] struct IDToken { sub: String, iss: String, @@ -35,7 +37,7 @@ struct IDToken { picture: String, } -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] struct ClientIDToken { sub: String, iss: String, diff --git a/src/lib.rs b/src/lib.rs index 1d5993d1..ddc32079 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,30 +2,30 @@ #![recursion_limit = "256"] extern crate chrono; +#[macro_use] +extern crate diesel; +#[macro_use] +extern crate diesel_migrations; +#[macro_use] +extern crate lazy_static; extern crate lettre; +extern crate log; extern crate pwhash; extern crate rand; extern crate regex; -extern crate simple_logger; -extern crate thiserror; -extern crate toml; -extern crate validator; - #[macro_use] extern crate rocket; extern crate rocket_sync_db_pools; #[macro_use] extern crate serde_derive; -#[macro_use] -extern crate lazy_static; -extern crate log; -#[macro_use] -extern crate diesel; -#[macro_use] -extern crate diesel_migrations; +extern crate simple_logger; +extern crate thiserror; +extern crate toml; +extern crate validator; #[macro_use] pub mod views; + pub mod config; pub mod controllers; pub mod db_seed; @@ -39,6 +39,8 @@ pub mod token_store; pub mod util; pub mod webauthn; +use std::str::FromStr; + use diesel_migrations::MigrationHarness; use jwt::JWTBuilder; use lettre::message::Mailbox; @@ -60,8 +62,6 @@ use crate::errors::{ use crate::mailer::Mailer; use crate::token_store::TokenStore; -use std::str::FromStr; - #[database("postgresql_database")] pub struct DbConn(PgConnection); pub type ConcreteConnection = PgConnection; @@ -108,20 +108,19 @@ fn assemble(rocket: Rocket) -> Rocket { clients_controller::list_clients, clients_controller::update_client_page, clients_controller::update_client, - clients_controller::create_client, clients_controller::delete_client, + clients_controller::create_client, clients_controller::get_generate_secret, clients_controller::post_generate_secret, clients_controller::current_client, clients_controller::add_role, clients_controller::delete_role, - webauthn_controller::start_register, - webauthn_controller::finish_register, - webauthn_controller::start_authentication, - webauthn_controller::finish_authentication, - webauthn_controller::list_passkeys, - webauthn_controller::new_passkey, - webauthn_controller::delete_passkey, + mailing_list_controller::list_mails, + mailing_list_controller::send_mail_as_user, + mailing_list_controller::send_mail_as_client, + mailing_list_controller::show_create_mail_page, + mailing_list_controller::show_mail, + oauth_controller::get_well_known_openid_configuration, oauth_controller::authorize, oauth_controller::do_authorize, oauth_controller::grant_get, @@ -129,47 +128,53 @@ fn assemble(rocket: Rocket) -> Rocket { oauth_controller::token, oauth_controller::jwks, pages_controller::home_page, - sessions_controller::create_session, + roles_controller::list_roles, + roles_controller::create_role, + roles_controller::show_role_page, + roles_controller::update_description, + roles_controller::update_visibility, + roles_controller::add_limited_to_client, + roles_controller::add_user, + roles_controller::add_client, + roles_controller::delete_limited_to_client, + roles_controller::delete_user, + roles_controller::delete_client, + roles_controller::delete_role, sessions_controller::new_session, sessions_controller::delete_session, + sessions_controller::create_session, sessions_controller::destroy_session, - users_controller::create_user_page, - users_controller::create_user, - users_controller::register_page, - users_controller::register, - users_controller::current_user, users_controller::current_user_as_client, + users_controller::current_user, users_controller::show_user, users_controller::show_ssh_key, users_controller::list_users, + users_controller::create_user_page, + users_controller::create_user, + users_controller::register_page, + users_controller::register, users_controller::update_user, - users_controller::change_state, users_controller::set_admin, + users_controller::change_state, users_controller::set_approved, users_controller::reject, users_controller::forgot_password_get, users_controller::forgot_password_post, + users_controller::show_confirm_unsubscribe, + users_controller::unsubscribe_user, users_controller::reset_password_get, users_controller::reset_password_post, users_controller::confirm_email_get, users_controller::confirm_email_post, - users_controller::show_confirm_unsubscribe, - users_controller::unsubscribe_user, users_controller::add_role, users_controller::delete_role, - mailing_list_controller::list_mails, - mailing_list_controller::send_mail_as_user, - mailing_list_controller::send_mail_as_client, - mailing_list_controller::show_create_mail_page, - mailing_list_controller::show_mail, - roles_controller::list_roles, - roles_controller::create_role, - roles_controller::delete_role, - roles_controller::show_role_page, - roles_controller::add_user, - roles_controller::delete_user, - roles_controller::add_client, - roles_controller::delete_client, + webauthn_controller::start_register, + webauthn_controller::finish_register, + webauthn_controller::start_authentication, + webauthn_controller::finish_authentication, + webauthn_controller::list_passkeys, + webauthn_controller::new_passkey, + webauthn_controller::delete_passkey, ], ) .register( diff --git a/src/mailer.rs b/src/mailer.rs index dbca3872..b48af64e 100644 --- a/src/mailer.rs +++ b/src/mailer.rs @@ -1,5 +1,5 @@ -use crate::config::Config; -use crate::errors::{InternalError, LaunchError, Result, ZauthError}; +use std::convert::TryInto; +use std::time::Duration; use lettre::message::{Mailbox, header::ContentType}; use lettre::transport::smtp::authentication::Credentials; @@ -9,8 +9,9 @@ use rocket::Config as RocketConfig; use rocket::tokio::sync::mpsc::Receiver; use rocket::tokio::sync::mpsc::{self, UnboundedReceiver}; use rocket::tokio::time::sleep; -use std::convert::TryInto; -use std::time::Duration; + +use crate::config::Config; +use crate::errors::{InternalError, LaunchError, Result, ZauthError}; #[derive(Clone)] pub struct Mailer { diff --git a/src/models/client.rs b/src/models/client.rs index 0c1d03cb..7510c28f 100644 --- a/src/models/client.rs +++ b/src/models/client.rs @@ -1,20 +1,17 @@ +use chrono::NaiveDateTime; use diesel::{self, prelude::*}; +use validator::Validate; +use super::role::{ClientRole, Role}; use crate::DbConn; use crate::errors::{AuthenticationError, Result, ZauthError}; - use crate::models::schema::{clients, roles}; - use crate::util::random_token; -use chrono::NaiveDateTime; -use validator::Validate; - -use super::role::{ClientRole, Role}; const SECRET_LENGTH: usize = 64; #[derive( - Serialize, AsChangeset, Queryable, Debug, Clone, Identifiable, Selectable, + AsChangeset, Clone, Debug, Identifiable, Queryable, Selectable, Serialize, )] pub struct Client { pub id: i32, @@ -26,20 +23,20 @@ pub struct Client { pub created_at: NaiveDateTime, } -#[derive(Validate, FromForm, Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, FromForm, Validate)] pub struct NewClient { #[validate(length(min = 3, max = 80))] pub name: String, } -#[derive(Insertable, Debug, Clone)] +#[derive(Clone, Debug, Insertable)] #[diesel(table_name = clients)] pub struct NewClientWithSecret { pub name: String, pub secret: String, } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ClientChange { pub name: Option, pub needs_grant: Option, diff --git a/src/models/mail.rs b/src/models/mail.rs index 4bc40a5c..513c719e 100644 --- a/src/models/mail.rs +++ b/src/models/mail.rs @@ -1,19 +1,18 @@ use std::cmp::Reverse; -use crate::DbConn; -use crate::errors::{self, ZauthError}; use chrono::NaiveDateTime; +use diesel::result::Error as DieselError; use diesel::{self, prelude::*}; use diesel_derive_enum::DbEnum; -use markdown::{Options, to_html_with_options}; - -use diesel::result::Error as DieselError; +use markdown; use rocket::serde::Serialize; use validator::Validate; use super::schema::mails; +use crate::DbConn; +use crate::errors::{self, ZauthError}; -#[derive(DbEnum, Debug, Deserialize, FromFormField, Serialize, Copy, Clone)] +#[derive(Clone, Copy, DbEnum, Debug, Deserialize, FromFormField, Serialize)] #[db_enum(existing_type_path = "crate::models::schema::sql_types::ContentType")] pub enum ContentType { #[db_enum(rename = "text/plain")] @@ -38,7 +37,7 @@ pub struct Mail { } #[derive( - Clone, Debug, Deserialize, Serialize, FromForm, Insertable, Validate, + Clone, Debug, Deserialize, FromForm, Insertable, Serialize, Validate, )] #[diesel(table_name = mails)] pub struct NewMail { @@ -95,9 +94,9 @@ impl Mail { pub fn render_body(&self) -> errors::Result { match self.content_type { ContentType::Plain => Ok(self.body.clone()), - ContentType::Markdown => to_html_with_options( + ContentType::Markdown => markdown::to_html_with_options( &self.body, - &Options::gfm(), + &markdown::Options::gfm(), ) .map_err(|_| { ZauthError::Unprocessable("could not parse markdown".into()) diff --git a/src/models/mod.rs b/src/models/mod.rs index d22d05a5..6d41cd48 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -3,5 +3,6 @@ pub mod mail; pub mod passkey; pub mod role; pub mod schema; +pub mod schema_custom_sql_types; pub mod session; pub mod user; diff --git a/src/models/passkey.rs b/src/models/passkey.rs index a4c75296..1a06ee00 100644 --- a/src/models/passkey.rs +++ b/src/models/passkey.rs @@ -11,7 +11,7 @@ use crate::{ use super::schema::passkeys; #[derive( - Queryable, Selectable, PartialEq, Debug, Clone, Serialize, AsChangeset, + AsChangeset, Clone, Debug, PartialEq, Queryable, Selectable, Serialize, )] #[diesel(table_name = passkeys)] pub struct PassKey { diff --git a/src/models/role.rs b/src/models/role.rs index a763cc27..3d67d9b8 100644 --- a/src/models/role.rs +++ b/src/models/role.rs @@ -1,48 +1,104 @@ -use crate::{ - DbConn, - errors::{Result, ZauthError}, -}; +use core::convert::Into; +use core::fmt; + use diesel::{self, prelude::*}; +use diesel_derive_enum::DbEnum; use validator::Validate; +use crate::DbConn; +use crate::errors::{Result, ZauthError}; +use crate::models::client::Client; use crate::models::schema::{ - clients, clients_roles, roles, users, users_roles, + clients, clients_assigned_roles, roles, roles_limited_to_clients, users, + users_assigned_roles, }; -use crate::models::{client::Client, user::User}; +use crate::models::user::User; #[derive( + Clone, + Copy, + DbEnum, + Debug, Deserialize, + FromFormField, + Eq, + PartialEq, Serialize, - Queryable, - Debug, +)] +#[db_enum( + existing_type_path = "crate::models::schema::sql_types::RoleVisibility" +)] +pub enum RoleVisibility { + #[db_enum(rename = "global")] + #[serde(rename = "global")] + #[field(value = "global")] + Global, + #[db_enum(rename = "limited")] + #[serde(rename = "limited")] + #[field(value = "limited")] + Limited, +} + +impl fmt::Display for RoleVisibility { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Global => write!(f, "global"), + Self::Limited => write!(f, "limited"), + } + } +} + +impl RoleVisibility { + pub const VALUES: [Self; 2] = [Self::Global, Self::Limited]; +} + +#[derive( + AsChangeset, Clone, + Debug, + Deserialize, Identifiable, PartialEq, + Queryable, + QueryableByName, Selectable, + Serialize, )] pub struct Role { pub id: i32, pub name: String, pub description: String, - pub client_id: Option, + pub visibility: RoleVisibility, } -#[derive(Validate, FromForm, Debug, Insertable, Deserialize)] +#[derive(Debug, Deserialize, FromForm, Insertable, Validate)] #[diesel(table_name = roles)] pub struct NewRole { #[validate(length(min = 1, max = 30))] pub name: String, #[validate(length(min = 1, max = 100))] pub description: String, - pub client_id: Option, + pub visibility: RoleVisibility, } #[derive( - Identifiable, Selectable, Queryable, Associations, Debug, Insertable, + Associations, Debug, Identifiable, Insertable, Queryable, Selectable, +)] +#[diesel(belongs_to(Role))] +#[diesel(belongs_to(Client))] +#[diesel(table_name = roles_limited_to_clients)] +#[diesel(primary_key(role_id, client_id))] +pub struct RoleLimitedToClient { + pub role_id: i32, + pub client_id: i32, +} + +#[derive( + Associations, Debug, Identifiable, Insertable, Queryable, Selectable, )] #[diesel(belongs_to(Role))] #[diesel(belongs_to(User))] -#[diesel(table_name = users_roles)] +#[diesel(table_name = users_assigned_roles)] #[diesel(primary_key(role_id, user_id))] pub struct UserRole { pub role_id: i32, @@ -50,11 +106,11 @@ pub struct UserRole { } #[derive( - Identifiable, Selectable, Queryable, Associations, Debug, Insertable, + Associations, Debug, Identifiable, Insertable, Queryable, Selectable, )] #[diesel(belongs_to(Role))] #[diesel(belongs_to(Client))] -#[diesel(table_name = clients_roles)] +#[diesel(table_name = clients_assigned_roles)] #[diesel(primary_key(role_id, client_id))] pub struct ClientRole { pub role_id: i32, @@ -74,13 +130,50 @@ impl Role { .map_err(ZauthError::from) } + pub async fn add_client_to_limited_to( + &self, + client_id: i32, + db: &DbConn, + ) -> Result { + let role_id = self.id; + let role_limited_to_client = db + .run(move |conn| { + roles_limited_to_clients::table + .filter(roles_limited_to_clients::role_id.eq(role_id)) + .filter(roles_limited_to_clients::client_id.eq(client_id)) + .first::(conn) + .optional() + }) + .await + .map_err(ZauthError::from)?; + + match role_limited_to_client { + Some(_) => { + // Tuple exists + Ok(false) + }, + None => { + // Tuple doesn't exist yet + let tuple = RoleLimitedToClient { role_id, client_id }; + db.run(move |conn| { + diesel::insert_into(roles_limited_to_clients::table) + .values(&tuple) + .execute(conn) + }) + .await + .map_err(ZauthError::from)?; + Ok(true) + }, + } + } + pub async fn add_user(&self, user_id: i32, db: &DbConn) -> Result { let id = self.id; let user_role = db .run(move |conn| { - users_roles::table - .filter(users_roles::user_id.eq(user_id)) - .filter(users_roles::role_id.eq(id)) + users_assigned_roles::table + .filter(users_assigned_roles::user_id.eq(user_id)) + .filter(users_assigned_roles::role_id.eq(id)) .first::(conn) .optional() }) @@ -94,7 +187,7 @@ impl Role { user_id, }; db.run(move |conn| { - diesel::insert_into(users_roles::table) + diesel::insert_into(users_assigned_roles::table) .values(&user_role) .execute(conn) }) @@ -114,9 +207,9 @@ impl Role { let id = self.id; let client_role = db .run(move |conn| { - clients_roles::table - .filter(clients_roles::client_id.eq(client_id)) - .filter(clients_roles::role_id.eq(id)) + clients_assigned_roles::table + .filter(clients_assigned_roles::client_id.eq(client_id)) + .filter(clients_assigned_roles::role_id.eq(id)) .first::(conn) .optional() }) @@ -130,7 +223,7 @@ impl Role { client_id, }; db.run(move |conn| { - diesel::insert_into(clients_roles::table) + diesel::insert_into(clients_assigned_roles::table) .values(&client_role) .execute(conn) }) @@ -142,13 +235,35 @@ impl Role { } } + pub async fn remove_limited_to_client( + self, + client_id: i32, + db: &DbConn, + ) -> Result { + let role_id = self.id; + let count = db + .run(move |conn| { + diesel::delete( + roles_limited_to_clients::table + .filter( + roles_limited_to_clients::client_id.eq(client_id), + ) + .filter(roles_limited_to_clients::role_id.eq(role_id)), + ) + .execute(conn) + }) + .await + .map_err(ZauthError::from)?; + Ok(count > 0) + } + pub async fn remove_user(self, user_id: i32, db: &DbConn) -> Result { let count = db .run(move |conn| { diesel::delete( - users_roles::table - .filter(users_roles::user_id.eq(user_id)) - .filter(users_roles::role_id.eq(self.id)), + users_assigned_roles::table + .filter(users_assigned_roles::user_id.eq(user_id)) + .filter(users_assigned_roles::role_id.eq(self.id)), ) .execute(conn) }) @@ -165,9 +280,9 @@ impl Role { let count = db .run(move |conn| { diesel::delete( - clients_roles::table - .filter(clients_roles::client_id.eq(client_id)) - .filter(clients_roles::role_id.eq(self.id)), + clients_assigned_roles::table + .filter(clients_assigned_roles::client_id.eq(client_id)) + .filter(clients_assigned_roles::role_id.eq(self.id)), ) .execute(conn) }) @@ -176,6 +291,17 @@ impl Role { Ok(count > 0) } + pub async fn limited_to_clients(self, db: &DbConn) -> Result> { + db.run(move |conn| { + RoleLimitedToClient::belonging_to(&self) + .inner_join(clients::table) + .select(Client::as_select()) + .load(conn) + }) + .await + .map_err(ZauthError::from) + } + pub async fn users(self, db: &DbConn) -> Result> { db.run(move |conn| { UserRole::belonging_to(&self) @@ -204,6 +330,18 @@ impl Role { .map_err(ZauthError::from) } + pub async fn update(self, db: &DbConn) -> Result { + db.run(move |conn| { + conn.transaction(|conn| { + let id = self.id; + diesel::update(&self).set(&self).execute(conn)?; + roles::table.find(id).first(conn) + }) + }) + .await + .map_err(ZauthError::from) + } + pub async fn all(db: &DbConn) -> Result> { let all_roles = db.run(move |conn| roles::table.load::(conn)).await?; diff --git a/src/models/schema.rs b/src/models/schema.rs index 75b77647..ad9084a3 100644 --- a/src/models/schema.rs +++ b/src/models/schema.rs @@ -6,11 +6,14 @@ pub mod sql_types { pub struct ContentType; #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "user_state"))] - pub struct UserState; + #[diesel(postgres_type(name = "role_visibility"))] + pub struct RoleVisibility; } diesel::table! { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + clients (id) { id -> Int4, #[max_length = 255] @@ -25,7 +28,10 @@ diesel::table! { } diesel::table! { - clients_roles (client_id, role_id) { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + + clients_assigned_roles (client_id, role_id) { client_id -> Int4, role_id -> Int4, } @@ -33,6 +39,7 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; use super::sql_types::ContentType; mails (id) { @@ -47,6 +54,9 @@ diesel::table! { } diesel::table! { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + passkeys (id) { id -> Int4, user_id -> Int4, @@ -60,17 +70,34 @@ diesel::table! { } diesel::table! { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + use super::sql_types::RoleVisibility; + roles (id) { id -> Int4, #[max_length = 255] name -> Varchar, #[max_length = 255] description -> Varchar, - client_id -> Nullable, + visibility -> RoleVisibility, } } diesel::table! { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + + roles_limited_to_clients (role_id, client_id) { + role_id -> Int4, + client_id -> Int4, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + sessions (id) { id -> Int4, #[max_length = 255] @@ -86,7 +113,7 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; - use crate::models::user::UserStateMapping; + use crate::models::schema_custom_sql_types::*; users (id) { id -> Int4, @@ -103,7 +130,7 @@ diesel::table! { #[max_length = 255] email -> Varchar, ssh_key -> Nullable, - state -> UserStateMapping, + state -> UserState, last_login -> Timestamp, created_at -> Timestamp, #[max_length = 255] @@ -118,28 +145,33 @@ diesel::table! { } diesel::table! { - users_roles (user_id, role_id) { + use diesel::sql_types::*; + use crate::models::schema_custom_sql_types::*; + + users_assigned_roles (user_id, role_id) { user_id -> Int4, role_id -> Int4, } } -diesel::joinable!(clients_roles -> clients (client_id)); -diesel::joinable!(clients_roles -> roles (role_id)); +diesel::joinable!(clients_assigned_roles -> clients (client_id)); +diesel::joinable!(clients_assigned_roles -> roles (role_id)); diesel::joinable!(passkeys -> users (user_id)); -diesel::joinable!(roles -> clients (client_id)); +diesel::joinable!(roles_limited_to_clients -> clients (client_id)); +diesel::joinable!(roles_limited_to_clients -> roles (role_id)); diesel::joinable!(sessions -> clients (client_id)); diesel::joinable!(sessions -> users (user_id)); -diesel::joinable!(users_roles -> roles (role_id)); -diesel::joinable!(users_roles -> users (user_id)); +diesel::joinable!(users_assigned_roles -> roles (role_id)); +diesel::joinable!(users_assigned_roles -> users (user_id)); diesel::allow_tables_to_appear_in_same_query!( clients, - clients_roles, + clients_assigned_roles, mails, passkeys, roles, + roles_limited_to_clients, sessions, users, - users_roles, + users_assigned_roles, ); diff --git a/src/models/schema_custom_sql_types.rs b/src/models/schema_custom_sql_types.rs new file mode 100644 index 00000000..9cdbdc8a --- /dev/null +++ b/src/models/schema_custom_sql_types.rs @@ -0,0 +1,2 @@ +// See diesel.toml. +pub use crate::models::user::UserStateMapping as UserState; diff --git a/src/models/session.rs b/src/models/session.rs index 1a0ceb3b..119e9b8d 100644 --- a/src/models/session.rs +++ b/src/models/session.rs @@ -1,16 +1,15 @@ use chrono::{Duration, NaiveDateTime, Utc}; use diesel::{self, prelude::*}; -use crate::DbConn; - use super::schema::sessions; +use crate::DbConn; use crate::config::Config; use crate::errors::{Result, ZauthError}; use crate::models::client::Client; use crate::models::user::User; use crate::util::random_token; -#[derive(Serialize, AsChangeset, Queryable, Associations, Debug, Clone)] +#[derive(AsChangeset, Associations, Clone, Debug, Queryable, Serialize)] #[diesel(belongs_to(User))] #[diesel(belongs_to(Client))] #[diesel(table_name = sessions)] @@ -25,7 +24,7 @@ pub struct Session { pub scope: Option, } -#[derive(Insertable, Debug, Clone)] +#[derive(Clone, Debug, Insertable)] #[diesel(table_name = sessions)] pub struct NewSession { pub key: Option, diff --git a/src/models/user.rs b/src/models/user.rs index 7ccdf6e4..71eb4439 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,33 +1,32 @@ -use super::schema::{roles, users}; -use crate::DbConn; -use crate::errors::{self, InternalError, LoginError, ZauthError}; -use diesel::{self, prelude::*}; -use diesel_derive_enum::DbEnum; use std::fmt; use std::sync::LazyLock; -use crate::Config; -use crate::util::random_token; use chrono::{NaiveDateTime, Utc}; use diesel::result::{DatabaseErrorKind, Error as DieselError}; +use diesel::{self, prelude::*}; +use diesel_derive_enum::DbEnum; use lettre::message::Mailbox; use pwhash::bcrypt::{self, BcryptSetup}; use regex::Regex; use rocket::{FromFormField, serde::Serialize}; -use std::convert::TryFrom; use validator::{Validate, ValidationError, ValidationErrors}; -use super::role::{Role, UserRole}; +use crate::Config; +use crate::DbConn; +use crate::errors::{self, InternalError, LoginError, ZauthError}; +use crate::models::role::{Role, UserRole}; +use crate::models::schema::{roles, users}; +use crate::util::random_token; #[derive( + Clone, DbEnum, Debug, Deserialize, FromFormField, - Serialize, - Clone, PartialEq, QueryId, + Serialize, )] pub enum UserState { PendingApproval, @@ -52,15 +51,15 @@ impl fmt::Display for UserState { } #[derive( - Validate, - Serialize, AsChangeset, - Selectable, - Queryable, - Debug, Clone, - PartialEq, + Debug, Identifiable, + PartialEq, + Queryable, + Selectable, + Serialize, + Validate, )] #[diesel(table_name = users)] #[diesel(treat_none_as_null = true)] @@ -103,7 +102,7 @@ pub struct User { static NEW_USER_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"^[a-z][-a-z0-9_]{2,31}$").unwrap()); -#[derive(Validate, FromForm, Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, FromForm, Validate)] pub struct NewUser { #[validate(regex( path = *NEW_USER_REGEX, @@ -126,7 +125,7 @@ pub struct NewUser { pub not_a_robot: bool, } -#[derive(Serialize, Insertable, Debug, Clone)] +#[derive(Clone, Debug, Insertable, Serialize)] #[diesel(table_name = users)] struct PendingUserHashed { username: String, @@ -140,7 +139,7 @@ struct PendingUserHashed { pending_email_expiry: NaiveDateTime, } -#[derive(Serialize, Insertable, Debug, Clone)] +#[derive(Clone, Debug, Insertable, Serialize)] #[diesel(table_name = users)] struct NewUserHashed { username: String, @@ -151,7 +150,7 @@ struct NewUserHashed { email: String, } -#[derive(FromForm, Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, FromForm)] pub struct UserChange { pub username: Option, pub password: Option, @@ -161,17 +160,17 @@ pub struct UserChange { pub subscribed_to_mailing_list: bool, } -#[derive(FromForm, Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, FromForm)] pub struct ChangeAdmin { pub admin: bool, } -#[derive(FromForm, Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, FromForm)] pub struct ChangeStatus { pub state: UserState, } -#[derive(Validate, FromForm, Deserialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, FromForm, Validate)] pub struct ChangePassword { #[validate(length( min = 8, @@ -553,6 +552,26 @@ impl User { client_id: i32, db: &DbConn, ) -> Result, ZauthError> { + db.run(move |conn| { + diesel::sql_query(" + SELECT roles.* + FROM users + INNER JOIN users_assigned_roles ON users.id = users_assigned_roles.user_id + INNER JOIN roles ON users_assigned_roles.role_id = roles.id + LEFT OUTER JOIN roles_limited_to_clients ON roles.id = roles_limited_to_clients.role_id + WHERE users.id = $1 AND ( + roles.visibility = 'global' OR + (roles.visibility = 'limited' AND roles_limited_to_clients.client_id = $2) + ); + ") + .bind::(self.id) + .bind::(client_id) + .get_results(conn) + }) + .await + .map_err(ZauthError::from) + + /* db.run(move |conn| { UserRole::belonging_to(&self) .inner_join(roles::table) @@ -566,6 +585,37 @@ impl User { }) .await .map_err(ZauthError::from) + */ + + /* + db.run(move |conn| { + users::table + .filter(users::id.eq(self.id)) + .inner_join( + users_assigned_roles::table.inner_join( + roles::table + //.left_outer_join(roles_limited_to_clients::table), + ), + ) + .filter(roles::visibility.eq(RoleVisibility::Global)) + .select(Role::as_select()) + .load(conn) + }) + .await + .map_err(ZauthError::from) + */ + + /* + .union( + UserRole::belonging_to(&self) + .inner_join( + roles::table + .inner_join(roles_limited_to_clients::table), + ) + .filter(roles::visibility.eq(RoleVisibility::Limited)) + .select(Role::as_select()), + ) + */ } } diff --git a/src/token_store.rs b/src/token_store.rs index df61b18e..74045b64 100644 --- a/src/token_store.rs +++ b/src/token_store.rs @@ -1,8 +1,10 @@ -use crate::config::Config; -use crate::util; +use std::collections::HashMap; + use chrono::{DateTime, Duration, Local}; use rocket::tokio::sync::Mutex; -use std::collections::HashMap; + +use crate::config::Config; +use crate::util; #[derive(Debug)] pub struct Token { diff --git a/src/util.rs b/src/util.rs index b096cb48..7d4f79ea 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,5 @@ use core::iter; + use rand::{Rng, distr::Alphanumeric, rng}; pub fn random_token(token_length: usize) -> String { diff --git a/src/views/accepter.rs b/src/views/accepter.rs index 8828a02f..a18f4822 100644 --- a/src/views/accepter.rs +++ b/src/views/accepter.rs @@ -1,5 +1,6 @@ use rocket::http::{MediaType, QMediaType, Status}; use rocket::request::Request; +use rocket::response::content::RawHtml; use rocket::response::status::Custom; use rocket::response::{self, Responder}; @@ -9,7 +10,7 @@ pub struct Accepter { } fn not_acceptable<'r>() -> impl Responder<'r, 'static> { - Custom(Status::NotAcceptable, template!("errors/406.html")) + Custom(Status::NotAcceptable, RawHtml(template!("errors/406.html"))) } fn preferred_media<'r>(request: &'r Request<'_>) -> Vec<&'r MediaType> { diff --git a/src/views/mod.rs b/src/views/mod.rs index 051dd671..efc2e951 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,3 +1,4 @@ #[macro_use] pub mod template; + pub mod accepter; diff --git a/src/views/template.rs b/src/views/template.rs index e93bd64c..70eaaf6d 100644 --- a/src/views/template.rs +++ b/src/views/template.rs @@ -1,38 +1,48 @@ +#[derive(Debug)] +pub struct CommonTemplateData { + pub zauth_version: &'static str, +} + +impl Default for CommonTemplateData { + fn default() -> Self { + CommonTemplateData { + zauth_version: crate::ZAUTH_VERSION, + } + } +} + #[macro_export] macro_rules! template { - ($template_name:literal) => { + ( + $template_name:literal $(,{ + $($name:ident : $type:ty = $value:expr_2021),* + $(,)? + })? + ) => { { use askama::Template; - #[derive(Template, Debug)] - #[template(path = $template_name)] - struct TemplateStruct { - #[allow(dead_code)] - zauth_version: &'static str - } - TemplateStruct { - zauth_version: $crate::ZAUTH_VERSION, - } - } - }; - ($template_name:literal; $($name:ident: $type:ty = $value:expr_2021),+$(,)?) => { - { - use askama::Template; - #[derive(Template, Debug)] + use $crate::errors::{InternalError,Result,ZauthError}; + use $crate::views::template::CommonTemplateData; + + #[derive(Template)] #[template(path = $template_name)] struct TemplateStruct { #[allow(dead_code)] - zauth_version: &'static str, - $( - $name: $type, - )+ - } - TemplateStruct { - zauth_version: $crate::ZAUTH_VERSION, - $( - $name: $value, - )+ + common: CommonTemplateData, + $($($name: $type,)*)? } + + let instance = TemplateStruct { + common: CommonTemplateData::default(), + $($($name: $value,)*)? + }; + + let res: Result = instance + .render() + .map_err(InternalError::from) + .map_err(ZauthError::from); + res } - } + }; } diff --git a/src/webauthn.rs b/src/webauthn.rs index fd9d3825..23172892 100644 --- a/src/webauthn.rs +++ b/src/webauthn.rs @@ -10,7 +10,8 @@ use webauthn_rs::{ }, }; -use crate::{config::Config, errors::Either}; +use crate::config::Config; +use crate::errors::Either; type Authentication = Either; diff --git a/templates/base_error.html b/templates/base_error.html index 4d389426..40e345e0 100644 --- a/templates/base_error.html +++ b/templates/base_error.html @@ -2,31 +2,30 @@ {% block content %} -
-
- - -
- Error image -
+
+
+ +
+ Error image +
- -
+ +
- -
- {% block error_title %} - Something went wrong! - {% endblock error_title %} -
+ +
+ {% block error_title %} + Something went wrong! + {% endblock error_title %} +
- -
- {% block error_content %} - An unexpected error occurred. - {% endblock error_content %} + +
+ {% block error_content %} + An unexpected error occurred. + {% endblock error_content %} +
-
-
+
{% endblock %} diff --git a/templates/base_logged_in.html b/templates/base_logged_in.html index 18e146ba..45139e1f 100644 --- a/templates/base_logged_in.html +++ b/templates/base_logged_in.html @@ -1,33 +1,31 @@ {% extends "layout.html" %} -{% block nav %} - -
+ {% endblock nav %} diff --git a/templates/base_not_logged_in.html b/templates/base_not_logged_in.html index 86df7730..fdc730cd 100644 --- a/templates/base_not_logged_in.html +++ b/templates/base_not_logged_in.html @@ -1,25 +1,23 @@ {% extends "layout.html" %} -{% block nav %} - - + + +
+ {% endblock nav %} diff --git a/templates/clients/confirm_generate_secret.html b/templates/clients/confirm_generate_secret.html index 03dffcba..553f4fdc 100644 --- a/templates/clients/confirm_generate_secret.html +++ b/templates/clients/confirm_generate_secret.html @@ -2,25 +2,25 @@ {% block content %} -
-
-
- -
- Generate a new secret? -
+
+
+
+ +
+ Generate a new secret? +
- -
- This will overwrite the current secret of {{ client.name }} with a newly generated one. -
+ +
+ This will overwrite the current secret of {{ client.name }} with a newly generated one. +
- - - - Cancel - -
-
-
+ +
+ + Cancel +
+
+
+
{% endblock content %} diff --git a/templates/clients/edit_client.html b/templates/clients/edit_client.html index 125db046..9c2caca9 100644 --- a/templates/clients/edit_client.html +++ b/templates/clients/edit_client.html @@ -3,9 +3,9 @@ {% block content %}
+
-
Update {{ client.name }} @@ -13,7 +13,7 @@
- +
@@ -26,7 +26,8 @@ minlength="3" maxlength="80" value="{{ client.name }}" - required> + required + />
@@ -37,7 +38,10 @@ name="description" rows="4" cols="50" - placeholder="Describe what the application does.">{{- client.description -}} + placeholder="Describe what the application does." + > + {{- client.description -}} +
@@ -46,13 +50,21 @@
@@ -66,13 +78,18 @@ name="redirect_uri_list" placeholder="http://localhost:3000/auth/callback https://example.com/auth/callback" rows="4" - cols="50">{{- client.redirect_uri_list -}} + cols="50" + > + {{- client.redirect_uri_list -}} +
- +
+ +
@@ -85,7 +102,10 @@ + disabled + > + {{- client.secret -}} +
@@ -97,31 +117,27 @@
- +
{% for role in client_roles %} - {% if let Some(_) = role.client_id %} - - {% else %} - - {% endif %} - {{ role.name }} - - - + +
diff --git a/templates/clients/index.html b/templates/clients/index.html index 4b56c444..38b0878b 100644 --- a/templates/clients/index.html +++ b/templates/clients/index.html @@ -3,7 +3,6 @@ {% block content %}
-
@@ -13,11 +12,10 @@
- An OAuth client is an application which can use our server for authentication.
- The client would send the user to our webpage to login and we then tell the client who the user is.
+ An OAuth client is an application which can use our server for authentication.
+ The client would send the user to our webpage to login and we then tell the client who the user is.
Each application using this server should have its own client and maybe even a separate client for development purposes.
-
@@ -35,14 +33,12 @@ - {% for client in clients %} - - {{ client.name }} + {{ client.name }} @@ -53,8 +49,8 @@ - - + + diff --git a/templates/errors/401.html b/templates/errors/401.html index e1052476..6f2be17b 100644 --- a/templates/errors/401.html +++ b/templates/errors/401.html @@ -2,11 +2,11 @@ {% block error_title %} - Not acceptable + Not acceptable {% endblock error_title %} {% block error_content %} -

Sorry, but you're not allowed to do this.

-

Maybe you want to sign in?

+

Sorry, but you're not allowed to do this.

+

Maybe you want to sign in?

{% endblock error_content %} diff --git a/templates/errors/404.html b/templates/errors/404.html index 41f4654d..8ab39974 100644 --- a/templates/errors/404.html +++ b/templates/errors/404.html @@ -2,10 +2,10 @@ {% block error_title %} - Not found + Not found {% endblock error_title %} {% block error_content %} - Sorry, but we couldn't find the page you were looking for. + Sorry, but we couldn't find the page you were looking for. {% endblock error_content %} diff --git a/templates/errors/406.html b/templates/errors/406.html index fe37b837..89d71f9c 100644 --- a/templates/errors/406.html +++ b/templates/errors/406.html @@ -2,10 +2,10 @@ {% block error_title %} - Not acceptable + Not acceptable {% endblock error_title %} {% block error_content %} - Sorry, but we couldn't find an HTML representation for the resource you requested. + Sorry, but we couldn't find an HTML representation for the resource you requested. {% endblock error_content %} diff --git a/templates/errors/422.html b/templates/errors/422.html index c21cbf84..4267896e 100644 --- a/templates/errors/422.html +++ b/templates/errors/422.html @@ -2,18 +2,16 @@ {% block error_title %} - Unprocessable request + Unprocessable request {% endblock error_title %} {% block error_content %} -

Sorry, but your request could not be processed because. - Probably because it would cause an illegal state. -{% match message %} -{% when Some with (msg) %} -

Message: {{ msg }} -{% when None %} -{% endmatch %} - - +

+ Sorry, but your request could not be processed because. + Probably because it would cause an illegal state. +

+ {% if let Some(message) = message %} +

Message: {{ message }}

+ {% endif %} {% endblock error_content %} diff --git a/templates/errors/500.html b/templates/errors/500.html index f13ade77..3dfc042c 100644 --- a/templates/errors/500.html +++ b/templates/errors/500.html @@ -2,15 +2,15 @@ {% block error_title %} - Not acceptable + Not acceptable {% endblock error_title %} {% block error_content %} -

Sorry, but something went extremely wrong on our end.

-

Contact your local Zauth programmer and tell them the following:

+

Sorry, but something went extremely wrong on our end.

+

Contact your local Zauth programmer and tell them the following:

-
-  {{ error }}
-  
+
+		{{ error }}
+	
{% endblock error_content %} diff --git a/templates/errors/501.html b/templates/errors/501.html index be50701c..8632c820 100644 --- a/templates/errors/501.html +++ b/templates/errors/501.html @@ -2,14 +2,14 @@ {% block error_title %} - A programmer made a mistake. + A programmer made a mistake. {% endblock error_title %} {% block error_content %} -

They forgot to handle an error. Tell them the following:

+

They forgot to handle an error. Tell them the following:

-
-  {{ error }}
-  
+
+		{{ error }}
+	
{% endblock error_content %} diff --git a/templates/layout.html b/templates/layout.html index bc9d9c4f..783bee06 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -1,32 +1,33 @@ - - + + - - {% block title %} + <title> + {% block title %} Zauth - {% endblock title %} - + {% endblock title %} + - + - {% block head %} - {% endblock head %} + {% block head %} + {% endblock head %} - - {% block nav %}{% endblock nav %} + {% block nav %} + {% endblock nav %}
- {% block content %}{% endblock content %} + {% block content %} + {% endblock content %}
@@ -35,7 +36,7 @@
diff --git a/templates/maillist/index.html b/templates/maillist/index.html index 61941fba..197db45d 100644 --- a/templates/maillist/index.html +++ b/templates/maillist/index.html @@ -2,56 +2,52 @@ {% block content %} -
- - -
- - -
-
-
Mails ({{ mails.len() }})
-
- - {% if current_user.admin %} +
+
+ +
- New mail +
Mails ({{ mails.len() }})
- {% endif %} -
- - - - - - - - - - + {% if current_user.admin %} +
+ New mail +
+ {% endif %} + - - {% for mail in mails %} + +
Sent onAuthorSubjectBody
+ - - - - - - - - - - - - {% endfor %} - -
- - {{ mail.sent_on.format("%d/%m/%y").to_string() }} - - {{ mail.author }}{{ mail.subject }}{{ mail.body|truncate(50) }}
+ Sent on + Author + Subject + Body + + + + {% for mail in mails %} + + + + + {{ mail.sent_on.format("%d/%m/%y").to_string() }} + + + + + {{ mail.author }} + + + {{ mail.subject }} + + + {{ mail.body | truncate(50) }} + {% endfor %} + + +
-
{% endblock content %} diff --git a/templates/maillist/new_mail.html b/templates/maillist/new_mail.html index c09825e3..4a9473ed 100644 --- a/templates/maillist/new_mail.html +++ b/templates/maillist/new_mail.html @@ -3,7 +3,6 @@ {% block content %}
-
Send new mail @@ -35,7 +34,6 @@
-
diff --git a/templates/maillist/show_mail.html b/templates/maillist/show_mail.html index a6237d02..911a6f2d 100644 --- a/templates/maillist/show_mail.html +++ b/templates/maillist/show_mail.html @@ -2,24 +2,25 @@ {% block content %} -
+
+
+
+ +
+ ({{ mail.sent_on.format("%d/%m/%y").to_string() }}) {{ mail.subject }} +
- -
-
- -
({{ mail.sent_on.format("%d/%m/%y").to_string() }}) {{ mail.subject }}
- -
- {% if let Some(body) = rendered_body %} - {{ body|safe }} - {% else %} -
- Could not render the body -
- {% endif %} + +
+ {% if let Some(body) = rendered_body %} + {{ body | safe }} + {% else %} +
+ Could not render the body +
+ {% endif %} +
-
{% endblock content %} diff --git a/templates/mails/confirm_user_registration.txt b/templates/mails/confirm_user_registration.txt index 16ff9d96..e7d0fbd2 100644 --- a/templates/mails/confirm_user_registration.txt +++ b/templates/mails/confirm_user_registration.txt @@ -1,8 +1,8 @@ -Hi {{name}} +Hi {{ name }} Welcome at Zeus WPI! Please confirm your email by clicking the following link: -{{confirm_url}} +{{ confirm_url }} After your email has been confirmed, an admin will need to approve your account. Once your account is approved, you will receive another email saying your account has been activated, after which you can enjoy the Zeus services. diff --git a/templates/mails/mailinglist_mail.html b/templates/mails/mailinglist_mail.html index aa020534..6be183db 100644 --- a/templates/mails/mailinglist_mail.html +++ b/templates/mails/mailinglist_mail.html @@ -1,7 +1,7 @@ - - {{ body }} - -

You can unsubscribe from the mailing list at {{ unsubscribe_url }}

- + + {{ body }} + +

You can unsubscribe from the mailing list at {{ unsubscribe_url }}

+ diff --git a/templates/mails/new_user_registration.txt b/templates/mails/new_user_registration.txt index e489183e..2c8018ad 100644 --- a/templates/mails/new_user_registration.txt +++ b/templates/mails/new_user_registration.txt @@ -1,6 +1,6 @@ Hi admins -A new user with name '{{name}}' has registered on Zauth. Please go to {{user_list_url}} to verify this registration. +A new user with name '{{ name }}' has registered on Zauth. Please go to {{ user_list_url }} to verify this registration. Kind regards -The Zeus Authentication Server \ No newline at end of file +The Zeus Authentication Server diff --git a/templates/mails/password_reset_success.txt b/templates/mails/password_reset_success.txt index 7032bc93..3d4d528b 100644 --- a/templates/mails/password_reset_success.txt +++ b/templates/mails/password_reset_success.txt @@ -7,4 +7,4 @@ to snoop our previous mail and has now chosen a new password for your account. Please contact your nearest system administrator if that was the case. Kind regards -The Zeus Authentication Server \ No newline at end of file +The Zeus Authentication Server diff --git a/templates/mails/password_reset_token.txt b/templates/mails/password_reset_token.txt index 9f186e81..bc37f690 100644 --- a/templates/mails/password_reset_token.txt +++ b/templates/mails/password_reset_token.txt @@ -12,4 +12,4 @@ telling you your email has been reset. In that case you have a good reason to panic because someone was able to snoop this email from you. Kind regards -The Zeus Authentication Server \ No newline at end of file +The Zeus Authentication Server diff --git a/templates/mails/user_approved.txt b/templates/mails/user_approved.txt index 1dfe9e7b..c5c85469 100644 --- a/templates/mails/user_approved.txt +++ b/templates/mails/user_approved.txt @@ -1,4 +1,4 @@ -Hi {{name}} +Hi {{ name }} Your account has been approved and you can now use our services with your account. You can use the following link to login: diff --git a/templates/oauth/authorize.html b/templates/oauth/authorize.html index 85b91072..6eeea5cd 100644 --- a/templates/oauth/authorize.html +++ b/templates/oauth/authorize.html @@ -2,31 +2,30 @@ {% block content %} -
-
+
+
+ + + Zeus Logo + - - - Zeus Logo - +
+ +
+ Authorize for {{ client_description }}? +
-
- -
- Authorize for {{ client_description }}? -
+ +
+ Are you sure you want to log in with your Zeus account? +
- -
- Are you sure you want to log in with your Zeus account? -
- - -
- - -
-
-
-
+ +
+ + +
+
+
+
{% endblock content %} diff --git a/templates/oauth/grant.html b/templates/oauth/grant.html index 172c963c..2f072b41 100644 --- a/templates/oauth/grant.html +++ b/templates/oauth/grant.html @@ -2,31 +2,30 @@ {% block content %} -
-
+
+
+ + + Zeus Logo + - - - Zeus Logo - +
+ +
+ Grant {{ client_description }} access? +
-
- -
- Grant {{ client_description }} access? -
+ +
+ Are you sure you want to grant this service access to your account? +
- -
- Are you sure you want to grant this service access to your account? -
- - -
- - -
-
-
-
+ +
+ + +
+
+
+
{% endblock content %} diff --git a/templates/pages/home.html b/templates/pages/home.html index 0b65cecb..9374588f 100644 --- a/templates/pages/home.html +++ b/templates/pages/home.html @@ -5,7 +5,6 @@
-
{{ name }} @@ -22,17 +21,19 @@ {% block content %} -
+
Zauth
+
The Zeus Authentication Platform for logging in to all Zeus services.
+
Login Create an account @@ -51,11 +52,11 @@ With a Zeus account you have access to all of our services in- and outside the Kelder.
- {%- call service_card("Haldis", "Order food from our favorite places and eat together with other Zeus members.", "https://haldis.zeus.gent") -%} - {%- call service_card("Tap", "Grab drinks and snacks in the kelder and easily pay for them.", "https://tap.zeus.gent") -%} - {%- call service_card("Tab", "Manage your Zeus balance for paying in Haldis or Tap.", "https://tab.zeus.gent") -%} - {%- call service_card("Gamification", "Get points for code contributions and join the leaderboard.", "https://zeus.ugent.be/game") -%} - {%- call service_card("Cat", "Keep track of who has given hugs to who. We all need some affection sometimes…", "https://cat.zeus.gent") -%} + {%- call service_card("Haldis", "Order food from our favorite places and eat together with other Zeus members.", "https://haldis.zeus.gent") -%}{%- endcall -%} + {%- call service_card("Tap", "Grab drinks and snacks in the kelder and easily pay for them.", "https://tap.zeus.gent") -%}{%- endcall -%} + {%- call service_card("Tab", "Manage your Zeus balance for paying in Haldis or Tap.", "https://tab.zeus.gent") -%}{%- endcall -%} + {%- call service_card("Gamification", "Get points for code contributions and join the leaderboard.", "https://zeus.ugent.be/game") -%}{%- endcall -%} + {%- call service_card("Cat", "Keep track of who has given hugs to who. We all need some affection sometimes…", "https://cat.zeus.gent") -%}{%- endcall -%}
diff --git a/templates/passkeys/index.html b/templates/passkeys/index.html index b1f95903..97270e9f 100644 --- a/templates/passkeys/index.html +++ b/templates/passkeys/index.html @@ -2,11 +2,8 @@ {% block content %} -
- -
@@ -25,14 +22,12 @@ Name Last Used Created at - Delete + - {% for passkey in passkeys %} - {{ passkey.name }} @@ -42,7 +37,7 @@ {{ passkey.created_at.format("%d/%m/%y").to_string() }} - +
@@ -55,4 +50,3 @@
{% endblock content %} - diff --git a/templates/passkeys/new_passkey.html b/templates/passkeys/new_passkey.html index 9634acca..62c0a3a4 100644 --- a/templates/passkeys/new_passkey.html +++ b/templates/passkeys/new_passkey.html @@ -3,19 +3,17 @@ {% block content %}
- +
Register a new passkey
- {% match errors %} - {% when Some with (errors) %} -
- {{ errors }} -
- {% when None %} - {% endmatch %} + {% if let Some(errors) = errors %} +
+ {{ errors }} +
+ {% endif %}
@@ -25,7 +23,7 @@

- A resident key allows for a discoverable credential, so giving a username is not required to log in.
+ A resident key allows for a discoverable credential, so giving a username is not required to log in.
It however takes up extra storage, and physical security keys have therefore often a limit on the number of resident keys they can store.

diff --git a/templates/roles/index.html b/templates/roles/index.html index 57a74f04..28572106 100644 --- a/templates/roles/index.html +++ b/templates/roles/index.html @@ -3,7 +3,6 @@ {% block content %}
-
@@ -13,29 +12,25 @@
- Roles are given to users, which allows for more fine-grained client-level permissions.
- If a client requests the user info after login, it can give this user additional permissions based on the included roles.
- Global roles are always returned; client-specific roles are only returned for that client.
- The OAuth scope must include 'roles' for the roles to be included in the ID token or user info.
- Clients can also be given roles, these are returned during a client credentials flow. + Roles are given to users, which allows for more fine-grained client-level permissions.
+ If a client requests the user info after login, it can give this user additional permissions based on the included roles.
+ Global roles are always returned; client-specific roles are only returned for that client.
+ The OAuth scope must include 'roles' for the roles to be included in the ID token or user info.
+ Clients can also be given roles, these are returned during a client credentials flow.
- -
- +
- {% match error %} - {% when Some with (error) %} -
- {{ error }} -
- {% when None %} - {% endmatch %} + {% if let Some(error) = error %} +
+ {{ error }} +
+ {% endif %} @@ -43,25 +38,29 @@ - + + - - {% for role in roles %} + {% for role in roles.iter() %} - - + + + +
Name DescriptionVisibility
- {{ role.name }} + {{ role.name }} {{ role.description }} + {{ role.visibility }} + - + @@ -79,7 +78,7 @@
- +
- +
- + {% for el in crate::models::role::RoleVisibility::VALUES %} + {% endfor %}
-

- Keeping this empty will create a global role -

diff --git a/templates/roles/show_role.html b/templates/roles/show_role.html index f756011c..14b0db7e 100644 --- a/templates/roles/show_role.html +++ b/templates/roles/show_role.html @@ -1,161 +1,340 @@ {% extends "base_logged_in.html" %} + {% block content %} + +
+ Role {{ role.name }} +
+ + + {% if let Some(error) = error %} +
+ {{ error }} +
+ {% endif %} -
- Role {{role.name}} -
- -
- {% match client %} - {% when Some with (client) %} - for client {{client.name}} - {% when None %} - Global role - {% endmatch %} -
- - -{% match error %} -{% when Some with (error) %} -
- {{ error }} -
-{% when None %} -{% endmatch %} - - -{% match info %} -{% when Some with (info) %} -
- {{ info }} -
-{% when None %} -{% endmatch %} - -
-
- -
-
- -
- Users -
- -
- Users that have the this role assigned to. -
- -
- Add user -
+ + {% if let Some(success) = success %} +
+ {{ success }} +
+ {% endif %} + +
+ +
+ +
Description
+ + +
+
{{ role.description }}
+
+ + +
+ Edit
+ +
+ +
+ Visibility +
- - - - - - - - + +
+ {% match role.visibility %} + {% when crate::models::role::RoleVisibility::Global %} + Global role, visible to all clients + {% when crate::models::role::RoleVisibility::Limited %} + Limited role, only visible the following clients: + {% endmatch %} +
- - {% for user in users %} - + +
+ + + Change visibility + + {% if role.visibility == crate::models::role::RoleVisibility::Limited %} + + + Add client to “limited to” list + + {% endif %} +
- - - - - + + {% if role.visibility == crate::models::role::RoleVisibility::Limited %} +
Name
- {{ user.username }} - -
- - -
-
+ + + + + + + + {% for client in limited_to_clients %} + + + + + + + + {% else %} + + + + + {% endfor %} + +
Name
+ {{ client.name }} + +
+ + +
+
The “limited to” list is empty
+ {% endif %} +
+ + +
+ +
+ Users +
+ + +
+ Users that have this role assigned to them. +
+ + +
+ Add user +
+ + + + + + + - {% endfor %} + + + {% for user in users %} + + + + + + + + {% else %} + + + + + {% endfor %} + +
Name
+ {{ user.username }} + +
+ + +
+
No users mapped yet
+
+ + +
+ +
+ Clients +
+ + +
+ Clients that have this role assigned to them. +
+ + + - - {% if users.len() == 0 %} + + + - + + - {% endif %} - -
No users mapped yetName
+ + + {% for client in clients %} + + + + {{ client.name }} + + + + +
+ + +
+ + + {% else %} + + + No clients mapped yet + + {% endfor %} + + +
+ + - -